Our Personal Data Server from scratch! tranquil.farm
oauth atproto pds rust postgresql objectstorage fun

fix: oauth consolidation, include-scope improvements #4

merged opened by lewis.moe targeting main from fix/oauth-on-niche-apps
  • auth extraction should be happening in the auth crate, yes, who coulda thought
  • include: scope should actually be doing the right thing and going out and requesting stuff to expand out the perms
  • more tests!!!1!
  • more correct parsing of the #bsky-appview or whatever suffixes on did webs that come through auth
Labels

None yet.

assignee
Participants 2
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3md3bluniqt22
+28 -30
Interdiff #0 โ†’ #1
.sqlx/query-06eb7c6e1983b6121526ba63612236391290c2e63d37d2bb1cd89ea822950a82.json

This file has not been changed.

.sqlx/query-5031b96c65078d6c54954ce6e57ff9cbba4c48dd8a7546882ab5647114ffab4a.json

This file has not been changed.

.sqlx/query-6258398accee69e0c5f455a3c0ecc273b3da6ef5bb4d8660adafe63d8e3cd2d4.json

This file has not been changed.

.sqlx/query-a4dc8fb22bd094d414c55b9da20b610f7b122b485ab0fd0d0646d68ae8e64fe6.json

This file has not been changed.

.sqlx/query-dec3a21a8e60cc8d2c5dad727750bc88f5535dedae244f7b6e4afa95769b8f1a.json

This file has not been changed.

Cargo.lock

This file has not been changed.

crates/tranquil-pds/src/api/error.rs

This file has not been changed.

crates/tranquil-pds/src/api/identity/account.rs

This file has not been changed.

crates/tranquil-pds/src/api/proxy.rs

This file has not been changed.

crates/tranquil-pds/src/api/repo/blob.rs

This file has not been changed.

crates/tranquil-pds/src/api/repo/record/delete.rs

This file has not been changed.

crates/tranquil-pds/src/api/repo/record/write.rs

This file has not been changed.

