Alternative ATProto PDS implementation

implement account_manager

Changed files
+119 -17
src
account_manager
-1
Cargo.lock
··· 1318 1318 "regex", 1319 1319 "reqwest 0.12.15", 1320 1320 "reqwest-middleware", 1321 - "rocket_sync_db_pools", 1322 1321 "rsky-common", 1323 1322 "rsky-lexicon", 1324 1323 "rsky-pds",
+11 -5
Cargo.toml
··· 129 129 130 130 [dependencies] 131 131 multihash = "0.19.3" 132 - diesel = { version = "2.1.5", features = ["chrono", "sqlite", "r2d2"] } 132 + diesel = { version = "2.1.5", features = [ 133 + "chrono", 134 + "sqlite", 135 + "r2d2", 136 + "returning_clauses_for_sqlite_3_35", 137 + ] } 133 138 diesel_migrations = { version = "2.1.0" } 134 139 r2d2 = "0.8.10" 135 140 ··· 252 257 lazy_static = "1.5.0" 253 258 secp256k1 = "0.28.2" 254 259 dotenvy = "0.15.7" 255 - deadpool-diesel = { version = "0.6.1", features = ["serde", "sqlite", "tracing"] } 256 - [dependencies.rocket_sync_db_pools] 257 - version = "=0.1.0" 258 - features = ["diesel_sqlite_pool"] 260 + deadpool-diesel = { version = "0.6.1", features = [ 261 + "serde", 262 + "sqlite", 263 + "tracing", 264 + ] }
+28 -3
src/account_manager/helpers/account.rs
··· 12 12 use rsky_lexicon::com::atproto::admin::StatusAttr; 13 13 #[expect(unused_imports)] 14 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, 15 + AccountStatus, ActorAccount, ActorJoinAccount, AvailabilityFlags, FormattedAccountStatus, 16 + GetAccountAdminStatusOutput, format_account_status, 17 17 }; 18 18 use rsky_pds::schema::pds::account::dsl as AccountSchema; 19 19 use rsky_pds::schema::pds::actor::dsl as ActorSchema; ··· 27 27 UserAlreadyExistsError, 28 28 #[error("DatabaseError: `{0}`")] 29 29 DieselError(String), 30 + } 31 + 32 + pub type BoxedQuery<'life> = dsl::IntoBoxed<'life, ActorJoinAccount, sqlite::Sqlite>; 33 + pub fn select_account_qb(flags: Option<AvailabilityFlags>) -> BoxedQuery<'static> { 34 + let AvailabilityFlags { 35 + include_taken_down, 36 + include_deactivated, 37 + } = flags.unwrap_or_else(|| AvailabilityFlags { 38 + include_taken_down: Some(false), 39 + include_deactivated: Some(false), 40 + }); 41 + let include_taken_down = include_taken_down.unwrap_or(false); 42 + let include_deactivated = include_deactivated.unwrap_or(false); 43 + 44 + let mut builder = ActorSchema::actor 45 + .left_join(AccountSchema::account.on(ActorSchema::did.eq(AccountSchema::did))) 46 + .into_boxed(); 47 + if !include_taken_down { 48 + builder = builder.filter(ActorSchema::takedownRef.is_null()); 49 + } 50 + if !include_deactivated { 51 + builder = builder.filter(ActorSchema::deactivatedAt.is_null()); 52 + } 53 + builder 30 54 } 31 55 32 56 pub async fn get_account( ··· 358 382 )) 359 383 .execute(conn) 360 384 }) 361 - .await; 385 + .await 386 + .expect("Failed to update email"); 362 387 363 388 match res { 364 389 Ok(_) => Ok(()),
+78 -1
src/account_manager/helpers/email_token.rs
··· 7 7 use rsky_common::time::{MINUTE, from_str_to_utc, less_than_ago_s}; 8 8 use rsky_pds::apis::com::atproto::server::get_random_token; 9 9 use rsky_pds::models::EmailToken; 10 - use rsky_pds::models::models::EmailTokenPurpose; 11 10 12 11 pub async fn create_email_token( 13 12 did: &str, ··· 121 120 Ok(res.did) 122 121 } else { 123 122 bail!("Token is invalid") 123 + } 124 + } 125 + 126 + #[derive( 127 + Clone, 128 + Copy, 129 + Debug, 130 + PartialEq, 131 + Eq, 132 + Hash, 133 + Default, 134 + serde::Serialize, 135 + serde::Deserialize, 136 + AsExpression, 137 + )] 138 + #[diesel(sql_type = sql_types::Text)] 139 + pub enum EmailTokenPurpose { 140 + #[default] 141 + ConfirmEmail, 142 + UpdateEmail, 143 + ResetPassword, 144 + DeleteAccount, 145 + PlcOperation, 146 + } 147 + 148 + impl EmailTokenPurpose { 149 + pub fn as_str(&self) -> &'static str { 150 + match self { 151 + EmailTokenPurpose::ConfirmEmail => "confirm_email", 152 + EmailTokenPurpose::UpdateEmail => "update_email", 153 + EmailTokenPurpose::ResetPassword => "reset_password", 154 + EmailTokenPurpose::DeleteAccount => "delete_account", 155 + EmailTokenPurpose::PlcOperation => "plc_operation", 156 + } 157 + } 158 + 159 + pub fn from_str(s: &str) -> Result<Self> { 160 + match s { 161 + "confirm_email" => Ok(EmailTokenPurpose::ConfirmEmail), 162 + "update_email" => Ok(EmailTokenPurpose::UpdateEmail), 163 + "reset_password" => Ok(EmailTokenPurpose::ResetPassword), 164 + "delete_account" => Ok(EmailTokenPurpose::DeleteAccount), 165 + "plc_operation" => Ok(EmailTokenPurpose::PlcOperation), 166 + _ => bail!("Unable to parse as EmailTokenPurpose: `{s:?}`"), 167 + } 168 + } 169 + } 170 + 171 + impl<DB> Queryable<sql_types::Text, DB> for EmailTokenPurpose 172 + where 173 + DB: backend::Backend, 174 + String: deserialize::FromSql<sql_types::Text, DB>, 175 + { 176 + type Row = String; 177 + 178 + fn build(s: String) -> deserialize::Result<Self> { 179 + Ok(EmailTokenPurpose::from_str(&s)?) 180 + } 181 + } 182 + 183 + impl serialize::ToSql<sql_types::Text, sqlite::Sqlite> for EmailTokenPurpose 184 + where 185 + String: serialize::ToSql<sql_types::Text, sqlite::Sqlite>, 186 + { 187 + fn to_sql<'lifetime>( 188 + &'lifetime self, 189 + out: &mut serialize::Output<'lifetime, '_, sqlite::Sqlite>, 190 + ) -> serialize::Result { 191 + serialize::ToSql::<sql_types::Text, sqlite::Sqlite>::to_sql( 192 + match self { 193 + EmailTokenPurpose::ConfirmEmail => "confirm_email", 194 + EmailTokenPurpose::UpdateEmail => "update_email", 195 + EmailTokenPurpose::ResetPassword => "reset_password", 196 + EmailTokenPurpose::DeleteAccount => "delete_account", 197 + EmailTokenPurpose::PlcOperation => "plc_operation", 198 + }, 199 + out, 200 + ) 124 201 } 125 202 } 126 203
+1 -6
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 anyhow::{Result, anyhow, bail}; 6 - use argon2::{ 7 - Argon2, 8 - password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng}, 9 - }; 10 - // use base64ct::{Base64, Encoding}; 5 + use anyhow::{Result, bail}; 11 6 use diesel::*; 12 7 use rsky_common::{get_random_str, now}; 13 8 use rsky_lexicon::com::atproto::server::CreateAppPasswordOutput;
+1 -1
src/account_manager/mod.rs
··· 16 16 use cidv10::Cid; 17 17 use diesel::*; 18 18 use futures::try_join; 19 + use helpers::email_token::EmailTokenPurpose; 19 20 use helpers::{account, auth, email_token, invite, password, repo}; 20 21 use rsky_common::RFC3339_VARIANT; 21 22 use rsky_common::time::{HOUR, from_micros_to_str, from_str_to_micros}; ··· 26 27 UpdateAccountPasswordOpts, UpdateEmailOpts, 27 28 }; 28 29 use rsky_pds::auth_verifier::AuthScope; 29 - use rsky_pds::models::models::EmailTokenPurpose; 30 30 use secp256k1::{Keypair, Secp256k1, SecretKey}; 31 31 use std::collections::BTreeMap; 32 32 use std::env;