+7
-7
src/main.rs
+7
-7
src/main.rs
···
21
use tower_governor::governor::GovernorConfigBuilder;
22
use tower_http::compression::CompressionLayer;
23
use tower_http::cors::{Any, CorsLayer};
24
-
use tracing::{error, log};
25
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
26
27
mod middleware;
···
74
75
let intro = "\n\nThis is a PDS gatekeeper\n\nCode: https://tangled.sh/@baileytownsend.dev/pds-gatekeeper\n";
76
77
-
let banner = format!(" {}\n{}", body, intro);
78
79
(
80
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
···
85
#[tokio::main]
86
async fn main() -> Result<(), Box<dyn std::error::Error>> {
87
setup_tracing();
88
-
//TODO prod
89
dotenvy::from_path(Path::new("./pds.env"))?;
90
let pds_root = env::var("PDS_DATA_DIRECTORY")?;
91
-
let account_db_url = format!("{}/account.sqlite", pds_root);
92
-
log::info!("accounts_db_url: {}", account_db_url);
93
94
let account_options = SqliteConnectOptions::new()
95
.journal_mode(SqliteJournalMode::Wal)
···
100
.connect_with(account_options)
101
.await?;
102
103
-
let bells_db_url = format!("{}/pds_gatekeeper.sqlite", pds_root);
104
let options = SqliteConnectOptions::new()
105
.journal_mode(SqliteJournalMode::Wal)
106
.filename(bells_db_url)
···
149
.per_second(60)
150
.burst_size(5)
151
.finish()
152
-
.unwrap();
153
let governor_limiter = governor_conf.limiter().clone();
154
let interval = Duration::from_secs(60);
155
// a separate background task to clean up
···
21
use tower_governor::governor::GovernorConfigBuilder;
22
use tower_http::compression::CompressionLayer;
23
use tower_http::cors::{Any, CorsLayer};
24
+
use tracing::{error};
25
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
26
27
mod middleware;
···
74
75
let intro = "\n\nThis is a PDS gatekeeper\n\nCode: https://tangled.sh/@baileytownsend.dev/pds-gatekeeper\n";
76
77
+
let banner = format!(" {body}\n{intro}");
78
79
(
80
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
···
85
#[tokio::main]
86
async fn main() -> Result<(), Box<dyn std::error::Error>> {
87
setup_tracing();
88
+
//TODO may need to change where this reads from? Like an env variable for it's location?
89
dotenvy::from_path(Path::new("./pds.env"))?;
90
let pds_root = env::var("PDS_DATA_DIRECTORY")?;
91
+
let account_db_url = format!("{pds_root}/account.sqlite");
92
93
let account_options = SqliteConnectOptions::new()
94
.journal_mode(SqliteJournalMode::Wal)
···
99
.connect_with(account_options)
100
.await?;
101
102
+
let bells_db_url = format!("{pds_root}/pds_gatekeeper.sqlite");
103
let options = SqliteConnectOptions::new()
104
.journal_mode(SqliteJournalMode::Wal)
105
.filename(bells_db_url)
···
148
.per_second(60)
149
.burst_size(5)
150
.finish()
151
+
.expect("failed to create governor config. this hsould not happen and is a bug");
152
+
153
let governor_limiter = governor_conf.limiter().clone();
154
let interval = Duration::from_secs(60);
155
// a separate background task to clean up
+9
-11
src/middleware.rs
+9
-11
src/middleware.rs
···
23
match token {
24
Ok(token) => {
25
match token {
26
-
None => json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "").unwrap(),
27
Some(token) => {
28
let token = UntrustedToken::new(&token);
29
-
//Doing weird unwraps cause I can't do Result for middleware?
30
if token.is_err() {
31
return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "")
32
-
.unwrap();
33
}
34
-
let parsed_token = token.unwrap();
35
let claims: Result<Claims<TokenClaims>, ValidationError> =
36
parsed_token.deserialize_claims_unchecked();
37
if claims.is_err() {
38
return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "")
39
-
.unwrap();
40
}
41
42
-
let key = Hs256Key::new(env::var("PDS_JWT_SECRET").unwrap());
43
let token: Result<Token<TokenClaims>, ValidationError> =
44
Hs256.validator(&key).validate(&parsed_token);
45
if token.is_err() {
46
return json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "")
47
-
.unwrap();
48
}
49
-
let token = token.unwrap();
50
//Not going to worry about expiration since it still goes to the PDS
51
-
52
req.extensions_mut()
53
.insert(Did(Some(token.claims().custom.sub.clone())));
54
next.run(req).await
···
56
}
57
}
58
Err(err) => {
59
-
log::error!("Error extracting token: {}", err);
60
-
json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "").unwrap()
61
}
62
}
63
}
···
23
match token {
24
Ok(token) => {
25
match token {
26
+
None => json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "").expect("Error creating an error response"),
27
Some(token) => {
28
let token = UntrustedToken::new(&token);
29
if token.is_err() {
30
return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "")
31
+
.expect("Error creating an error response");
32
}
33
+
let parsed_token = token.expect("Already checked for error");
34
let claims: Result<Claims<TokenClaims>, ValidationError> =
35
parsed_token.deserialize_claims_unchecked();
36
if claims.is_err() {
37
return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "")
38
+
.expect("Error creating an error response");
39
}
40
41
+
let key = Hs256Key::new(env::var("PDS_JWT_SECRET").expect("PDS_JWT_SECRET not set in the pds.env"));
42
let token: Result<Token<TokenClaims>, ValidationError> =
43
Hs256.validator(&key).validate(&parsed_token);
44
if token.is_err() {
45
return json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "")
46
+
.expect("Error creating an error response");
47
}
48
+
let token = token.expect("Already checked for error,");
49
//Not going to worry about expiration since it still goes to the PDS
50
req.extensions_mut()
51
.insert(Did(Some(token.claims().custom.sub.clone())));
52
next.run(req).await
···
54
}
55
}
56
Err(err) => {
57
+
log::error!("Error extracting token: {err}");
58
+
json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "").expect("Error creating an error response")
59
}
60
}
61
}
+2
-2
src/xrpc/com_atproto_server.rs
+2
-2
src/xrpc/com_atproto_server.rs
+27
-32
src/xrpc/helpers.rs
+27
-32
src/xrpc/helpers.rs
···
1
use crate::AppState;
2
use crate::xrpc::helpers::TokenCheckError::InvalidToken;
3
use axum::body::{Body, to_bytes};
···
103
//Just going a head and doing uppercase here.
104
let slice_one = &full_code[0..5].to_ascii_uppercase();
105
let slice_two = &full_code[5..10].to_ascii_uppercase();
106
-
format!("{}-{}", slice_one, slice_two)
107
}
108
109
pub enum TokenCheckError {
···
151
let sha = hasher.finalize();
152
let salt = hex::encode(&sha[..16]);
153
let hash_hex = scrypt_hex(password, &salt)?;
154
-
Ok(format!("{}:{}", salt, hash_hex))
155
}
156
157
-
async fn verify_password(password: &str, password_scrypt: &str) -> Result<bool, StatusCode> {
158
// Expected format: "salt:hash" where hash is hex of scrypt(password, salt, 64 bytes)
159
let mut parts = password_scrypt.splitn(2, ':');
160
let salt = match parts.next() {
···
195
)
196
.bind(identifier)
197
.fetch_optional(&state.account_pool)
198
-
.await
199
-
.map_err(|_| StatusCode::BAD_REQUEST)?,
200
IdentifierType::Handle => sqlx::query_as::<_, (String, String, String, String)>(
201
"SELECT account.did, account.passwordScrypt, account.email, actor.handle
202
FROM actor
···
205
)
206
.bind(identifier)
207
.fetch_optional(&state.account_pool)
208
-
.await
209
-
.map_err(|_| StatusCode::BAD_REQUEST)?,
210
IdentifierType::Did => sqlx::query_as::<_, (String, String, String, String)>(
211
"SELECT account.did, account.passwordScrypt, account.email, actor.handle
212
FROM actor
···
215
)
216
.bind(identifier)
217
.fetch_optional(&state.account_pool)
218
-
.await
219
-
.map_err(|_| StatusCode::BAD_REQUEST)?,
220
};
221
222
if let Some((did, password_scrypt, email, handle)) = account_row {
···
226
)
227
.bind(did.clone())
228
.fetch_optional(&state.pds_gatekeeper_pool)
229
-
.await
230
-
.map_err(|_| StatusCode::BAD_REQUEST)?;
231
232
let two_factor_required = match required_opt {
233
Some(row) => row.0 != 0,
···
249
}
250
}
251
Err(err) => {
252
-
log::error!("Error checking the app password: {}", err);
253
-
Err(StatusCode::BAD_REQUEST)
254
}
255
};
256
}
···
266
.await
267
{
268
Ok(_) => {
269
-
let _ = delete_all_email_tokens(&state.account_pool, did.clone()).await;
270
Ok(AuthResult::ProxyThrough)
271
}
272
Err(err) => Ok(AuthResult::TokenCheckFailed(err)),
···
275
}
276
277
return match create_two_factor_token(&state.account_pool, did).await {
278
-
//TODO replace unwraps with the mythical ?
279
Ok(code) => {
280
let mut email_data = Map::new();
281
email_data.insert("token".to_string(), Value::from(code.clone()));
282
email_data.insert("handle".to_string(), Value::from(handle.clone()));
283
-
//TODO bad unwrap
284
let email_body = state
285
.template_engine
286
-
.render("two_factor_code.hbs", email_data)
287
-
.unwrap();
288
289
let email = Message::builder()
290
//TODO prob get the proper type in the state
291
-
.from(state.mailer_from.parse().unwrap())
292
-
.to(email.parse().unwrap())
293
.subject("Sign in to Bluesky")
294
.multipart(
295
MultiPart::alternative() // This is composed of two parts.
296
.singlepart(
297
SinglePart::builder()
298
.header(header::ContentType::TEXT_PLAIN)
299
-
.body(format!("We received a sign-in request for the account @{}. Use the code: {} to sign in. If this wasn't you, we recommend taking steps to protect your account by changing your password at https://bsky.app/settings.", handle, code)), // Every message should have a plain text fallback.
300
)
301
.singlepart(
302
SinglePart::builder()
303
.header(header::ContentType::TEXT_HTML)
304
.body(email_body),
305
),
306
-
)
307
-
//TODO bad
308
-
.unwrap();
309
match state.mailer.send(email).await {
310
Ok(_) => Ok(AuthResult::TwoFactorRequired),
311
Err(err) => {
312
-
log::error!("Error sending the 2FA email: {}", err);
313
-
Err(StatusCode::BAD_REQUEST)
314
}
315
}
316
}
317
Err(err) => {
318
-
log::error!("error on creating a 2fa token: {}", err);
319
-
Err(StatusCode::BAD_REQUEST)
320
}
321
};
322
}
···
351
352
match res {
353
Ok(_) => Ok(token),
354
-
Err(e) => {
355
-
log::error!("Error creating a two factor token: {}", e);
356
-
Err(anyhow::anyhow!(e))
357
}
358
}
359
}
···
383
.fetch_optional(account_db)
384
.await
385
.map_err(|err| {
386
-
log::error!("Error getting the 2fa token: {}", err);
387
InvalidToken
388
})?;
389
···
1
+
use anyhow::anyhow;
2
use crate::AppState;
3
use crate::xrpc::helpers::TokenCheckError::InvalidToken;
4
use axum::body::{Body, to_bytes};
···
104
//Just going a head and doing uppercase here.
105
let slice_one = &full_code[0..5].to_ascii_uppercase();
106
let slice_two = &full_code[5..10].to_ascii_uppercase();
107
+
format!("{slice_one}-{slice_two}")
108
}
109
110
pub enum TokenCheckError {
···
152
let sha = hasher.finalize();
153
let salt = hex::encode(&sha[..16]);
154
let hash_hex = scrypt_hex(password, &salt)?;
155
+
Ok(format!("{salt}:{hash_hex}"))
156
}
157
158
+
async fn verify_password(password: &str, password_scrypt: &str) -> anyhow::Result<bool> {
159
// Expected format: "salt:hash" where hash is hex of scrypt(password, salt, 64 bytes)
160
let mut parts = password_scrypt.splitn(2, ':');
161
let salt = match parts.next() {
···
196
)
197
.bind(identifier)
198
.fetch_optional(&state.account_pool)
199
+
.await?,
200
IdentifierType::Handle => sqlx::query_as::<_, (String, String, String, String)>(
201
"SELECT account.did, account.passwordScrypt, account.email, actor.handle
202
FROM actor
···
205
)
206
.bind(identifier)
207
.fetch_optional(&state.account_pool)
208
+
.await?,
209
IdentifierType::Did => sqlx::query_as::<_, (String, String, String, String)>(
210
"SELECT account.did, account.passwordScrypt, account.email, actor.handle
211
FROM actor
···
214
)
215
.bind(identifier)
216
.fetch_optional(&state.account_pool)
217
+
.await?,
218
};
219
220
if let Some((did, password_scrypt, email, handle)) = account_row {
···
224
)
225
.bind(did.clone())
226
.fetch_optional(&state.pds_gatekeeper_pool)
227
+
.await?;
228
229
let two_factor_required = match required_opt {
230
Some(row) => row.0 != 0,
···
246
}
247
}
248
Err(err) => {
249
+
log::error!("Error checking the app password: {err}");
250
+
Err(err)
251
}
252
};
253
}
···
263
.await
264
{
265
Ok(_) => {
266
+
let result_of_cleanup = delete_all_email_tokens(&state.account_pool, did.clone()).await;
267
+
if result_of_cleanup.is_err(){
268
+
log::error!("There was an error deleting the email tokens after login: {:?}", result_of_cleanup.err())
269
+
}
270
Ok(AuthResult::ProxyThrough)
271
}
272
Err(err) => Ok(AuthResult::TokenCheckFailed(err)),
···
275
}
276
277
return match create_two_factor_token(&state.account_pool, did).await {
278
Ok(code) => {
279
let mut email_data = Map::new();
280
email_data.insert("token".to_string(), Value::from(code.clone()));
281
email_data.insert("handle".to_string(), Value::from(handle.clone()));
282
let email_body = state
283
.template_engine
284
+
.render("two_factor_code.hbs", email_data)?;
285
286
let email = Message::builder()
287
//TODO prob get the proper type in the state
288
+
.from(state.mailer_from.parse()?)
289
+
.to(email.parse()?)
290
.subject("Sign in to Bluesky")
291
.multipart(
292
MultiPart::alternative() // This is composed of two parts.
293
.singlepart(
294
SinglePart::builder()
295
.header(header::ContentType::TEXT_PLAIN)
296
+
.body(format!("We received a sign-in request for the account @{handle}. Use the code: {code} to sign in. If this wasn't you, we recommend taking steps to protect your account by changing your password at https://bsky.app/settings.")), // Every message should have a plain text fallback.
297
)
298
.singlepart(
299
SinglePart::builder()
300
.header(header::ContentType::TEXT_HTML)
301
.body(email_body),
302
),
303
+
)?;
304
match state.mailer.send(email).await {
305
Ok(_) => Ok(AuthResult::TwoFactorRequired),
306
Err(err) => {
307
+
log::error!("Error sending the 2FA email: {err}");
308
+
Err(anyhow!(err))
309
}
310
}
311
}
312
Err(err) => {
313
+
log::error!("error on creating a 2fa token: {err}");
314
+
Err(anyhow!(err))
315
}
316
};
317
}
···
346
347
match res {
348
Ok(_) => Ok(token),
349
+
Err(err) => {
350
+
log::error!("Error creating a two factor token: {err}");
351
+
Err(anyhow::anyhow!(err))
352
}
353
}
354
}
···
378
.fetch_optional(account_db)
379
.await
380
.map_err(|err| {
381
+
log::error!("Error getting the 2fa token: {err}");
382
InvalidToken
383
})?;
384