+5 -5
crates/tranquil-pds/src/api/server/account_status.rs
··· 525 525 State(state): State<AppState>, 526 526 auth: crate::auth::BearerAuthAllowDeactivated, 527 527 ) -> Response { 528 - let did = auth.0.did.clone(); 528 + let did = &auth.0.did; 529 529 530 - if !crate::api::server::reauth::check_legacy_session_mfa(&*state.session_repo, &did).await { 530 + if !crate::api::server::reauth::check_legacy_session_mfa(&*state.session_repo, did).await { 531 531 return crate::api::server::reauth::legacy_mfa_required_response( 532 532 &*state.user_repo, 533 533 &*state.session_repo, 534 - &did, 534 + did, 535 535 ) 536 536 .await; 537 537 } 538 538 539 - let user_id = match state.user_repo.get_id_by_did(&did).await { 539 + let user_id = match state.user_repo.get_id_by_did(did).await { 540 540 Ok(Some(id)) => id, 541 541 _ => { 542 542 return ApiError::InternalError(None).into_response(); ··· 546 546 let expires_at = Utc::now() + Duration::minutes(15); 547 547 if let Err(e) = state 548 548 .infra_repo 549 - .create_deletion_request(&confirmation_token, &did, expires_at) 549 + .create_deletion_request(&confirmation_token, did, expires_at) 550 550 .await 551 551 { 552 552 error!("DB error creating deletion token: {:?}", e);
crates/tranquil-pds/src/api/server/migration.rs

This file has not been changed.

crates/tranquil-pds/src/api/temp.rs

This file has not been changed.

crates/tranquil-pds/src/auth/auth_extractor.rs

This file has not been changed.

+2 -6
crates/tranquil-pds/src/auth/extractor.rs
··· 160 160 .await 161 161 { 162 162 Ok(result) => { 163 - let result_did: Did = result 164 - .did 165 - .parse() 166 - .map_err(|_| AuthError::AuthenticationFailed)?; 167 163 let user_info = state 168 164 .user_repo 169 - .get_user_info_by_did(&result_did) 165 + .get_user_info_by_did(&result.did) 170 166 .await 171 167 .ok() 172 168 .flatten() ··· 182 178 return Err(AuthError::AccountTakedown); 183 179 } 184 180 Ok(AuthenticatedUser { 185 - did: result_did, 181 + did: result.did, 186 182 key_bytes: user_info.key_bytes.and_then(|kb| { 187 183 crate::config::decrypt_key(&kb, user_info.encryption_version).ok() 188 184 }),
crates/tranquil-pds/src/auth/mod.rs

This file has not been changed.

crates/tranquil-pds/src/lib.rs

This file has not been changed.

+4 -6
crates/tranquil-pds/src/oauth/endpoints/authorize.rs
··· 1118 1118 .map(|s| s.to_string()) 1119 1119 .collect(); 1120 1120 let client_id_typed = ClientId::from(request_data.parameters.client_id.clone()); 1121 - let did_typed = tranquil_types::Did::from(did.to_string()); 1122 1121 let needs_consent = should_show_consent( 1123 1122 state.oauth_repo.as_ref(), 1124 - &did_typed, 1123 + &did, 1125 1124 &client_id_typed, 1126 1125 &requested_scopes, 1127 1126 ) ··· 3648 3647 headers: HeaderMap, 3649 3648 auth: crate::auth::BearerAuth, 3650 3649 ) -> Response { 3651 - let did = auth.0.did.clone(); 3652 - let did_typed = tranquil_types::Did::from(did.to_string()); 3650 + let did = &auth.0.did; 3653 3651 3654 3652 let existing_device = extract_device_cookie(&headers); 3655 3653 ··· 3658 3656 let device_typed = DeviceIdType::from(id.clone()); 3659 3657 let _ = state 3660 3658 .oauth_repo 3661 - .upsert_account_device(&did_typed, &device_typed) 3659 + .upsert_account_device(did, &device_typed) 3662 3660 .await; 3663 3661 (id, None) 3664 3662 } ··· 3684 3682 .into_response(); 3685 3683 } 3686 3684 3687 - if let Err(e) = state.oauth_repo.upsert_account_device(&did_typed, &device_typed).await { 3685 + if let Err(e) = state.oauth_repo.upsert_account_device(did, &device_typed).await { 3688 3686 tracing::error!(error = ?e, "Failed to link device to account"); 3689 3687 return ( 3690 3688 StatusCode::INTERNAL_SERVER_ERROR,
crates/tranquil-pds/src/oauth/endpoints/delegation.rs

This file has not been changed.

+17 -13
crates/tranquil-pds/src/oauth/verify.rs
··· 10 10 use sha2::Sha256; 11 11 use subtle::ConstantTimeEq; 12 12 use tranquil_db_traits::{OAuthRepository, UserRepository}; 13 - use tranquil_types::TokenId; 13 + use tranquil_types::{ClientId, TokenId}; 14 + 15 + use crate::types::Did; 14 16 15 17 use super::scopes::ScopePermissions; 16 18 use super::{DPoPVerifier, OAuthError}; ··· 27 29 } 28 30 29 31 pub struct VerifyResult { 30 - pub did: String, 31 - pub token_id: String, 32 - pub client_id: String, 32 + pub did: Did, 33 + pub token_id: TokenId, 34 + pub client_id: ClientId, 33 35 pub scope: Option<String>, 34 36 } 35 37 ··· 91 93 )); 92 94 } 93 95 } 96 + let did: Did = token_data 97 + .did 98 + .parse() 99 + .map_err(|_| OAuthError::InvalidToken("Invalid DID in token".to_string()))?; 94 100 Ok(VerifyResult { 95 - did: token_data.did, 96 - token_id: token_id.to_string(), 97 - client_id: token_data.client_id, 101 + did, 102 + token_id, 103 + client_id: ClientId::from(token_data.client_id), 98 104 scope: token_data.scope, 99 105 }) 100 106 } ··· 202 208 } 203 209 204 210 pub struct OAuthUser { 205 - pub did: String, 206 - pub client_id: Option<String>, 211 + pub did: Did, 212 + pub client_id: Option<ClientId>, 207 213 pub scope: Option<String>, 208 214 pub is_oauth: bool, 209 215 pub permissions: ScopePermissions, ··· 382 388 } 383 389 384 390 struct LegacyAuthResult { 385 - did: String, 391 + did: Did, 386 392 } 387 393 388 394 async fn try_legacy_auth( ··· 390 396 token: &str, 391 397 ) -> Result<LegacyAuthResult, ()> { 392 398 match crate::auth::validate_bearer_token(user_repo, token).await { 393 - Ok(user) if !user.is_oauth => Ok(LegacyAuthResult { 394 - did: user.did.to_string(), 395 - }), 399 + Ok(user) if !user.is_oauth => Ok(LegacyAuthResult { did: user.did }), 396 400 _ => Err(()), 397 401 } 398 402 }
crates/tranquil-pds/tests/auth_extractor.rs

This file has not been changed.

crates/tranquil-pds/tests/common/mod.rs

This file has not been changed.

crates/tranquil-pds/tests/oauth_security.rs

This file has not been changed.

crates/tranquil-scopes/Cargo.toml

This file has not been changed.

crates/tranquil-scopes/src/permission_set.rs

This file has not been changed.

crates/tranquil-scopes/src/permissions.rs

This file has not been changed.

crates/tranquil-storage/src/lib.rs

This file has not been changed.

frontend/src/lib/api.ts

This file has not been changed.

frontend/src/lib/auth.svelte.ts

This file has not been changed.

frontend/src/lib/migration/atproto-client.ts

This file has not been changed.

frontend/src/lib/migration/flow.svelte.ts

This file has not been changed.

frontend/src/lib/migration/offline-flow.svelte.ts

This file has not been changed.

frontend/src/lib/oauth.ts

This file has not been changed.

frontend/src/locales/en.json

This file has not been changed.

frontend/src/locales/fi.json

This file has not been changed.

frontend/src/locales/ja.json

This file has not been changed.

frontend/src/locales/ko.json

This file has not been changed.

frontend/src/locales/sv.json

This file has not been changed.

frontend/src/locales/zh.json

This file has not been changed.

frontend/src/routes/Migration.svelte

This file has not been changed.

frontend/src/routes/OAuthAccounts.svelte

This file has not been changed.

frontend/src/routes/OAuthConsent.svelte

This file has not been changed.

History

6 rounds 1 comment
sign up or login to add to the discussion
3 commits
expand
fix: oauth consolidation, include-scope improvements
fix: consolidate auth extractors & standardize usage
fix: match ref pds permission-levels for some endpoints
expand 0 comments
pull request successfully merged
3 commits
expand
fix: oauth consolidation, include-scope improvements
fix: consolidate auth extractors & standardize usage
fix: match ref pds permission-levels for some endpoints
expand 0 comments
2 commits
expand
fix: oauth consolidation, include-scope improvements
fix: consolidate auth extractors & standardize usage
expand 0 comments
2 commits
expand
fix: oauth consolidation, include-scope improvements
fix: consolidate auth extractors & standardize usage
expand 1 comment

so three things:

crates/tranquil-pds/src/auth/auth_extractor.rs does not seem to be used at all anywhere?

crates/tranquil-pds/src/api/temp.rs feels like it just ... shouldnt excist based on the name

and i still dont really like these extractors. the separation of inter service auth is weird to me. inter-service auth is a form of user auth. it shouldnt be separated out from the other types. the AuthExtractor should just be AuthExtractor(pub AuthenticatedUser).

i also discovered https://docs.rs/axum/0.8.8/axum/extract/trait.OptionalFromRequestParts.html which we should be able to reduce optional vs not optional with just an AuthExtractor vs Option.

principly id want whether or not its required that the account is active or not and whether its an admin account or not to both also be type safe configurations on the extractor. probably with generics of some sort. but i cant think of a specific design i like right now so. if you come up with one feel free to do it. otherwise we can do it later

lewis.moe submitted #1
1 commit
expand
fix: oauth consolidation, include-scope improvements
expand 0 comments
lewis.moe submitted #0
1 commit
expand
fix: oauth consolidation, include-scope improvements
expand 0 comments