Alternative ATProto PDS implementation
at oauth 192 lines 6.0 kB view raw
1//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/helpers/password.rs 2//! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3//! 4//! Modified for SQLite backend 5use crate::models::pds as models; 6use crate::models::pds::AppPassword; 7use anyhow::{Result, bail}; 8use diesel::*; 9use rsky_common::{get_random_str, now}; 10use rsky_lexicon::com::atproto::server::CreateAppPasswordOutput; 11#[expect(unused_imports)] 12pub(crate) use rsky_pds::account_manager::helpers::password::{ 13 UpdateUserPasswordOpts, gen_salt_and_hash, hash_app_password, hash_with_salt, verify, 14}; 15 16pub async fn verify_account_password( 17 did: &str, 18 password: &String, 19 db: &deadpool_diesel::Pool< 20 deadpool_diesel::Manager<SqliteConnection>, 21 deadpool_diesel::sqlite::Object, 22 >, 23) -> Result<bool> { 24 use crate::schema::pds::account::dsl as AccountSchema; 25 26 let did = did.to_owned(); 27 let found = db 28 .get() 29 .await? 30 .interact(move |conn| { 31 AccountSchema::account 32 .filter(AccountSchema::did.eq(did)) 33 .select(models::Account::as_select()) 34 .first(conn) 35 .optional() 36 }) 37 .await 38 .expect("Failed to get account")?; 39 if let Some(found) = found { 40 verify(password, &found.password) 41 } else { 42 Ok(false) 43 } 44} 45 46pub async fn verify_app_password( 47 did: &str, 48 password: &str, 49 db: &deadpool_diesel::Pool< 50 deadpool_diesel::Manager<SqliteConnection>, 51 deadpool_diesel::sqlite::Object, 52 >, 53) -> Result<Option<String>> { 54 use crate::schema::pds::app_password::dsl as AppPasswordSchema; 55 56 let did = did.to_owned(); 57 let password = password.to_owned(); 58 let password_encrypted = hash_app_password(&did, &password).await?; 59 let found = db 60 .get() 61 .await? 62 .interact(move |conn| { 63 AppPasswordSchema::app_password 64 .filter(AppPasswordSchema::did.eq(did)) 65 .filter(AppPasswordSchema::password.eq(password_encrypted)) 66 .select(AppPassword::as_select()) 67 .first(conn) 68 .optional() 69 }) 70 .await 71 .expect("Failed to get app password")?; 72 if let Some(found) = found { 73 Ok(Some(found.name)) 74 } else { 75 Ok(None) 76 } 77} 78 79/// create an app password with format: 80/// 1234-abcd-5678-efgh 81pub async fn create_app_password( 82 did: String, 83 name: String, 84 db: &deadpool_diesel::Pool< 85 deadpool_diesel::Manager<SqliteConnection>, 86 deadpool_diesel::sqlite::Object, 87 >, 88) -> Result<CreateAppPasswordOutput> { 89 let str = &get_random_str()[0..16].to_lowercase(); 90 let chunks = [&str[0..4], &str[4..8], &str[8..12], &str[12..16]]; 91 let password = chunks.join("-"); 92 let password_encrypted = hash_app_password(&did, &password).await?; 93 94 use crate::schema::pds::app_password::dsl as AppPasswordSchema; 95 96 let created_at = now(); 97 98 db.get() 99 .await? 100 .interact(move |conn| { 101 let got: Option<AppPassword> = insert_into(AppPasswordSchema::app_password) 102 .values(( 103 AppPasswordSchema::did.eq(did), 104 AppPasswordSchema::name.eq(&name), 105 AppPasswordSchema::password.eq(password_encrypted), 106 AppPasswordSchema::createdAt.eq(&created_at), 107 )) 108 .returning(AppPassword::as_select()) 109 .get_result(conn) 110 .optional()?; 111 if got.is_some() { 112 Ok(CreateAppPasswordOutput { 113 name, 114 password, 115 created_at, 116 }) 117 } else { 118 bail!("could not create app-specific password") 119 } 120 }) 121 .await 122 .expect("Failed to create app password") 123} 124 125pub async fn list_app_passwords( 126 did: &str, 127 db: &deadpool_diesel::Pool< 128 deadpool_diesel::Manager<SqliteConnection>, 129 deadpool_diesel::sqlite::Object, 130 >, 131) -> Result<Vec<(String, String)>> { 132 use crate::schema::pds::app_password::dsl as AppPasswordSchema; 133 134 let did = did.to_owned(); 135 db.get() 136 .await? 137 .interact(move |conn| { 138 Ok(AppPasswordSchema::app_password 139 .filter(AppPasswordSchema::did.eq(did)) 140 .select((AppPasswordSchema::name, AppPasswordSchema::createdAt)) 141 .get_results(conn)?) 142 }) 143 .await 144 .expect("Failed to list app passwords") 145} 146 147pub async fn update_user_password( 148 opts: UpdateUserPasswordOpts, 149 db: &deadpool_diesel::Pool< 150 deadpool_diesel::Manager<SqliteConnection>, 151 deadpool_diesel::sqlite::Object, 152 >, 153) -> Result<()> { 154 use crate::schema::pds::account::dsl as AccountSchema; 155 156 db.get() 157 .await? 158 .interact(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 166 .expect("Failed to update user password") 167} 168 169pub async fn delete_app_password( 170 did: &str, 171 name: &str, 172 db: &deadpool_diesel::Pool< 173 deadpool_diesel::Manager<SqliteConnection>, 174 deadpool_diesel::sqlite::Object, 175 >, 176) -> Result<()> { 177 use crate::schema::pds::app_password::dsl as AppPasswordSchema; 178 179 let did = did.to_owned(); 180 let name = name.to_owned(); 181 db.get() 182 .await? 183 .interact(move |conn| { 184 _ = delete(AppPasswordSchema::app_password) 185 .filter(AppPasswordSchema::did.eq(did)) 186 .filter(AppPasswordSchema::name.eq(name)) 187 .execute(conn)?; 188 Ok(()) 189 }) 190 .await 191 .expect("Failed to delete app password") 192}