Microservice to bring 2FA to self hosted PDSes

little clean up

Changed files
+19 -75
src
+4 -6
src/xrpc/com_atproto_server.rs
··· 120 Ok(proxied) 121 } 122 AuthResult::TokenCheckFailed(err) => match err { 123 - TokenCheckError::InvalidToken => json_error_response( 124 - StatusCode::BAD_REQUEST, 125 - "InvalidToken", 126 - "Hey this token is invalid and this is a custom message to show it's bot a normal PDS", 127 - ), 128 TokenCheckError::ExpiredToken => { 129 json_error_response(StatusCode::BAD_REQUEST, "ExpiredToken", "Token is expired") 130 } ··· 141 ) -> Result<Response<Body>, StatusCode> { 142 //If email auth is not set at all it is a update email address 143 let email_auth_not_set = payload.email_auth_factor.is_none(); 144 - //If email aurth is set it is to either turn on or off 2fa 145 let email_auth_update = payload.email_auth_factor.unwrap_or(false); 146 147 // Email update asked for
··· 120 Ok(proxied) 121 } 122 AuthResult::TokenCheckFailed(err) => match err { 123 + TokenCheckError::InvalidToken => { 124 + json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "Token is invalid") 125 + } 126 TokenCheckError::ExpiredToken => { 127 json_error_response(StatusCode::BAD_REQUEST, "ExpiredToken", "Token is expired") 128 } ··· 139 ) -> Result<Response<Body>, StatusCode> { 140 //If email auth is not set at all it is a update email address 141 let email_auth_not_set = payload.email_auth_factor.is_none(); 142 + //If email auth is set it is to either turn on or off 2fa 143 let email_auth_update = payload.email_auth_factor.unwrap_or(false); 144 145 // Email update asked for
+15 -69
src/xrpc/helpers.rs
··· 77 } 78 } 79 80 - /// Proxy the incoming request as a POST to the PDS base URL plus the provided path and attempt to parse 81 - /// the successful response body as JSON into `T`. 82 - /// 83 - /// Behavior mirrors `proxy_get_json`: 84 - /// - If the proxied response is non-200, returns Passthrough with the original response. 85 - /// - If the response is 200 but JSON parsing fails, returns Passthrough with the original body and headers. 86 - /// - If parsing succeeds, returns Parsed { value, headers }. 87 - pub async fn _proxy_post_json<T>( 88 - state: &AppState, 89 - mut req: Request, 90 - path: &str, 91 - ) -> Result<ProxiedResult<T>, StatusCode> 92 - where 93 - T: DeserializeOwned, 94 - { 95 - let uri = format!("{}{}", state.pds_base_url, path); 96 - *req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?; 97 - *req.method_mut() = Method::POST; 98 - 99 - let result = state 100 - .reverse_proxy_client 101 - .request(req) 102 - .await 103 - .map_err(|_| StatusCode::BAD_REQUEST)? 104 - .into_response(); 105 - 106 - if result.status() != StatusCode::OK { 107 - return Ok(ProxiedResult::Passthrough(result)); 108 - } 109 - 110 - let response_headers = result.headers().clone(); 111 - let body = result.into_body(); 112 - let body_bytes = to_bytes(body, usize::MAX) 113 - .await 114 - .map_err(|_| StatusCode::BAD_REQUEST)?; 115 - 116 - match serde_json::from_slice::<T>(&body_bytes) { 117 - Ok(value) => Ok(ProxiedResult::Parsed { 118 - value, 119 - _headers: response_headers, 120 - }), 121 - Err(err) => { 122 - error!(%err, "failed to parse proxied JSON response (POST); returning original body"); 123 - let mut builder = Response::builder().status(StatusCode::OK); 124 - if let Some(headers) = builder.headers_mut() { 125 - *headers = response_headers; 126 - } 127 - let resp = builder 128 - .body(Body::from(body_bytes)) 129 - .map_err(|_| StatusCode::BAD_REQUEST)?; 130 - Ok(ProxiedResult::Passthrough(resp)) 131 - } 132 - } 133 - } 134 - 135 /// Build a JSON error response with the required Content-Type header 136 /// Content-Type: application/json;charset=utf-8 137 /// Body shape: { "error": string, "message": string } ··· 294 } 295 //Two factor is required and a taken was provided 296 if let Some(two_factor_code) = two_factor_code { 297 - //Seems it sends over a empty on login without it set? As in no input is shown on the ui for first login try 298 if two_factor_code != "" { 299 return match assert_valid_token( 300 &state.account_pool, ··· 313 } 314 315 return match create_two_factor_token(&state.account_pool, did).await { 316 Ok(code) => { 317 let mut email_data = Map::new(); 318 email_data.insert("token".to_string(), Value::from(code.clone())); ··· 372 loop { 373 let token = get_random_token(); 374 let right_now = Utc::now(); 375 - let query = "INSERT INTO email_token (purpose, did, token, requestedAt) 376 VALUES (?, ?, ?, ?) 377 ON CONFLICT(purpose, did) DO UPDATE SET 378 token=excluded.token, 379 - requestedAt=excluded.requestedAt"; 380 - 381 - let res = sqlx::query(query) 382 - .bind(purpose) 383 - .bind(&did) 384 - .bind(&token) 385 - .bind(right_now) 386 - .execute(account_db) 387 - .await; 388 389 return match res { 390 Ok(_) => Ok(token), ··· 422 .await 423 .map_err(|err| { 424 log::error!("Error getting the 2fa token: {}", err); 425 - TokenCheckError::InvalidToken 426 })?; 427 428 match row { 429 - None => Err(TokenCheckError::InvalidToken), 430 Some(row) => { 431 // Token lives for 15 minutes 432 let expiration_ms = 15 * 60_000; 433 434 - // Parse requestedAt; assume RFC3339-like string (as created_by PDS or by our code) 435 let requested_at_utc = match chrono::DateTime::parse_from_rfc3339(&row.0) { 436 Ok(dt) => dt.with_timezone(&Utc), 437 Err(_) => {
··· 77 } 78 } 79 80 /// Build a JSON error response with the required Content-Type header 81 /// Content-Type: application/json;charset=utf-8 82 /// Body shape: { "error": string, "message": string } ··· 239 } 240 //Two factor is required and a taken was provided 241 if let Some(two_factor_code) = two_factor_code { 242 + //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 243 if two_factor_code != "" { 244 return match assert_valid_token( 245 &state.account_pool, ··· 258 } 259 260 return match create_two_factor_token(&state.account_pool, did).await { 261 + //TODO replace unwraps with the mythical ? 262 Ok(code) => { 263 let mut email_data = Map::new(); 264 email_data.insert("token".to_string(), Value::from(code.clone())); ··· 318 loop { 319 let token = get_random_token(); 320 let right_now = Utc::now(); 321 + 322 + let res = sqlx::query( 323 + "INSERT INTO email_token (purpose, did, token, requestedAt) 324 VALUES (?, ?, ?, ?) 325 ON CONFLICT(purpose, did) DO UPDATE SET 326 token=excluded.token, 327 + requestedAt=excluded.requestedAt", 328 + ) 329 + .bind(purpose) 330 + .bind(&did) 331 + .bind(&token) 332 + .bind(right_now) 333 + .execute(account_db) 334 + .await; 335 336 return match res { 337 Ok(_) => Ok(token), ··· 369 .await 370 .map_err(|err| { 371 log::error!("Error getting the 2fa token: {}", err); 372 + InvalidToken 373 })?; 374 375 match row { 376 + None => Err(InvalidToken), 377 Some(row) => { 378 // Token lives for 15 minutes 379 let expiration_ms = 15 * 60_000; 380 381 let requested_at_utc = match chrono::DateTime::parse_from_rfc3339(&row.0) { 382 Ok(dt) => dt.with_timezone(&Utc), 383 Err(_) => {