oauth: newtype client, token, device & request ids

Lewis: May this revision serve well! <lu5a@proton.me>
This commit is contained in:
Lewis
2026-07-12 00:25:08 +03:00
parent 3c46e5fc73
commit a405d523ca
33 changed files with 244 additions and 246 deletions
@@ -264,19 +264,18 @@ pub async fn create_and_store_session(
scope: &str,
controller_did: Option<&Did>,
) -> Result<SessionResult, ApiError> {
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(),
+4 -4
View File
@@ -1130,13 +1130,13 @@ pub async fn list_sessions(
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 @@ pub async fn revoke_all_sessions(
.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
@@ -52,7 +52,7 @@ impl DeviceTrustState {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TrustedDevice {
pub id: String,
pub id: DeviceId,
pub user_agent: Option<String>,
pub friendly_name: Option<String>,
pub trusted_at: Option<DateTime<Utc>>,
+2 -2
View File
@@ -59,7 +59,7 @@ pub struct DeviceAccountRow {
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<Utc>,
@@ -68,7 +68,7 @@ pub struct TwoFactorChallenge {
#[derive(Debug, Clone)]
pub struct TrustedDeviceRow {
pub id: String,
pub id: DeviceId,
pub user_agent: Option<String>,
pub friendly_name: Option<String>,
pub trusted_at: Option<DateTime<Utc>>,
+1 -1
View File
@@ -124,7 +124,7 @@ pub trait UserRepository: Send + Sync {
async fn get_oauth_token_with_user(
&self,
token_id: &str,
token_id: &TokenId,
) -> Result<Option<OAuthTokenWithUser>, DbError>;
async fn get_user_info_by_did(&self, did: &Did) -> Result<Option<UserInfoForAuth>, DbError>;
+35 -35
View File
@@ -65,13 +65,13 @@ impl OAuthRepository for PostgresOAuthRepository {
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 @@ impl OAuthRepository for PostgresOAuthRepository {
.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 @@ impl OAuthRepository for PostgresOAuthRepository {
.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 @@ impl OAuthRepository for PostgresOAuthRepository {
.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 @@ impl OAuthRepository for PostgresOAuthRepository {
.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 @@ impl OAuthRepository for PostgresOAuthRepository {
"#,
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 @@ impl OAuthRepository for PostgresOAuthRepository {
};
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 @@ impl OAuthRepository for PostgresOAuthRepository {
.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 @@ impl OAuthRepository for PostgresOAuthRepository {
};
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 @@ impl OAuthRepository for PostgresOAuthRepository {
.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 @@ impl OAuthRepository for PostgresOAuthRepository {
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 @@ impl OAuthRepository for PostgresOAuthRepository {
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 @@ impl OAuthRepository for PostgresOAuthRepository {
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,
+1 -1
View File
@@ -192,7 +192,7 @@ impl UserRepository for PostgresUserRepository {
async fn get_oauth_token_with_user(
&self,
token_id: &str,
token_id: &TokenId,
) -> Result<Option<OAuthTokenWithUser>, DbError> {
let row = sqlx::query!(
r#"SELECT t.did, t.expires_at, u.deactivated_at, u.takedown_ref, u.is_admin,
@@ -13,7 +13,7 @@ pub struct ScopeInfo {
#[derive(Debug, Serialize)]
pub struct ConsentResponse {
pub request_uri: String,
pub client_id: String,
pub client_id: ClientId,
pub client_name: Option<String>,
pub client_uri: Option<String>,
pub logo_uri: Option<String>,
@@ -136,11 +136,10 @@ pub async fn consent_get(
}
};
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 @@ pub async fn consent_get(
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 @@ pub async fn consent_post(
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 @@ pub async fn consent_post(
{
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()
@@ -606,14 +606,13 @@ pub async fn authorize_post(
}
}
}
let mut device_id: Option<DeviceIdType> = extract_device_cookie(&headers);
let mut device_id: Option<DeviceId> = extract_device_cookie(&headers);
let mut new_cookie: Option<String> = 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 @@ pub async fn authorize_post(
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 @@ pub async fn authorize_post(
.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 @@ pub async fn authorize_post(
}
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 @@ pub async fn authorize_select(
.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 @@ pub async fn authorize_select(
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 @@ pub async fn authorize_select(
}
}
}
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()
{
@@ -14,7 +14,7 @@ use tranquil_db_traits::{ScopePreference, WebauthnChallengeType};
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::rate_limit::{
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 extract_user_agent(headers: &HeaderMap) -> Option<String> {
}
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 @@ pub struct AuthorizeQuery {
#[derive(Debug, Serialize)]
pub struct AuthorizeResponse {
pub client_id: String,
pub client_id: ClientId,
pub client_name: Option<String>,
pub scope: Option<String>,
pub redirect_uri: String,
@@ -627,11 +627,10 @@ pub async fn passkey_finish(
.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 @@ pub async fn passkey_finish(
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
@@ -237,11 +237,10 @@ pub async fn register_complete(
.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 @@ pub async fn register_complete(
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 @@ pub async fn establish_session(
(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 @@ pub async fn establish_session(
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 @@ pub async fn establish_session(
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 @@ pub async fn establish_session(
.into_response();
}
let cookie = make_device_cookie(&device_typed);
(device_typed, Some(cookie))
let cookie = make_device_cookie(&device_id);
(device_id, Some(cookie))
}
};
@@ -162,7 +162,7 @@ pub async fn authorize_2fa_post(
);
}
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 @@ pub async fn authorize_2fa_post(
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 @@ pub async fn authorize_2fa_post(
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 @@ pub async fn authorize_2fa_post(
.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 @@ pub async fn authorize_2fa_post(
}
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
@@ -450,7 +450,7 @@ pub async fn delegation_auth_token(
#[derive(Debug, Deserialize)]
pub struct CrossPdsCallbackParams {
pub code: String,
pub code: tranquil_types::AuthorizationCode,
pub state: String,
pub iss: Option<String>,
}
@@ -9,14 +9,14 @@ use tranquil_pds::oauth::{
};
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<String>,
@@ -113,11 +113,10 @@ pub async fn pushed_authorization_request(
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 @@ pub async fn pushed_authorization_request(
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),
}),
))
@@ -33,7 +33,7 @@ pub async fn handle_authorization_code_grant(
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 @@ pub async fn handle_authorization_code_grant(
));
}
};
let auth_code = AuthorizationCode::from(code);
let auth_request = state
.repos
.oauth
@@ -249,7 +248,7 @@ pub async fn handle_authorization_code_grant(
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 @@ pub async fn handle_refresh_token_grant(
request: ValidatedTokenRequest,
dpop_proof: Option<String>,
) -> Result<(HeaderMap, Json<TokenResponse>), 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 @@ pub async fn handle_refresh_token_grant(
));
}
};
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 @@ pub async fn handle_refresh_token_grant(
"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 @@ pub async fn handle_refresh_token_grant(
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 @@ pub async fn handle_refresh_token_grant(
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 @@ pub async fn handle_refresh_token_grant(
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()),
}),
@@ -30,10 +30,10 @@ pub fn verify_pkce(code_challenge: &str, code_verifier: &str) -> Result<(), OAut
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<String, OAuthError> {
create_access_token_with_delegation(session_id, sub, dpop_jkt, scope, None)
@@ -62,7 +62,7 @@ pub struct IntrospectResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_id: Option<String>,
pub client_id: Option<tranquil_types::ClientId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -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 @@ pub struct TokenRequest {
#[derive(Debug, Clone)]
pub enum TokenGrant {
AuthorizationCode {
code: String,
code: AuthorizationCode,
code_verifier: String,
redirect_uri: Option<String>,
},
RefreshToken {
refresh_token: String,
refresh_token: RefreshToken,
},
}
@@ -123,7 +124,7 @@ impl TokenRequest {
)
})?;
TokenGrant::AuthorizationCode {
code,
code: AuthorizationCode::from(code),
code_verifier,
redirect_uri: self.redirect_uri,
}
@@ -134,7 +135,9 @@ impl TokenRequest {
"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 struct TokenResponse {
pub token_type: TokenType,
pub expires_in: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
pub refresh_token: Option<tranquil_types::RefreshToken>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -1256,7 +1256,7 @@ pub async fn complete_registration(
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);
+12 -11
View File
@@ -6,10 +6,11 @@ use tokio::sync::RwLock;
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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -38,7 +39,7 @@ pub struct ClientMetadata {
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 @@ impl ClientMetadataCache {
}
}
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 @@ impl ClientMetadataCache {
}
}
fn build_loopback_metadata(client_id: &str) -> Result<ClientMetadata, OAuthError> {
fn build_loopback_metadata(client_id: &ClientId) -> Result<ClientMetadata, OAuthError> {
let url = reqwest::Url::parse(client_id)
.map_err(|_| OAuthError::InvalidClient("Invalid loopback client_id URL".into()))?;
let mut redirect_uris = Vec::<String>::new();
@@ -129,7 +130,7 @@ impl ClientMetadataCache {
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 @@ impl ClientMetadataCache {
})
}
pub async fn get(&self, client_id: &str) -> Result<ClientMetadata, OAuthError> {
pub async fn get(&self, client_id: &ClientId) -> Result<ClientMetadata, OAuthError> {
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 @@ impl ClientMetadataCache {
Ok(jwks)
}
async fn fetch_metadata(&self, client_id: &str) -> Result<ClientMetadata, OAuthError> {
async fn fetch_metadata(&self, client_id: &ClientId) -> Result<ClientMetadata, OAuthError> {
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 @@ impl ClientMetadataCache {
}
let response = self
.http_client
.get(client_id)
.get(client_id.as_str())
.header("Accept", "application/json")
.send()
.await
@@ -276,8 +277,8 @@ impl ClientMetadataCache {
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(),
));
+4 -4
View File
@@ -11,10 +11,10 @@ pub use dpop::{
};
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,
};
+26 -43
View File
@@ -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 @@ pub struct TokenId(pub String);
#[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 @@ impl Prompt {
#[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<String>,
pub state: Option<String>,
@@ -171,13 +154,13 @@ pub struct AuthorizationRequestParameters {
#[derive(Debug, Clone)]
pub struct RequestData {
pub client_id: String,
pub client_id: ClientId,
pub client_auth: Option<ClientAuth>,
pub parameters: AuthorizationRequestParameters,
pub expires_at: DateTime<Utc>,
pub did: Option<Did>,
pub device_id: Option<DeviceId>,
pub code: Option<Code>,
pub code: Option<AuthorizationCode>,
pub controller_did: Option<Did>,
}
@@ -196,12 +179,12 @@ pub struct TokenData {
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub expires_at: DateTime<Utc>,
pub client_id: String,
pub client_id: ClientId,
pub client_auth: ClientAuth,
pub device_id: Option<DeviceId>,
pub parameters: AuthorizationRequestParameters,
pub details: Option<JsonValue>,
pub code: Option<Code>,
pub code: Option<AuthorizationCode>,
pub current_refresh_token: Option<RefreshToken>,
pub scope: Option<String>,
pub controller_did: Option<Did>,
@@ -215,7 +198,7 @@ pub struct AuthorizedClientData {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthClientMetadata {
pub client_id: String,
pub client_id: ClientId,
pub client_name: Option<String>,
pub client_uri: Option<String>,
pub logo_uri: Option<String>,
@@ -270,7 +253,7 @@ pub struct TokenResponse {
pub token_type: String,
pub expires_in: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
pub refresh_token: Option<RefreshToken>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -280,11 +263,11 @@ pub struct TokenResponse {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenRequest {
pub grant_type: String,
pub code: Option<String>,
pub code: Option<AuthorizationCode>,
pub redirect_uri: Option<String>,
pub code_verifier: Option<String>,
pub refresh_token: Option<String>,
pub client_id: Option<String>,
pub refresh_token: Option<RefreshToken>,
pub client_id: Option<ClientId>,
pub client_secret: Option<String>,
}
@@ -320,7 +303,7 @@ pub struct Jwks {
#[derive(Debug, Clone)]
pub struct FlowPending {
pub parameters: AuthorizationRequestParameters,
pub client_id: String,
pub client_id: ClientId,
pub client_auth: Option<ClientAuth>,
pub expires_at: DateTime<Utc>,
pub controller_did: Option<Did>,
@@ -329,7 +312,7 @@ pub struct FlowPending {
#[derive(Debug, Clone)]
pub struct FlowAuthenticated {
pub parameters: AuthorizationRequestParameters,
pub client_id: String,
pub client_id: ClientId,
pub client_auth: Option<ClientAuth>,
pub expires_at: DateTime<Utc>,
pub did: Did,
@@ -340,12 +323,12 @@ pub struct FlowAuthenticated {
#[derive(Debug, Clone)]
pub struct FlowAuthorized {
pub parameters: AuthorizationRequestParameters,
pub client_id: String,
pub client_id: ClientId,
pub client_auth: Option<ClientAuth>,
pub expires_at: DateTime<Utc>,
pub did: Did,
pub device_id: Option<DeviceId>,
pub code: Code,
pub code: AuthorizationCode,
pub controller_did: Option<Did>,
}
@@ -444,7 +427,7 @@ impl AuthFlowWithUser {
}
}
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 @@ mod tests {
fn make_request_data(
did: Option<Did>,
code: Option<Code>,
code: Option<AuthorizationCode>,
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 @@ mod tests {
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]
+3 -3
View File
@@ -126,7 +126,7 @@ impl AuthConfig {
&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<Sha256>;
@@ -144,7 +144,7 @@ impl AuthConfig {
format!("{}.{}.{}", device_id, timestamp, signature)
}
pub fn verify_device_cookie(&self, cookie_value: &str) -> Option<String> {
pub fn verify_device_cookie(&self, cookie_value: &str) -> Option<crate::types::DeviceId> {
use hmac::Mac;
type HmacSha256 = hmac::Hmac<Sha256>;
@@ -181,7 +181,7 @@ impl AuthConfig {
.ct_eq(expected_signature.as_bytes())
.into()
{
Some(device_id.to_string())
Some(crate::types::DeviceId::new(device_id))
} else {
None
}
+9 -6
View File
@@ -10,7 +10,7 @@ use tranquil_oauth::{
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 ParResult {
}
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 @@ impl CrossPdsOAuthClient {
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 @@ impl CrossPdsOAuthClient {
pub async fn exchange_code(
&self,
auth_state: &CrossPdsAuthState,
code: &str,
client_id: &str,
code: &AuthorizationCode,
client_id: &ClientId,
redirect_uri: &str,
) -> Result<String, CrossPdsError> {
let meta = self
+8 -8
View File
@@ -9,14 +9,14 @@ pub fn db_err_to_oauth(err: tranquil_db_traits::DbError) -> OAuthError {
}
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,
};
+1 -13
View File
@@ -23,7 +23,6 @@ use crate::state::AppState;
pub struct OAuthTokenInfo {
pub did: Did,
pub token_id: TokenId,
pub client_id: ClientId,
pub scope: Option<String>,
pub dpop_jkt: Option<String>,
pub controller_did: Option<Did>,
@@ -101,7 +100,7 @@ pub async fn verify_oauth_access_token(
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 @@ pub fn extract_oauth_token_info(token: &str) -> Result<OAuthTokenInfo, OAuthErro
.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 @@ pub fn extract_oauth_token_info(token: &str) -> Result<OAuthTokenInfo, OAuthErro
Ok(OAuthTokenInfo {
did,
token_id,
client_id,
scope,
dpop_jkt,
controller_did,
+1 -1
View File
@@ -145,7 +145,7 @@ fn test_token_type_confusion() {
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!(
@@ -39,10 +39,10 @@ async fn create_account_and_get_did(handle: &str, email: &str, password: &str) -
}
fn make_token_data(did: &Did, token_id: &str, created_at: DateTime<Utc>) -> 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),
+3 -3
View File
@@ -619,12 +619,12 @@ async fn test_sso_get_pending_registration_token_too_long() {
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(),
@@ -3343,7 +3343,7 @@ impl<S: StorageIO + 'static> tranquil_db_traits::UserRepository for MetastoreCli
async fn get_oauth_token_with_user(
&self,
token_id: &str,
token_id: &TokenId,
) -> Result<Option<OAuthTokenWithUser>, DbError> {
let (tx, rx) = oneshot::channel();
self.pool
@@ -103,11 +103,11 @@ impl OAuthOps {
fn token_value_to_data(&self, v: &OAuthTokenValue) -> Result<TokenData, MetastoreError> {
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 @@ impl OAuthOps {
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 @@ impl OAuthOps {
.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 @@ impl OAuthOps {
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 @@ impl OAuthOps {
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 @@ impl OAuthOps {
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 @@ impl OAuthOps {
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 @@ impl OAuthOps {
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 @@ impl OAuthOps {
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 @@ impl OAuthOps {
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 @@ impl OAuthOps {
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,
+39
View File
@@ -727,6 +727,12 @@ simple_string_newtype! {
pub struct TokenId;
}
impl TokenId {
pub fn generate() -> Self {
Self(uuid::Uuid::new_v4().to_string())
}
}
simple_string_newtype! {
pub struct ClientId;
}
@@ -735,10 +741,25 @@ simple_string_newtype! {
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! {
pub struct Jti;
}
@@ -747,10 +768,28 @@ simple_string_newtype! {
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! {
pub struct InviteCode;
}