+494
src/account_manager/helpers/account.rs
+494
src/account_manager/helpers/account.rs
···
1
+
//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/helpers/account.rs
2
+
//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
3
+
//!
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
+
use anyhow::Result;
11
+
use chrono::DateTime;
12
+
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;
16
+
use diesel::result::{DatabaseErrorKind, Error as DieselError};
17
+
use diesel::*;
18
+
use rsky_common;
19
+
use rsky_common::RFC3339_VARIANT;
20
+
use rsky_lexicon::com::atproto::admin::StatusAttr;
21
+
use std::ops::Add;
22
+
use std::time::SystemTime;
23
+
use thiserror::Error;
24
+
25
+
#[derive(Error, Debug)]
26
+
pub enum AccountHelperError {
27
+
#[error("UserAlreadyExistsError")]
28
+
UserAlreadyExistsError,
29
+
#[error("DatabaseError: `{0}`")]
30
+
DieselError(String),
31
+
}
32
+
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
+
pub async fn get_account(
108
+
_handle_or_did: &str,
109
+
flags: Option<AvailabilityFlags>,
110
+
db: &DbConn,
111
+
) -> Result<Option<ActorAccount>> {
112
+
let handle_or_did = _handle_or_did.to_owned();
113
+
let found = db
114
+
.run(move |conn| {
115
+
let mut builder = select_account_qb(flags);
116
+
if handle_or_did.starts_with("did:") {
117
+
builder = builder.filter(ActorSchema::did.eq(handle_or_did));
118
+
} else {
119
+
builder = builder.filter(ActorSchema::handle.eq(handle_or_did));
120
+
}
121
+
122
+
builder
123
+
.select((
124
+
ActorSchema::did,
125
+
ActorSchema::handle,
126
+
ActorSchema::createdAt,
127
+
ActorSchema::takedownRef,
128
+
ActorSchema::deactivatedAt,
129
+
ActorSchema::deleteAfter,
130
+
AccountSchema::email.nullable(),
131
+
AccountSchema::emailConfirmedAt.nullable(),
132
+
AccountSchema::invitesDisabled.nullable(),
133
+
))
134
+
.first::<(
135
+
String,
136
+
Option<String>,
137
+
String,
138
+
Option<String>,
139
+
Option<String>,
140
+
Option<String>,
141
+
Option<String>,
142
+
Option<String>,
143
+
Option<i16>,
144
+
)>(conn)
145
+
.map(|res| ActorAccount {
146
+
did: res.0,
147
+
handle: res.1,
148
+
created_at: res.2,
149
+
takedown_ref: res.3,
150
+
deactivated_at: res.4,
151
+
delete_after: res.5,
152
+
email: res.6,
153
+
email_confirmed_at: res.7,
154
+
invites_disabled: res.8,
155
+
})
156
+
.optional()
157
+
})
158
+
.await?;
159
+
Ok(found)
160
+
}
161
+
162
+
pub async fn get_account_by_email(
163
+
_email: &str,
164
+
flags: Option<AvailabilityFlags>,
165
+
db: &DbConn,
166
+
) -> Result<Option<ActorAccount>> {
167
+
let email = _email.to_owned();
168
+
let found = db
169
+
.run(move |conn| {
170
+
select_account_qb(flags)
171
+
.select((
172
+
ActorSchema::did,
173
+
ActorSchema::handle,
174
+
ActorSchema::createdAt,
175
+
ActorSchema::takedownRef,
176
+
ActorSchema::deactivatedAt,
177
+
ActorSchema::deleteAfter,
178
+
AccountSchema::email.nullable(),
179
+
AccountSchema::emailConfirmedAt.nullable(),
180
+
AccountSchema::invitesDisabled.nullable(),
181
+
))
182
+
.filter(AccountSchema::email.eq(email.to_lowercase()))
183
+
.first::<(
184
+
String,
185
+
Option<String>,
186
+
String,
187
+
Option<String>,
188
+
Option<String>,
189
+
Option<String>,
190
+
Option<String>,
191
+
Option<String>,
192
+
Option<i16>,
193
+
)>(conn)
194
+
.map(|res| ActorAccount {
195
+
did: res.0,
196
+
handle: res.1,
197
+
created_at: res.2,
198
+
takedown_ref: res.3,
199
+
deactivated_at: res.4,
200
+
delete_after: res.5,
201
+
email: res.6,
202
+
email_confirmed_at: res.7,
203
+
invites_disabled: res.8,
204
+
})
205
+
.optional()
206
+
})
207
+
.await?;
208
+
Ok(found)
209
+
}
210
+
211
+
pub async fn register_actor(
212
+
did: String,
213
+
handle: String,
214
+
deactivated: Option<bool>,
215
+
db: &DbConn,
216
+
) -> Result<()> {
217
+
let system_time = SystemTime::now();
218
+
let dt: DateTime<UtcOffset> = system_time.into();
219
+
let created_at = format!("{}", dt.format(RFC3339_VARIANT));
220
+
let deactivate_at = match deactivated {
221
+
Some(true) => Some(created_at.clone()),
222
+
_ => None,
223
+
};
224
+
let deactivate_after = match deactivated {
225
+
Some(true) => {
226
+
let exp = dt.add(chrono::Duration::days(3));
227
+
Some(format!("{}", exp.format(RFC3339_VARIANT)))
228
+
}
229
+
_ => None,
230
+
};
231
+
232
+
let _: String = db
233
+
.run(move |conn| {
234
+
insert_into(ActorSchema::actor)
235
+
.values((
236
+
ActorSchema::did.eq(did),
237
+
ActorSchema::handle.eq(handle),
238
+
ActorSchema::createdAt.eq(created_at),
239
+
ActorSchema::deactivatedAt.eq(deactivate_at),
240
+
ActorSchema::deleteAfter.eq(deactivate_after),
241
+
))
242
+
.on_conflict_do_nothing()
243
+
.returning(ActorSchema::did)
244
+
.get_result(conn)
245
+
})
246
+
.await?;
247
+
Ok(())
248
+
}
249
+
250
+
pub async fn register_account(
251
+
did: String,
252
+
email: String,
253
+
password: String,
254
+
db: &DbConn,
255
+
) -> Result<()> {
256
+
let created_at = rsky_common::now();
257
+
258
+
// @TODO record recovery key for bring your own recovery key
259
+
let _: String = db
260
+
.run(move |conn| {
261
+
insert_into(AccountSchema::account)
262
+
.values((
263
+
AccountSchema::did.eq(did),
264
+
AccountSchema::email.eq(email),
265
+
AccountSchema::password.eq(password),
266
+
AccountSchema::createdAt.eq(created_at),
267
+
))
268
+
.on_conflict_do_nothing()
269
+
.returning(AccountSchema::did)
270
+
.get_result(conn)
271
+
})
272
+
.await?;
273
+
Ok(())
274
+
}
275
+
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;
280
+
281
+
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?;
300
+
Ok(())
301
+
}
302
+
303
+
pub async fn update_account_takedown_status(
304
+
did: &str,
305
+
takedown: StatusAttr,
306
+
db: &DbConn,
307
+
) -> Result<()> {
308
+
let takedown_ref: Option<String> = match takedown.applied {
309
+
true => match takedown.r#ref {
310
+
Some(takedown_ref) => Some(takedown_ref),
311
+
None => Some(rsky_common::now()),
312
+
},
313
+
false => None,
314
+
};
315
+
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?;
323
+
Ok(())
324
+
}
325
+
326
+
pub async fn deactivate_account(
327
+
did: &str,
328
+
delete_after: Option<String>,
329
+
db: &DbConn,
330
+
) -> Result<()> {
331
+
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?;
342
+
Ok(())
343
+
}
344
+
345
+
pub async fn activate_account(did: &str, db: &DbConn) -> Result<()> {
346
+
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?;
357
+
Ok(())
358
+
}
359
+
360
+
pub async fn update_email(did: &str, email: &str, db: &DbConn) -> Result<()> {
361
+
let did = did.to_owned();
362
+
let email = email.to_owned();
363
+
let res = db
364
+
.run(move |conn| {
365
+
update(AccountSchema::account)
366
+
.filter(AccountSchema::did.eq(did))
367
+
.set((
368
+
AccountSchema::email.eq(email.to_lowercase()),
369
+
AccountSchema::emailConfirmedAt.eq::<Option<String>>(None),
370
+
))
371
+
.execute(conn)
372
+
})
373
+
.await;
374
+
375
+
match res {
376
+
Ok(_) => Ok(()),
377
+
Err(DieselError::DatabaseError(kind, _)) => match kind {
378
+
DatabaseErrorKind::UniqueViolation => Err(anyhow::Error::new(
379
+
AccountHelperError::UserAlreadyExistsError,
380
+
)),
381
+
_ => Err(anyhow::Error::new(AccountHelperError::DieselError(
382
+
format!("{:?}", kind),
383
+
))),
384
+
},
385
+
Err(e) => Err(anyhow::Error::new(e)),
386
+
}
387
+
}
388
+
389
+
pub async fn update_handle(did: &str, handle: &str, db: &DbConn) -> Result<()> {
390
+
use crate::schema::pds::actor;
391
+
392
+
let actor2 = diesel::alias!(actor as actor2);
393
+
394
+
let did = did.to_owned();
395
+
let handle = handle.to_owned();
396
+
let res = db
397
+
.run(move |conn| {
398
+
update(ActorSchema::actor)
399
+
.filter(ActorSchema::did.eq(did))
400
+
.filter(not(exists(actor2.filter(ActorSchema::handle.eq(&handle)))))
401
+
.set((ActorSchema::handle.eq(&handle),))
402
+
.execute(conn)
403
+
})
404
+
.await?;
405
+
406
+
if res < 1 {
407
+
return Err(anyhow::Error::new(
408
+
AccountHelperError::UserAlreadyExistsError,
409
+
));
410
+
}
411
+
Ok(())
412
+
}
413
+
414
+
pub async fn set_email_confirmed_at(
415
+
did: &str,
416
+
email_confirmed_at: String,
417
+
db: &DbConn,
418
+
) -> Result<()> {
419
+
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?;
427
+
Ok(())
428
+
}
429
+
430
+
pub async fn get_account_admin_status(
431
+
did: &str,
432
+
db: &DbConn,
433
+
) -> Result<Option<GetAccountAdminStatusOutput>> {
434
+
let did = did.to_owned();
435
+
let res: Option<(Option<String>, Option<String>)> = db
436
+
.run(move |conn| {
437
+
ActorSchema::actor
438
+
.filter(ActorSchema::did.eq(did))
439
+
.select((ActorSchema::takedownRef, ActorSchema::deactivatedAt))
440
+
.first(conn)
441
+
.optional()
442
+
})
443
+
.await?;
444
+
match res {
445
+
None => Ok(None),
446
+
Some(res) => {
447
+
let takedown = match res.0 {
448
+
Some(takedown_ref) => StatusAttr {
449
+
applied: true,
450
+
r#ref: Some(takedown_ref),
451
+
},
452
+
None => StatusAttr {
453
+
applied: false,
454
+
r#ref: None,
455
+
},
456
+
};
457
+
let deactivated = match res.1 {
458
+
Some(_) => StatusAttr {
459
+
applied: true,
460
+
r#ref: None,
461
+
},
462
+
None => StatusAttr {
463
+
applied: false,
464
+
r#ref: None,
465
+
},
466
+
};
467
+
Ok(Some(GetAccountAdminStatusOutput {
468
+
takedown,
469
+
deactivated,
470
+
}))
471
+
}
472
+
}
473
+
}
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
+
}
+349
src/account_manager/helpers/auth.rs
+349
src/account_manager/helpers/auth.rs
···
1
+
//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/helpers/auth.rs
2
+
//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
3
+
//!
4
+
//! Modified for SQLite backend
5
+
use crate::auth_verifier::AuthScope;
6
+
use crate::db::DbConn;
7
+
use crate::models;
8
+
use anyhow::Result;
9
+
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
+
}
220
+
221
+
pub async fn store_refresh_token(
222
+
payload: RefreshToken,
223
+
app_password_name: Option<String>,
224
+
db: &DbConn,
225
+
) -> Result<()> {
226
+
use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
227
+
228
+
let exp = from_micros_to_utc((payload.exp.as_millis() / 1000) as i64);
229
+
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?;
242
+
243
+
Ok(())
244
+
}
245
+
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)?;
252
+
253
+
Ok(!deleted_rows.is_empty())
254
+
})
255
+
.await
256
+
}
257
+
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;
260
+
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)?;
265
+
266
+
Ok(!deleted_rows.is_empty())
267
+
})
268
+
.await
269
+
}
270
+
271
+
pub async fn revoke_app_password_refresh_token(
272
+
did: &str,
273
+
app_pass_name: &str,
274
+
db: &DbConn,
275
+
) -> Result<bool> {
276
+
use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
277
+
278
+
let did = did.to_owned();
279
+
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)?;
285
+
286
+
Ok(!deleted_rows.is_empty())
287
+
})
288
+
.await
289
+
}
290
+
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;
293
+
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
301
+
}
302
+
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;
305
+
let did = did.to_owned();
306
+
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
315
+
}
316
+
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;
325
+
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
345
+
}
346
+
347
+
pub fn get_refresh_token_id() -> String {
348
+
get_random_str()
349
+
}
+136
src/account_manager/helpers/email_token.rs
+136
src/account_manager/helpers/email_token.rs
···
1
+
//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/helpers/email_token.rs
2
+
//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
3
+
//!
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
+
use anyhow::{Result, bail};
10
+
use diesel::*;
11
+
use rsky_common;
12
+
use rsky_common::time::{MINUTE, from_str_to_utc, less_than_ago_s};
13
+
14
+
pub async fn create_email_token(
15
+
did: &str,
16
+
purpose: EmailTokenPurpose,
17
+
db: &DbConn,
18
+
) -> Result<String> {
19
+
use crate::schema::pds::email_token::dsl as EmailTokenSchema;
20
+
let token = get_random_token().to_uppercase();
21
+
let now = rsky_common::now();
22
+
23
+
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
42
+
}
43
+
44
+
pub async fn assert_valid_token(
45
+
did: &str,
46
+
purpose: EmailTokenPurpose,
47
+
token: &str,
48
+
expiration_len: Option<i32>,
49
+
db: &DbConn,
50
+
) -> Result<()> {
51
+
let expiration_len = expiration_len.unwrap_or(MINUTE * 15);
52
+
use crate::schema::pds::email_token::dsl as EmailTokenSchema;
53
+
54
+
let did = did.to_owned();
55
+
let token = token.to_owned();
56
+
let res = db
57
+
.run(move |conn| {
58
+
EmailTokenSchema::email_token
59
+
.filter(EmailTokenSchema::purpose.eq(purpose))
60
+
.filter(EmailTokenSchema::did.eq(did))
61
+
.filter(EmailTokenSchema::token.eq(token.to_uppercase()))
62
+
.select(EmailToken::as_select())
63
+
.first(conn)
64
+
.optional()
65
+
})
66
+
.await?;
67
+
if let Some(res) = res {
68
+
let requested_at = from_str_to_utc(&res.requested_at);
69
+
let expired = !less_than_ago_s(requested_at, expiration_len);
70
+
if expired {
71
+
bail!("Token is expired")
72
+
}
73
+
Ok(())
74
+
} else {
75
+
bail!("Token is invalid")
76
+
}
77
+
}
78
+
79
+
pub async fn assert_valid_token_and_find_did(
80
+
purpose: EmailTokenPurpose,
81
+
token: &str,
82
+
expiration_len: Option<i32>,
83
+
db: &DbConn,
84
+
) -> Result<String> {
85
+
let expiration_len = expiration_len.unwrap_or(MINUTE * 15);
86
+
use crate::schema::pds::email_token::dsl as EmailTokenSchema;
87
+
88
+
let token = token.to_owned();
89
+
let res = db
90
+
.run(move |conn| {
91
+
EmailTokenSchema::email_token
92
+
.filter(EmailTokenSchema::purpose.eq(purpose))
93
+
.filter(EmailTokenSchema::token.eq(token.to_uppercase()))
94
+
.select(EmailToken::as_select())
95
+
.first(conn)
96
+
.optional()
97
+
})
98
+
.await?;
99
+
if let Some(res) = res {
100
+
let requested_at = from_str_to_utc(&res.requested_at);
101
+
let expired = !less_than_ago_s(requested_at, expiration_len);
102
+
if expired {
103
+
bail!("Token is expired")
104
+
}
105
+
Ok(res.did)
106
+
} else {
107
+
bail!("Token is invalid")
108
+
}
109
+
}
110
+
111
+
pub async fn delete_email_token(did: &str, purpose: EmailTokenPurpose, db: &DbConn) -> Result<()> {
112
+
use crate::schema::pds::email_token::dsl as EmailTokenSchema;
113
+
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?;
121
+
Ok(())
122
+
}
123
+
124
+
pub async fn delete_all_email_tokens(did: &str, db: &DbConn) -> Result<()> {
125
+
use crate::schema::pds::email_token::dsl as EmailTokenSchema;
126
+
127
+
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?;
134
+
135
+
Ok(())
136
+
}
+322
src/account_manager/helpers/invite.rs
+322
src/account_manager/helpers/invite.rs
···
1
+
//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/helpers/invite.rs
2
+
//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
3
+
//!
4
+
//! Modified for SQLite backend
5
+
use crate::account_manager::DisableInviteCodesOpts;
6
+
use crate::db::DbConn;
7
+
use crate::models::models;
8
+
use anyhow::{Result, bail};
9
+
use diesel::*;
10
+
use rsky_common;
11
+
use rsky_lexicon::com::atproto::server::AccountCodes;
12
+
use rsky_lexicon::com::atproto::server::{
13
+
InviteCode as LexiconInviteCode, InviteCodeUse as LexiconInviteCodeUse,
14
+
};
15
+
use std::collections::BTreeMap;
16
+
use std::mem;
17
+
18
+
pub type CodeUse = LexiconInviteCodeUse;
19
+
pub type CodeDetail = LexiconInviteCode;
20
+
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;
25
+
26
+
db.run(move |conn| {
27
+
let invite: Option<models::InviteCode> = InviteCodeSchema::invite_code
28
+
.left_join(
29
+
ActorSchema::actor.on(InviteCodeSchema::forAccount
30
+
.eq(ActorSchema::did)
31
+
.and(ActorSchema::takedownRef.is_null())),
32
+
)
33
+
.filter(InviteCodeSchema::code.eq(&invite_code))
34
+
.select(models::InviteCode::as_select())
35
+
.first(conn)
36
+
.optional()?;
37
+
38
+
if invite.is_none() || invite.clone().unwrap().disabled > 0 {
39
+
bail!("InvalidInviteCode: None or disabled. Provided invite code not available `{invite_code:?}`")
40
+
}
41
+
42
+
let uses: i64 = InviteCodeUseSchema::invite_code_use
43
+
.count()
44
+
.filter(InviteCodeUseSchema::code.eq(&invite_code))
45
+
.first(conn)?;
46
+
47
+
if invite.unwrap().available_uses as i64 <= uses {
48
+
bail!("InvalidInviteCode: Not enough uses. Provided invite code not available `{invite_code:?}`")
49
+
}
50
+
Ok(())
51
+
}).await?;
52
+
53
+
Ok(())
54
+
}
55
+
56
+
pub async fn record_invite_use(
57
+
did: String,
58
+
invite_code: Option<String>,
59
+
now: String,
60
+
db: &DbConn,
61
+
) -> Result<()> {
62
+
if let Some(invite_code) = invite_code {
63
+
use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
64
+
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?;
75
+
}
76
+
Ok(())
77
+
}
78
+
79
+
pub async fn create_invite_codes(
80
+
to_create: Vec<AccountCodes>,
81
+
use_count: i32,
82
+
db: &DbConn,
83
+
) -> Result<()> {
84
+
use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
85
+
let created_at = rsky_common::now();
86
+
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?;
111
+
Ok(())
112
+
}
113
+
114
+
pub async fn create_account_invite_codes(
115
+
for_account: &str,
116
+
codes: Vec<String>,
117
+
expected_total: usize,
118
+
disabled: bool,
119
+
db: &DbConn,
120
+
) -> Result<Vec<CodeDetail>> {
121
+
use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
122
+
123
+
let for_account = for_account.to_owned();
124
+
let rows = db
125
+
.run(move |conn| {
126
+
let now = rsky_common::now();
127
+
128
+
let rows: Vec<models::InviteCode> = codes
129
+
.into_iter()
130
+
.map(|code| models::InviteCode {
131
+
code,
132
+
available_uses: 1,
133
+
disabled: if disabled { 1 } else { 0 },
134
+
for_account: for_account.clone(),
135
+
created_by: for_account.clone(),
136
+
created_at: now.clone(),
137
+
})
138
+
.collect();
139
+
140
+
insert_into(InviteCodeSchema::invite_code)
141
+
.values(&rows)
142
+
.execute(conn)?;
143
+
144
+
let final_routine_invite_codes: Vec<models::InviteCode> = InviteCodeSchema::invite_code
145
+
.filter(InviteCodeSchema::forAccount.eq(for_account))
146
+
.filter(InviteCodeSchema::createdBy.ne("admin")) // don't count admin-gifted codes against the user
147
+
.select(models::InviteCode::as_select())
148
+
.get_results(conn)?;
149
+
150
+
if final_routine_invite_codes.len() > expected_total {
151
+
bail!("DuplicateCreate: attempted to create additional codes in another request")
152
+
}
153
+
154
+
Ok(rows.into_iter().map(|row| CodeDetail {
155
+
code: row.code,
156
+
available: 1,
157
+
disabled: row.disabled == 1,
158
+
for_account: row.for_account,
159
+
created_by: row.created_by,
160
+
created_at: row.created_at,
161
+
uses: Vec::new(),
162
+
}))
163
+
})
164
+
.await?;
165
+
Ok(rows.collect())
166
+
}
167
+
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;
170
+
171
+
let did = did.to_owned();
172
+
let res: Vec<models::InviteCode> = db
173
+
.run(move |conn| {
174
+
InviteCodeSchema::invite_code
175
+
.filter(InviteCodeSchema::forAccount.eq(did))
176
+
.select(models::InviteCode::as_select())
177
+
.get_results(conn)
178
+
})
179
+
.await?;
180
+
181
+
let codes: Vec<String> = res.iter().map(|row| row.code.clone()).collect();
182
+
let mut uses = get_invite_codes_uses_v2(codes, db).await?;
183
+
Ok(res
184
+
.into_iter()
185
+
.map(|row| CodeDetail {
186
+
code: row.code.clone(),
187
+
available: row.available_uses,
188
+
disabled: row.disabled == 1,
189
+
for_account: row.for_account,
190
+
created_by: row.created_by,
191
+
created_at: row.created_at,
192
+
uses: mem::take(uses.get_mut(&row.code).unwrap_or(&mut Vec::new())),
193
+
})
194
+
.collect::<Vec<CodeDetail>>())
195
+
}
196
+
197
+
pub async fn get_invite_codes_uses_v2(
198
+
codes: Vec<String>,
199
+
db: &DbConn,
200
+
) -> Result<BTreeMap<String, Vec<CodeUse>>> {
201
+
use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
202
+
203
+
let mut uses: BTreeMap<String, Vec<CodeUse>> = BTreeMap::new();
204
+
if !codes.is_empty() {
205
+
let uses_res: Vec<models::InviteCodeUse> = db
206
+
.run(|conn| {
207
+
InviteCodeUseSchema::invite_code_use
208
+
.filter(InviteCodeUseSchema::code.eq_any(codes))
209
+
.order_by(InviteCodeUseSchema::usedAt.desc())
210
+
.select(models::InviteCodeUse::as_select())
211
+
.get_results(conn)
212
+
})
213
+
.await?;
214
+
for invite_code_use in uses_res {
215
+
let models::InviteCodeUse {
216
+
code,
217
+
used_by,
218
+
used_at,
219
+
} = invite_code_use;
220
+
match uses.get_mut(&code) {
221
+
None => {
222
+
uses.insert(code, vec![CodeUse { used_by, used_at }]);
223
+
}
224
+
Some(matched_uses) => matched_uses.push(CodeUse { used_by, used_at }),
225
+
};
226
+
}
227
+
}
228
+
Ok(uses)
229
+
}
230
+
231
+
pub async fn get_invited_by_for_accounts(
232
+
dids: Vec<String>,
233
+
db: &DbConn,
234
+
) -> Result<BTreeMap<String, CodeDetail>> {
235
+
if dids.is_empty() {
236
+
return Ok(BTreeMap::new());
237
+
}
238
+
use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
239
+
use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
240
+
241
+
let dids = dids.clone();
242
+
let res: Vec<models::InviteCode> = db
243
+
.run(|conn| {
244
+
InviteCodeSchema::invite_code
245
+
.filter(
246
+
InviteCodeSchema::forAccount.eq_any(
247
+
InviteCodeUseSchema::invite_code_use
248
+
.filter(InviteCodeUseSchema::usedBy.eq_any(dids))
249
+
.select(InviteCodeUseSchema::code)
250
+
.distinct(),
251
+
),
252
+
)
253
+
.select(models::InviteCode::as_select())
254
+
.get_results(conn)
255
+
})
256
+
.await?;
257
+
let codes: Vec<String> = res.iter().map(|row| row.code.clone()).collect();
258
+
let mut uses = get_invite_codes_uses_v2(codes, db).await?;
259
+
260
+
let code_details = res
261
+
.into_iter()
262
+
.map(|row| CodeDetail {
263
+
code: row.code.clone(),
264
+
available: row.available_uses,
265
+
disabled: row.disabled == 1,
266
+
for_account: row.for_account,
267
+
created_by: row.created_by,
268
+
created_at: row.created_at,
269
+
uses: mem::take(uses.get_mut(&row.code).unwrap_or(&mut Vec::new())),
270
+
})
271
+
.collect::<Vec<CodeDetail>>();
272
+
273
+
Ok(code_details.iter().fold(
274
+
BTreeMap::new(),
275
+
|mut acc: BTreeMap<String, CodeDetail>, cur| {
276
+
for code_use in &cur.uses {
277
+
acc.insert(code_use.used_by.clone(), cur.clone());
278
+
}
279
+
acc
280
+
},
281
+
))
282
+
}
283
+
284
+
pub async fn set_account_invites_disabled(did: &str, disabled: bool, db: &DbConn) -> Result<()> {
285
+
use crate::schema::pds::account::dsl as AccountSchema;
286
+
287
+
let disabled: i16 = if disabled { 1 } else { 0 };
288
+
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?;
296
+
Ok(())
297
+
}
298
+
299
+
pub async fn disable_invite_codes(opts: DisableInviteCodesOpts, db: &DbConn) -> Result<()> {
300
+
use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
301
+
302
+
let DisableInviteCodesOpts { codes, accounts } = opts;
303
+
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?;
311
+
}
312
+
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?;
320
+
}
321
+
Ok(())
322
+
}
+181
src/account_manager/helpers/password.rs
+181
src/account_manager/helpers/password.rs
···
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
5
+
use crate::db::DbConn;
6
+
use crate::models;
7
+
use crate::models::AppPassword;
8
+
use anyhow::{Result, anyhow, bail};
9
+
use argon2::{
10
+
Argon2,
11
+
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
12
+
};
13
+
use base64ct::{Base64, Encoding};
14
+
use diesel::*;
15
+
use rsky_common::{get_random_str, now};
16
+
use rsky_lexicon::com::atproto::server::CreateAppPasswordOutput;
17
+
use sha2::{Digest, Sha256};
18
+
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;
26
+
27
+
let did = did.to_owned();
28
+
let found = db
29
+
.run(move |conn| {
30
+
AccountSchema::account
31
+
.filter(AccountSchema::did.eq(did))
32
+
.select(models::Account::as_select())
33
+
.first(conn)
34
+
.optional()
35
+
})
36
+
.await?;
37
+
if let Some(found) = found {
38
+
verify(password, &found.password)
39
+
} else {
40
+
Ok(false)
41
+
}
42
+
}
43
+
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;
46
+
47
+
let did = did.to_owned();
48
+
let password = password.to_owned();
49
+
let password_encrypted = hash_app_password(&did, &password).await?;
50
+
let found = db
51
+
.run(move |conn| {
52
+
AppPasswordSchema::app_password
53
+
.filter(AppPasswordSchema::did.eq(did))
54
+
.filter(AppPasswordSchema::password.eq(password_encrypted))
55
+
.select(AppPassword::as_select())
56
+
.first(conn)
57
+
.optional()
58
+
})
59
+
.await?;
60
+
if let Some(found) = found {
61
+
Ok(Some(found.name))
62
+
} else {
63
+
Ok(None)
64
+
}
65
+
}
66
+
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
+
/// create an app password with format:
103
+
/// 1234-abcd-5678-efgh
104
+
pub async fn create_app_password(
105
+
did: String,
106
+
name: String,
107
+
db: &DbConn,
108
+
) -> Result<CreateAppPasswordOutput> {
109
+
let str = &get_random_str()[0..16].to_lowercase();
110
+
let chunks = [&str[0..4], &str[4..8], &str[8..12], &str[12..16]];
111
+
let password = chunks.join("-");
112
+
let password_encrypted = hash_app_password(&did, &password).await?;
113
+
114
+
use crate::schema::pds::app_password::dsl as AppPasswordSchema;
115
+
116
+
let created_at = now();
117
+
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
140
+
}
141
+
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;
144
+
145
+
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
153
+
}
154
+
155
+
pub async fn update_user_password(opts: UpdateUserPasswordOpts, db: &DbConn) -> Result<()> {
156
+
use crate::schema::pds::account::dsl as AccountSchema;
157
+
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
166
+
}
167
+
168
+
pub async fn delete_app_password(did: &str, name: &str, db: &DbConn) -> Result<()> {
169
+
use crate::schema::pds::app_password::dsl as AppPasswordSchema;
170
+
171
+
let did = did.to_owned();
172
+
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
181
+
}
+36
src/account_manager/helpers/repo.rs
+36
src/account_manager/helpers/repo.rs
···
1
+
//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/helpers/repo.rs
2
+
//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
3
+
//!
4
+
//! Modified for SQLite backend
5
+
use crate::db::DbConn;
6
+
use anyhow::Result;
7
+
use diesel::*;
8
+
use libipld::Cid;
9
+
use rsky_common;
10
+
11
+
pub async fn update_root(did: String, cid: Cid, rev: String, db: &DbConn) -> Result<()> {
12
+
// @TODO balance risk of a race in the case of a long retry
13
+
use crate::schema::pds::repo_root::dsl as RepoRootSchema;
14
+
15
+
let now = rsky_common::now();
16
+
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?;
34
+
35
+
Ok(())
36
+
}
+48
-12
src/account_manager/mod.rs
+48
-12
src/account_manager/mod.rs
···
1
+
//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/mod.rs
2
+
//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
3
+
//!
4
+
//! Modified for SQLite backend
1
5
use anyhow::Result;
2
6
use chrono::DateTime;
3
7
use chrono::offset::Utc as UtcOffset;
4
8
use cidv10::Cid;
5
9
use futures::try_join;
10
+
use helpers::{account, auth, email_token, invite, password};
6
11
use rsky_common::RFC3339_VARIANT;
7
12
use rsky_common::time::{HOUR, from_micros_to_str, from_str_to_micros};
8
13
use rsky_lexicon::com::atproto::admin::StatusAttr;
9
14
use rsky_lexicon::com::atproto::server::{AccountCodes, CreateAppPasswordOutput};
10
-
use rsky_pds::account_manager::CreateAccountOpts;
11
15
use rsky_pds::account_manager::helpers::account::{
12
16
AccountStatus, ActorAccount, AvailabilityFlags, GetAccountAdminStatusOutput,
13
17
};
···
17
21
use rsky_pds::account_manager::helpers::invite::CodeDetail;
18
22
use rsky_pds::account_manager::helpers::password::UpdateUserPasswordOpts;
19
23
use rsky_pds::account_manager::helpers::repo;
20
-
use rsky_pds::account_manager::helpers::{account, auth, email_token, invite, password};
24
+
use rsky_pds::account_manager::{
25
+
ConfirmEmailOpts, CreateAccountOpts, DisableInviteCodesOpts, ResetPasswordOpts,
26
+
UpdateAccountPasswordOpts, UpdateEmailOpts,
27
+
};
21
28
use rsky_pds::auth_verifier::AuthScope;
22
29
use rsky_pds::models::models::EmailTokenPurpose;
23
30
use secp256k1::{Keypair, Secp256k1, SecretKey};
24
31
use std::collections::BTreeMap;
25
32
use std::env;
26
-
use std::sync::Arc;
27
33
use std::time::SystemTime;
28
34
29
-
use crate::db::DbConn;
35
+
pub(crate) mod helpers {
36
+
pub mod account;
37
+
pub mod auth;
38
+
pub mod email_token;
39
+
pub mod invite;
40
+
pub mod password;
41
+
pub mod repo;
42
+
}
30
43
31
-
#[derive(Clone, Debug)]
44
+
#[derive(Clone)]
32
45
pub struct AccountManager {
33
-
pub db: deadpool_diesel::Connection<SqliteConnection>,
46
+
pub db: deadpool_diesel::Pool<
47
+
deadpool_diesel::Manager<diesel::SqliteConnection>,
48
+
deadpool_diesel::sqlite::Object,
49
+
>,
50
+
}
51
+
impl std::fmt::Debug for AccountManager {
52
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53
+
f.debug_struct("AccountManager").finish()
54
+
}
34
55
}
35
56
36
-
pub type AccountManagerCreator =
37
-
Box<dyn Fn(deadpool_diesel::Connection<SqliteConnection>) -> AccountManager + Send + Sync>;
57
+
pub type AccountManagerCreator = Box<
58
+
dyn Fn(
59
+
deadpool_diesel::Pool<
60
+
deadpool_diesel::Manager<diesel::SqliteConnection>,
61
+
deadpool_diesel::sqlite::Object,
62
+
>,
63
+
) -> AccountManager
64
+
+ Send
65
+
+ Sync,
66
+
>;
38
67
39
68
impl AccountManager {
40
-
pub fn new(db: deadpool_diesel::Connection<SqliteConnection>) -> Self {
69
+
pub fn new(
70
+
db: deadpool_diesel::Pool<
71
+
deadpool_diesel::Manager<diesel::SqliteConnection>,
72
+
deadpool_diesel::sqlite::Object,
73
+
>,
74
+
) -> Self {
41
75
Self { db }
42
76
}
43
77
44
78
pub fn creator() -> AccountManagerCreator {
45
79
Box::new(
46
-
move |db: deadpool_diesel::Connection<SqliteConnection>| -> AccountManager {
47
-
AccountManager::new(db)
48
-
},
80
+
move |db: deadpool_diesel::Pool<
81
+
deadpool_diesel::Manager<diesel::SqliteConnection>,
82
+
deadpool_diesel::sqlite::Object,
83
+
>|
84
+
-> AccountManager { AccountManager::new(db) },
49
85
)
50
86
}
51
87
+31
-14
src/actor_endpoints.rs
+31
-14
src/actor_endpoints.rs
···
7
7
8
8
async fn put_preferences(
9
9
user: AuthenticatedUser,
10
-
State(db): State<Db>,
10
+
State(actor_pools): State<std::collections::HashMap<String, ActorPools>>,
11
11
Json(input): Json<actor::put_preferences::Input>,
12
12
) -> Result<()> {
13
13
let did = user.did();
14
14
let json_string =
15
15
serde_json::to_string(&input.preferences).context("failed to serialize preferences")?;
16
16
17
-
// Use the db connection pool to execute the update
18
-
let conn = &mut db.get().context("failed to get database connection")?;
19
-
diesel::sql_query("UPDATE accounts SET private_prefs = ? WHERE did = ?")
20
-
.bind::<diesel::sql_types::Text, _>(json_string)
21
-
.bind::<diesel::sql_types::Text, _>(did)
22
-
.execute(conn)
23
-
.context("failed to update user preferences")?;
17
+
let conn = &mut actor_pools
18
+
.get(&did)
19
+
.context("failed to get actor pool")?
20
+
.repo
21
+
.get()
22
+
.await
23
+
.expect("failed to get database connection");
24
+
conn.interact(move |conn| {
25
+
diesel::update(accounts::table)
26
+
.filter(accounts::did.eq(did))
27
+
.set(accounts::private_prefs.eq(json_string))
28
+
.execute(conn)
29
+
.context("failed to update user preferences")
30
+
});
24
31
25
32
Ok(())
26
33
}
27
34
28
35
async fn get_preferences(
29
36
user: AuthenticatedUser,
30
-
State(db): State<Db>,
37
+
State(actor_pools): State<std::collections::HashMap<String, ActorPools>>,
31
38
) -> Result<Json<actor::get_preferences::Output>> {
32
39
let did = user.did();
33
-
let conn = &mut db.get().context("failed to get database connection")?;
40
+
let conn = &mut actor_pools
41
+
.get(&did)
42
+
.context("failed to get actor pool")?
43
+
.repo
44
+
.get()
45
+
.await
46
+
.expect("failed to get database connection");
34
47
35
48
#[derive(QueryableByName)]
36
49
struct Prefs {
···
38
51
private_prefs: Option<String>,
39
52
}
40
53
41
-
let result = diesel::sql_query("SELECT private_prefs FROM accounts WHERE did = ?")
42
-
.bind::<diesel::sql_types::Text, _>(did)
43
-
.get_result::<Prefs>(conn)
44
-
.context("failed to fetch preferences")?;
54
+
let result = conn
55
+
.interact(move |conn| {
56
+
diesel::sql_query("SELECT private_prefs FROM accounts WHERE did = ?")
57
+
.bind::<diesel::sql_types::Text, _>(did)
58
+
.get_result::<Prefs>(conn)
59
+
})
60
+
.await
61
+
.expect("failed to fetch preferences");
45
62
46
63
if let Some(prefs_json) = result.private_prefs {
47
64
let prefs: actor::defs::Preferences =
+17
-8
src/auth.rs
+17
-8
src/auth.rs
···
8
8
use diesel::prelude::*;
9
9
use sha2::{Digest as _, Sha256};
10
10
11
-
use crate::{AppState, Error, db::DbConn, error::ErrorMessage};
11
+
use crate::{AppState, Error, error::ErrorMessage};
12
12
13
13
/// Request extractor for authenticated users.
14
14
/// If specified in an API endpoint, this guarantees the API can only be called
···
130
130
131
131
// Extract subject (DID)
132
132
if let Some(did) = claims.get("sub").and_then(serde_json::Value::as_str) {
133
-
// Convert SQLx query to Diesel query
134
133
use crate::schema::accounts::dsl as AccountSchema;
135
134
136
135
let _status = state
137
136
.db
138
-
.run(move |conn| {
137
+
.get()
138
+
.await
139
+
.expect("failed to get db connection")
140
+
.interact(move |conn| {
139
141
AccountSchema::accounts
140
142
.filter(AccountSchema::did.eq(did.to_string()))
141
143
.select(AccountSchema::status)
···
336
338
337
339
let timestamp = chrono::Utc::now().timestamp();
338
340
339
-
// Convert SQLx JTI check to Diesel
340
341
use crate::schema::oauth_used_jtis::dsl as JtiSchema;
341
342
342
343
// Check if JTI has been used before
343
344
let jti_string = jti.to_string();
344
345
let jti_used = state
345
346
.db
346
-
.run(move |conn| {
347
+
.get()
348
+
.await
349
+
.expect("failed to get db connection")
350
+
.interact(move |conn| {
347
351
JtiSchema::oauth_used_jtis
348
352
.filter(JtiSchema::jti.eq(jti_string))
349
353
.count()
···
371
375
let thumbprint_str = calculated_thumbprint.to_string();
372
376
state
373
377
.db
374
-
.run(move |conn| {
378
+
.get()
379
+
.await
380
+
.expect("failed to get db connection")
381
+
.interact(move |conn| {
375
382
diesel::insert_into(JtiSchema::oauth_used_jtis)
376
383
.values((
377
384
JtiSchema::jti.eq(jti_str),
···
386
393
387
394
// Extract subject (DID) from access token
388
395
if let Some(did) = claims.get("sub").and_then(|v| v.as_str) {
389
-
// Convert SQLx query to Diesel
390
396
use crate::schema::accounts::dsl as AccountSchema;
391
397
392
398
let _status = state
393
399
.db
394
-
.run(move |conn| {
400
+
.get()
401
+
.await
402
+
.expect("failed to get db connection")
403
+
.interact(move |conn| {
395
404
AccountSchema::accounts
396
405
.filter(AccountSchema::did.eq(did.to_string()))
397
406
.select(AccountSchema::status)
+81
-81
src/tests.rs
+81
-81
src/tests.rs
···
174
174
175
175
/// Start the application in a background task.
176
176
async fn start_app(&self) -> Result<()> {
177
-
// Get a reference to the config that can be moved into the task
178
-
let config = self.config.clone();
179
-
let address = self.address;
177
+
// // Get a reference to the config that can be moved into the task
178
+
// let config = self.config.clone();
179
+
// let address = self.address;
180
180
181
-
// Start the application in a background task
182
-
let _handle = tokio::spawn(async move {
183
-
// Set up the application
184
-
use crate::*;
181
+
// // Start the application in a background task
182
+
// let _handle = tokio::spawn(async move {
183
+
// // Set up the application
184
+
// use crate::*;
185
185
186
-
// Initialize metrics (noop in test mode)
187
-
drop(metrics::setup(None));
186
+
// // Initialize metrics (noop in test mode)
187
+
// drop(metrics::setup(None));
188
188
189
-
// Create client
190
-
let simple_client = reqwest::Client::builder()
191
-
.user_agent(APP_USER_AGENT)
192
-
.build()
193
-
.context("failed to build requester client")?;
194
-
let client = reqwest_middleware::ClientBuilder::new(simple_client.clone())
195
-
.with(http_cache_reqwest::Cache(http_cache_reqwest::HttpCache {
196
-
mode: CacheMode::Default,
197
-
manager: MokaManager::default(),
198
-
options: HttpCacheOptions::default(),
199
-
}))
200
-
.build();
189
+
// // Create client
190
+
// let simple_client = reqwest::Client::builder()
191
+
// .user_agent(APP_USER_AGENT)
192
+
// .build()
193
+
// .context("failed to build requester client")?;
194
+
// let client = reqwest_middleware::ClientBuilder::new(simple_client.clone())
195
+
// .with(http_cache_reqwest::Cache(http_cache_reqwest::HttpCache {
196
+
// mode: CacheMode::Default,
197
+
// manager: MokaManager::default(),
198
+
// options: HttpCacheOptions::default(),
199
+
// }))
200
+
// .build();
201
201
202
-
// Create a test keypair
203
-
std::fs::create_dir_all(config.key.parent().context("should have parent")?)?;
204
-
let (skey, rkey) = {
205
-
let skey = Secp256k1Keypair::create(&mut rand::thread_rng());
206
-
let rkey = Secp256k1Keypair::create(&mut rand::thread_rng());
202
+
// // Create a test keypair
203
+
// std::fs::create_dir_all(config.key.parent().context("should have parent")?)?;
204
+
// let (skey, rkey) = {
205
+
// let skey = Secp256k1Keypair::create(&mut rand::thread_rng());
206
+
// let rkey = Secp256k1Keypair::create(&mut rand::thread_rng());
207
207
208
-
let keys = KeyData {
209
-
skey: skey.export(),
210
-
rkey: rkey.export(),
211
-
};
208
+
// let keys = KeyData {
209
+
// skey: skey.export(),
210
+
// rkey: rkey.export(),
211
+
// };
212
212
213
-
let mut f =
214
-
std::fs::File::create(&config.key).context("failed to create key file")?;
215
-
serde_ipld_dagcbor::to_writer(&mut f, &keys)
216
-
.context("failed to serialize crypto keys")?;
213
+
// let mut f =
214
+
// std::fs::File::create(&config.key).context("failed to create key file")?;
215
+
// serde_ipld_dagcbor::to_writer(&mut f, &keys)
216
+
// .context("failed to serialize crypto keys")?;
217
217
218
-
(SigningKey(Arc::new(skey)), RotationKey(Arc::new(rkey)))
219
-
};
218
+
// (SigningKey(Arc::new(skey)), RotationKey(Arc::new(rkey)))
219
+
// };
220
220
221
-
// Set up database
222
-
let opts = SqliteConnectOptions::from_str(&config.db)
223
-
.context("failed to parse database options")?
224
-
.create_if_missing(true);
225
-
let db = SqliteDbConn::connect_with(opts).await?;
221
+
// // Set up database
222
+
// let opts = SqliteConnectOptions::from_str(&config.db)
223
+
// .context("failed to parse database options")?
224
+
// .create_if_missing(true);
225
+
// let db = SqliteDbConn::connect_with(opts).await?;
226
226
227
-
sqlx::migrate!()
228
-
.run(&db)
229
-
.await
230
-
.context("failed to apply migrations")?;
227
+
// sqlx::migrate!()
228
+
// .run(&db)
229
+
// .await
230
+
// .context("failed to apply migrations")?;
231
231
232
-
// Create firehose
233
-
let (_fh, fhp) = firehose::spawn(client.clone(), config.clone());
232
+
// // Create firehose
233
+
// let (_fh, fhp) = firehose::spawn(client.clone(), config.clone());
234
234
235
-
// Create the application state
236
-
let app_state = AppState {
237
-
cred: azure_identity::DefaultAzureCredential::new()?,
238
-
config: config.clone(),
239
-
db: db.clone(),
240
-
client: client.clone(),
241
-
simple_client,
242
-
firehose: fhp,
243
-
signing_key: skey,
244
-
rotation_key: rkey,
245
-
};
235
+
// // Create the application state
236
+
// let app_state = AppState {
237
+
// cred: azure_identity::DefaultAzureCredential::new()?,
238
+
// config: config.clone(),
239
+
// db: db.clone(),
240
+
// client: client.clone(),
241
+
// simple_client,
242
+
// firehose: fhp,
243
+
// signing_key: skey,
244
+
// rotation_key: rkey,
245
+
// };
246
246
247
-
// Create the router
248
-
let app = Router::new()
249
-
.route("/", get(index))
250
-
.merge(oauth::routes())
251
-
.nest(
252
-
"/xrpc",
253
-
endpoints::routes()
254
-
.merge(actor_endpoints::routes())
255
-
.fallback(service_proxy),
256
-
)
257
-
.layer(CorsLayer::permissive())
258
-
.layer(TraceLayer::new_for_http())
259
-
.with_state(app_state);
247
+
// // Create the router
248
+
// let app = Router::new()
249
+
// .route("/", get(index))
250
+
// .merge(oauth::routes())
251
+
// .nest(
252
+
// "/xrpc",
253
+
// endpoints::routes()
254
+
// .merge(actor_endpoints::routes())
255
+
// .fallback(service_proxy),
256
+
// )
257
+
// .layer(CorsLayer::permissive())
258
+
// .layer(TraceLayer::new_for_http())
259
+
// .with_state(app_state);
260
260
261
-
// Listen for connections
262
-
let listener = TcpListener::bind(&address)
263
-
.await
264
-
.context("failed to bind address")?;
261
+
// // Listen for connections
262
+
// let listener = TcpListener::bind(&address)
263
+
// .await
264
+
// .context("failed to bind address")?;
265
265
266
-
axum::serve(listener, app.into_make_service())
267
-
.await
268
-
.context("failed to serve app")
269
-
});
266
+
// axum::serve(listener, app.into_make_service())
267
+
// .await
268
+
// .context("failed to serve app")
269
+
// });
270
270
271
-
// Give the server a moment to start
272
-
tokio::time::sleep(Duration::from_millis(500)).await;
271
+
// // Give the server a moment to start
272
+
// tokio::time::sleep(Duration::from_millis(500)).await;
273
273
274
274
Ok(())
275
275
}