mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-07-20 23:12:48 +00:00
oauth: newtype client, token, device & request ids
Lewis: May this revision serve well! <lu5a@proton.me>
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user