+23
-10
src/helpers.rs
+23
-10
src/helpers.rs
···
10
10
use chrono::Utc;
11
11
use lettre::message::{MultiPart, SinglePart, header};
12
12
use lettre::{AsyncTransport, Message};
13
+
use rand::Rng;
13
14
use rand::distr::{Alphabetic, Alphanumeric, SampleString};
14
15
use serde::de::DeserializeOwned;
15
16
use serde_json::{Map, Value};
17
+
use sha2::{Digest, Sha256};
16
18
use sqlx::SqlitePool;
17
19
use tracing::{error, log};
20
+
21
+
///Used to generate the email 2fa code
22
+
const UPPERCASE_BASE32_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
18
23
19
24
/// The result of a proxied call that attempts to parse JSON.
20
25
pub enum ProxiedResult<T> {
···
97
102
.map_err(|_| StatusCode::BAD_REQUEST)
98
103
}
99
104
105
+
/// Build a JSON error response with the required Content-Type header
106
+
/// Content-Type: application/json (oauth endpoint does not like utf ending)
107
+
/// Body shape: { "error": string, "error_description": string }
100
108
pub fn oauth_json_error_response(
101
109
status: StatusCode,
102
110
error: impl Into<String>,
···
118
126
}
119
127
120
128
/// Creates a random token of 10 characters for email 2FA
121
-
pub fn get_random_token(oauth: bool) -> String {
122
-
let full_code = match oauth {
123
-
true => Alphabetic.sample_string(&mut rand::rng(), 10),
124
-
false => Alphanumeric.sample_string(&mut rand::rng(), 10),
125
-
};
129
+
pub fn get_random_token() -> String {
130
+
let mut rng = rand::rng();
131
+
132
+
let mut full_code = String::with_capacity(10);
133
+
for _ in 0..10 {
134
+
let idx = rng.random_range(0..UPPERCASE_BASE32_CHARS.len());
135
+
full_code.push(UPPERCASE_BASE32_CHARS[idx] as char);
136
+
}
137
+
126
138
//The PDS implementation creates in lowercase, then converts to uppercase.
127
139
//Just going a head and doing uppercase here.
128
140
let slice_one = &full_code[0..5].to_ascii_uppercase();
···
162
174
}
163
175
}
164
176
177
+
/// Creates a hex string from the password and salt to find app passwords
165
178
fn scrypt_hex(password: &str, salt: &str) -> anyhow::Result<String> {
166
179
let params = scrypt::Params::new(14, 8, 1, 64)?;
167
180
let mut derived = [0u8; 64];
···
169
182
Ok(hex::encode(derived))
170
183
}
171
184
185
+
/// Hashes the app password. did is used as the salt.
172
186
pub fn hash_app_password(did: &str, password: &str) -> anyhow::Result<String> {
173
-
use sha2::{Digest, Sha256};
174
187
let mut hasher = Sha256::new();
175
188
hasher.update(did.as_bytes());
176
189
let sha = hasher.finalize();
···
253
266
let verified = verify_password(password, &password_scrypt).await?;
254
267
if !verified {
255
268
if oauth {
269
+
//OAuth does not allow app password logins so just go ahead and send it along it's way
256
270
return Ok(AuthResult::WrongIdentityOrPassword);
257
271
}
258
272
//Theres a chance it could be an app password so check that as well
···
288
302
if two_factor_required {
289
303
//Two factor is required and a taken was provided
290
304
if let Some(two_factor_code) = two_factor_code {
291
-
//It seems it sends over a empty on login without it set? As in no input is shown on the ui for the first login try
305
+
//if the two_factor_code is set need to see if we have a valid token
292
306
if !two_factor_code.is_empty() {
293
307
return match assert_valid_token(
294
308
&state.account_pool,
···
313
327
}
314
328
}
315
329
316
-
return match create_two_factor_token(&state.account_pool, did, oauth).await {
330
+
return match create_two_factor_token(&state.account_pool, did).await {
317
331
Ok(code) => {
318
332
let mut email_data = Map::new();
319
333
email_data.insert("token".to_string(), Value::from(code.clone()));
···
363
377
pub async fn create_two_factor_token(
364
378
account_db: &SqlitePool,
365
379
did: String,
366
-
oauth: bool,
367
380
) -> anyhow::Result<String> {
368
381
let purpose = "2fa_code";
369
382
370
-
let token = get_random_token(oauth);
383
+
let token = get_random_token();
371
384
let right_now = Utc::now();
372
385
373
386
let res = sqlx::query(
+8
-16
src/oauth_provider.rs
+8
-16
src/oauth_provider.rs
···
1
1
use crate::AppState;
2
-
use crate::helpers::{
3
-
AuthResult, TokenCheckError, json_error_response, oauth_json_error_response, preauth_check,
4
-
};
2
+
use crate::helpers::{AuthResult, TokenCheckError, oauth_json_error_response, preauth_check};
5
3
use axum::body::Body;
6
4
use axum::extract::State;
7
5
use axum::http::header::CONTENT_TYPE;
···
90
88
91
89
Ok(proxied)
92
90
}
93
-
AuthResult::TokenCheckFailed(err) => match err {
94
-
TokenCheckError::InvalidToken => oauth_json_error_response(
95
-
StatusCode::BAD_REQUEST,
96
-
"InvalidToken",
97
-
"Token is invalid",
98
-
),
99
-
TokenCheckError::ExpiredToken => oauth_json_error_response(
100
-
StatusCode::BAD_REQUEST,
101
-
"ExpiredToken",
102
-
"Token is expired",
103
-
),
104
-
},
91
+
AuthResult::TokenCheckFailed(err) => oauth_json_error_response(
92
+
StatusCode::BAD_REQUEST,
93
+
"invalid_request",
94
+
"Unable to sign-in due to an unexpected server error",
95
+
),
105
96
},
106
97
Err(err) => {
107
98
log::error!(
108
99
"Error during pre-auth check. This happens on the create_session endpoint when trying to decide if the user has access\n {err}"
109
100
);
110
-
json_error_response(
101
+
//TODO throw a hard error and test this
102
+
oauth_json_error_response(
111
103
StatusCode::INTERNAL_SERVER_ERROR,
112
104
"InternalServerError",
113
105
"This error was not generated by the PDS, but PDS Gatekeeper. Please contact your PDS administrator for help and for them to review the server logs.",