From b34027b096c78b300b82b3aece2cbe430f7b6663 Mon Sep 17 00:00:00 2001 From: Simon Bihel Date: Tue, 25 Jan 2022 16:29:02 +0000 Subject: [PATCH] Add ENS domain information --- Cargo.lock | 361 +++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 5 +- README.md | 14 +- src/axum_lib.rs | 2 + src/config.rs | 2 + src/db/mod.rs | 3 +- src/oidc.rs | 81 +++++++--- src/worker_lib.rs | 21 ++- wrangle_example.toml | 1 + 9 files changed, 447 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d25ba47..f375e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,17 @@ dependencies = [ "syn", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.0", +] + [[package]] name = "atomic" version = "0.5.1" @@ -212,6 +223,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" +[[package]] +name = "auto_impl" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "0.1.7" @@ -528,6 +551,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.1" @@ -778,6 +817,40 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "ethers-providers" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68d511a99f39a26c9b32a6f62360789ba0e214d8f4c012bf1fbdc7b00da0e4f" +dependencies = [ + "async-trait", + "auto_impl", + "bytes", + "ethers-core", + "futures-channel", + "futures-core", + "futures-timer", + "futures-util", + "hex", + "parking_lot", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tracing", + "tracing-futures", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-timer", + "web-sys", + "ws_stream_wasm", +] + [[package]] name = "event-listener" version = "2.5.1" @@ -937,6 +1010,12 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.19" @@ -1149,9 +1228,9 @@ checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ "http", "hyper", - "rustls", + "rustls 0.20.2", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.2", ] [[package]] @@ -1226,6 +1305,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -1570,6 +1652,12 @@ dependencies = [ "url", ] +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "ordered-float" version = "1.1.1" @@ -1704,6 +1792,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.0", +] + [[package]] name = "pin-project" version = "1.0.10" @@ -1813,6 +1911,30 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -2013,18 +2135,18 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.20.2", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.2", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.22.2", "winreg", ] @@ -2096,7 +2218,29 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.4", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", ] [[package]] @@ -2107,8 +2251,20 @@ checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" dependencies = [ "log", "ring", - "sct", - "webpki", + "sct 0.7.0", + "webpki 0.22.0", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls 0.19.1", + "schannel", + "security-framework", ] [[package]] @@ -2126,12 +2282,32 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sct" version = "0.7.0" @@ -2155,6 +2331,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09d3c15d814eda1d6a836f2f2b56a6abc1446c8a34351cb3180d3db92ffe4ce" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e90dd10c41c6bfc633da6e0c659bd25d31e0791e5974ac42970267d59eba87f7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -2164,12 +2363,24 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "send_wrapper" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7" + [[package]] name = "serde" version = "1.0.133" @@ -2306,9 +2517,9 @@ dependencies = [ [[package]] name = "siwe" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6557098a7ca2e561eb96f99a118edeadaad998567c28dea137c89a052545245" +checksum = "51a371151a398635bf39e36ed90cd34c6264e7fd7bf1fb2337156f06f8ce836c" dependencies = [ "chrono", "ethers-core", @@ -2316,6 +2527,7 @@ dependencies = [ "http", "iri-string", "k256", + "rand", "sha3", "thiserror", ] @@ -2334,11 +2546,14 @@ dependencies = [ "chrono", "console_error_panic_hook", "cookie", + "ethers-core", + "ethers-providers", "figment", "getrandom", "headers", "hex", "iri-string", + "lazy_static", "matchit", "openidconnect", "rand", @@ -2429,7 +2644,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" dependencies = [ "discard", - "rustc_version", + "rustc_version 0.2.3", "stdweb-derive", "stdweb-internal-macros", "stdweb-internal-runtime", @@ -2653,15 +2868,43 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + [[package]] name = "tokio-rustls" version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" dependencies = [ - "rustls", + "rustls 0.20.2", "tokio", - "webpki", + "webpki 0.22.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "rustls 0.19.1", + "tokio", + "tokio-rustls 0.22.0", + "tungstenite", + "webpki 0.21.4", + "webpki-roots 0.21.1", ] [[package]] @@ -2773,6 +3016,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.1.2" @@ -2808,6 +3061,28 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls 0.19.1", + "rustls-native-certs", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki 0.21.4", +] + [[package]] name = "typenum" version = "1.15.0" @@ -2896,6 +3171,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8-ranges" version = "1.0.4" @@ -3018,6 +3299,21 @@ version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.55" @@ -3028,6 +3324,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki" version = "0.22.0" @@ -3038,13 +3344,22 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki 0.21.4", +] + [[package]] name = "webpki-roots" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" dependencies = [ - "webpki", + "webpki 0.22.0", ] [[package]] @@ -3164,6 +3479,24 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ws_stream_wasm" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47ca1ab42f5afed7fc332b22b6e932ca5414b209465412c8cdf0ad23bc0de645" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "pharos", + "rustc_version 0.4.0", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 07804f6..2d59f8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ rand = "0.8.4" rsa = { version = "0.5.0", features = ["alloc"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.72" -siwe = "0.1.2" +siwe = "0.1.3" thiserror = "1.0.30" tracing = "0.1.29" url = { version = "2.2", features = ["serde"] } @@ -30,6 +30,9 @@ sha2 = "0.9.0" cookie = "0.15.1" bincode = "1.3.3" async-trait = "0.1.52" +ethers-core = "0.6.3" +ethers-providers = "0.6.2" +lazy_static = "1.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-session = "3.0.0" diff --git a/README.md b/README.md index ec3713d..883f133 100644 --- a/README.md +++ b/README.md @@ -69,16 +69,22 @@ through environment variables: ### OIDC Functionalities The current flow is very basic -- after the user is authenticated you will -receive an Ethereum address as the subject (`sub` field). +receive: +- an Ethereum address as the subject (`sub` field); and +- an ENS domain as the `preferred_username` (with a fallback to the address). For the core OIDC information, it is available under `/.well-known/openid-configuration`. +OIDC Conformance Suite: +- 🟨 (25/29, and 10 skipped) [basic](https://www.certification.openid.net/plan-detail.html?plan=gXe7Ju1O1afZa&public=true) (`email` scope skipped, `profile` scope partially supported, ACR, `prompt=none` and request URIs yet to be supported); +- 🟩 [config](https://www.certification.openid.net/plan-detail.html?plan=SAmBjvtyfTDVn&public=true); +- 🟧 [dynamic code](https://www.certification.openid.net/plan-detail.html?plan=7rexGcCd4SWJa&public=true). + ### TODO Items -* Additional information, from native projects (e.g. ENS domains), to more - traditional ones (e.g. email). -* PKCE support (code challenge). +* Additional information, from native projects (e.g. ENS domains profile + pictures), to more traditional ones (e.g. email). * Browser session support for the Worker version. ## Development diff --git a/src/axum_lib.rs b/src/axum_lib.rs index 71ef06c..b2690db 100644 --- a/src/axum_lib.rs +++ b/src/axum_lib.rs @@ -108,6 +108,7 @@ async fn token( private_key, config.base_url, config.require_secret, + config.eth_provider, &redis_client, ) .await?; @@ -272,6 +273,7 @@ async fn userinfo( }; let claims = oidc::userinfo( config.base_url, + config.eth_provider, private_key, bearer.map(|b| b.0 .0), payload, diff --git a/src/config.rs b/src/config.rs index 484a67d..060128a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ pub struct Config { pub default_clients: HashMap, // TODO secret is more complicated than that, and needs to be in the well-known config pub require_secret: bool, + pub eth_provider: Option, } impl Default for Config { @@ -27,6 +28,7 @@ impl Default for Config { redis_url: Url::parse("redis://localhost").unwrap(), default_clients: HashMap::default(), require_secret: false, + eth_provider: None, } } } diff --git a/src/db/mod.rs b/src/db/mod.rs index 382c845..7c2bf9a 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,6 +1,7 @@ use anyhow::Result; use async_trait::async_trait; use chrono::{offset::Utc, DateTime}; +use ethers_core::types::H160; use openidconnect::{core::CoreClientMetadata, Nonce}; use serde::{Deserialize, Serialize}; @@ -19,7 +20,7 @@ pub const ENTRY_LIFETIME: usize = 30; #[derive(Clone, Serialize, Deserialize)] pub struct CodeEntry { pub exchange_count: usize, - pub address: String, + pub address: H160, pub nonce: Option, pub client_id: String, pub auth_time: DateTime, diff --git a/src/oidc.rs b/src/oidc.rs index 78c2887..0a14d3c 100644 --- a/src/oidc.rs +++ b/src/oidc.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, Result}; use chrono::{Duration, Utc}; +use ethers_core::{types::H160, utils::to_checksum}; use headers::{self, authorization::Bearer}; use hex::FromHex; use iri_string::types::UriString; @@ -15,16 +16,16 @@ use openidconnect::{ registration::{EmptyAdditionalClientMetadata, EmptyAdditionalClientRegistrationResponse}, url::Url, AccessToken, Audience, AuthUrl, ClientId, ClientSecret, EmptyAdditionalClaims, - EmptyAdditionalProviderMetadata, EmptyExtraTokenFields, IssuerUrl, JsonWebKeyId, - JsonWebKeySetUrl, Nonce, PrivateSigningKey, RedirectUrl, RegistrationUrl, RequestUrl, - ResponseTypes, Scope, StandardClaims, SubjectIdentifier, TokenUrl, UserInfoUrl, + EmptyAdditionalProviderMetadata, EmptyExtraTokenFields, EndUserUsername, IssuerUrl, + JsonWebKeyId, JsonWebKeySetUrl, Nonce, PrivateSigningKey, RedirectUrl, RegistrationUrl, + RequestUrl, ResponseTypes, Scope, StandardClaims, SubjectIdentifier, TokenUrl, UserInfoUrl, }; use rsa::{pkcs1::ToRsaPrivateKey, RsaPrivateKey}; use serde::{Deserialize, Serialize}; use siwe::eip4361::{Message, Version}; use std::{str::FromStr, time}; use thiserror::Error; -use tracing::info; +use tracing::{error, info}; use urlencoding::decode; use uuid::Uuid; @@ -33,6 +34,12 @@ use super::db::*; #[cfg(not(target_arch = "wasm32"))] use siwe_oidc::db::*; +lazy_static::lazy_static! { + static ref SCOPES: [Scope; 2] = [ + Scope::new("openid".to_string()), + Scope::new("profile".to_string()), + ]; +} const SIGNING_ALG: [CoreJwsSigningAlgorithm; 1] = [CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256]; const KID: &str = "key1"; pub const METADATA_PATH: &str = "/.well-known/openid-configuration"; @@ -100,6 +107,7 @@ pub fn metadata(base_url: Url) -> Result { ), vec![ ResponseTypes::new(vec![CoreResponseType::Code]), + ResponseTypes::new(vec![CoreResponseType::IdToken]), ResponseTypes::new(vec![CoreResponseType::Token, CoreResponseType::IdToken]), ], vec![CoreSubjectIdentifierType::Pairwise], @@ -117,24 +125,14 @@ pub fn metadata(base_url: Url) -> Result { .map_err(|e| anyhow!("Unable to join URL: {}", e))?, ))) .set_userinfo_signing_alg_values_supported(Some(SIGNING_ALG.to_vec())) - .set_scopes_supported(Some(vec![ - Scope::new("openid".to_string()), - // Scope::new("email".to_string()), - // Scope::new("profile".to_string()), - ])) + .set_scopes_supported(Some(SCOPES.to_vec())) .set_claims_supported(Some(vec![ CoreClaimName::new("sub".to_string()), CoreClaimName::new("aud".to_string()), - // CoreClaimName::new("email".to_string()), - // CoreClaimName::new("email_verified".to_string()), CoreClaimName::new("exp".to_string()), CoreClaimName::new("iat".to_string()), CoreClaimName::new("iss".to_string()), - // CoreClaimName::new("name".to_string()), - // CoreClaimName::new("given_name".to_string()), - // CoreClaimName::new("family_name".to_string()), - // CoreClaimName::new("picture".to_string()), - // CoreClaimName::new("locale".to_string()), + CoreClaimName::new("preferred_username".to_string()), ])) .set_registration_endpoint(Some(RegistrationUrl::from_url( base_url @@ -150,6 +148,29 @@ pub fn metadata(base_url: Url) -> Result { Ok(pm) } +async fn resolve_name(eth_provider: Option, address: H160) -> String { + let address_string = to_checksum(&address, None); + if eth_provider.is_none() { + return address_string; + } + + use ethers_providers::{Http, Middleware, Provider}; + let provider = match Provider::::try_from(eth_provider.unwrap().to_string()) { + Ok(p) => p, + Err(e) => { + error!("Failed to initialise Eth provider: {}", e); + return address_string; + } + }; + match provider.lookup_address(address).await { + Ok(n) => n, + Err(e) => { + error!("Failed to resolve Eth domain: {}", e); + address_string + } + } +} + #[derive(Serialize, Deserialize)] pub struct TokenForm { pub code: String, @@ -165,6 +186,7 @@ pub async fn token( private_key: RsaPrivateKey, base_url: Url, require_secret: bool, + eth_provider: Option, db_client: &DBClientType, ) -> Result { let code_entry = if let Some(c) = db_client.get_code(form.code.to_string()).await? { @@ -218,7 +240,13 @@ pub async fn token( vec![Audience::new(client_id.clone())], Utc::now() + Duration::seconds(60), Utc::now(), - StandardClaims::new(SubjectIdentifier::new(code_entry.address)), + StandardClaims::new(SubjectIdentifier::new(to_checksum( + &code_entry.address, + None, + ))) + .set_preferred_username(Some(EndUserUsername::new( + resolve_name(eth_provider, code_entry.address).await, + ))), EmptyAdditionalClaims {}, ) .set_nonce(code_entry.nonce) @@ -340,8 +368,10 @@ pub async fn authorize( } let _response_type = params.response_type.as_ref().unwrap(); - if params.scope != Scope::new("openid".to_string()) { - return Err(anyhow!("Scope not supported").into()); + for scope in params.scope.as_str().split(' ') { + if !SCOPES.contains(&Scope::new(scope.to_string())) { + return Err(anyhow!("Scope not supported: {}", scope).into()); + } } let domain = params.redirect_uri.url().host().unwrap(); @@ -366,7 +396,7 @@ pub struct SiweCookie { #[serde(rename_all = "camelCase")] struct Web3ModalMessage { pub domain: String, - pub address: String, + pub address: H160, pub statement: String, pub uri: String, pub version: String, @@ -394,7 +424,7 @@ impl Web3ModalMessage { Ok(Message { domain: self.domain.clone().try_into()?, - address: <[u8; 20]>::from_hex(self.address.chars().skip(2).collect::())?, + address: self.address.0, statement: self.statement.to_string(), uri: UriString::from_str(&self.uri)?, version: Version::from_str(&self.version)?, @@ -529,6 +559,7 @@ pub enum UserInfoResponse { pub async fn userinfo( base_url: Url, + eth_provider: Option, private_key: RsaPrivateKey, bearer: Option, payload: UserInfoPayload, @@ -554,7 +585,13 @@ pub async fn userinfo( }; let response = CoreUserInfoClaims::new( - StandardClaims::new(SubjectIdentifier::new(code_entry.address)), + StandardClaims::new(SubjectIdentifier::new(to_checksum( + &code_entry.address, + None, + ))) + .set_preferred_username(Some(EndUserUsername::new( + resolve_name(eth_provider, code_entry.address).await, + ))), EmptyAdditionalClaims::default(), ) .set_issuer(Some(IssuerUrl::from_url(base_url.clone()))) diff --git a/src/worker_lib.rs b/src/worker_lib.rs index 02455a3..9de1841 100644 --- a/src/worker_lib.rs +++ b/src/worker_lib.rs @@ -12,6 +12,7 @@ use super::db::CFClient; use super::oidc::{self, CustomError, TokenForm, UserInfoPayload}; const BASE_URL_KEY: &str = "BASE_URL"; +const ETH_PROVIDER_KEY: &str = "ETH_PROVIDER"; const RSA_PEM_KEY: &str = "RSA_PEM"; // https://github.com/cloudflare/workers-rs/issues/64 @@ -61,12 +62,25 @@ pub async fn main(req: Request, env: Env) -> Result { UserInfoPayload { access_token: None } }; let base_url = ctx.var(BASE_URL_KEY)?.to_string().parse().unwrap(); + let eth_provider = ctx + .var(ETH_PROVIDER_KEY) + .map(|p| p.to_string().parse().unwrap()) + .ok(); let private_key = RsaPrivateKey::from_pkcs1_pem(&ctx.secret(RSA_PEM_KEY)?.to_string()) .map_err(|e| anyhow!("Failed to load private key: {}", e)) .unwrap(); let url = req.url()?; let db_client = CFClient { ctx, url }; - match oidc::userinfo(base_url, private_key, bearer, payload, &db_client).await { + match oidc::userinfo( + base_url, + eth_provider, + private_key, + bearer, + payload, + &db_client, + ) + .await + { Ok(oidc::UserInfoResponse::Json(r)) => Ok(Response::from_json(&r)?), Ok(oidc::UserInfoResponse::Jwt(r)) => { let mut headers = Headers::new(); @@ -144,6 +158,10 @@ pub async fn main(req: Request, env: Env) -> Result { .unwrap(); let base_url = ctx.var(BASE_URL_KEY)?.to_string().parse().unwrap(); let url = req.url()?; + let eth_provider = ctx + .var(ETH_PROVIDER_KEY) + .map(|p| p.to_string().parse().unwrap()) + .ok(); let db_client = CFClient { ctx, url }; let token_response = oidc::token( TokenForm { @@ -156,6 +174,7 @@ pub async fn main(req: Request, env: Env) -> Result { private_key, base_url, false, + eth_provider, &db_client, ) .await; diff --git a/wrangle_example.toml b/wrangle_example.toml index a3df0e9..05da884 100644 --- a/wrangle_example.toml +++ b/wrangle_example.toml @@ -12,6 +12,7 @@ kv_namespaces = [ [vars] WORKERS_RS_VERSION = "0.0.7" BASE_URL = "https://siweoidc.spruceid.xyz" +# ETH_PROVIDER = "" [durable_objects] bindings = [