Alternative ATProto PDS implementation

prototype account_manager

Changed files
+795 -859
src
+174 -185
src/account_manager/helpers/account.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 - use crate::db::DbConn; 6 - use crate::schema::pds::account::dsl as AccountSchema; 7 - use crate::schema::pds::account::table as AccountTable; 8 - use crate::schema::pds::actor::dsl as ActorSchema; 9 - use crate::schema::pds::actor::table as ActorTable; 10 5 use anyhow::Result; 11 6 use chrono::DateTime; 12 7 use chrono::offset::Utc as UtcOffset; 13 - use diesel::dsl::{LeftJoinOn, exists, not}; 14 - use diesel::helper_types::{Eq, IntoBoxed}; 15 - use diesel::pg::Pg; 8 + use diesel::dsl::{exists, not}; 16 9 use diesel::result::{DatabaseErrorKind, Error as DieselError}; 17 10 use diesel::*; 18 - use rsky_common; 19 11 use rsky_common::RFC3339_VARIANT; 20 12 use rsky_lexicon::com::atproto::admin::StatusAttr; 13 + #[expect(unused_imports)] 14 + pub(crate) use rsky_pds::account_manager::helpers::account::{ 15 + AccountStatus, ActorAccount, ActorJoinAccount, AvailabilityFlags, BoxedQuery, 16 + FormattedAccountStatus, GetAccountAdminStatusOutput, format_account_status, select_account_qb, 17 + }; 18 + use rsky_pds::schema::pds::account::dsl as AccountSchema; 19 + use rsky_pds::schema::pds::actor::dsl as ActorSchema; 21 20 use std::ops::Add; 22 21 use std::time::SystemTime; 23 22 use thiserror::Error; ··· 30 29 DieselError(String), 31 30 } 32 31 33 - pub struct AvailabilityFlags { 34 - pub include_taken_down: Option<bool>, 35 - pub include_deactivated: Option<bool>, 36 - } 37 - 38 - #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] 39 - pub enum AccountStatus { 40 - Active, 41 - Takendown, 42 - Suspended, 43 - Deleted, 44 - Deactivated, 45 - Desynchronized, 46 - Throttled, 47 - } 48 - 49 - #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] 50 - pub struct FormattedAccountStatus { 51 - pub active: bool, 52 - pub status: Option<AccountStatus>, 53 - } 54 - 55 - #[derive(Debug)] 56 - pub struct GetAccountAdminStatusOutput { 57 - pub takedown: StatusAttr, 58 - pub deactivated: StatusAttr, 59 - } 60 - 61 - pub type ActorJoinAccount = 62 - LeftJoinOn<ActorTable, AccountTable, Eq<ActorSchema::did, AccountSchema::did>>; 63 - pub type BoxedQuery<'a> = IntoBoxed<'a, ActorJoinAccount, Pg>; 64 - 65 - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 66 - pub struct ActorAccount { 67 - pub did: String, 68 - pub handle: Option<String>, 69 - #[serde(rename = "createdAt")] 70 - pub created_at: String, 71 - #[serde(rename = "takedownRef")] 72 - pub takedown_ref: Option<String>, 73 - #[serde(rename = "deactivatedAt")] 74 - pub deactivated_at: Option<String>, 75 - #[serde(rename = "deleteAfter")] 76 - pub delete_after: Option<String>, 77 - pub email: Option<String>, 78 - #[serde(rename = "invitesDisabled")] 79 - pub invites_disabled: Option<i16>, 80 - #[serde(rename = "emailConfirmedAt")] 81 - pub email_confirmed_at: Option<String>, 82 - } 83 - 84 - pub fn select_account_qb(flags: Option<AvailabilityFlags>) -> BoxedQuery<'static> { 85 - let AvailabilityFlags { 86 - include_taken_down, 87 - include_deactivated, 88 - } = flags.unwrap_or_else(|| AvailabilityFlags { 89 - include_taken_down: Some(false), 90 - include_deactivated: Some(false), 91 - }); 92 - let include_taken_down = include_taken_down.unwrap_or(false); 93 - let include_deactivated = include_deactivated.unwrap_or(false); 94 - 95 - let mut builder = ActorSchema::actor 96 - .left_join(AccountSchema::account.on(ActorSchema::did.eq(AccountSchema::did))) 97 - .into_boxed(); 98 - if !include_taken_down { 99 - builder = builder.filter(ActorSchema::takedownRef.is_null()); 100 - } 101 - if !include_deactivated { 102 - builder = builder.filter(ActorSchema::deactivatedAt.is_null()); 103 - } 104 - builder 105 - } 106 - 107 32 pub async fn get_account( 108 33 _handle_or_did: &str, 109 34 flags: Option<AvailabilityFlags>, 110 - db: &DbConn, 35 + db: &deadpool_diesel::Pool< 36 + deadpool_diesel::Manager<SqliteConnection>, 37 + deadpool_diesel::sqlite::Object, 38 + >, 111 39 ) -> Result<Option<ActorAccount>> { 112 40 let handle_or_did = _handle_or_did.to_owned(); 113 41 let found = db 114 - .run(move |conn| { 42 + .get() 43 + .await? 44 + .interact(move |conn| { 115 45 let mut builder = select_account_qb(flags); 116 46 if handle_or_did.starts_with("did:") { 117 47 builder = builder.filter(ActorSchema::did.eq(handle_or_did)); ··· 155 85 }) 156 86 .optional() 157 87 }) 158 - .await?; 88 + .await 89 + .expect("Failed to get account")?; 159 90 Ok(found) 160 91 } 161 92 162 93 pub async fn get_account_by_email( 163 94 _email: &str, 164 95 flags: Option<AvailabilityFlags>, 165 - db: &DbConn, 96 + db: &deadpool_diesel::Pool< 97 + deadpool_diesel::Manager<SqliteConnection>, 98 + deadpool_diesel::sqlite::Object, 99 + >, 166 100 ) -> Result<Option<ActorAccount>> { 167 101 let email = _email.to_owned(); 168 102 let found = db 169 - .run(move |conn| { 103 + .get() 104 + .await? 105 + .interact(move |conn| { 170 106 select_account_qb(flags) 171 107 .select(( 172 108 ActorSchema::did, ··· 204 140 }) 205 141 .optional() 206 142 }) 207 - .await?; 143 + .await 144 + .expect("Failed to get account")?; 208 145 Ok(found) 209 146 } 210 147 ··· 212 149 did: String, 213 150 handle: String, 214 151 deactivated: Option<bool>, 215 - db: &DbConn, 152 + db: &deadpool_diesel::Pool< 153 + deadpool_diesel::Manager<SqliteConnection>, 154 + deadpool_diesel::sqlite::Object, 155 + >, 216 156 ) -> Result<()> { 217 157 let system_time = SystemTime::now(); 218 158 let dt: DateTime<UtcOffset> = system_time.into(); ··· 230 170 }; 231 171 232 172 let _: String = db 233 - .run(move |conn| { 173 + .get() 174 + .await? 175 + .interact(move |conn| { 234 176 insert_into(ActorSchema::actor) 235 177 .values(( 236 178 ActorSchema::did.eq(did), ··· 243 185 .returning(ActorSchema::did) 244 186 .get_result(conn) 245 187 }) 246 - .await?; 188 + .await 189 + .expect("Failed to register actor")?; 247 190 Ok(()) 248 191 } 249 192 ··· 251 194 did: String, 252 195 email: String, 253 196 password: String, 254 - db: &DbConn, 197 + db: &deadpool_diesel::Pool< 198 + deadpool_diesel::Manager<SqliteConnection>, 199 + deadpool_diesel::sqlite::Object, 200 + >, 255 201 ) -> Result<()> { 256 202 let created_at = rsky_common::now(); 257 203 258 204 // @TODO record recovery key for bring your own recovery key 259 205 let _: String = db 260 - .run(move |conn| { 206 + .get() 207 + .await? 208 + .interact(move |conn| { 261 209 insert_into(AccountSchema::account) 262 210 .values(( 263 211 AccountSchema::did.eq(did), ··· 269 217 .returning(AccountSchema::did) 270 218 .get_result(conn) 271 219 }) 272 - .await?; 220 + .await 221 + .expect("Failed to register account")?; 273 222 Ok(()) 274 223 } 275 224 276 - pub async fn delete_account(did: &str, db: &DbConn) -> Result<()> { 277 - use crate::schema::pds::email_token::dsl as EmailTokenSchema; 278 - use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema; 279 - use crate::schema::pds::repo_root::dsl as RepoRootSchema; 225 + pub async fn delete_account( 226 + did: &str, 227 + db: &deadpool_diesel::Pool< 228 + deadpool_diesel::Manager<SqliteConnection>, 229 + deadpool_diesel::sqlite::Object, 230 + >, 231 + ) -> Result<()> { 232 + use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema; 233 + use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema; 234 + use rsky_pds::schema::pds::repo_root::dsl as RepoRootSchema; 280 235 281 236 let did = did.to_owned(); 282 - db.run(move |conn| { 283 - delete(RepoRootSchema::repo_root) 284 - .filter(RepoRootSchema::did.eq(&did)) 285 - .execute(conn)?; 286 - delete(EmailTokenSchema::email_token) 287 - .filter(EmailTokenSchema::did.eq(&did)) 288 - .execute(conn)?; 289 - delete(RefreshTokenSchema::refresh_token) 290 - .filter(RefreshTokenSchema::did.eq(&did)) 291 - .execute(conn)?; 292 - delete(AccountSchema::account) 293 - .filter(AccountSchema::did.eq(&did)) 294 - .execute(conn)?; 295 - delete(ActorSchema::actor) 296 - .filter(ActorSchema::did.eq(&did)) 297 - .execute(conn) 298 - }) 299 - .await?; 237 + db.get() 238 + .await? 239 + .interact(move |conn| { 240 + delete(RepoRootSchema::repo_root) 241 + .filter(RepoRootSchema::did.eq(&did)) 242 + .execute(conn)?; 243 + delete(EmailTokenSchema::email_token) 244 + .filter(EmailTokenSchema::did.eq(&did)) 245 + .execute(conn)?; 246 + delete(RefreshTokenSchema::refresh_token) 247 + .filter(RefreshTokenSchema::did.eq(&did)) 248 + .execute(conn)?; 249 + delete(AccountSchema::account) 250 + .filter(AccountSchema::did.eq(&did)) 251 + .execute(conn)?; 252 + delete(ActorSchema::actor) 253 + .filter(ActorSchema::did.eq(&did)) 254 + .execute(conn) 255 + }) 256 + .await 257 + .expect("Failed to delete account")?; 300 258 Ok(()) 301 259 } 302 260 303 261 pub async fn update_account_takedown_status( 304 262 did: &str, 305 263 takedown: StatusAttr, 306 - db: &DbConn, 264 + db: &deadpool_diesel::Pool< 265 + deadpool_diesel::Manager<SqliteConnection>, 266 + deadpool_diesel::sqlite::Object, 267 + >, 307 268 ) -> Result<()> { 308 269 let takedown_ref: Option<String> = match takedown.applied { 309 270 true => match takedown.r#ref { ··· 313 274 false => None, 314 275 }; 315 276 let did = did.to_owned(); 316 - db.run(move |conn| { 317 - update(ActorSchema::actor) 318 - .filter(ActorSchema::did.eq(did)) 319 - .set((ActorSchema::takedownRef.eq(takedown_ref),)) 320 - .execute(conn) 321 - }) 322 - .await?; 277 + db.get() 278 + .await? 279 + .interact(move |conn| { 280 + update(ActorSchema::actor) 281 + .filter(ActorSchema::did.eq(did)) 282 + .set((ActorSchema::takedownRef.eq(takedown_ref),)) 283 + .execute(conn) 284 + }) 285 + .await 286 + .expect("Failed to update account takedown status")?; 323 287 Ok(()) 324 288 } 325 289 326 290 pub async fn deactivate_account( 327 291 did: &str, 328 292 delete_after: Option<String>, 329 - db: &DbConn, 293 + db: &deadpool_diesel::Pool< 294 + deadpool_diesel::Manager<SqliteConnection>, 295 + deadpool_diesel::sqlite::Object, 296 + >, 330 297 ) -> Result<()> { 331 298 let did = did.to_owned(); 332 - db.run(move |conn| { 333 - update(ActorSchema::actor) 334 - .filter(ActorSchema::did.eq(did)) 335 - .set(( 336 - ActorSchema::deactivatedAt.eq(rsky_common::now()), 337 - ActorSchema::deleteAfter.eq(delete_after), 338 - )) 339 - .execute(conn) 340 - }) 341 - .await?; 299 + db.get() 300 + .await? 301 + .interact(move |conn| { 302 + update(ActorSchema::actor) 303 + .filter(ActorSchema::did.eq(did)) 304 + .set(( 305 + ActorSchema::deactivatedAt.eq(rsky_common::now()), 306 + ActorSchema::deleteAfter.eq(delete_after), 307 + )) 308 + .execute(conn) 309 + }) 310 + .await 311 + .expect("Failed to deactivate account")?; 342 312 Ok(()) 343 313 } 344 314 345 - pub async fn activate_account(did: &str, db: &DbConn) -> Result<()> { 315 + pub async fn activate_account( 316 + did: &str, 317 + db: &deadpool_diesel::Pool< 318 + deadpool_diesel::Manager<SqliteConnection>, 319 + deadpool_diesel::sqlite::Object, 320 + >, 321 + ) -> Result<()> { 346 322 let did = did.to_owned(); 347 - db.run(move |conn| { 348 - update(ActorSchema::actor) 349 - .filter(ActorSchema::did.eq(did)) 350 - .set(( 351 - ActorSchema::deactivatedAt.eq::<Option<String>>(None), 352 - ActorSchema::deleteAfter.eq::<Option<String>>(None), 353 - )) 354 - .execute(conn) 355 - }) 356 - .await?; 323 + db.get() 324 + .await? 325 + .interact(move |conn| { 326 + update(ActorSchema::actor) 327 + .filter(ActorSchema::did.eq(did)) 328 + .set(( 329 + ActorSchema::deactivatedAt.eq::<Option<String>>(None), 330 + ActorSchema::deleteAfter.eq::<Option<String>>(None), 331 + )) 332 + .execute(conn) 333 + }) 334 + .await 335 + .expect("Failed to activate account")?; 357 336 Ok(()) 358 337 } 359 338 360 - pub async fn update_email(did: &str, email: &str, db: &DbConn) -> Result<()> { 339 + pub async fn update_email( 340 + did: &str, 341 + email: &str, 342 + db: &deadpool_diesel::Pool< 343 + deadpool_diesel::Manager<SqliteConnection>, 344 + deadpool_diesel::sqlite::Object, 345 + >, 346 + ) -> Result<()> { 361 347 let did = did.to_owned(); 362 348 let email = email.to_owned(); 363 349 let res = db 364 - .run(move |conn| { 350 + .get() 351 + .await? 352 + .interact(move |conn| { 365 353 update(AccountSchema::account) 366 354 .filter(AccountSchema::did.eq(did)) 367 355 .set(( ··· 386 374 } 387 375 } 388 376 389 - pub async fn update_handle(did: &str, handle: &str, db: &DbConn) -> Result<()> { 390 - use crate::schema::pds::actor; 377 + pub async fn update_handle( 378 + did: &str, 379 + handle: &str, 380 + db: &deadpool_diesel::Pool< 381 + deadpool_diesel::Manager<SqliteConnection>, 382 + deadpool_diesel::sqlite::Object, 383 + >, 384 + ) -> Result<()> { 385 + use rsky_pds::schema::pds::actor; 391 386 392 387 let actor2 = diesel::alias!(actor as actor2); 393 388 394 389 let did = did.to_owned(); 395 390 let handle = handle.to_owned(); 396 391 let res = db 397 - .run(move |conn| { 392 + .get() 393 + .await? 394 + .interact(move |conn| { 398 395 update(ActorSchema::actor) 399 396 .filter(ActorSchema::did.eq(did)) 400 397 .filter(not(exists(actor2.filter(ActorSchema::handle.eq(&handle))))) 401 398 .set((ActorSchema::handle.eq(&handle),)) 402 399 .execute(conn) 403 400 }) 404 - .await?; 401 + .await 402 + .expect("Failed to update handle")?; 405 403 406 404 if res < 1 { 407 405 return Err(anyhow::Error::new( ··· 414 412 pub async fn set_email_confirmed_at( 415 413 did: &str, 416 414 email_confirmed_at: String, 417 - db: &DbConn, 415 + db: &deadpool_diesel::Pool< 416 + deadpool_diesel::Manager<SqliteConnection>, 417 + deadpool_diesel::sqlite::Object, 418 + >, 418 419 ) -> Result<()> { 419 420 let did = did.to_owned(); 420 - db.run(move |conn| { 421 - update(AccountSchema::account) 422 - .filter(AccountSchema::did.eq(did)) 423 - .set(AccountSchema::emailConfirmedAt.eq(email_confirmed_at)) 424 - .execute(conn) 425 - }) 426 - .await?; 421 + db.get() 422 + .await? 423 + .interact(move |conn| { 424 + update(AccountSchema::account) 425 + .filter(AccountSchema::did.eq(did)) 426 + .set(AccountSchema::emailConfirmedAt.eq(email_confirmed_at)) 427 + .execute(conn) 428 + }) 429 + .await 430 + .expect("Failed to set email confirmed at")?; 427 431 Ok(()) 428 432 } 429 433 430 434 pub async fn get_account_admin_status( 431 435 did: &str, 432 - db: &DbConn, 436 + db: &deadpool_diesel::Pool< 437 + deadpool_diesel::Manager<SqliteConnection>, 438 + deadpool_diesel::sqlite::Object, 439 + >, 433 440 ) -> Result<Option<GetAccountAdminStatusOutput>> { 434 441 let did = did.to_owned(); 435 442 let res: Option<(Option<String>, Option<String>)> = db 436 - .run(move |conn| { 443 + .get() 444 + .await? 445 + .interact(move |conn| { 437 446 ActorSchema::actor 438 447 .filter(ActorSchema::did.eq(did)) 439 448 .select((ActorSchema::takedownRef, ActorSchema::deactivatedAt)) 440 449 .first(conn) 441 450 .optional() 442 451 }) 443 - .await?; 452 + .await 453 + .expect("Failed to get account admin status")?; 444 454 match res { 445 455 None => Ok(None), 446 456 Some(res) => { ··· 471 481 } 472 482 } 473 483 } 474 - 475 - pub fn format_account_status(account: Option<ActorAccount>) -> FormattedAccountStatus { 476 - match account { 477 - None => FormattedAccountStatus { 478 - active: false, 479 - status: Some(AccountStatus::Deleted), 480 - }, 481 - Some(got) if got.takedown_ref.is_some() => FormattedAccountStatus { 482 - active: false, 483 - status: Some(AccountStatus::Takendown), 484 - }, 485 - Some(got) if got.deactivated_at.is_some() => FormattedAccountStatus { 486 - active: false, 487 - status: Some(AccountStatus::Deactivated), 488 - }, 489 - _ => FormattedAccountStatus { 490 - active: true, 491 - status: None, 492 - }, 493 - } 494 - }
+155 -301
src/account_manager/helpers/auth.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 - use crate::auth_verifier::AuthScope; 6 - use crate::db::DbConn; 7 - use crate::models; 8 5 use anyhow::Result; 9 6 use diesel::*; 10 - use jwt_simple::prelude::*; 11 - use rsky_common::time::{MINUTE, from_micros_to_utc}; 12 - use rsky_common::{RFC3339_VARIANT, get_random_str, json_to_b64url}; 13 - use secp256k1::{Keypair, Message, SecretKey}; 14 - use sha2::{Digest, Sha256}; 15 - use std::time::SystemTime; 16 - use thiserror::Error; 17 - 18 - pub struct CreateTokensOpts { 19 - pub did: String, 20 - pub jwt_key: Keypair, 21 - pub service_did: String, 22 - pub scope: Option<AuthScope>, 23 - pub jti: Option<String>, 24 - pub expires_in: Option<Duration>, 25 - } 26 - 27 - pub struct RefreshGracePeriodOpts { 28 - pub id: String, 29 - pub expires_at: String, 30 - pub next_id: String, 31 - } 32 - 33 - pub struct AuthToken { 34 - pub scope: AuthScope, 35 - pub sub: String, 36 - pub exp: Duration, 37 - } 38 - 39 - pub struct RefreshToken { 40 - pub scope: AuthScope, // AuthScope::Refresh 41 - pub sub: String, 42 - pub exp: Duration, 43 - pub jti: String, 44 - } 45 - 46 - #[derive(Debug, Deserialize, Serialize, Clone)] 47 - pub struct ServiceJwtPayload { 48 - pub iss: String, 49 - pub aud: String, 50 - pub exp: Option<u64>, 51 - pub lxm: Option<String>, 52 - pub jti: Option<String>, 53 - } 54 - 55 - #[derive(Debug, Deserialize, Serialize, Clone)] 56 - pub struct ServiceJwtHeader { 57 - pub typ: String, 58 - pub alg: String, 59 - } 60 - 61 - pub struct ServiceJwtParams { 62 - pub iss: String, 63 - pub aud: String, 64 - pub exp: Option<u64>, 65 - pub lxm: Option<String>, 66 - pub jti: Option<String>, 67 - pub keypair: SecretKey, 68 - } 69 - 70 - #[derive(Serialize, Deserialize)] 71 - pub struct CustomClaimObj { 72 - pub scope: String, 73 - } 74 - 75 - #[derive(Error, Debug)] 76 - pub enum AuthHelperError { 77 - #[error("ConcurrentRefreshError")] 78 - ConcurrentRefresh, 79 - } 80 - 81 - pub fn create_tokens(opts: CreateTokensOpts) -> Result<(String, String)> { 82 - let CreateTokensOpts { 83 - did, 84 - jwt_key, 85 - service_did, 86 - scope, 87 - jti, 88 - expires_in, 89 - } = opts; 90 - let access_jwt = create_access_token(CreateTokensOpts { 91 - did: did.clone(), 92 - jwt_key, 93 - service_did: service_did.clone(), 94 - scope, 95 - expires_in, 96 - jti: None, 97 - })?; 98 - let refresh_jwt = create_refresh_token(CreateTokensOpts { 99 - did, 100 - jwt_key, 101 - service_did, 102 - jti, 103 - expires_in, 104 - scope: None, 105 - })?; 106 - Ok((access_jwt, refresh_jwt)) 107 - } 108 - 109 - pub fn create_access_token(opts: CreateTokensOpts) -> Result<String> { 110 - let CreateTokensOpts { 111 - did, 112 - jwt_key, 113 - service_did, 114 - scope, 115 - expires_in, 116 - .. 117 - } = opts; 118 - let scope = scope.unwrap_or(AuthScope::Access); 119 - let expires_in = expires_in.unwrap_or_else(|| Duration::from_hours(2)); 120 - let claims = Claims::with_custom_claims( 121 - CustomClaimObj { 122 - scope: scope.as_str().to_owned(), 123 - }, 124 - expires_in, 125 - ) 126 - .with_audience(service_did) 127 - .with_subject(did); 128 - // alg ES256K 129 - let key = ES256kKeyPair::from_bytes(jwt_key.secret_bytes().as_slice())?; 130 - let token = key.sign(claims)?; 131 - Ok(token) 132 - } 133 - 134 - pub fn create_refresh_token(opts: CreateTokensOpts) -> Result<String> { 135 - let CreateTokensOpts { 136 - did, 137 - jwt_key, 138 - service_did, 139 - jti, 140 - expires_in, 141 - .. 142 - } = opts; 143 - let jti = jti.unwrap_or_else(get_random_str); 144 - let expires_in = expires_in.unwrap_or_else(|| Duration::from_days(90)); 145 - let claims = Claims::with_custom_claims( 146 - CustomClaimObj { 147 - scope: AuthScope::Refresh.as_str().to_owned(), 148 - }, 149 - expires_in, 150 - ) 151 - .with_audience(service_did) 152 - .with_subject(did) 153 - .with_jwt_id(jti); 154 - // alg ES256K 155 - let key = ES256kKeyPair::from_bytes(jwt_key.secret_bytes().as_slice())?; 156 - let token = key.sign(claims)?; 157 - Ok(token) 158 - } 159 - 160 - pub async fn create_service_jwt(params: ServiceJwtParams) -> Result<String> { 161 - let ServiceJwtParams { 162 - iss, aud, keypair, .. 163 - } = params; 164 - let now = SystemTime::now() 165 - .duration_since(SystemTime::UNIX_EPOCH) 166 - .expect("timestamp in micros since UNIX epoch") 167 - .as_micros() as usize; 168 - let exp = params 169 - .exp 170 - .unwrap_or(((now + MINUTE as usize) / 1000) as u64); 171 - let lxm = params.lxm; 172 - let jti = get_random_str(); 173 - let header = ServiceJwtHeader { 174 - typ: "JWT".to_string(), 175 - alg: "ES256K".to_string(), 176 - }; 177 - let payload = ServiceJwtPayload { 178 - iss, 179 - aud, 180 - exp: Some(exp), 181 - lxm, 182 - jti: Some(jti), 183 - }; 184 - let to_sign_str = format!( 185 - "{0}.{1}", 186 - json_to_b64url(&header)?, 187 - json_to_b64url(&payload)? 188 - ); 189 - let hash = Sha256::digest(to_sign_str.clone()); 190 - let message = Message::from_digest_slice(hash.as_ref())?; 191 - let mut sig = keypair.sign_ecdsa(message); 192 - // Convert to low-s 193 - sig.normalize_s(); 194 - // ASN.1 encoded per decode_dss_signature 195 - let compact_sig = sig.serialize_compact(); 196 - Ok(format!( 197 - "{0}.{1}", 198 - to_sign_str, 199 - base64_url::encode(&compact_sig).replace("=", "") // Base 64 encode signature bytes 200 - )) 201 - } 202 - 203 - // @NOTE unsafe for verification, should only be used w/ direct output from createRefreshToken() or createTokens() 204 - pub fn decode_refresh_token(jwt: String, jwt_key: Keypair) -> Result<RefreshToken> { 205 - let key = ES256kKeyPair::from_bytes(jwt_key.secret_bytes().as_slice())?; 206 - let public_key = key.public_key(); 207 - let claims = public_key.verify_token::<CustomClaimObj>(&jwt, None)?; 208 - assert_eq!( 209 - claims.custom.scope, 210 - AuthScope::Refresh.as_str().to_owned(), 211 - "not a refresh token" 212 - ); 213 - Ok(RefreshToken { 214 - scope: AuthScope::from_str(&claims.custom.scope)?, 215 - sub: claims.subject.unwrap(), 216 - exp: claims.expires_at.unwrap(), 217 - jti: claims.jwt_id.unwrap(), 218 - }) 219 - } 7 + use rsky_common::time::from_micros_to_utc; 8 + use rsky_common::{RFC3339_VARIANT, get_random_str}; 9 + #[expect(unused_imports)] 10 + pub(crate) use rsky_pds::account_manager::helpers::auth::{ 11 + AuthHelperError, AuthToken, CreateTokensOpts, CustomClaimObj, RefreshGracePeriodOpts, 12 + RefreshToken, ServiceJwtHeader, ServiceJwtParams, ServiceJwtPayload, create_access_token, 13 + create_refresh_token, create_service_jwt, create_tokens, decode_refresh_token, 14 + }; 15 + use rsky_pds::models; 220 16 221 17 pub async fn store_refresh_token( 222 18 payload: RefreshToken, 223 19 app_password_name: Option<String>, 224 - db: &DbConn, 20 + db: &deadpool_diesel::Pool< 21 + deadpool_diesel::Manager<SqliteConnection>, 22 + deadpool_diesel::sqlite::Object, 23 + >, 225 24 ) -> Result<()> { 226 - use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema; 25 + use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema; 227 26 228 27 let exp = from_micros_to_utc((payload.exp.as_millis() / 1000) as i64); 229 28 230 - db.run(move |conn| { 231 - insert_into(RefreshTokenSchema::refresh_token) 232 - .values(( 233 - RefreshTokenSchema::id.eq(payload.jti), 234 - RefreshTokenSchema::did.eq(payload.sub), 235 - RefreshTokenSchema::appPasswordName.eq(app_password_name), 236 - RefreshTokenSchema::expiresAt.eq(format!("{}", exp.format(RFC3339_VARIANT))), 237 - )) 238 - .on_conflict_do_nothing() // E.g. when re-granting during a refresh grace period 239 - .execute(conn) 240 - }) 241 - .await?; 29 + db.get() 30 + .await? 31 + .interact(move |conn| { 32 + insert_into(RefreshTokenSchema::refresh_token) 33 + .values(( 34 + RefreshTokenSchema::id.eq(payload.jti), 35 + RefreshTokenSchema::did.eq(payload.sub), 36 + RefreshTokenSchema::appPasswordName.eq(app_password_name), 37 + RefreshTokenSchema::expiresAt.eq(format!("{}", exp.format(RFC3339_VARIANT))), 38 + )) 39 + .on_conflict_do_nothing() // E.g. when re-granting during a refresh grace period 40 + .execute(conn) 41 + }) 42 + .await 43 + .expect("Failed to store refresh token")?; 242 44 243 45 Ok(()) 244 46 } 245 47 246 - pub async fn revoke_refresh_token(id: String, db: &DbConn) -> Result<bool> { 247 - use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema; 248 - db.run(move |conn| { 249 - let deleted_rows = delete(RefreshTokenSchema::refresh_token) 250 - .filter(RefreshTokenSchema::id.eq(id)) 251 - .get_results::<models::RefreshToken>(conn)?; 48 + pub async fn revoke_refresh_token( 49 + id: String, 50 + db: &deadpool_diesel::Pool< 51 + deadpool_diesel::Manager<SqliteConnection>, 52 + deadpool_diesel::sqlite::Object, 53 + >, 54 + ) -> Result<bool> { 55 + use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema; 56 + db.get() 57 + .await? 58 + .interact(move |conn| { 59 + let deleted_rows = delete(RefreshTokenSchema::refresh_token) 60 + .filter(RefreshTokenSchema::id.eq(id)) 61 + .get_results::<models::RefreshToken>(conn)?; 252 62 253 - Ok(!deleted_rows.is_empty()) 254 - }) 255 - .await 63 + Ok(!deleted_rows.is_empty()) 64 + }) 65 + .await 66 + .expect("Failed to revoke refresh token") 256 67 } 257 68 258 - pub async fn revoke_refresh_tokens_by_did(did: &str, db: &DbConn) -> Result<bool> { 259 - use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema; 69 + pub async fn revoke_refresh_tokens_by_did( 70 + did: &str, 71 + db: &deadpool_diesel::Pool< 72 + deadpool_diesel::Manager<SqliteConnection>, 73 + deadpool_diesel::sqlite::Object, 74 + >, 75 + ) -> Result<bool> { 76 + use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema; 260 77 let did = did.to_owned(); 261 - db.run(move |conn| { 262 - let deleted_rows = delete(RefreshTokenSchema::refresh_token) 263 - .filter(RefreshTokenSchema::did.eq(did)) 264 - .get_results::<models::RefreshToken>(conn)?; 78 + db.get() 79 + .await? 80 + .interact(move |conn| { 81 + let deleted_rows = delete(RefreshTokenSchema::refresh_token) 82 + .filter(RefreshTokenSchema::did.eq(did)) 83 + .get_results::<models::RefreshToken>(conn)?; 265 84 266 - Ok(!deleted_rows.is_empty()) 267 - }) 268 - .await 85 + Ok(!deleted_rows.is_empty()) 86 + }) 87 + .await 88 + .expect("Failed to revoke refresh tokens by DID") 269 89 } 270 90 271 91 pub async fn revoke_app_password_refresh_token( 272 92 did: &str, 273 93 app_pass_name: &str, 274 - db: &DbConn, 94 + db: &deadpool_diesel::Pool< 95 + deadpool_diesel::Manager<SqliteConnection>, 96 + deadpool_diesel::sqlite::Object, 97 + >, 275 98 ) -> Result<bool> { 276 - use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema; 99 + use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema; 277 100 278 101 let did = did.to_owned(); 279 102 let app_pass_name = app_pass_name.to_owned(); 280 - db.run(move |conn| { 281 - let deleted_rows = delete(RefreshTokenSchema::refresh_token) 282 - .filter(RefreshTokenSchema::did.eq(did)) 283 - .filter(RefreshTokenSchema::appPasswordName.eq(app_pass_name)) 284 - .get_results::<models::RefreshToken>(conn)?; 103 + db.get() 104 + .await? 105 + .interact(move |conn| { 106 + let deleted_rows = delete(RefreshTokenSchema::refresh_token) 107 + .filter(RefreshTokenSchema::did.eq(did)) 108 + .filter(RefreshTokenSchema::appPasswordName.eq(app_pass_name)) 109 + .get_results::<models::RefreshToken>(conn)?; 285 110 286 - Ok(!deleted_rows.is_empty()) 287 - }) 288 - .await 111 + Ok(!deleted_rows.is_empty()) 112 + }) 113 + .await 114 + .expect("Failed to revoke app password refresh token") 289 115 } 290 116 291 - pub async fn get_refresh_token(id: &str, db: &DbConn) -> Result<Option<models::RefreshToken>> { 292 - use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema; 117 + pub async fn get_refresh_token( 118 + id: &str, 119 + db: &deadpool_diesel::Pool< 120 + deadpool_diesel::Manager<SqliteConnection>, 121 + deadpool_diesel::sqlite::Object, 122 + >, 123 + ) -> Result<Option<models::RefreshToken>> { 124 + use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema; 293 125 let id = id.to_owned(); 294 - db.run(move |conn| { 295 - Ok(RefreshTokenSchema::refresh_token 296 - .find(id) 297 - .first(conn) 298 - .optional()?) 299 - }) 300 - .await 126 + db.get() 127 + .await? 128 + .interact(move |conn| { 129 + Ok(RefreshTokenSchema::refresh_token 130 + .find(id) 131 + .first(conn) 132 + .optional()?) 133 + }) 134 + .await 135 + .expect("Failed to get refresh token") 301 136 } 302 137 303 - pub async fn delete_expired_refresh_tokens(did: &str, now: String, db: &DbConn) -> Result<()> { 304 - use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema; 138 + pub async fn delete_expired_refresh_tokens( 139 + did: &str, 140 + now: String, 141 + db: &deadpool_diesel::Pool< 142 + deadpool_diesel::Manager<SqliteConnection>, 143 + deadpool_diesel::sqlite::Object, 144 + >, 145 + ) -> Result<()> { 146 + use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema; 305 147 let did = did.to_owned(); 306 148 307 - db.run(move |conn| { 308 - delete(RefreshTokenSchema::refresh_token) 309 - .filter(RefreshTokenSchema::did.eq(did)) 310 - .filter(RefreshTokenSchema::expiresAt.le(now)) 311 - .execute(conn)?; 312 - Ok(()) 313 - }) 314 - .await 149 + db.get() 150 + .await? 151 + .interact(move |conn| { 152 + delete(RefreshTokenSchema::refresh_token) 153 + .filter(RefreshTokenSchema::did.eq(did)) 154 + .filter(RefreshTokenSchema::expiresAt.le(now)) 155 + .execute(conn)?; 156 + Ok(()) 157 + }) 158 + .await 159 + .expect("Failed to delete expired refresh tokens") 315 160 } 316 161 317 - pub async fn add_refresh_grace_period(opts: RefreshGracePeriodOpts, db: &DbConn) -> Result<()> { 318 - db.run(move |conn| { 319 - let RefreshGracePeriodOpts { 320 - id, 321 - expires_at, 322 - next_id, 323 - } = opts; 324 - use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema; 162 + pub async fn add_refresh_grace_period( 163 + opts: RefreshGracePeriodOpts, 164 + db: &deadpool_diesel::Pool< 165 + deadpool_diesel::Manager<SqliteConnection>, 166 + deadpool_diesel::sqlite::Object, 167 + >, 168 + ) -> Result<()> { 169 + db.get() 170 + .await? 171 + .interact(move |conn| { 172 + let RefreshGracePeriodOpts { 173 + id, 174 + expires_at, 175 + next_id, 176 + } = opts; 177 + use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema; 325 178 326 - update(RefreshTokenSchema::refresh_token) 327 - .filter(RefreshTokenSchema::id.eq(id)) 328 - .filter( 329 - RefreshTokenSchema::nextId 330 - .is_null() 331 - .or(RefreshTokenSchema::nextId.eq(&next_id)), 332 - ) 333 - .set(( 334 - RefreshTokenSchema::expiresAt.eq(expires_at), 335 - RefreshTokenSchema::nextId.eq(&next_id), 336 - )) 337 - .returning(models::RefreshToken::as_select()) 338 - .get_results(conn) 339 - .map_err(|error| { 340 - anyhow::Error::new(AuthHelperError::ConcurrentRefresh).context(error) 341 - })?; 342 - Ok(()) 343 - }) 344 - .await 179 + update(RefreshTokenSchema::refresh_token) 180 + .filter(RefreshTokenSchema::id.eq(id)) 181 + .filter( 182 + RefreshTokenSchema::nextId 183 + .is_null() 184 + .or(RefreshTokenSchema::nextId.eq(&next_id)), 185 + ) 186 + .set(( 187 + RefreshTokenSchema::expiresAt.eq(expires_at), 188 + RefreshTokenSchema::nextId.eq(&next_id), 189 + )) 190 + .returning(models::RefreshToken::as_select()) 191 + .get_results(conn) 192 + .map_err(|error| { 193 + anyhow::Error::new(AuthHelperError::ConcurrentRefresh).context(error) 194 + })?; 195 + Ok(()) 196 + }) 197 + .await 198 + .expect("Failed to add refresh grace period") 345 199 } 346 200 347 201 pub fn get_refresh_token_id() -> String {
+85 -50
src/account_manager/helpers/email_token.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 - use crate::apis::com::atproto::server::get_random_token; 6 - use crate::db::DbConn; 7 - use crate::models::EmailToken; 8 - use crate::models::models::EmailTokenPurpose; 9 5 use anyhow::{Result, bail}; 10 6 use diesel::*; 11 - use rsky_common; 12 7 use rsky_common::time::{MINUTE, from_str_to_utc, less_than_ago_s}; 8 + use rsky_pds::apis::com::atproto::server::get_random_token; 9 + use rsky_pds::models::EmailToken; 10 + use rsky_pds::models::models::EmailTokenPurpose; 13 11 14 12 pub async fn create_email_token( 15 13 did: &str, 16 14 purpose: EmailTokenPurpose, 17 - db: &DbConn, 15 + db: &deadpool_diesel::Pool< 16 + deadpool_diesel::Manager<SqliteConnection>, 17 + deadpool_diesel::sqlite::Object, 18 + >, 18 19 ) -> Result<String> { 19 - use crate::schema::pds::email_token::dsl as EmailTokenSchema; 20 + use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema; 20 21 let token = get_random_token().to_uppercase(); 21 22 let now = rsky_common::now(); 22 23 23 24 let did = did.to_owned(); 24 - db.run(move |conn| { 25 - insert_into(EmailTokenSchema::email_token) 26 - .values(( 27 - EmailTokenSchema::purpose.eq(purpose), 28 - EmailTokenSchema::did.eq(did), 29 - EmailTokenSchema::token.eq(&token), 30 - EmailTokenSchema::requestedAt.eq(&now), 31 - )) 32 - .on_conflict((EmailTokenSchema::purpose, EmailTokenSchema::did)) 33 - .do_update() 34 - .set(( 35 - EmailTokenSchema::token.eq(&token), 36 - EmailTokenSchema::requestedAt.eq(&now), 37 - )) 38 - .execute(conn)?; 39 - Ok(token) 40 - }) 41 - .await 25 + db.get() 26 + .await? 27 + .interact(move |conn| { 28 + insert_into(EmailTokenSchema::email_token) 29 + .values(( 30 + EmailTokenSchema::purpose.eq(purpose), 31 + EmailTokenSchema::did.eq(did), 32 + EmailTokenSchema::token.eq(&token), 33 + EmailTokenSchema::requestedAt.eq(&now), 34 + )) 35 + .on_conflict((EmailTokenSchema::purpose, EmailTokenSchema::did)) 36 + .do_update() 37 + .set(( 38 + EmailTokenSchema::token.eq(&token), 39 + EmailTokenSchema::requestedAt.eq(&now), 40 + )) 41 + .execute(conn)?; 42 + Ok(token) 43 + }) 44 + .await 45 + .expect("Failed to create email token") 42 46 } 43 47 44 48 pub async fn assert_valid_token( ··· 46 50 purpose: EmailTokenPurpose, 47 51 token: &str, 48 52 expiration_len: Option<i32>, 49 - db: &DbConn, 53 + db: &deadpool_diesel::Pool< 54 + deadpool_diesel::Manager<SqliteConnection>, 55 + deadpool_diesel::sqlite::Object, 56 + >, 50 57 ) -> Result<()> { 51 58 let expiration_len = expiration_len.unwrap_or(MINUTE * 15); 52 - use crate::schema::pds::email_token::dsl as EmailTokenSchema; 59 + use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema; 53 60 54 61 let did = did.to_owned(); 55 62 let token = token.to_owned(); 56 63 let res = db 57 - .run(move |conn| { 64 + .get() 65 + .await? 66 + .interact(move |conn| { 58 67 EmailTokenSchema::email_token 59 68 .filter(EmailTokenSchema::purpose.eq(purpose)) 60 69 .filter(EmailTokenSchema::did.eq(did)) ··· 63 72 .first(conn) 64 73 .optional() 65 74 }) 66 - .await?; 75 + .await 76 + .expect("Failed to assert token")?; 67 77 if let Some(res) = res { 68 78 let requested_at = from_str_to_utc(&res.requested_at); 69 79 let expired = !less_than_ago_s(requested_at, expiration_len); ··· 80 90 purpose: EmailTokenPurpose, 81 91 token: &str, 82 92 expiration_len: Option<i32>, 83 - db: &DbConn, 93 + db: &deadpool_diesel::Pool< 94 + deadpool_diesel::Manager<SqliteConnection>, 95 + deadpool_diesel::sqlite::Object, 96 + >, 84 97 ) -> Result<String> { 85 98 let expiration_len = expiration_len.unwrap_or(MINUTE * 15); 86 - use crate::schema::pds::email_token::dsl as EmailTokenSchema; 99 + use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema; 87 100 88 101 let token = token.to_owned(); 89 102 let res = db 90 - .run(move |conn| { 103 + .get() 104 + .await? 105 + .interact(move |conn| { 91 106 EmailTokenSchema::email_token 92 107 .filter(EmailTokenSchema::purpose.eq(purpose)) 93 108 .filter(EmailTokenSchema::token.eq(token.to_uppercase())) ··· 95 110 .first(conn) 96 111 .optional() 97 112 }) 98 - .await?; 113 + .await 114 + .expect("Failed to assert token")?; 99 115 if let Some(res) = res { 100 116 let requested_at = from_str_to_utc(&res.requested_at); 101 117 let expired = !less_than_ago_s(requested_at, expiration_len); ··· 108 124 } 109 125 } 110 126 111 - pub async fn delete_email_token(did: &str, purpose: EmailTokenPurpose, db: &DbConn) -> Result<()> { 112 - use crate::schema::pds::email_token::dsl as EmailTokenSchema; 127 + pub async fn delete_email_token( 128 + did: &str, 129 + purpose: EmailTokenPurpose, 130 + db: &deadpool_diesel::Pool< 131 + deadpool_diesel::Manager<SqliteConnection>, 132 + deadpool_diesel::sqlite::Object, 133 + >, 134 + ) -> Result<()> { 135 + use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema; 113 136 let did = did.to_owned(); 114 - db.run(move |conn| { 115 - delete(EmailTokenSchema::email_token) 116 - .filter(EmailTokenSchema::did.eq(did)) 117 - .filter(EmailTokenSchema::purpose.eq(purpose)) 118 - .execute(conn) 119 - }) 120 - .await?; 137 + db.get() 138 + .await? 139 + .interact(move |conn| { 140 + delete(EmailTokenSchema::email_token) 141 + .filter(EmailTokenSchema::did.eq(did)) 142 + .filter(EmailTokenSchema::purpose.eq(purpose)) 143 + .execute(conn) 144 + }) 145 + .await 146 + .expect("Failed to delete token")?; 121 147 Ok(()) 122 148 } 123 149 124 - pub async fn delete_all_email_tokens(did: &str, db: &DbConn) -> Result<()> { 125 - use crate::schema::pds::email_token::dsl as EmailTokenSchema; 150 + pub async fn delete_all_email_tokens( 151 + did: &str, 152 + db: &deadpool_diesel::Pool< 153 + deadpool_diesel::Manager<SqliteConnection>, 154 + deadpool_diesel::sqlite::Object, 155 + >, 156 + ) -> Result<()> { 157 + use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema; 126 158 127 159 let did = did.to_owned(); 128 - db.run(move |conn| { 129 - delete(EmailTokenSchema::email_token) 130 - .filter(EmailTokenSchema::did.eq(did)) 131 - .execute(conn) 132 - }) 133 - .await?; 160 + db.get() 161 + .await? 162 + .interact(move |conn| { 163 + delete(EmailTokenSchema::email_token) 164 + .filter(EmailTokenSchema::did.eq(did)) 165 + .execute(conn) 166 + }) 167 + .await 168 + .expect("Failed to delete all tokens")?; 134 169 135 170 Ok(()) 136 171 }
+155 -90
src/account_manager/helpers/invite.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 - use crate::account_manager::DisableInviteCodesOpts; 6 - use crate::db::DbConn; 7 - use crate::models::models; 8 5 use anyhow::{Result, bail}; 9 6 use diesel::*; 10 - use rsky_common; 11 7 use rsky_lexicon::com::atproto::server::AccountCodes; 12 8 use rsky_lexicon::com::atproto::server::{ 13 9 InviteCode as LexiconInviteCode, InviteCodeUse as LexiconInviteCodeUse, 14 10 }; 11 + use rsky_pds::account_manager::DisableInviteCodesOpts; 12 + use rsky_pds::models::models; 15 13 use std::collections::BTreeMap; 16 14 use std::mem; 17 15 18 16 pub type CodeUse = LexiconInviteCodeUse; 19 17 pub type CodeDetail = LexiconInviteCode; 20 18 21 - pub async fn ensure_invite_is_available(invite_code: String, db: &DbConn) -> Result<()> { 22 - use crate::schema::pds::actor::dsl as ActorSchema; 23 - use crate::schema::pds::invite_code::dsl as InviteCodeSchema; 24 - use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema; 19 + pub async fn ensure_invite_is_available( 20 + invite_code: String, 21 + db: &deadpool_diesel::Pool< 22 + deadpool_diesel::Manager<SqliteConnection>, 23 + deadpool_diesel::sqlite::Object, 24 + >, 25 + ) -> Result<()> { 26 + use rsky_pds::schema::pds::actor::dsl as ActorSchema; 27 + use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema; 28 + use rsky_pds::schema::pds::invite_code_use::dsl as InviteCodeUseSchema; 25 29 26 - db.run(move |conn| { 30 + db.get().await?.interact(move |conn| { 27 31 let invite: Option<models::InviteCode> = InviteCodeSchema::invite_code 28 32 .left_join( 29 33 ActorSchema::actor.on(InviteCodeSchema::forAccount ··· 48 52 bail!("InvalidInviteCode: Not enough uses. Provided invite code not available `{invite_code:?}`") 49 53 } 50 54 Ok(()) 51 - }).await?; 55 + }).await.expect("Failed to check invite code availability")?; 52 56 53 57 Ok(()) 54 58 } ··· 57 61 did: String, 58 62 invite_code: Option<String>, 59 63 now: String, 60 - db: &DbConn, 64 + db: &deadpool_diesel::Pool< 65 + deadpool_diesel::Manager<SqliteConnection>, 66 + deadpool_diesel::sqlite::Object, 67 + >, 61 68 ) -> Result<()> { 62 69 if let Some(invite_code) = invite_code { 63 - use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema; 70 + use rsky_pds::schema::pds::invite_code_use::dsl as InviteCodeUseSchema; 64 71 65 - db.run(move |conn| { 66 - insert_into(InviteCodeUseSchema::invite_code_use) 67 - .values(( 68 - InviteCodeUseSchema::code.eq(invite_code), 69 - InviteCodeUseSchema::usedBy.eq(did), 70 - InviteCodeUseSchema::usedAt.eq(now), 71 - )) 72 - .execute(conn) 73 - }) 74 - .await?; 72 + db.get() 73 + .await? 74 + .interact(move |conn| { 75 + insert_into(InviteCodeUseSchema::invite_code_use) 76 + .values(( 77 + InviteCodeUseSchema::code.eq(invite_code), 78 + InviteCodeUseSchema::usedBy.eq(did), 79 + InviteCodeUseSchema::usedAt.eq(now), 80 + )) 81 + .execute(conn) 82 + }) 83 + .await 84 + .expect("Failed to record invite code use")?; 75 85 } 76 86 Ok(()) 77 87 } ··· 79 89 pub async fn create_invite_codes( 80 90 to_create: Vec<AccountCodes>, 81 91 use_count: i32, 82 - db: &DbConn, 92 + db: &deadpool_diesel::Pool< 93 + deadpool_diesel::Manager<SqliteConnection>, 94 + deadpool_diesel::sqlite::Object, 95 + >, 83 96 ) -> Result<()> { 84 - use crate::schema::pds::invite_code::dsl as InviteCodeSchema; 97 + use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema; 85 98 let created_at = rsky_common::now(); 86 99 87 - db.run(move |conn| { 88 - let rows: Vec<models::InviteCode> = to_create 89 - .into_iter() 90 - .flat_map(|account| { 91 - let for_account = account.account; 92 - account 93 - .codes 94 - .iter() 95 - .map(|code| models::InviteCode { 96 - code: code.clone(), 97 - available_uses: use_count, 98 - disabled: 0, 99 - for_account: for_account.clone(), 100 - created_by: "admin".to_owned(), 101 - created_at: created_at.clone(), 102 - }) 103 - .collect::<Vec<models::InviteCode>>() 104 - }) 105 - .collect(); 106 - insert_into(InviteCodeSchema::invite_code) 107 - .values(&rows) 108 - .execute(conn) 109 - }) 110 - .await?; 100 + db.get() 101 + .await? 102 + .interact(move |conn| { 103 + let rows: Vec<models::InviteCode> = to_create 104 + .into_iter() 105 + .flat_map(|account| { 106 + let for_account = account.account; 107 + account 108 + .codes 109 + .iter() 110 + .map(|code| models::InviteCode { 111 + code: code.clone(), 112 + available_uses: use_count, 113 + disabled: 0, 114 + for_account: for_account.clone(), 115 + created_by: "admin".to_owned(), 116 + created_at: created_at.clone(), 117 + }) 118 + .collect::<Vec<models::InviteCode>>() 119 + }) 120 + .collect(); 121 + insert_into(InviteCodeSchema::invite_code) 122 + .values(&rows) 123 + .execute(conn) 124 + }) 125 + .await 126 + .expect("Failed to create invite codes")?; 111 127 Ok(()) 112 128 } 113 129 ··· 116 132 codes: Vec<String>, 117 133 expected_total: usize, 118 134 disabled: bool, 119 - db: &DbConn, 135 + db: &deadpool_diesel::Pool< 136 + deadpool_diesel::Manager<SqliteConnection>, 137 + deadpool_diesel::sqlite::Object, 138 + >, 120 139 ) -> Result<Vec<CodeDetail>> { 121 - use crate::schema::pds::invite_code::dsl as InviteCodeSchema; 140 + use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema; 122 141 123 142 let for_account = for_account.to_owned(); 124 143 let rows = db 125 - .run(move |conn| { 144 + .get() 145 + .await? 146 + .interact(move |conn| { 126 147 let now = rsky_common::now(); 127 148 128 149 let rows: Vec<models::InviteCode> = codes ··· 161 182 uses: Vec::new(), 162 183 })) 163 184 }) 164 - .await?; 185 + .await 186 + .expect("Failed to create account invite codes")?; 165 187 Ok(rows.collect()) 166 188 } 167 189 168 - pub async fn get_account_invite_codes(did: &str, db: &DbConn) -> Result<Vec<CodeDetail>> { 169 - use crate::schema::pds::invite_code::dsl as InviteCodeSchema; 190 + pub async fn get_account_invite_codes( 191 + did: &str, 192 + db: &deadpool_diesel::Pool< 193 + deadpool_diesel::Manager<SqliteConnection>, 194 + deadpool_diesel::sqlite::Object, 195 + >, 196 + ) -> Result<Vec<CodeDetail>> { 197 + use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema; 170 198 171 199 let did = did.to_owned(); 172 200 let res: Vec<models::InviteCode> = db 173 - .run(move |conn| { 201 + .get() 202 + .await? 203 + .interact(move |conn| { 174 204 InviteCodeSchema::invite_code 175 205 .filter(InviteCodeSchema::forAccount.eq(did)) 176 206 .select(models::InviteCode::as_select()) 177 207 .get_results(conn) 178 208 }) 179 - .await?; 209 + .await 210 + .expect("Failed to get account invite codes")?; 180 211 181 212 let codes: Vec<String> = res.iter().map(|row| row.code.clone()).collect(); 182 213 let mut uses = get_invite_codes_uses_v2(codes, db).await?; ··· 196 227 197 228 pub async fn get_invite_codes_uses_v2( 198 229 codes: Vec<String>, 199 - db: &DbConn, 230 + db: &deadpool_diesel::Pool< 231 + deadpool_diesel::Manager<SqliteConnection>, 232 + deadpool_diesel::sqlite::Object, 233 + >, 200 234 ) -> Result<BTreeMap<String, Vec<CodeUse>>> { 201 - use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema; 235 + use rsky_pds::schema::pds::invite_code_use::dsl as InviteCodeUseSchema; 202 236 203 237 let mut uses: BTreeMap<String, Vec<CodeUse>> = BTreeMap::new(); 204 238 if !codes.is_empty() { 205 239 let uses_res: Vec<models::InviteCodeUse> = db 206 - .run(|conn| { 240 + .get() 241 + .await? 242 + .interact(|conn| { 207 243 InviteCodeUseSchema::invite_code_use 208 244 .filter(InviteCodeUseSchema::code.eq_any(codes)) 209 245 .order_by(InviteCodeUseSchema::usedAt.desc()) 210 246 .select(models::InviteCodeUse::as_select()) 211 247 .get_results(conn) 212 248 }) 213 - .await?; 249 + .await 250 + .expect("Failed to get invite code uses")?; 214 251 for invite_code_use in uses_res { 215 252 let models::InviteCodeUse { 216 253 code, ··· 230 267 231 268 pub async fn get_invited_by_for_accounts( 232 269 dids: Vec<String>, 233 - db: &DbConn, 270 + db: &deadpool_diesel::Pool< 271 + deadpool_diesel::Manager<SqliteConnection>, 272 + deadpool_diesel::sqlite::Object, 273 + >, 234 274 ) -> Result<BTreeMap<String, CodeDetail>> { 235 275 if dids.is_empty() { 236 276 return Ok(BTreeMap::new()); 237 277 } 238 - use crate::schema::pds::invite_code::dsl as InviteCodeSchema; 239 - use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema; 278 + use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema; 279 + use rsky_pds::schema::pds::invite_code_use::dsl as InviteCodeUseSchema; 240 280 241 281 let dids = dids.clone(); 242 282 let res: Vec<models::InviteCode> = db 243 - .run(|conn| { 283 + .get() 284 + .await? 285 + .interact(|conn| { 244 286 InviteCodeSchema::invite_code 245 287 .filter( 246 288 InviteCodeSchema::forAccount.eq_any( ··· 253 295 .select(models::InviteCode::as_select()) 254 296 .get_results(conn) 255 297 }) 256 - .await?; 298 + .await 299 + .expect("Failed to get account invite codes")?; 257 300 let codes: Vec<String> = res.iter().map(|row| row.code.clone()).collect(); 258 301 let mut uses = get_invite_codes_uses_v2(codes, db).await?; 259 302 ··· 281 324 )) 282 325 } 283 326 284 - pub async fn set_account_invites_disabled(did: &str, disabled: bool, db: &DbConn) -> Result<()> { 285 - use crate::schema::pds::account::dsl as AccountSchema; 327 + pub async fn set_account_invites_disabled( 328 + did: &str, 329 + disabled: bool, 330 + db: &deadpool_diesel::Pool< 331 + deadpool_diesel::Manager<SqliteConnection>, 332 + deadpool_diesel::sqlite::Object, 333 + >, 334 + ) -> Result<()> { 335 + use rsky_pds::schema::pds::account::dsl as AccountSchema; 286 336 287 337 let disabled: i16 = if disabled { 1 } else { 0 }; 288 338 let did = did.to_owned(); 289 - db.run(move |conn| { 290 - update(AccountSchema::account) 291 - .filter(AccountSchema::did.eq(did)) 292 - .set((AccountSchema::invitesDisabled.eq(disabled),)) 293 - .execute(conn) 294 - }) 295 - .await?; 339 + db.get() 340 + .await? 341 + .interact(move |conn| { 342 + update(AccountSchema::account) 343 + .filter(AccountSchema::did.eq(did)) 344 + .set((AccountSchema::invitesDisabled.eq(disabled),)) 345 + .execute(conn) 346 + }) 347 + .await 348 + .expect("Failed to set account invites disabled")?; 296 349 Ok(()) 297 350 } 298 351 299 - pub async fn disable_invite_codes(opts: DisableInviteCodesOpts, db: &DbConn) -> Result<()> { 300 - use crate::schema::pds::invite_code::dsl as InviteCodeSchema; 352 + pub async fn disable_invite_codes( 353 + opts: DisableInviteCodesOpts, 354 + db: &deadpool_diesel::Pool< 355 + deadpool_diesel::Manager<SqliteConnection>, 356 + deadpool_diesel::sqlite::Object, 357 + >, 358 + ) -> Result<()> { 359 + use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema; 301 360 302 361 let DisableInviteCodesOpts { codes, accounts } = opts; 303 362 if !codes.is_empty() { 304 - db.run(move |conn| { 305 - update(InviteCodeSchema::invite_code) 306 - .filter(InviteCodeSchema::code.eq_any(&codes)) 307 - .set((InviteCodeSchema::disabled.eq(1),)) 308 - .execute(conn) 309 - }) 310 - .await?; 363 + db.get() 364 + .await? 365 + .interact(move |conn| { 366 + update(InviteCodeSchema::invite_code) 367 + .filter(InviteCodeSchema::code.eq_any(&codes)) 368 + .set((InviteCodeSchema::disabled.eq(1),)) 369 + .execute(conn) 370 + }) 371 + .await 372 + .expect("Failed to disable invite codes")?; 311 373 } 312 374 if !accounts.is_empty() { 313 - db.run(move |conn| { 314 - update(InviteCodeSchema::invite_code) 315 - .filter(InviteCodeSchema::forAccount.eq_any(&accounts)) 316 - .set((InviteCodeSchema::disabled.eq(1),)) 317 - .execute(conn) 318 - }) 319 - .await?; 375 + db.get() 376 + .await? 377 + .interact(move |conn| { 378 + update(InviteCodeSchema::invite_code) 379 + .filter(InviteCodeSchema::forAccount.eq_any(&accounts)) 380 + .set((InviteCodeSchema::disabled.eq(1),)) 381 + .execute(conn) 382 + }) 383 + .await 384 + .expect("Failed to disable invite codes")?; 320 385 } 321 386 Ok(()) 322 387 }
+122 -106
src/account_manager/helpers/password.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 - use crate::db::DbConn; 6 - use crate::models; 7 - use crate::models::AppPassword; 8 5 use anyhow::{Result, anyhow, bail}; 9 6 use argon2::{ 10 7 Argon2, 11 8 password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng}, 12 9 }; 13 - use base64ct::{Base64, Encoding}; 10 + // use base64ct::{Base64, Encoding}; 14 11 use diesel::*; 15 12 use rsky_common::{get_random_str, now}; 16 13 use rsky_lexicon::com::atproto::server::CreateAppPasswordOutput; 17 - use sha2::{Digest, Sha256}; 14 + #[expect(unused_imports)] 15 + pub(crate) use rsky_pds::account_manager::helpers::password::{ 16 + UpdateUserPasswordOpts, gen_salt_and_hash, hash_app_password, hash_with_salt, verify, 17 + }; 18 + use rsky_pds::models; 19 + use rsky_pds::models::AppPassword; 18 20 19 - pub struct UpdateUserPasswordOpts { 20 - pub did: String, 21 - pub password_encrypted: String, 22 - } 23 - 24 - pub async fn verify_account_password(did: &str, password: &String, db: &DbConn) -> Result<bool> { 25 - use crate::schema::pds::account::dsl as AccountSchema; 21 + pub async fn verify_account_password( 22 + did: &str, 23 + password: &String, 24 + db: &deadpool_diesel::Pool< 25 + deadpool_diesel::Manager<SqliteConnection>, 26 + deadpool_diesel::sqlite::Object, 27 + >, 28 + ) -> Result<bool> { 29 + use rsky_pds::schema::pds::account::dsl as AccountSchema; 26 30 27 31 let did = did.to_owned(); 28 32 let found = db 29 - .run(move |conn| { 33 + .get() 34 + .await? 35 + .interact(move |conn| { 30 36 AccountSchema::account 31 37 .filter(AccountSchema::did.eq(did)) 32 38 .select(models::Account::as_select()) 33 39 .first(conn) 34 40 .optional() 35 41 }) 36 - .await?; 42 + .await 43 + .expect("Failed to get account")?; 37 44 if let Some(found) = found { 38 45 verify(password, &found.password) 39 46 } else { ··· 41 48 } 42 49 } 43 50 44 - pub async fn verify_app_password(did: &str, password: &str, db: &DbConn) -> Result<Option<String>> { 45 - use crate::schema::pds::app_password::dsl as AppPasswordSchema; 51 + pub async fn verify_app_password( 52 + did: &str, 53 + password: &str, 54 + db: &deadpool_diesel::Pool< 55 + deadpool_diesel::Manager<SqliteConnection>, 56 + deadpool_diesel::sqlite::Object, 57 + >, 58 + ) -> Result<Option<String>> { 59 + use rsky_pds::schema::pds::app_password::dsl as AppPasswordSchema; 46 60 47 61 let did = did.to_owned(); 48 62 let password = password.to_owned(); 49 63 let password_encrypted = hash_app_password(&did, &password).await?; 50 64 let found = db 51 - .run(move |conn| { 65 + .get() 66 + .await? 67 + .interact(move |conn| { 52 68 AppPasswordSchema::app_password 53 69 .filter(AppPasswordSchema::did.eq(did)) 54 70 .filter(AppPasswordSchema::password.eq(password_encrypted)) ··· 56 72 .first(conn) 57 73 .optional() 58 74 }) 59 - .await?; 75 + .await 76 + .expect("Failed to get app password")?; 60 77 if let Some(found) = found { 61 78 Ok(Some(found.name)) 62 79 } else { ··· 64 81 } 65 82 } 66 83 67 - // We use Argon because it's 3x faster than scrypt. 68 - pub fn gen_salt_and_hash(password: String) -> Result<String> { 69 - let salt = SaltString::generate(&mut OsRng); 70 - // Hash password to PHC string 71 - let argon2 = Argon2::default(); 72 - let password_hash = argon2 73 - .hash_password(password.as_ref(), &salt) 74 - .map_err(|error| anyhow!(error.to_string()))? 75 - .to_string(); 76 - Ok(password_hash) 77 - } 78 - 79 - pub fn hash_with_salt(password: &String, salt: &str) -> Result<String> { 80 - let salt = SaltString::from_b64(salt).map_err(|error| anyhow!(error.to_string()))?; 81 - let argon2 = Argon2::default(); 82 - let password_hash = argon2 83 - .hash_password(password.as_ref(), &salt) 84 - .map_err(|error| anyhow!(error.to_string()))? 85 - .to_string(); 86 - Ok(password_hash) 87 - } 88 - 89 - pub fn verify(password: &String, stored_hash: &str) -> Result<bool> { 90 - let parsed_hash = PasswordHash::new(stored_hash).map_err(|error| anyhow!(error.to_string()))?; 91 - Ok(Argon2::default() 92 - .verify_password(password.as_ref(), &parsed_hash) 93 - .is_ok()) 94 - } 95 - 96 - pub async fn hash_app_password(did: &String, password: &String) -> Result<String> { 97 - let hash = Sha256::digest(did); 98 - let salt = Base64::encode_string(&hash).replace("=", ""); 99 - hash_with_salt(password, &salt) 100 - } 101 - 102 84 /// create an app password with format: 103 85 /// 1234-abcd-5678-efgh 104 86 pub async fn create_app_password( 105 87 did: String, 106 88 name: String, 107 - db: &DbConn, 89 + db: &deadpool_diesel::Pool< 90 + deadpool_diesel::Manager<SqliteConnection>, 91 + deadpool_diesel::sqlite::Object, 92 + >, 108 93 ) -> Result<CreateAppPasswordOutput> { 109 94 let str = &get_random_str()[0..16].to_lowercase(); 110 95 let chunks = [&str[0..4], &str[4..8], &str[8..12], &str[12..16]]; 111 96 let password = chunks.join("-"); 112 97 let password_encrypted = hash_app_password(&did, &password).await?; 113 98 114 - use crate::schema::pds::app_password::dsl as AppPasswordSchema; 99 + use rsky_pds::schema::pds::app_password::dsl as AppPasswordSchema; 115 100 116 101 let created_at = now(); 117 102 118 - db.run(move |conn| { 119 - let got: Option<AppPassword> = insert_into(AppPasswordSchema::app_password) 120 - .values(( 121 - AppPasswordSchema::did.eq(did), 122 - AppPasswordSchema::name.eq(&name), 123 - AppPasswordSchema::password.eq(password_encrypted), 124 - AppPasswordSchema::createdAt.eq(&created_at), 125 - )) 126 - .returning(AppPassword::as_select()) 127 - .get_result(conn) 128 - .optional()?; 129 - if got.is_some() { 130 - Ok(CreateAppPasswordOutput { 131 - name, 132 - password, 133 - created_at, 134 - }) 135 - } else { 136 - bail!("could not create app-specific password") 137 - } 138 - }) 139 - .await 103 + db.get() 104 + .await? 105 + .interact(move |conn| { 106 + let got: Option<AppPassword> = insert_into(AppPasswordSchema::app_password) 107 + .values(( 108 + AppPasswordSchema::did.eq(did), 109 + AppPasswordSchema::name.eq(&name), 110 + AppPasswordSchema::password.eq(password_encrypted), 111 + AppPasswordSchema::createdAt.eq(&created_at), 112 + )) 113 + .returning(AppPassword::as_select()) 114 + .get_result(conn) 115 + .optional()?; 116 + if got.is_some() { 117 + Ok(CreateAppPasswordOutput { 118 + name, 119 + password, 120 + created_at, 121 + }) 122 + } else { 123 + bail!("could not create app-specific password") 124 + } 125 + }) 126 + .await 127 + .expect("Failed to create app password") 140 128 } 141 129 142 - pub async fn list_app_passwords(did: &str, db: &DbConn) -> Result<Vec<(String, String)>> { 143 - use crate::schema::pds::app_password::dsl as AppPasswordSchema; 130 + pub async fn list_app_passwords( 131 + did: &str, 132 + db: &deadpool_diesel::Pool< 133 + deadpool_diesel::Manager<SqliteConnection>, 134 + deadpool_diesel::sqlite::Object, 135 + >, 136 + ) -> Result<Vec<(String, String)>> { 137 + use rsky_pds::schema::pds::app_password::dsl as AppPasswordSchema; 144 138 145 139 let did = did.to_owned(); 146 - db.run(move |conn| { 147 - Ok(AppPasswordSchema::app_password 148 - .filter(AppPasswordSchema::did.eq(did)) 149 - .select((AppPasswordSchema::name, AppPasswordSchema::createdAt)) 150 - .get_results(conn)?) 151 - }) 152 - .await 140 + db.get() 141 + .await? 142 + .interact(move |conn| { 143 + Ok(AppPasswordSchema::app_password 144 + .filter(AppPasswordSchema::did.eq(did)) 145 + .select((AppPasswordSchema::name, AppPasswordSchema::createdAt)) 146 + .get_results(conn)?) 147 + }) 148 + .await 149 + .expect("Failed to list app passwords") 153 150 } 154 151 155 - pub async fn update_user_password(opts: UpdateUserPasswordOpts, db: &DbConn) -> Result<()> { 156 - use crate::schema::pds::account::dsl as AccountSchema; 152 + pub async fn update_user_password( 153 + opts: UpdateUserPasswordOpts, 154 + db: &deadpool_diesel::Pool< 155 + deadpool_diesel::Manager<SqliteConnection>, 156 + deadpool_diesel::sqlite::Object, 157 + >, 158 + ) -> Result<()> { 159 + use rsky_pds::schema::pds::account::dsl as AccountSchema; 157 160 158 - db.run(move |conn| { 159 - update(AccountSchema::account) 160 - .filter(AccountSchema::did.eq(opts.did)) 161 - .set(AccountSchema::password.eq(opts.password_encrypted)) 162 - .execute(conn)?; 163 - Ok(()) 164 - }) 165 - .await 161 + db.get() 162 + .await? 163 + .interact(move |conn| { 164 + update(AccountSchema::account) 165 + .filter(AccountSchema::did.eq(opts.did)) 166 + .set(AccountSchema::password.eq(opts.password_encrypted)) 167 + .execute(conn)?; 168 + Ok(()) 169 + }) 170 + .await 171 + .expect("Failed to update user password") 166 172 } 167 173 168 - pub async fn delete_app_password(did: &str, name: &str, db: &DbConn) -> Result<()> { 169 - use crate::schema::pds::app_password::dsl as AppPasswordSchema; 174 + pub async fn delete_app_password( 175 + did: &str, 176 + name: &str, 177 + db: &deadpool_diesel::Pool< 178 + deadpool_diesel::Manager<SqliteConnection>, 179 + deadpool_diesel::sqlite::Object, 180 + >, 181 + ) -> Result<()> { 182 + use rsky_pds::schema::pds::app_password::dsl as AppPasswordSchema; 170 183 171 184 let did = did.to_owned(); 172 185 let name = name.to_owned(); 173 - db.run(move |conn| { 174 - delete(AppPasswordSchema::app_password) 175 - .filter(AppPasswordSchema::did.eq(did)) 176 - .filter(AppPasswordSchema::name.eq(name)) 177 - .execute(conn)?; 178 - Ok(()) 179 - }) 180 - .await 186 + db.get() 187 + .await? 188 + .interact(move |conn| { 189 + delete(AppPasswordSchema::app_password) 190 + .filter(AppPasswordSchema::did.eq(did)) 191 + .filter(AppPasswordSchema::name.eq(name)) 192 + .execute(conn)?; 193 + Ok(()) 194 + }) 195 + .await 196 + .expect("Failed to delete app password") 181 197 }
+31 -22
src/account_manager/helpers/repo.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 - use crate::db::DbConn; 6 5 use anyhow::Result; 6 + use cidv10::Cid; 7 7 use diesel::*; 8 - use libipld::Cid; 9 - use rsky_common; 10 8 11 - pub async fn update_root(did: String, cid: Cid, rev: String, db: &DbConn) -> Result<()> { 9 + pub async fn update_root( 10 + did: String, 11 + cid: Cid, 12 + rev: String, 13 + db: &deadpool_diesel::Pool< 14 + deadpool_diesel::Manager<SqliteConnection>, 15 + deadpool_diesel::sqlite::Object, 16 + >, 17 + ) -> Result<()> { 12 18 // @TODO balance risk of a race in the case of a long retry 13 - use crate::schema::pds::repo_root::dsl as RepoRootSchema; 19 + use rsky_pds::schema::pds::repo_root::dsl as RepoRootSchema; 14 20 15 21 let now = rsky_common::now(); 16 22 17 - db.run(move |conn| { 18 - insert_into(RepoRootSchema::repo_root) 19 - .values(( 20 - RepoRootSchema::did.eq(did), 21 - RepoRootSchema::cid.eq(cid.to_string()), 22 - RepoRootSchema::rev.eq(rev.clone()), 23 - RepoRootSchema::indexedAt.eq(now), 24 - )) 25 - .on_conflict(RepoRootSchema::did) 26 - .do_update() 27 - .set(( 28 - RepoRootSchema::cid.eq(cid.to_string()), 29 - RepoRootSchema::rev.eq(rev), 30 - )) 31 - .execute(conn) 32 - }) 33 - .await?; 23 + db.get() 24 + .await? 25 + .interact(move |conn| { 26 + insert_into(RepoRootSchema::repo_root) 27 + .values(( 28 + RepoRootSchema::did.eq(did), 29 + RepoRootSchema::cid.eq(cid.to_string()), 30 + RepoRootSchema::rev.eq(rev.clone()), 31 + RepoRootSchema::indexedAt.eq(now), 32 + )) 33 + .on_conflict(RepoRootSchema::did) 34 + .do_update() 35 + .set(( 36 + RepoRootSchema::cid.eq(cid.to_string()), 37 + RepoRootSchema::rev.eq(rev), 38 + )) 39 + .execute(conn) 40 + }) 41 + .await 42 + .expect("Failed to update repo root")?; 34 43 35 44 Ok(()) 36 45 }
+64 -95
src/account_manager/mod.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 + use crate::account_manager::helpers::account::{ 6 + AccountStatus, ActorAccount, AvailabilityFlags, GetAccountAdminStatusOutput, 7 + }; 8 + use crate::account_manager::helpers::auth::{ 9 + AuthHelperError, CreateTokensOpts, RefreshGracePeriodOpts, 10 + }; 11 + use crate::account_manager::helpers::invite::CodeDetail; 12 + use crate::account_manager::helpers::password::UpdateUserPasswordOpts; 5 13 use anyhow::Result; 6 14 use chrono::DateTime; 7 15 use chrono::offset::Utc as UtcOffset; 8 16 use cidv10::Cid; 17 + use diesel::*; 9 18 use futures::try_join; 10 - use helpers::{account, auth, email_token, invite, password}; 19 + use helpers::{account, auth, email_token, invite, password, repo}; 11 20 use rsky_common::RFC3339_VARIANT; 12 21 use rsky_common::time::{HOUR, from_micros_to_str, from_str_to_micros}; 13 22 use rsky_lexicon::com::atproto::admin::StatusAttr; 14 23 use rsky_lexicon::com::atproto::server::{AccountCodes, CreateAppPasswordOutput}; 15 - use rsky_pds::account_manager::helpers::account::{ 16 - AccountStatus, ActorAccount, AvailabilityFlags, GetAccountAdminStatusOutput, 17 - }; 18 - use rsky_pds::account_manager::helpers::auth::{ 19 - AuthHelperError, CreateTokensOpts, RefreshGracePeriodOpts, 20 - }; 21 - use rsky_pds::account_manager::helpers::invite::CodeDetail; 22 - use rsky_pds::account_manager::helpers::password::UpdateUserPasswordOpts; 23 - use rsky_pds::account_manager::helpers::repo; 24 24 use rsky_pds::account_manager::{ 25 25 ConfirmEmailOpts, CreateAccountOpts, DisableInviteCodesOpts, ResetPasswordOpts, 26 26 UpdateAccountPasswordOpts, UpdateEmailOpts, ··· 44 44 #[derive(Clone)] 45 45 pub struct AccountManager { 46 46 pub db: deadpool_diesel::Pool< 47 - deadpool_diesel::Manager<diesel::SqliteConnection>, 47 + deadpool_diesel::Manager<SqliteConnection>, 48 48 deadpool_diesel::sqlite::Object, 49 49 >, 50 50 } ··· 57 57 pub type AccountManagerCreator = Box< 58 58 dyn Fn( 59 59 deadpool_diesel::Pool< 60 - deadpool_diesel::Manager<diesel::SqliteConnection>, 60 + deadpool_diesel::Manager<SqliteConnection>, 61 61 deadpool_diesel::sqlite::Object, 62 62 >, 63 63 ) -> AccountManager ··· 68 68 impl AccountManager { 69 69 pub fn new( 70 70 db: deadpool_diesel::Pool< 71 - deadpool_diesel::Manager<diesel::SqliteConnection>, 71 + deadpool_diesel::Manager<SqliteConnection>, 72 72 deadpool_diesel::sqlite::Object, 73 73 >, 74 74 ) -> Self { ··· 78 78 pub fn creator() -> AccountManagerCreator { 79 79 Box::new( 80 80 move |db: deadpool_diesel::Pool< 81 - deadpool_diesel::Manager<diesel::SqliteConnection>, 81 + deadpool_diesel::Manager<SqliteConnection>, 82 82 deadpool_diesel::sqlite::Object, 83 83 >| 84 84 -> AccountManager { AccountManager::new(db) }, ··· 90 90 handle_or_did: &str, 91 91 flags: Option<AvailabilityFlags>, 92 92 ) -> Result<Option<ActorAccount>> { 93 - let db = self.db.clone(); 94 - account::get_account(handle_or_did, flags, db.as_ref()).await 93 + account::get_account(handle_or_did, flags, &self.db).await 95 94 } 96 95 97 96 pub async fn get_account_by_email( ··· 99 98 email: &str, 100 99 flags: Option<AvailabilityFlags>, 101 100 ) -> Result<Option<ActorAccount>> { 102 - let db = self.db.clone(); 103 - account::get_account_by_email(email, flags, db.as_ref()).await 101 + account::get_account_by_email(email, flags, &self.db).await 104 102 } 105 103 106 104 pub async fn is_account_activated(&self, did: &str) -> Result<bool> { ··· 132 130 } 133 131 134 132 pub async fn create_account(&self, opts: CreateAccountOpts) -> Result<(String, String)> { 135 - let db = self.db.clone(); 136 133 let CreateAccountOpts { 137 134 did, 138 135 handle, ··· 165 162 let now = rsky_common::now(); 166 163 167 164 if let Some(invite_code) = invite_code.clone() { 168 - invite::ensure_invite_is_available(invite_code, db.as_ref()).await?; 165 + invite::ensure_invite_is_available(invite_code, &self.db).await?; 169 166 } 170 - account::register_actor(did.clone(), handle, deactivated, db.as_ref()).await?; 167 + account::register_actor(did.clone(), handle, deactivated, &self.db).await?; 171 168 if let (Some(email), Some(password_encrypted)) = (email, password_encrypted) { 172 - account::register_account(did.clone(), email, password_encrypted, db.as_ref()).await?; 169 + account::register_account(did.clone(), email, password_encrypted, &self.db).await?; 173 170 } 174 - invite::record_invite_use(did.clone(), invite_code, now, db.as_ref()).await?; 175 - auth::store_refresh_token(refresh_payload, None, db.as_ref()).await?; 176 - repo::update_root(did, repo_cid, repo_rev, db.as_ref()).await?; 171 + invite::record_invite_use(did.clone(), invite_code, now, &self.db).await?; 172 + auth::store_refresh_token(refresh_payload, None, &self.db).await?; 173 + repo::update_root(did, repo_cid, repo_rev, &self.db).await?; 177 174 Ok((access_jwt, refresh_jwt)) 178 175 } 179 176 ··· 181 178 &self, 182 179 did: &str, 183 180 ) -> Result<Option<GetAccountAdminStatusOutput>> { 184 - let db = self.db.clone(); 185 - account::get_account_admin_status(did, db.as_ref()).await 181 + account::get_account_admin_status(did, &self.db).await 186 182 } 187 183 188 184 pub async fn update_repo_root(&self, did: String, cid: Cid, rev: String) -> Result<()> { 189 - let db = self.db.clone(); 190 - repo::update_root(did, cid, rev, db.as_ref()).await 185 + repo::update_root(did, cid, rev, &self.db).await 191 186 } 192 187 193 188 pub async fn delete_account(&self, did: &str) -> Result<()> { 194 - let db = self.db.clone(); 195 - account::delete_account(did, db.as_ref()).await 189 + account::delete_account(did, &self.db).await 196 190 } 197 191 198 192 pub async fn takedown_account(&self, did: &str, takedown: StatusAttr) -> Result<()> { 199 193 (_, _) = try_join!( 200 - account::update_account_takedown_status(did, takedown, self.db.as_ref()), 201 - auth::revoke_refresh_tokens_by_did(did, self.db.as_ref()) 194 + account::update_account_takedown_status(did, takedown, &self.db), 195 + auth::revoke_refresh_tokens_by_did(did, &self.db) 202 196 )?; 203 197 Ok(()) 204 198 } 205 199 206 200 // @NOTE should always be paired with a sequenceHandle(). 207 201 pub async fn update_handle(&self, did: &str, handle: &str) -> Result<()> { 208 - let db = self.db.clone(); 209 - account::update_handle(did, handle, db.as_ref()).await 202 + account::update_handle(did, handle, &self.db).await 210 203 } 211 204 212 205 pub async fn deactivate_account(&self, did: &str, delete_after: Option<String>) -> Result<()> { 213 - account::deactivate_account(did, delete_after, self.db.as_ref()).await 206 + account::deactivate_account(did, delete_after, &self.db).await 214 207 } 215 208 216 209 pub async fn activate_account(&self, did: &str) -> Result<()> { 217 - let db = self.db.clone(); 218 - account::activate_account(did, db.as_ref()).await 210 + account::activate_account(did, &self.db).await 219 211 } 220 212 221 213 pub async fn get_account_status(&self, handle_or_did: &str) -> Result<AccountStatus> { ··· 225 217 include_deactivated: Some(true), 226 218 include_taken_down: Some(true), 227 219 }), 228 - self.db.as_ref(), 220 + &self.db, 229 221 ) 230 222 .await?; 231 223 let res = account::format_account_status(got); ··· 242 234 did: String, 243 235 app_password_name: Option<String>, 244 236 ) -> Result<(String, String)> { 245 - let db = self.db.clone(); 246 237 let secp = Secp256k1::new(); 247 238 let private_key = env::var("PDS_JWT_KEY_K256_PRIVATE_KEY_HEX")?; 248 239 let secret_key = SecretKey::from_slice(&hex::decode(private_key.as_bytes())?)?; ··· 261 252 expires_in: None, 262 253 })?; 263 254 let refresh_payload = auth::decode_refresh_token(refresh_jwt.clone(), jwt_key)?; 264 - auth::store_refresh_token(refresh_payload, app_password_name, db.as_ref()).await?; 255 + auth::store_refresh_token(refresh_payload, app_password_name, &self.db).await?; 265 256 Ok((access_jwt, refresh_jwt)) 266 257 } 267 258 268 259 pub async fn rotate_refresh_token(&self, id: &String) -> Result<Option<(String, String)>> { 269 - let token = auth::get_refresh_token(id, self.db.as_ref()).await?; 260 + let token = auth::get_refresh_token(id, &self.db).await?; 270 261 if let Some(token) = token { 271 262 let system_time = SystemTime::now(); 272 263 let dt: DateTime<UtcOffset> = system_time.into(); ··· 274 265 275 266 // take the chance to tidy all of a user's expired tokens 276 267 // does not need to be transactional since this is just best-effort 277 - auth::delete_expired_refresh_tokens(&token.did, now, self.db.as_ref()).await?; 268 + auth::delete_expired_refresh_tokens(&token.did, now, &self.db).await?; 278 269 279 270 // Shorten the refresh token lifespan down from its 280 271 // original expiration time to its revocation grace period. ··· 323 314 expires_at: from_micros_to_str(expires_at), 324 315 next_id 325 316 }, 326 - self.db.as_ref() 317 + &self.db 327 318 ), 328 - auth::store_refresh_token( 329 - refresh_payload, 330 - token.app_password_name, 331 - self.db.as_ref() 332 - ) 319 + auth::store_refresh_token(refresh_payload, token.app_password_name, &self.db) 333 320 ) { 334 321 Ok(_) => Ok(Some((access_jwt, refresh_jwt))), 335 322 Err(e) => match e.downcast_ref() { ··· 345 332 } 346 333 347 334 pub async fn revoke_refresh_token(&self, id: String) -> Result<bool> { 348 - auth::revoke_refresh_token(id, self.db.as_ref()).await 335 + auth::revoke_refresh_token(id, &self.db).await 349 336 } 350 337 351 338 // Invites ··· 356 343 to_create: Vec<AccountCodes>, 357 344 use_count: i32, 358 345 ) -> Result<()> { 359 - let db = self.db.clone(); 360 - invite::create_invite_codes(to_create, use_count, db.as_ref()).await 346 + invite::create_invite_codes(to_create, use_count, &self.db).await 361 347 } 362 348 363 349 pub async fn create_account_invite_codes( ··· 367 353 expected_total: usize, 368 354 disabled: bool, 369 355 ) -> Result<Vec<CodeDetail>> { 370 - invite::create_account_invite_codes( 371 - for_account, 372 - codes, 373 - expected_total, 374 - disabled, 375 - self.db.as_ref(), 376 - ) 377 - .await 356 + invite::create_account_invite_codes(for_account, codes, expected_total, disabled, &self.db) 357 + .await 378 358 } 379 359 380 360 pub async fn get_account_invite_codes(&self, did: &str) -> Result<Vec<CodeDetail>> { 381 - let db = self.db.clone(); 382 - invite::get_account_invite_codes(did, db.as_ref()).await 361 + invite::get_account_invite_codes(did, &self.db).await 383 362 } 384 363 385 364 pub async fn get_invited_by_for_accounts( 386 365 &self, 387 366 dids: Vec<String>, 388 367 ) -> Result<BTreeMap<String, CodeDetail>> { 389 - let db = self.db.clone(); 390 - invite::get_invited_by_for_accounts(dids, db.as_ref()).await 368 + invite::get_invited_by_for_accounts(dids, &self.db).await 391 369 } 392 370 393 371 pub async fn set_account_invites_disabled(&self, did: &str, disabled: bool) -> Result<()> { 394 - invite::set_account_invites_disabled(did, disabled, self.db.as_ref()).await 372 + invite::set_account_invites_disabled(did, disabled, &self.db).await 395 373 } 396 374 397 375 pub async fn disable_invite_codes(&self, opts: DisableInviteCodesOpts) -> Result<()> { 398 - invite::disable_invite_codes(opts, self.db.as_ref()).await 376 + invite::disable_invite_codes(opts, &self.db).await 399 377 } 400 378 401 379 // Passwords ··· 406 384 did: String, 407 385 name: String, 408 386 ) -> Result<CreateAppPasswordOutput> { 409 - password::create_app_password(did, name, self.db.as_ref()).await 387 + password::create_app_password(did, name, &self.db).await 410 388 } 411 389 412 390 pub async fn list_app_passwords(&self, did: &str) -> Result<Vec<(String, String)>> { 413 - password::list_app_passwords(did, self.db.as_ref()).await 391 + password::list_app_passwords(did, &self.db).await 414 392 } 415 393 416 394 pub async fn verify_account_password(&self, did: &str, password_str: &String) -> Result<bool> { 417 - let db = self.db.clone(); 418 - password::verify_account_password(did, password_str, db.as_ref()).await 395 + password::verify_account_password(did, password_str, &self.db).await 419 396 } 420 397 421 398 pub async fn verify_app_password( ··· 423 400 did: &str, 424 401 password_str: &str, 425 402 ) -> Result<Option<String>> { 426 - let db = self.db.clone(); 427 - password::verify_app_password(did, password_str, db.as_ref()).await 403 + password::verify_app_password(did, password_str, &self.db).await 428 404 } 429 405 430 406 pub async fn reset_password(&self, opts: ResetPasswordOpts) -> Result<()> { 431 - let db = self.db.clone(); 432 407 let did = email_token::assert_valid_token_and_find_did( 433 408 EmailTokenPurpose::ResetPassword, 434 409 &opts.token, 435 410 None, 436 - db.as_ref(), 411 + &self.db, 437 412 ) 438 413 .await?; 439 414 self.update_account_password(UpdateAccountPasswordOpts { ··· 444 419 } 445 420 446 421 pub async fn update_account_password(&self, opts: UpdateAccountPasswordOpts) -> Result<()> { 447 - let db = self.db.clone(); 448 422 let UpdateAccountPasswordOpts { did, .. } = opts; 449 423 let password_encrypted = password::gen_salt_and_hash(opts.password)?; 450 424 try_join!( ··· 453 427 did: did.clone(), 454 428 password_encrypted 455 429 }, 456 - self.db.as_ref() 430 + &self.db 457 431 ), 458 - email_token::delete_email_token(&did, EmailTokenPurpose::ResetPassword, db.as_ref()), 459 - auth::revoke_refresh_tokens_by_did(&did, self.db.as_ref()) 432 + email_token::delete_email_token(&did, EmailTokenPurpose::ResetPassword, &self.db), 433 + auth::revoke_refresh_tokens_by_did(&did, &self.db) 460 434 )?; 461 435 Ok(()) 462 436 } 463 437 464 438 pub async fn revoke_app_password(&self, did: String, name: String) -> Result<()> { 465 439 try_join!( 466 - password::delete_app_password(&did, &name, self.db.as_ref()), 467 - auth::revoke_app_password_refresh_token(&did, &name, self.db.as_ref()) 440 + password::delete_app_password(&did, &name, &self.db), 441 + auth::revoke_app_password_refresh_token(&did, &name, &self.db) 468 442 )?; 469 443 Ok(()) 470 444 } 471 445 472 446 // Email Tokens 473 447 // ---------- 474 - pub async fn confirm_email<'em>(&self, opts: ConfirmEmailOpts<'em>) -> Result<()> { 475 - let db = self.db.clone(); 448 + pub async fn confirm_email(&self, opts: ConfirmEmailOpts<'_>) -> Result<()> { 476 449 let ConfirmEmailOpts { did, token } = opts; 477 450 email_token::assert_valid_token( 478 451 did, 479 452 EmailTokenPurpose::ConfirmEmail, 480 453 token, 481 454 None, 482 - db.as_ref(), 455 + &self.db, 483 456 ) 484 457 .await?; 485 458 let now = rsky_common::now(); 486 459 try_join!( 487 - email_token::delete_email_token(did, EmailTokenPurpose::ConfirmEmail, db.as_ref()), 488 - account::set_email_confirmed_at(did, now, self.db.as_ref()) 460 + email_token::delete_email_token(did, EmailTokenPurpose::ConfirmEmail, &self.db), 461 + account::set_email_confirmed_at(did, now, &self.db) 489 462 )?; 490 463 Ok(()) 491 464 } 492 465 493 466 pub async fn update_email(&self, opts: UpdateEmailOpts) -> Result<()> { 494 - let db = self.db.clone(); 495 467 let UpdateEmailOpts { did, email } = opts; 496 468 try_join!( 497 - account::update_email(&did, &email, db.as_ref()), 498 - email_token::delete_all_email_tokens(&did, db.as_ref()) 469 + account::update_email(&did, &email, &self.db), 470 + email_token::delete_all_email_tokens(&did, &self.db) 499 471 )?; 500 472 Ok(()) 501 473 } ··· 506 478 purpose: EmailTokenPurpose, 507 479 token: &str, 508 480 ) -> Result<()> { 509 - let db = self.db.clone(); 510 - email_token::assert_valid_token(did, purpose, token, None, db.as_ref()).await 481 + email_token::assert_valid_token(did, purpose, token, None, &self.db).await 511 482 } 512 483 513 484 pub async fn assert_valid_email_token_and_cleanup( ··· 516 487 purpose: EmailTokenPurpose, 517 488 token: &str, 518 489 ) -> Result<()> { 519 - let db = self.db.clone(); 520 - email_token::assert_valid_token(did, purpose, token, None, db.as_ref()).await?; 521 - email_token::delete_email_token(did, purpose, db.as_ref()).await 490 + email_token::assert_valid_token(did, purpose, token, None, &self.db).await?; 491 + email_token::delete_email_token(did, purpose, &self.db).await 522 492 } 523 493 524 494 pub async fn create_email_token( ··· 526 496 did: &str, 527 497 purpose: EmailTokenPurpose, 528 498 ) -> Result<String> { 529 - let db = self.db.clone(); 530 - email_token::create_email_token(did, purpose, db.as_ref()).await 499 + email_token::create_email_token(did, purpose, &self.db).await 531 500 } 532 501 }
+9 -10
src/endpoints/mod.rs
··· 1 1 //! Root module for all endpoints. 2 - mod identity; 3 - mod repo; 4 - mod server; 5 - mod sync; 2 + // mod identity; 3 + // mod repo; 4 + // mod server; 5 + // mod sync; 6 6 7 7 use axum::{Json, Router, routing::get}; 8 8 use serde_json::json; ··· 18 18 19 19 /// Register all root routes. 20 20 pub(crate) fn routes() -> Router<AppState> { 21 - Router::new() 22 - .route("/_health", get(health)) 23 - .merge(identity::routes()) // com.atproto.identity 24 - .merge(repo::routes()) // com.atproto.repo 25 - .merge(server::routes()) // com.atproto.server 26 - .merge(sync::routes()) // com.atproto.sync 21 + Router::new().route("/_health", get(health)) 22 + // .merge(identity::routes()) // com.atproto.identity 23 + // .merge(repo::routes()) // com.atproto.repo 24 + // .merge(server::routes()) // com.atproto.server 25 + // .merge(sync::routes()) // com.atproto.sync 27 26 }