From a405d523ca9f8422cd2faae0fcbd51a9eb3d98f3 Mon Sep 17 00:00:00 2001 From: Lewis Date: Sat, 11 Jul 2026 21:25:08 +0000 Subject: [PATCH] oauth: newtype client, token, device & request ids Lewis: May this revision serve well! --- crates/tranquil-db-traits/src/oauth.rs | 4 ++-- crates/tranquil-db-traits/src/user.rs | 2 +- crates/tranquil-oauth-server/src/sso_endpoints.rs | 2 +- crates/tranquil-oauth/src/client.rs | 23 ++++++++++++----------- crates/tranquil-oauth/src/lib.rs | 8 ++++---- crates/tranquil-oauth/src/types.rs | 69 ++++++++++++++++++++++++++------------------------------------------- crates/tranquil-pds/src/config.rs | 6 +++--- crates/tranquil-pds/tests/jwt_security.rs | 2 +- crates/tranquil-pds/tests/oauth_token_eviction.rs | 4 ++-- crates/tranquil-pds/tests/sso.rs | 6 +++--- crates/tranquil-types/src/lib.rs | 39 +++++++++++++++++++++++++++++++++++++++ crates/tranquil-api/src/identity/provision.rs | 15 +++++++-------- crates/tranquil-api/src/server/session.rs | 8 ++++---- crates/tranquil-api/src/server/trusted_devices.rs | 2 +- crates/tranquil-db/src/postgres/oauth.rs | 70 +++++++++++++++++++++++++++++++++++----------------------------------- crates/tranquil-db/src/postgres/user.rs | 2 +- crates/tranquil-oauth-server/src/endpoints/delegation.rs | 2 +- crates/tranquil-oauth-server/src/endpoints/par.rs | 9 ++++----- crates/tranquil-pds/src/oauth/client.rs | 15 +++++++++------ crates/tranquil-pds/src/oauth/mod.rs | 16 ++++++++-------- crates/tranquil-pds/src/oauth/verify.rs | 14 +------------- crates/tranquil-store/src/metastore/client.rs | 2 +- crates/tranquil-store/src/metastore/oauth_ops.rs | 33 ++++++++++++++++++--------------- crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs | 21 +++++++-------------- crates/tranquil-oauth-server/src/endpoints/authorize/login.rs | 24 ++++++++++-------------- crates/tranquil-oauth-server/src/endpoints/authorize/mod.rs | 9 ++++----- crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs | 5 ++--- crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs | 19 ++++++++----------- crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs | 18 ++++++++---------- crates/tranquil-oauth-server/src/endpoints/token/grants.rs | 18 ++++++++---------- crates/tranquil-oauth-server/src/endpoints/token/helpers.rs | 8 ++++---- crates/tranquil-oauth-server/src/endpoints/token/introspect.rs | 2 +- crates/tranquil-oauth-server/src/endpoints/token/types.rs | 13 ++++++++----- 33 file(s) changed, 244 insertion(s)(+), 246 deletion(s)(-) diff --git a/crates/tranquil-db-traits/src/oauth.rs b/crates/tranquil-db-traits/src/oauth.rs --- a/crates/tranquil-db-traits/src/oauth.rs +++ b/crates/tranquil-db-traits/src/oauth.rs @@ -59,7 +59,7 @@ pub struct TwoFactorChallenge { pub id: Uuid, pub did: Did, - pub request_uri: String, + pub request_uri: RequestId, pub code: String, pub attempts: i32, pub created_at: DateTime, @@ -68,7 +68,7 @@ #[derive(Debug, Clone)] pub struct TrustedDeviceRow { - pub id: String, + pub id: DeviceId, pub user_agent: Option, pub friendly_name: Option, pub trusted_at: Option>, diff --git a/crates/tranquil-db-traits/src/user.rs b/crates/tranquil-db-traits/src/user.rs --- a/crates/tranquil-db-traits/src/user.rs +++ b/crates/tranquil-db-traits/src/user.rs @@ -124,7 +124,7 @@ async fn get_oauth_token_with_user( &self, - token_id: &str, + token_id: &TokenId, ) -> Result, DbError>; async fn get_user_info_by_did(&self, did: &Did) -> Result, DbError>; diff --git a/crates/tranquil-oauth-server/src/sso_endpoints.rs b/crates/tranquil-oauth-server/src/sso_endpoints.rs --- a/crates/tranquil-oauth-server/src/sso_endpoints.rs +++ b/crates/tranquil-oauth-server/src/sso_endpoints.rs @@ -1256,7 +1256,7 @@ if let Err(e) = state .repos .oauth - .set_authorization_did(&request_id, &did_typed, None) + .set_authorization_did(&request_id, &did, None) .await { tracing::error!("Failed to set authorization DID: {:?}", e); diff --git a/crates/tranquil-oauth/src/client.rs b/crates/tranquil-oauth/src/client.rs --- a/crates/tranquil-oauth/src/client.rs +++ b/crates/tranquil-oauth/src/client.rs @@ -6,10 +6,11 @@ use crate::OAuthError; use crate::types::ClientAuth; +use tranquil_types::ClientId; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClientMetadata { - pub client_id: String, + pub client_id: ClientId, #[serde(skip_serializing_if = "Option::is_none")] pub client_name: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -38,7 +39,7 @@ impl Default for ClientMetadata { fn default() -> Self { Self { - client_id: String::new(), + client_id: ClientId::new(""), client_name: None, client_uri: None, logo_uri: None, @@ -97,7 +98,7 @@ } } - fn is_loopback_client(client_id: &str) -> bool { + fn is_loopback_client(client_id: &ClientId) -> bool { if let Ok(url) = reqwest::Url::parse(client_id) { url.scheme() == "http" && url.host_str() == Some("localhost") @@ -108,7 +109,7 @@ } } - fn build_loopback_metadata(client_id: &str) -> Result { + fn build_loopback_metadata(client_id: &ClientId) -> Result { let url = reqwest::Url::parse(client_id) .map_err(|_| OAuthError::InvalidClient("Invalid loopback client_id URL".into()))?; let mut redirect_uris = Vec::::new(); @@ -129,7 +130,7 @@ scope = Some("atproto".into()); } Ok(ClientMetadata { - client_id: client_id.into(), + client_id: client_id.clone(), client_name: Some("Loopback Client".into()), client_uri: None, logo_uri: None, @@ -145,13 +146,13 @@ }) } - pub async fn get(&self, client_id: &str) -> Result { + pub async fn get(&self, client_id: &ClientId) -> Result { if Self::is_loopback_client(client_id) { return Self::build_loopback_metadata(client_id); } { let cache = self.cache.read().await; - if let Some(cached) = cache.get(client_id) + if let Some(cached) = cache.get(client_id.as_str()) && cached.cached_at.elapsed().as_secs() < self.cache_ttl_secs { return Ok(cached.metadata.clone()); @@ -241,7 +242,7 @@ Ok(jwks) } - async fn fetch_metadata(&self, client_id: &str) -> Result { + async fn fetch_metadata(&self, client_id: &ClientId) -> Result { if !client_id.starts_with("http://") && !client_id.starts_with("https://") { return Err(OAuthError::InvalidClient( "client_id must be a URL".to_string(), @@ -257,7 +258,7 @@ } let response = self .http_client - .get(client_id) + .get(client_id.as_str()) .header("Accept", "application/json") .send() .await @@ -276,8 +277,8 @@ OAuthError::InvalidClient(format!("Invalid client metadata JSON: {}", e)) })?; if metadata.client_id.is_empty() { - metadata.client_id = client_id.to_string(); - } else if metadata.client_id != client_id { + metadata.client_id = client_id.clone(); + } else if metadata.client_id != *client_id { return Err(OAuthError::InvalidClient( "client_id in metadata does not match request".to_string(), )); diff --git a/crates/tranquil-oauth/src/lib.rs b/crates/tranquil-oauth/src/lib.rs --- a/crates/tranquil-oauth/src/lib.rs +++ b/crates/tranquil-oauth/src/lib.rs @@ -11,10 +11,10 @@ }; pub use error::OAuthError; pub use types::{ - AuthFlow, AuthFlowWithUser, AuthorizationRequestParameters, AuthorizationServerMetadata, - AuthorizedClientData, ClientAuth, Code, CodeChallengeMethod, DPoPClaims, DeviceData, DeviceId, - FlowAuthenticated, FlowAuthorized, FlowExpired, FlowNotAuthenticated, FlowNotAuthorized, - FlowPending, JwkPublicKey, Jwks, OAuthClientMetadata, ParResponse, Prompt, + AuthFlow, AuthFlowWithUser, AuthorizationCode, AuthorizationRequestParameters, + AuthorizationServerMetadata, AuthorizedClientData, ClientAuth, CodeChallengeMethod, DeviceData, + DeviceId, FlowAuthenticated, FlowAuthorized, FlowExpired, FlowNotAuthenticated, + FlowNotAuthorized, FlowPending, JwkPublicKey, Jwks, OAuthClientMetadata, ParResponse, Prompt, ProtectedResourceMetadata, RefreshToken, RefreshTokenState, RequestData, RequestId, ResponseMode, ResponseType, SessionId, TokenData, TokenId, TokenRequest, TokenResponse, }; diff --git a/crates/tranquil-oauth/src/types.rs b/crates/tranquil-oauth/src/types.rs --- a/crates/tranquil-oauth/src/types.rs +++ b/crates/tranquil-oauth/src/types.rs @@ -1,7 +1,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; -use tranquil_types::Did; +use tranquil_types::{ClientId, Did}; + +pub use tranquil_types::{AuthorizationCode, DeviceId, RefreshToken, RequestId, TokenId}; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] #[serde(transparent)] @@ -18,27 +20,8 @@ #[sqlx(transparent)] pub struct DeviceId(pub String); -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] -#[serde(transparent)] -#[sqlx(transparent)] -pub struct SessionId(pub String); - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] -#[serde(transparent)] -#[sqlx(transparent)] -pub struct Code(pub String); - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] -#[serde(transparent)] -#[sqlx(transparent)] -pub struct RefreshToken(pub String); - -impl RequestId { - pub fn generate() -> Self { - Self(format!( - "urn:ietf:params:oauth:request_uri:{}", - uuid::Uuid::new_v4() - )) + pub fn as_str(&self) -> &str { + &self.0 } } @@ -155,7 +138,7 @@ #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthorizationRequestParameters { pub response_type: ResponseType, - pub client_id: String, + pub client_id: ClientId, pub redirect_uri: String, pub scope: Option, pub state: Option, @@ -171,13 +154,13 @@ #[derive(Debug, Clone)] pub struct RequestData { - pub client_id: String, + pub client_id: ClientId, pub client_auth: Option, pub parameters: AuthorizationRequestParameters, pub expires_at: DateTime, pub did: Option, pub device_id: Option, - pub code: Option, + pub code: Option, pub controller_did: Option, } @@ -196,12 +179,12 @@ pub created_at: DateTime, pub updated_at: DateTime, pub expires_at: DateTime, - pub client_id: String, + pub client_id: ClientId, pub client_auth: ClientAuth, pub device_id: Option, pub parameters: AuthorizationRequestParameters, pub details: Option, - pub code: Option, + pub code: Option, pub current_refresh_token: Option, pub scope: Option, pub controller_did: Option, @@ -215,7 +198,7 @@ #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OAuthClientMetadata { - pub client_id: String, + pub client_id: ClientId, pub client_name: Option, pub client_uri: Option, pub logo_uri: Option, @@ -270,7 +253,7 @@ pub token_type: String, pub expires_in: u64, #[serde(skip_serializing_if = "Option::is_none")] - pub refresh_token: Option, + pub refresh_token: Option, #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -280,11 +263,11 @@ #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TokenRequest { pub grant_type: String, - pub code: Option, + pub code: Option, pub redirect_uri: Option, pub code_verifier: Option, - pub refresh_token: Option, - pub client_id: Option, + pub refresh_token: Option, + pub client_id: Option, pub client_secret: Option, } @@ -320,7 +303,7 @@ #[derive(Debug, Clone)] pub struct FlowPending { pub parameters: AuthorizationRequestParameters, - pub client_id: String, + pub client_id: ClientId, pub client_auth: Option, pub expires_at: DateTime, pub controller_did: Option, @@ -329,7 +312,7 @@ #[derive(Debug, Clone)] pub struct FlowAuthenticated { pub parameters: AuthorizationRequestParameters, - pub client_id: String, + pub client_id: ClientId, pub client_auth: Option, pub expires_at: DateTime, pub did: Did, @@ -340,12 +323,12 @@ #[derive(Debug, Clone)] pub struct FlowAuthorized { pub parameters: AuthorizationRequestParameters, - pub client_id: String, + pub client_id: ClientId, pub client_auth: Option, pub expires_at: DateTime, pub did: Did, pub device_id: Option, - pub code: Code, + pub code: AuthorizationCode, pub controller_did: Option, } @@ -444,7 +427,7 @@ } } - pub fn client_id(&self) -> &str { + pub fn client_id(&self) -> &ClientId { match self { AuthFlowWithUser::Authenticated(a) => &a.client_id, AuthFlowWithUser::Authorized(a) => &a.client_id, @@ -522,16 +505,16 @@ fn make_request_data( did: Option, - code: Option, + code: Option, expires_in: Duration, ) -> RequestData { RequestData { - client_id: "test-client".into(), + client_id: ClientId::new("test-client"), client_auth: None, parameters: AuthorizationRequestParameters { response_type: ResponseType::Code, - client_id: "test-client".into(), - redirect_uri: "https://example.com/callback".into(), + client_id: ClientId::new("test-client"), + redirect_uri: "https://oyster.cafe/callback".into(), scope: Some("atproto".into()), state: None, code_challenge: "test".into(), @@ -554,8 +537,8 @@ s.parse().expect("valid test DID") } - fn test_code(s: &str) -> Code { - Code(s.to_string()) + fn test_code(s: &str) -> AuthorizationCode { + AuthorizationCode::new(s) } #[test] diff --git a/crates/tranquil-pds/src/config.rs b/crates/tranquil-pds/src/config.rs --- a/crates/tranquil-pds/src/config.rs +++ b/crates/tranquil-pds/src/config.rs @@ -126,7 +126,7 @@ &self.dpop_secret } - pub fn sign_device_cookie(&self, device_id: &str) -> String { + pub fn sign_device_cookie(&self, device_id: &crate::types::DeviceId) -> String { use hmac::Mac; type HmacSha256 = hmac::Hmac; @@ -144,7 +144,7 @@ format!("{}.{}.{}", device_id, timestamp, signature) } - pub fn verify_device_cookie(&self, cookie_value: &str) -> Option { + pub fn verify_device_cookie(&self, cookie_value: &str) -> Option { use hmac::Mac; type HmacSha256 = hmac::Hmac; @@ -181,7 +181,7 @@ .ct_eq(expected_signature.as_bytes()) .into() { - Some(device_id.to_string()) + Some(crate::types::DeviceId::new(device_id)) } else { None } diff --git a/crates/tranquil-pds/tests/jwt_security.rs b/crates/tranquil-pds/tests/jwt_security.rs --- a/crates/tranquil-pds/tests/jwt_security.rs +++ b/crates/tranquil-pds/tests/jwt_security.rs @@ -145,7 +145,7 @@ let key_bytes = generate_user_key(); let did = "did:plc:test"; - let refresh_token = create_refresh_token(did, &key_bytes).expect("create refresh token"); + let refresh_token = create_refresh_token(&did, &key_bytes).expect("create refresh token"); let result = verify_access_token(&refresh_token, &key_bytes); assert!(result.is_err(), "Refresh token as access must be rejected"); assert!( diff --git a/crates/tranquil-pds/tests/oauth_token_eviction.rs b/crates/tranquil-pds/tests/oauth_token_eviction.rs --- a/crates/tranquil-pds/tests/oauth_token_eviction.rs +++ b/crates/tranquil-pds/tests/oauth_token_eviction.rs @@ -39,10 +39,10 @@ } fn make_token_data(did: &Did, token_id: &str, created_at: DateTime) -> TokenData { - let client_id = "https://squid.nel.pet/client".to_string(); + let client_id = tranquil_types::ClientId::from("https://squid.nel.pet/client".to_string()); TokenData { did: did.clone(), - token_id: TokenId(token_id.to_string()), + token_id: TokenId::from(token_id.to_string()), created_at, updated_at: created_at, expires_at: created_at + Duration::hours(1), diff --git a/crates/tranquil-pds/tests/sso.rs b/crates/tranquil-pds/tests/sso.rs --- a/crates/tranquil-pds/tests/sso.rs +++ b/crates/tranquil-pds/tests/sso.rs @@ -619,12 +619,12 @@ fn test_request_data() -> RequestData { RequestData { - client_id: "https://test.example.com".to_string(), + client_id: tranquil_types::ClientId::from("https://squid.oyster.cafe".to_string()), client_auth: None, parameters: AuthorizationRequestParameters { response_type: ResponseType::Code, - client_id: "https://test.example.com".to_string(), - redirect_uri: "https://test.example.com/callback".to_string(), + client_id: tranquil_types::ClientId::from("https://squid.oyster.cafe".to_string()), + redirect_uri: "https://squid.oyster.cafe/callback".to_string(), scope: Some("atproto".to_string()), state: Some("teststate".to_string()), code_challenge: "testchallenge".to_string(), diff --git a/crates/tranquil-types/src/lib.rs b/crates/tranquil-types/src/lib.rs --- a/crates/tranquil-types/src/lib.rs +++ b/crates/tranquil-types/src/lib.rs @@ -727,6 +727,12 @@ pub struct TokenId; } +impl TokenId { + pub fn generate() -> Self { + Self(uuid::Uuid::new_v4().to_string()) + } +} + simple_string_newtype! { pub struct ClientId; } @@ -735,8 +741,23 @@ pub struct DeviceId; } +impl DeviceId { + pub fn generate() -> Self { + Self(uuid::Uuid::new_v4().to_string()) + } +} + simple_string_newtype! { pub struct RequestId; +} + +impl RequestId { + pub fn generate() -> Self { + Self(format!( + "urn:ietf:params:oauth:request_uri:{}", + uuid::Uuid::new_v4() + )) + } } simple_string_newtype! { @@ -747,8 +768,26 @@ pub struct AuthorizationCode; } +impl AuthorizationCode { + pub fn generate() -> Self { + Self(generate_url_safe_secret()) + } +} + simple_string_newtype! { pub struct RefreshToken; +} + +impl RefreshToken { + pub fn generate() -> Self { + Self(generate_url_safe_secret()) + } +} + +fn generate_url_safe_secret() -> String { + use rand::Rng; + let bytes: [u8; 32] = rand::thread_rng().r#gen(); + base64::Engine::encode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, bytes) } simple_string_newtype! { diff --git a/crates/tranquil-api/src/identity/provision.rs b/crates/tranquil-api/src/identity/provision.rs --- a/crates/tranquil-api/src/identity/provision.rs +++ b/crates/tranquil-api/src/identity/provision.rs @@ -264,19 +264,18 @@ scope: &str, controller_did: Option<&Did>, ) -> Result { - let access_meta = - tranquil_pds::auth::create_access_token_with_metadata(did_str, signing_key_bytes).map_err( + let access_meta = tranquil_pds::auth::create_access_token_with_metadata(did, signing_key_bytes) + .map_err(|e| { + tracing::error!("Error creating access token: {:?}", e); + ApiError::InternalError(None) + })?; + let refresh_meta = + tranquil_pds::auth::create_refresh_token_with_metadata(did, signing_key_bytes).map_err( |e| { tracing::error!("Error creating access token: {:?}", e); ApiError::InternalError(None) }, )?; - let refresh_meta = - tranquil_pds::auth::create_refresh_token_with_metadata(did_str, signing_key_bytes) - .map_err(|e| { - tracing::error!("Error creating refresh token: {:?}", e); - ApiError::InternalError(None) - })?; let session_data = tranquil_db_traits::SessionTokenCreate { did: did.clone(), access_jti: access_meta.jti.clone(), diff --git a/crates/tranquil-api/src/server/session.rs b/crates/tranquil-api/src/server/session.rs --- a/crates/tranquil-api/src/server/session.rs +++ b/crates/tranquil-api/src/server/session.rs @@ -1130,13 +1130,13 @@ Ok(Json(ListSessionsOutput { sessions })) } -fn extract_client_name(client_id: &str) -> String { +fn extract_client_name(client_id: &tranquil_types::ClientId) -> String { if client_id.starts_with("http://localhost") || client_id.starts_with("http://127.0.0.1") { "Localhost App".to_string() - } else if let Ok(parsed) = reqwest::Url::parse(client_id) { + } else if let Ok(parsed) = reqwest::Url::parse(client_id.as_str()) { parsed.host_str().unwrap_or("Unknown App").to_string() } else { - client_id.to_string() + client_id.as_str().to_string() } } @@ -1210,7 +1210,7 @@ .delete_sessions_by_did(&auth.did) .await .log_db_err("revoking JWT sessions")?; - let jti_typed = TokenId::from(jti.clone()); + let token_id = TokenId::from(jti.clone().into_inner()); state .repos .oauth diff --git a/crates/tranquil-api/src/server/trusted_devices.rs b/crates/tranquil-api/src/server/trusted_devices.rs --- a/crates/tranquil-api/src/server/trusted_devices.rs +++ b/crates/tranquil-api/src/server/trusted_devices.rs @@ -52,7 +52,7 @@ #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct TrustedDevice { - pub id: String, + pub id: DeviceId, pub user_agent: Option, pub friendly_name: Option, pub trusted_at: Option>, diff --git a/crates/tranquil-db/src/postgres/oauth.rs b/crates/tranquil-db/src/postgres/oauth.rs --- a/crates/tranquil-db/src/postgres/oauth.rs +++ b/crates/tranquil-db/src/postgres/oauth.rs @@ -65,13 +65,13 @@ data.created_at, data.updated_at, data.expires_at, - data.client_id, + data.client_id.as_str(), client_auth_json, - data.device_id.as_ref().map(|d| d.0.as_str()), + data.device_id.as_deref(), parameters_json, data.details, - data.code.as_ref().map(|c| c.0.as_str()), - data.current_refresh_token.as_ref().map(|r| r.0.as_str()), + data.code.as_deref(), + data.current_refresh_token.as_deref(), data.scope, data.controller_did.as_ref().map(|d| d.as_str()), ) @@ -100,17 +100,17 @@ .did .parse() .map_err(|_| DbError::Other("Invalid DID in token".into()))?, - token_id: OAuthTokenId(r.token_id), + token_id: TokenId::from(r.token_id), created_at: r.created_at, updated_at: r.updated_at, expires_at: r.expires_at, - client_id: r.client_id, + client_id: ClientId::from(r.client_id), client_auth: from_json(r.client_auth)?, - device_id: r.device_id.map(OAuthDeviceId), + device_id: r.device_id.map(DeviceId::from), parameters: from_json(r.parameters)?, details: r.details, - code: r.code.map(OAuthCode), - current_refresh_token: r.current_refresh_token.map(OAuthRefreshToken), + code: r.code.map(AuthorizationCode::from), + current_refresh_token: r.current_refresh_token.map(RefreshToken::from), scope: r.scope, controller_did: r .controller_did @@ -146,17 +146,17 @@ .did .parse() .map_err(|_| DbError::Other("Invalid DID in token".into()))?, - token_id: OAuthTokenId(r.token_id), + token_id: TokenId::from(r.token_id), created_at: r.created_at, updated_at: r.updated_at, expires_at: r.expires_at, - client_id: r.client_id, + client_id: ClientId::from(r.client_id), client_auth: from_json(r.client_auth)?, - device_id: r.device_id.map(OAuthDeviceId), + device_id: r.device_id.map(DeviceId::from), parameters: from_json(r.parameters)?, details: r.details, - code: r.code.map(OAuthCode), - current_refresh_token: r.current_refresh_token.map(OAuthRefreshToken), + code: r.code.map(AuthorizationCode::from), + current_refresh_token: r.current_refresh_token.map(RefreshToken::from), scope: r.scope, controller_did: r .controller_did @@ -195,17 +195,17 @@ .did .parse() .map_err(|_| DbError::Other("Invalid DID in token".into()))?, - token_id: OAuthTokenId(r.token_id), + token_id: TokenId::from(r.token_id), created_at: r.created_at, updated_at: r.updated_at, expires_at: r.expires_at, - client_id: r.client_id, + client_id: ClientId::from(r.client_id), client_auth: from_json(r.client_auth)?, - device_id: r.device_id.map(OAuthDeviceId), + device_id: r.device_id.map(DeviceId::from), parameters: from_json(r.parameters)?, details: r.details, - code: r.code.map(OAuthCode), - current_refresh_token: r.current_refresh_token.map(OAuthRefreshToken), + code: r.code.map(AuthorizationCode::from), + current_refresh_token: r.current_refresh_token.map(RefreshToken::from), scope: r.scope, controller_did: r .controller_did @@ -328,17 +328,17 @@ .did .parse() .map_err(|_| DbError::Other("Invalid DID in token".into()))?, - token_id: OAuthTokenId(r.token_id), + token_id: TokenId::from(r.token_id), created_at: r.created_at, updated_at: r.updated_at, expires_at: r.expires_at, - client_id: r.client_id, + client_id: ClientId::from(r.client_id), client_auth: from_json(r.client_auth)?, - device_id: r.device_id.map(OAuthDeviceId), + device_id: r.device_id.map(DeviceId::from), parameters: from_json(r.parameters)?, details: r.details, - code: r.code.map(OAuthCode), - current_refresh_token: r.current_refresh_token.map(OAuthRefreshToken), + code: r.code.map(AuthorizationCode::from), + current_refresh_token: r.current_refresh_token.map(RefreshToken::from), scope: r.scope, controller_did: r .controller_did @@ -437,8 +437,8 @@ "#, request_id.as_str(), data.did.as_ref().map(|d| d.as_str()), - data.device_id.as_ref().map(|d| d.0.as_str()), - data.client_id, + data.device_id.as_deref(), + data.client_id.as_str(), client_auth_json, parameters_json, data.expires_at, @@ -473,7 +473,7 @@ }; let parameters: AuthorizationRequestParameters = from_json(r.parameters)?; Ok(Some(RequestData { - client_id: r.client_id, + client_id: ClientId::from(r.client_id), client_auth, parameters, expires_at: r.expires_at, @@ -482,8 +482,8 @@ .map(|s| s.parse()) .transpose() .map_err(|_| DbError::Other("Invalid DID in DB".into()))?, - device_id: r.device_id.map(OAuthDeviceId), - code: r.code.map(OAuthCode), + device_id: r.device_id.map(DeviceId::from), + code: r.code.map(AuthorizationCode::from), controller_did: r .controller_did .map(|s| s.parse()) @@ -567,7 +567,7 @@ }; let parameters: AuthorizationRequestParameters = from_json(r.parameters)?; Ok(Some(RequestData { - client_id: r.client_id, + client_id: ClientId::from(r.client_id), client_auth, parameters, expires_at: r.expires_at, @@ -576,8 +576,8 @@ .map(|s| s.parse()) .transpose() .map_err(|_| DbError::Other("Invalid DID in DB".into()))?, - device_id: r.device_id.map(OAuthDeviceId), - code: r.code.map(OAuthCode), + device_id: r.device_id.map(DeviceId::from), + code: r.code.map(AuthorizationCode::from), controller_did: r .controller_did .map(|s| s.parse()) @@ -906,7 +906,7 @@ Ok(TwoFactorChallenge { id: row.id, did: Did::from(row.did), - request_uri: row.request_uri, + request_uri: RequestId::from(row.request_uri), code: row.code, attempts: row.attempts, created_at: row.created_at, @@ -932,7 +932,7 @@ Ok(row.map(|r| TwoFactorChallenge { id: r.id, did: Did::from(r.did), - request_uri: r.request_uri, + request_uri: RequestId::from(r.request_uri), code: r.code, attempts: r.attempts, created_at: r.created_at, @@ -1143,7 +1143,7 @@ Ok(rows .into_iter() .map(|r| TrustedDeviceRow { - id: r.id, + id: DeviceId::from(r.id), user_agent: r.user_agent, friendly_name: r.friendly_name, trusted_at: r.trusted_at, diff --git a/crates/tranquil-db/src/postgres/user.rs b/crates/tranquil-db/src/postgres/user.rs --- a/crates/tranquil-db/src/postgres/user.rs +++ b/crates/tranquil-db/src/postgres/user.rs @@ -192,7 +192,7 @@ async fn get_oauth_token_with_user( &self, - token_id: &str, + token_id: &TokenId, ) -> Result, DbError> { let row = sqlx::query!( r#"SELECT t.did, t.expires_at, u.deactivated_at, u.takedown_ref, u.is_admin, diff --git a/crates/tranquil-oauth-server/src/endpoints/delegation.rs b/crates/tranquil-oauth-server/src/endpoints/delegation.rs --- a/crates/tranquil-oauth-server/src/endpoints/delegation.rs +++ b/crates/tranquil-oauth-server/src/endpoints/delegation.rs @@ -450,7 +450,7 @@ #[derive(Debug, Deserialize)] pub struct CrossPdsCallbackParams { - pub code: String, + pub code: tranquil_types::AuthorizationCode, pub state: String, pub iss: Option, } diff --git a/crates/tranquil-oauth-server/src/endpoints/par.rs b/crates/tranquil-oauth-server/src/endpoints/par.rs --- a/crates/tranquil-oauth-server/src/endpoints/par.rs +++ b/crates/tranquil-oauth-server/src/endpoints/par.rs @@ -9,14 +9,14 @@ }; use tranquil_pds::rate_limit::{OAuthParLimit, OAuthRateLimited}; use tranquil_pds::state::AppState; -use tranquil_types::RequestId as RequestIdType; +use tranquil_types::{ClientId, JwkThumbprint}; const PAR_EXPIRY_SECONDS: i64 = 600; #[derive(Debug, Deserialize)] pub struct ParRequest { pub response_type: String, - pub client_id: String, + pub client_id: ClientId, pub redirect_uri: String, #[serde(default)] pub scope: Option, @@ -113,11 +113,10 @@ code: None, controller_did: None, }; - let request_id_typed = RequestIdType::from(request_id.0.clone()); state .repos .oauth - .create_authorization_request(&request_id_typed, &request_data) + .create_authorization_request(&request_id, &request_data) .await .map_err(tranquil_pds::oauth::db_err_to_oauth)?; tokio::spawn({ @@ -131,7 +130,7 @@ Ok(( axum::http::StatusCode::CREATED, Json(ParResponse { - request_uri: request_id.0, + request_uri: request_id.into_inner(), expires_in: u64::try_from(PAR_EXPIRY_SECONDS).unwrap_or(600), }), )) diff --git a/crates/tranquil-pds/src/oauth/client.rs b/crates/tranquil-pds/src/oauth/client.rs --- a/crates/tranquil-pds/src/oauth/client.rs +++ b/crates/tranquil-pds/src/oauth/client.rs @@ -10,7 +10,7 @@ AuthorizationServerMetadata, ClientMetadata, compute_es256_jkt, compute_pkce_challenge, create_dpop_proof, }; -use tranquil_types::Did; +use tranquil_types::{AuthorizationCode, ClientId, Did}; use crate::cache::Cache; @@ -46,13 +46,16 @@ } pub struct DelegationOAuthUrls { - pub client_id: String, + pub client_id: ClientId, pub redirect_uri: String, } pub fn delegation_oauth_urls(hostname: &str) -> DelegationOAuthUrls { DelegationOAuthUrls { - client_id: format!("https://{}/oauth/delegation/client-metadata", hostname), + client_id: ClientId::new(format!( + "https://{}/oauth/delegation/client-metadata", + hostname + )), redirect_uri: format!("https://{}/oauth/delegation/callback", hostname), } } @@ -278,7 +281,7 @@ let mut params = vec![ ("response_type", "code".to_string()), - ("client_id", urls.client_id.clone()), + ("client_id", urls.client_id.to_string()), ("redirect_uri", urls.redirect_uri.clone()), ("scope", "atproto".to_string()), ("state", state.clone()), @@ -340,8 +343,8 @@ pub async fn exchange_code( &self, auth_state: &CrossPdsAuthState, - code: &str, - client_id: &str, + code: &AuthorizationCode, + client_id: &ClientId, redirect_uri: &str, ) -> Result { let meta = self diff --git a/crates/tranquil-pds/src/oauth/mod.rs b/crates/tranquil-pds/src/oauth/mod.rs --- a/crates/tranquil-pds/src/oauth/mod.rs +++ b/crates/tranquil-pds/src/oauth/mod.rs @@ -9,14 +9,14 @@ } pub use tranquil_oauth::{ - AuthFlow, AuthFlowWithUser, AuthorizationRequestParameters, AuthorizationServerMetadata, - AuthorizedClientData, ClientAuth, ClientMetadata, ClientMetadataCache, Code, - CodeChallengeMethod, DPoPClaims, DPoPJwk, DPoPProofHeader, DPoPProofPayload, DPoPVerifier, - DPoPVerifyResult, DeviceData, DeviceId, FlowAuthenticated, FlowAuthorized, FlowExpired, - FlowNotAuthenticated, FlowNotAuthorized, FlowPending, JwkPublicKey, Jwks, OAuthClientMetadata, - OAuthError, ParResponse, Prompt, ProtectedResourceMetadata, RefreshToken, RefreshTokenState, - RequestData, RequestId, ResponseMode, ResponseType, SessionId, TokenData, TokenId, - TokenRequest, TokenResponse, compute_access_token_hash, compute_jwk_thumbprint, + AuthFlow, AuthFlowWithUser, AuthorizationCode, AuthorizationRequestParameters, + AuthorizationServerMetadata, AuthorizedClientData, ClientAuth, ClientMetadata, + ClientMetadataCache, CodeChallengeMethod, DPoPJwk, DPoPProofHeader, DPoPProofPayload, + DPoPVerifier, DPoPVerifyResult, DeviceData, DeviceId, FlowAuthenticated, FlowAuthorized, + FlowExpired, FlowNotAuthenticated, FlowNotAuthorized, FlowPending, JwkPublicKey, Jwks, + OAuthClientMetadata, OAuthError, ParResponse, Prompt, ProtectedResourceMetadata, RefreshToken, + RefreshTokenState, RequestData, RequestId, ResponseMode, ResponseType, SessionId, TokenData, + TokenId, TokenRequest, TokenResponse, compute_access_token_hash, compute_jwk_thumbprint, compute_pkce_challenge, verify_client_auth, }; diff --git a/crates/tranquil-pds/src/oauth/verify.rs b/crates/tranquil-pds/src/oauth/verify.rs --- a/crates/tranquil-pds/src/oauth/verify.rs +++ b/crates/tranquil-pds/src/oauth/verify.rs @@ -23,7 +23,6 @@ pub struct OAuthTokenInfo { pub did: Did, pub token_id: TokenId, - pub client_id: ClientId, pub scope: Option, pub dpop_jkt: Option, pub controller_did: Option, @@ -101,7 +100,7 @@ Ok(VerifyResult { did, token_id, - client_id: ClientId::from(token_data.client_id), + client_id: token_data.client_id, scope: token_data.scope, }) } @@ -171,16 +170,6 @@ .get("scope") .and_then(|s| s.as_str()) .map(|s| s.to_string()); - let dpop_jkt = payload - .get("cnf") - .and_then(|c| c.get("jkt")) - .and_then(|j| j.as_str()) - .map(|s| s.to_string()); - let client_id_str = payload - .get("client_id") - .and_then(|c| c.as_str()) - .unwrap_or_default(); - let client_id = ClientId::new(client_id_str); let controller_did = payload .get("act") .and_then(|a| a.get("sub")) @@ -193,7 +182,6 @@ Ok(OAuthTokenInfo { did, token_id, - client_id, scope, dpop_jkt, controller_did, diff --git a/crates/tranquil-store/src/metastore/client.rs b/crates/tranquil-store/src/metastore/client.rs --- a/crates/tranquil-store/src/metastore/client.rs +++ b/crates/tranquil-store/src/metastore/client.rs @@ -3343,7 +3343,7 @@ async fn get_oauth_token_with_user( &self, - token_id: &str, + token_id: &TokenId, ) -> Result, DbError> { let (tx, rx) = oneshot::channel(); self.pool diff --git a/crates/tranquil-store/src/metastore/oauth_ops.rs b/crates/tranquil-store/src/metastore/oauth_ops.rs --- a/crates/tranquil-store/src/metastore/oauth_ops.rs +++ b/crates/tranquil-store/src/metastore/oauth_ops.rs @@ -103,11 +103,11 @@ fn token_value_to_data(&self, v: &OAuthTokenValue) -> Result { let did = Did::new(v.did.clone()) .map_err(|_| MetastoreError::CorruptData("invalid did in oauth token"))?; - let token_id = tranquil_oauth::TokenId(v.token_id.clone()); + let token_id = tranquil_oauth::TokenId::from(v.token_id.clone()); let refresh_token = if v.refresh_token.is_empty() { None } else { - Some(tranquil_oauth::RefreshToken(v.refresh_token.clone())) + Some(tranquil_oauth::RefreshToken::from(v.refresh_token.clone())) }; Ok(TokenData { @@ -116,7 +116,7 @@ created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), updated_at: DateTime::from_timestamp_millis(v.updated_at_ms).unwrap_or_default(), expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), - client_id: v.client_id.clone(), + client_id: tranquil_types::ClientId::from(v.client_id.clone()), client_auth: tranquil_oauth::ClientAuth::None, device_id: None, parameters: serde_json::from_str(&v.parameters_json) @@ -189,7 +189,7 @@ .map_err(|_| MetastoreError::CorruptData("corrupt oauth client_auth"))?; Ok(RequestData { - client_id: v.client_id.clone(), + client_id: tranquil_types::ClientId::from(v.client_id.clone()), client_auth, parameters, expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), @@ -202,8 +202,11 @@ device_id: v .device_id .as_ref() - .map(|d| tranquil_oauth::DeviceId(d.clone())), - code: v.code.as_ref().map(|c| tranquil_oauth::Code(c.clone())), + .map(|d| tranquil_oauth::DeviceId::from(d.clone())), + code: v + .code + .as_ref() + .map(|c| tranquil_oauth::AuthorizationCode::from(c.clone())), controller_did: v .controller_did .as_ref() @@ -217,7 +220,7 @@ fn data_to_request_value(&self, data: &RequestData) -> OAuthRequestValue { OAuthRequestValue { - client_id: data.client_id.clone(), + client_id: data.client_id.to_string(), client_auth_json: data .client_auth .as_ref() @@ -225,8 +228,8 @@ parameters_json: serde_json::to_string(&data.parameters).unwrap_or_default(), expires_at_ms: data.expires_at.timestamp_millis(), did: data.did.as_ref().map(|d| d.to_string()), - device_id: data.device_id.as_ref().map(|d| d.0.clone()), - code: data.code.as_ref().map(|c| c.0.clone()), + device_id: data.device_id.as_ref().map(|d| d.to_string()), + code: data.code.as_ref().map(|c| c.to_string()), controller_did: data.controller_did.as_ref().map(|d| d.to_string()), } } @@ -239,8 +242,8 @@ let value = OAuthTokenValue { family_id, did: data.did.to_string(), - client_id: data.client_id.clone(), - token_id: data.token_id.0.clone(), + client_id: data.client_id.to_string(), + token_id: data.token_id.to_string(), refresh_token: data .current_refresh_token .as_ref() @@ -1100,7 +1103,7 @@ Ok(TwoFactorChallenge { id, did: did.clone(), - request_uri: request_uri.as_str().to_owned(), + request_uri: request_uri.clone(), code, attempts: 0, created_at: now, @@ -1140,7 +1143,7 @@ id: Uuid::from_bytes(v.id), did: Did::new(v.did) .map_err(|_| MetastoreError::CorruptData("invalid did in 2fa challenge"))?, - request_uri: v.request_uri, + request_uri: RequestId::from(v.request_uri), code: v.code, attempts: v.attempts, created_at: DateTime::from_timestamp_millis(v.created_at_ms).unwrap_or_default(), @@ -1358,7 +1361,7 @@ match DeviceTrustValue::deserialize(&val_bytes) { Some(v) => { acc.push(TrustedDeviceRow { - id: v.device_id, + id: DeviceId::from(v.device_id), user_agent: v.user_agent, friendly_name: v.friendly_name, trusted_at: v.trusted_at_ms.and_then(DateTime::from_timestamp_millis), @@ -1604,7 +1607,7 @@ fn default_parameters(client_id: &str) -> tranquil_oauth::AuthorizationRequestParameters { tranquil_oauth::AuthorizationRequestParameters { response_type: tranquil_oauth::ResponseType::Code, - client_id: client_id.to_owned(), + client_id: tranquil_types::ClientId::new(client_id), redirect_uri: String::new(), scope: None, state: None, diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs --- a/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/consent.rs @@ -13,7 +13,7 @@ #[derive(Debug, Serialize)] pub struct ConsentResponse { pub request_uri: String, - pub client_id: String, + pub client_id: ClientId, pub client_name: Option, pub client_uri: Option, pub logo_uri: Option, @@ -136,11 +136,10 @@ } }; let requested_scopes: Vec<&str> = expanded_scope_str.split_whitespace().collect(); - let consent_client_id = ClientId::from(request_data.parameters.client_id.clone()); let preferences = state .repos .oauth - .get_scope_preferences(&did, &consent_client_id) + .get_scope_preferences(&did, &request_data.parameters.client_id) .await .unwrap_or_default(); let pref_map: std::collections::HashMap<_, _> = preferences @@ -152,7 +151,7 @@ let show_consent = should_show_consent( state.repos.oauth.as_ref(), &did, - &consent_client_id, + &request_data.parameters.client_id, &requested_scope_strings, ) .await @@ -385,11 +384,10 @@ granted: form.approved_scopes.contains(&s.to_string()), }) .collect(); - let consent_post_client_id = ClientId::from(request_data.parameters.client_id.clone()); let _ = state .repos .oauth - .upsert_scope_preferences(&did, &consent_post_client_id, &preferences) + .upsert_scope_preferences(&did, &request_data.parameters.client_id, &preferences) .await; } if let Err(e) = state @@ -400,20 +398,15 @@ { tracing::warn!("Failed to update request scope: {:?}", e); } - let code = Code::generate(); - let consent_post_device_id = request_data - .device_id - .as_ref() - .map(|d| DeviceIdType::new(d.0.clone())); - let consent_post_code = AuthorizationCode::from(code.0.clone()); + let code = AuthorizationCode::generate(); if state .repos .oauth .update_authorization_request( &consent_post_request_id, &did, - consent_post_device_id.as_ref(), - &consent_post_code, + request_data.device_id.as_ref(), + &code, ) .await .is_err() diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/login.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/login.rs --- a/crates/tranquil-oauth-server/src/endpoints/authorize/login.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/login.rs @@ -606,14 +606,13 @@ } } } - let mut device_id: Option = extract_device_cookie(&headers); + let mut device_id: Option = extract_device_cookie(&headers); let mut new_cookie: Option = None; if form.remember_device { let final_device_id = if let Some(existing_id) = &device_id { existing_id.clone() } else { let new_id = DeviceId::generate(); - let new_device_id_typed = DeviceIdType::new(new_id.0.clone()); let device_data = DeviceData { session_id: SessionId::generate(), user_agent: extract_user_agent(&headers), @@ -623,14 +622,14 @@ if state .repos .oauth - .create_device(&new_device_id_typed, &device_data) + .create_device(&new_id, &device_data) .await .is_ok() { - new_cookie = Some(make_device_cookie(&new_device_id_typed)); - device_id = Some(new_device_id_typed.clone()); + new_cookie = Some(make_device_cookie(&new_id)); + device_id = Some(new_id.clone()); } - new_device_id_typed + new_id }; let _ = state .repos @@ -657,11 +656,10 @@ .split_whitespace() .map(|s| s.to_string()) .collect(); - let client_id_typed = ClientId::from(request_data.parameters.client_id.clone()); let needs_consent = should_show_consent( state.repos.oauth.as_ref(), &user.did, - &client_id_typed, + &request_data.parameters.client_id, &requested_scopes, ) .await @@ -691,7 +689,7 @@ } return redirect_see_other(&consent_url); } - let code = Code::generate(); + let code = AuthorizationCode::generate(); let auth_post_device_id = device_id.clone(); let auth_post_code = AuthorizationCode::from(code.0.clone()); if state @@ -869,7 +867,6 @@ .into_response(); } let has_totp = tranquil_api::server::has_totp_enabled(&state, &did).await; - let select_early_device_typed = device_id.clone(); if has_totp { let device_is_trusted = tranquil_api::server::is_device_trusted(state.repos.oauth.as_ref(), &device_id, &did) @@ -878,7 +875,7 @@ if state .repos .oauth - .set_authorization_did(&select_request_id, &did, Some(&select_early_device_typed)) + .set_authorization_did(&select_request_id, &did, Some(&device_id)) .await .is_err() { @@ -942,17 +939,16 @@ } } } - let select_device_typed = device_id.clone(); let _ = state .repos .oauth - .upsert_account_device(&did, &select_device_typed) + .upsert_account_device(&did, &device_id) .await; if state .repos .oauth - .set_authorization_did(&select_request_id, &did, Some(&select_device_typed)) + .set_authorization_did(&select_request_id, &did, Some(&device_id)) .await .is_err() { diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/mod.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/mod.rs --- a/crates/tranquil-oauth-server/src/endpoints/authorize/mod.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/mod.rs @@ -14,7 +14,7 @@ use tranquil_pds::auth::{BareLoginIdentifier, NormalizedLoginIdentifier}; use tranquil_pds::comms::comms_repo::enqueue_2fa_code; use tranquil_pds::oauth::{ - AuthFlow, ClientMetadataCache, Code, DeviceData, DeviceId, OAuthError, Prompt, SessionId, + AuthFlow, ClientMetadataCache, DeviceData, DeviceId, OAuthError, Prompt, SessionId, db::should_show_consent, scopes::expand_include_scopes, }; use tranquil_pds::rate_limit::{ @@ -24,7 +24,7 @@ use tranquil_pds::state::AppState; use tranquil_pds::types::{Did, Handle, PlainPassword}; use tranquil_pds::util::ClientIp; -use tranquil_types::{AuthorizationCode, ClientId, DeviceId as DeviceIdType, RequestId}; +use tranquil_types::{AuthorizationCode, ClientId, RequestId}; use urlencoding::encode as url_encode; const DEVICE_COOKIE_NAME: &str = "oauth_device_id"; @@ -111,8 +111,7 @@ } fn make_device_cookie(device_id: &tranquil_types::DeviceId) -> String { - let signed_value = - tranquil_pds::config::AuthConfig::get().sign_device_cookie(device_id.as_str()); + let signed_value = tranquil_pds::config::AuthConfig::get().sign_device_cookie(device_id); format!( "{}={}; Path=/oauth; HttpOnly; Secure; SameSite=Lax; Max-Age=31536000", DEVICE_COOKIE_NAME, signed_value @@ -128,7 +127,7 @@ #[derive(Debug, Serialize)] pub struct AuthorizeResponse { - pub client_id: String, + pub client_id: ClientId, pub client_name: Option, pub scope: Option, pub redirect_uri: String, diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs --- a/crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/passkey.rs @@ -627,11 +627,10 @@ .map(|s| s.to_string()) .collect(); - let passkey_finish_client_id = ClientId::from(request_data.parameters.client_id.clone()); let needs_consent = should_show_consent( state.repos.oauth.as_ref(), &did, - &passkey_finish_client_id, + &request_data.parameters.client_id, &requested_scopes, ) .await @@ -645,7 +644,7 @@ return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); } - let code = Code::generate(); + let code = AuthorizationCode::generate(); let passkey_final_device_id = device_id.clone(); let passkey_final_code = AuthorizationCode::from(code.0.clone()); if state diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs --- a/crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/registration.rs @@ -237,11 +237,10 @@ .split_whitespace() .map(|s| s.to_string()) .collect(); - let client_id_typed = ClientId::from(request_data.parameters.client_id.clone()); let needs_consent = should_show_consent( state.repos.oauth.as_ref(), &did, - &client_id_typed, + &request_data.parameters.client_id, &requested_scopes, ) .await @@ -260,12 +259,11 @@ return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); } - let code = Code::generate(); - let auth_code = AuthorizationCode::from(code.0.clone()); + let code = AuthorizationCode::generate(); if let Err(e) = state .repos .oauth - .update_authorization_request(&request_id, &did, None, &auth_code) + .update_authorization_request(&request_id, &did, None, &code) .await { tracing::error!( @@ -315,8 +313,7 @@ (id, None) } None => { - let new_id = DeviceId::generate(); - let device_typed = DeviceIdType::new(new_id.0.clone()); + let device_id = DeviceId::generate(); let device_data = DeviceData { session_id: SessionId::generate(), user_agent: extract_user_agent(&headers), @@ -327,7 +324,7 @@ if let Err(e) = state .repos .oauth - .create_device(&device_typed, &device_data) + .create_device(&device_id, &device_data) .await { tracing::error!(error = ?e, "Failed to create device"); @@ -344,7 +341,7 @@ if let Err(e) = state .repos .oauth - .upsert_account_device(did, &device_typed) + .upsert_account_device(did, &device_id) .await { tracing::error!(error = ?e, "Failed to link device to account"); @@ -358,8 +355,8 @@ .into_response(); } - let cookie = make_device_cookie(&device_typed); - (device_typed, Some(cookie)) + let cookie = make_device_cookie(&device_id); + (device_id, Some(cookie)) } }; diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs b/crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs --- a/crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize/two_factor.rs @@ -162,7 +162,7 @@ ); } let _ = state.repos.oauth.delete_2fa_challenge(challenge.id).await; - let code = Code::generate(); + let code = AuthorizationCode::generate(); let device_id = extract_device_cookie(&headers); let twofa_totp_device_id = device_id.clone(); let twofa_totp_code = AuthorizationCode::from(code.0.clone()); @@ -247,8 +247,7 @@ let trust_device_id = match &device_id { Some(existing_id) => existing_id.clone(), None => { - let new_id = DeviceId::generate(); - let new_device_id_typed = DeviceIdType::new(new_id.0.clone()); + let new_device_id = DeviceId::generate(); let device_data = DeviceData { session_id: SessionId::generate(), user_agent: extract_user_agent(&headers), @@ -258,14 +257,14 @@ if state .repos .oauth - .create_device(&new_device_id_typed, &device_data) + .create_device(&new_device_id, &device_data) .await .is_ok() { - new_cookie = Some(make_device_cookie(&new_device_id_typed)); - device_id = Some(new_device_id_typed.clone()); + new_cookie = Some(make_device_cookie(&new_device_id)); + device_id = Some(new_device_id.clone()); } - new_device_id_typed + new_device_id } }; let _ = state @@ -286,11 +285,10 @@ .split_whitespace() .map(|s| s.to_string()) .collect(); - let twofa_post_client_id = ClientId::from(request_data.parameters.client_id.clone()); let needs_consent = should_show_consent( state.repos.oauth.as_ref(), &did, - &twofa_post_client_id, + &request_data.parameters.client_id, &requested_scopes, ) .await @@ -310,7 +308,7 @@ } return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); } - let code = Code::generate(); + let code = AuthorizationCode::generate(); let twofa_final_device_id = device_id.clone(); let twofa_final_code = AuthorizationCode::from(code.0.clone()); if state diff --git a/crates/tranquil-oauth-server/src/endpoints/token/grants.rs b/crates/tranquil-oauth-server/src/endpoints/token/grants.rs --- a/crates/tranquil-oauth-server/src/endpoints/token/grants.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/grants.rs @@ -33,7 +33,7 @@ client_id = ?request.client_auth.client_id(), "Authorization code grant requested" ); - let (code, code_verifier, redirect_uri) = match request.grant { + let (auth_code, code_verifier, redirect_uri) = match request.grant { TokenGrant::AuthorizationCode { code, code_verifier, @@ -45,7 +45,6 @@ )); } }; - let auth_code = AuthorizationCode::from(code); let auth_request = state .repos .oauth @@ -249,7 +248,7 @@ None => TokenType::Bearer, }, expires_in: ACCESS_TOKEN_EXPIRY_SECONDS, - refresh_token: Some(refresh_token.0), + refresh_token: Some(refresh_token), scope: final_scope, sub: Some(did), }), @@ -262,7 +261,7 @@ request: ValidatedTokenRequest, dpop_proof: Option, ) -> Result<(HeaderMap, Json), OAuthError> { - let refresh_token_str = match request.grant { + let refresh_token = match request.grant { TokenGrant::RefreshToken { refresh_token } => refresh_token, _ => { return Err(OAuthError::InvalidRequest( @@ -270,6 +269,7 @@ )); } }; + let refresh_token_str = refresh_token.as_str(); let token_prefix = &refresh_token_str[..std::cmp::min(16, refresh_token_str.len())]; tracing::info!( refresh_token_prefix = %token_prefix, @@ -277,8 +277,7 @@ "Refresh token grant requested" ); - let refresh_token_typed = RefreshTokenType::from(refresh_token_str.clone()); - let lookup = lookup_refresh_token(state.repos.oauth.as_ref(), &refresh_token_typed).await?; + let lookup = lookup_refresh_token(state.repos.oauth.as_ref(), &refresh_token).await?; let token_state = lookup.state(); tracing::debug!(state = %token_state, "Refresh token state"); @@ -319,7 +318,7 @@ None => TokenType::Bearer, }, expires_in: ACCESS_TOKEN_EXPIRY_SECONDS, - refresh_token: token_data.current_refresh_token.map(|r| r.0), + refresh_token: token_data.current_refresh_token, scope: token_data.scope, sub: Some(token_data.did.to_string()), }), @@ -398,11 +397,10 @@ REFRESH_TOKEN_EXPIRY_DAYS_CONFIDENTIAL }; let new_expires_at = Utc::now() + Duration::days(refresh_expiry_days); - let new_refresh_typed = RefreshTokenType::from(new_refresh_token.0.clone()); state .repos .oauth - .rotate_token(db_id, &new_refresh_typed, new_expires_at) + .rotate_token(db_id, &new_refresh_token, new_expires_at) .await .map_err(tranquil_pds::oauth::db_err_to_oauth)?; tracing::info!( @@ -434,7 +432,7 @@ None => TokenType::Bearer, }, expires_in: ACCESS_TOKEN_EXPIRY_SECONDS, - refresh_token: Some(new_refresh_token.0), + refresh_token: Some(new_refresh_token), scope: token_data.scope, sub: Some(token_data.did.to_string()), }), diff --git a/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs b/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs --- a/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs @@ -30,10 +30,10 @@ Ok(()) } -pub fn create_access_token( - session_id: &str, - sub: &str, - dpop_jkt: Option<&str>, +pub fn create_access_token_with_delegation( + session_id: &tranquil_types::TokenId, + sub: &tranquil_types::Did, + dpop_jkt: Option<&tranquil_types::JwkThumbprint>, scope: Option<&str>, ) -> Result { create_access_token_with_delegation(session_id, sub, dpop_jkt, scope, None) diff --git a/crates/tranquil-oauth-server/src/endpoints/token/introspect.rs b/crates/tranquil-oauth-server/src/endpoints/token/introspect.rs --- a/crates/tranquil-oauth-server/src/endpoints/token/introspect.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/introspect.rs @@ -62,7 +62,7 @@ #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub client_id: Option, + pub client_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub username: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/tranquil-oauth-server/src/endpoints/token/types.rs b/crates/tranquil-oauth-server/src/endpoints/token/types.rs --- a/crates/tranquil-oauth-server/src/endpoints/token/types.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/types.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use tranquil_pds::oauth::OAuthError; +use tranquil_types::{AuthorizationCode, RefreshToken}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum GrantType { @@ -63,12 +64,12 @@ #[derive(Debug, Clone)] pub enum TokenGrant { AuthorizationCode { - code: String, + code: AuthorizationCode, code_verifier: String, redirect_uri: Option, }, RefreshToken { - refresh_token: String, + refresh_token: RefreshToken, }, } @@ -123,7 +124,7 @@ ) })?; TokenGrant::AuthorizationCode { - code, + code: AuthorizationCode::from(code), code_verifier, redirect_uri: self.redirect_uri, } @@ -134,7 +135,9 @@ "refresh_token is required for refresh_token grant".to_string(), ) })?; - TokenGrant::RefreshToken { refresh_token } + TokenGrant::RefreshToken { + refresh_token: RefreshToken::from(refresh_token), + } } }; @@ -177,7 +180,7 @@ pub token_type: TokenType, pub expires_in: u64, #[serde(skip_serializing_if = "Option::is_none")] - pub refresh_token: Option, + pub refresh_token: Option, #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, #[serde(skip_serializing_if = "Option::is_none")] -- tangled.sh