Our Personal Data Server from scratch!
0

Configure Feed

Select the types of activity you want to include in your feed.

1use super::*; 2 3#[derive(Debug, Deserialize)] 4pub struct Authorize2faQuery { 5 pub request_uri: String, 6 pub channel: Option<String>, 7} 8 9#[derive(Debug, Deserialize)] 10pub struct Authorize2faSubmit { 11 pub request_uri: String, 12 pub code: String, 13 #[serde(default)] 14 pub trust_device: bool, 15} 16 17const MAX_2FA_ATTEMPTS: i32 = 5; 18 19pub async fn authorize_2fa_get( 20 State(state): State<AppState>, 21 Query(query): Query<Authorize2faQuery>, 22) -> Response { 23 let twofa_request_id = RequestId::from(query.request_uri.clone()); 24 let challenge = match state.repos.oauth.get_2fa_challenge(&twofa_request_id).await { 25 Ok(Some(c)) => c, 26 Ok(None) => { 27 return redirect_to_frontend_error( 28 "invalid_request", 29 "No 2FA challenge found. Please start over.", 30 ); 31 } 32 Err(_) => { 33 return redirect_to_frontend_error( 34 "server_error", 35 "An error occurred. Please try again.", 36 ); 37 } 38 }; 39 if challenge.expires_at < Utc::now() { 40 let _ = state.repos.oauth.delete_2fa_challenge(challenge.id).await; 41 return redirect_to_frontend_error( 42 "invalid_request", 43 "2FA code has expired. Please start over.", 44 ); 45 } 46 let _request_data = match state 47 .repos 48 .oauth 49 .get_authorization_request(&twofa_request_id) 50 .await 51 { 52 Ok(Some(d)) => d, 53 Ok(None) => { 54 return redirect_to_frontend_error( 55 "invalid_request", 56 "Authorization request not found. Please start over.", 57 ); 58 } 59 Err(_) => { 60 return redirect_to_frontend_error( 61 "server_error", 62 "An error occurred. Please try again.", 63 ); 64 } 65 }; 66 let channel = query.channel.as_deref().unwrap_or("email"); 67 redirect_see_other(&format!( 68 "/app/oauth/2fa?request_uri={}&channel={}", 69 url_encode(&query.request_uri), 70 url_encode(channel) 71 )) 72} 73 74pub async fn authorize_2fa_post( 75 State(state): State<AppState>, 76 _rate_limit: OAuthRateLimited<OAuthAuthorizeLimit>, 77 headers: HeaderMap, 78 client_ip: ClientIp, 79 Json(form): Json<Authorize2faSubmit>, 80) -> Response { 81 let json_error = |status: StatusCode, error: &str, description: &str| -> Response { 82 ( 83 status, 84 Json(serde_json::json!({ 85 "error": error, 86 "error_description": description 87 })), 88 ) 89 .into_response() 90 }; 91 let twofa_post_request_id = RequestId::from(form.request_uri.clone()); 92 let request_data = match state 93 .repos 94 .oauth 95 .get_authorization_request(&twofa_post_request_id) 96 .await 97 { 98 Ok(Some(d)) => d, 99 Ok(None) => { 100 return json_error( 101 StatusCode::BAD_REQUEST, 102 "invalid_request", 103 "Authorization request not found.", 104 ); 105 } 106 Err(_) => { 107 return json_error( 108 StatusCode::INTERNAL_SERVER_ERROR, 109 "server_error", 110 "An error occurred.", 111 ); 112 } 113 }; 114 if request_data.expires_at < Utc::now() { 115 let _ = state 116 .repos 117 .oauth 118 .delete_authorization_request(&twofa_post_request_id) 119 .await; 120 return json_error( 121 StatusCode::BAD_REQUEST, 122 "invalid_request", 123 "Authorization request has expired.", 124 ); 125 } 126 let challenge = state 127 .repos 128 .oauth 129 .get_2fa_challenge(&twofa_post_request_id) 130 .await 131 .ok() 132 .flatten(); 133 if let Some(challenge) = challenge { 134 if challenge.expires_at < Utc::now() { 135 let _ = state.repos.oauth.delete_2fa_challenge(challenge.id).await; 136 return json_error( 137 StatusCode::BAD_REQUEST, 138 "invalid_request", 139 "2FA code has expired. Please start over.", 140 ); 141 } 142 if challenge.attempts >= MAX_2FA_ATTEMPTS { 143 let _ = state.repos.oauth.delete_2fa_challenge(challenge.id).await; 144 return json_error( 145 StatusCode::FORBIDDEN, 146 "access_denied", 147 "Too many failed attempts. Please start over.", 148 ); 149 } 150 let code_valid: bool = form 151 .code 152 .trim() 153 .as_bytes() 154 .ct_eq(challenge.code.as_bytes()) 155 .into(); 156 if !code_valid { 157 let _ = state.repos.oauth.increment_2fa_attempts(challenge.id).await; 158 return json_error( 159 StatusCode::FORBIDDEN, 160 "invalid_code", 161 "Invalid verification code. Please try again.", 162 ); 163 } 164 let _ = state.repos.oauth.delete_2fa_challenge(challenge.id).await; 165 let code = AuthorizationCode::generate(); 166 let device_id = extract_device_cookie(&headers); 167 let twofa_totp_device_id = device_id.clone(); 168 if state 169 .repos 170 .oauth 171 .update_authorization_request( 172 &twofa_post_request_id, 173 &challenge.did, 174 twofa_totp_device_id.as_ref(), 175 &code, 176 ) 177 .await 178 .is_err() 179 { 180 return json_error( 181 StatusCode::INTERNAL_SERVER_ERROR, 182 "server_error", 183 "An error occurred. Please try again.", 184 ); 185 } 186 let redirect_url = build_intermediate_redirect_url( 187 &request_data.parameters.redirect_uri, 188 &code.0, 189 request_data.parameters.state.as_deref(), 190 request_data.parameters.response_mode.map(|m| m.as_str()), 191 ); 192 return Json(serde_json::json!({ 193 "redirect_uri": redirect_url 194 })) 195 .into_response(); 196 } 197 let did_str = match &request_data.did { 198 Some(d) => d.clone(), 199 None => { 200 return json_error( 201 StatusCode::BAD_REQUEST, 202 "invalid_request", 203 "No 2FA challenge found. Please start over.", 204 ); 205 } 206 }; 207 let did: tranquil_types::Did = match did_str.parse() { 208 Ok(d) => d, 209 Err(_) => { 210 return json_error( 211 StatusCode::BAD_REQUEST, 212 "invalid_request", 213 "Invalid DID format.", 214 ); 215 } 216 }; 217 if !tranquil_api::server::has_totp_enabled(&state, &did).await { 218 return json_error( 219 StatusCode::BAD_REQUEST, 220 "invalid_request", 221 "No 2FA challenge found. Please start over.", 222 ); 223 } 224 let _rate_proof = match check_user_rate_limit::<TotpVerifyLimit>(&state, &did).await { 225 Ok(proof) => proof, 226 Err(_) => { 227 return json_error( 228 StatusCode::TOO_MANY_REQUESTS, 229 "RateLimitExceeded", 230 "Too many verification attempts. Please try again in a few minutes.", 231 ); 232 } 233 }; 234 let totp_valid = 235 tranquil_api::server::verify_totp_or_backup_for_user(&state, &did, &form.code).await; 236 if !totp_valid { 237 return json_error( 238 StatusCode::FORBIDDEN, 239 "invalid_code", 240 "Invalid verification code. Please try again.", 241 ); 242 } 243 let mut device_id = extract_device_cookie(&headers); 244 let mut new_cookie: Option<String> = None; 245 if form.trust_device { 246 let trust_device_id = match &device_id { 247 Some(existing_id) => existing_id.clone(), 248 None => { 249 let new_device_id = DeviceId::generate(); 250 let device_data = DeviceData { 251 session_id: SessionId::generate(), 252 user_agent: extract_user_agent(&headers), 253 ip_address: client_ip.into_string(), 254 last_seen_at: Utc::now(), 255 }; 256 if state 257 .repos 258 .oauth 259 .create_device(&new_device_id, &device_data) 260 .await 261 .is_ok() 262 { 263 new_cookie = Some(make_device_cookie(&new_device_id)); 264 device_id = Some(new_device_id.clone()); 265 } 266 new_device_id 267 } 268 }; 269 let _ = state 270 .repos 271 .oauth 272 .upsert_account_device(&did, &trust_device_id) 273 .await; 274 let _ = 275 tranquil_api::server::trust_device(state.repos.oauth.as_ref(), &trust_device_id, &did) 276 .await; 277 } 278 let requested_scope_str = request_data 279 .parameters 280 .scope 281 .as_deref() 282 .unwrap_or("atproto"); 283 let requested_scopes: Vec<String> = requested_scope_str 284 .split_whitespace() 285 .map(|s| s.to_string()) 286 .collect(); 287 let needs_consent = should_show_consent( 288 state.repos.oauth.as_ref(), 289 &did, 290 &request_data.parameters.client_id, 291 &requested_scopes, 292 ) 293 .await 294 .unwrap_or(true); 295 if needs_consent { 296 let consent_url = format!( 297 "/app/oauth/consent?request_uri={}", 298 url_encode(&form.request_uri) 299 ); 300 if let Some(cookie) = new_cookie { 301 return ( 302 StatusCode::OK, 303 [(SET_COOKIE, cookie)], 304 Json(serde_json::json!({"redirect_uri": consent_url})), 305 ) 306 .into_response(); 307 } 308 return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); 309 } 310 let code = AuthorizationCode::generate(); 311 let twofa_final_device_id = device_id.clone(); 312 if state 313 .repos 314 .oauth 315 .update_authorization_request( 316 &twofa_post_request_id, 317 &did, 318 twofa_final_device_id.as_ref(), 319 &code, 320 ) 321 .await 322 .is_err() 323 { 324 return json_error( 325 StatusCode::INTERNAL_SERVER_ERROR, 326 "server_error", 327 "An error occurred. Please try again.", 328 ); 329 } 330 let redirect_url = build_intermediate_redirect_url( 331 &request_data.parameters.redirect_uri, 332 &code.0, 333 request_data.parameters.state.as_deref(), 334 request_data.parameters.response_mode.map(|m| m.as_str()), 335 ); 336 if let Some(cookie) = new_cookie { 337 ( 338 StatusCode::OK, 339 [(SET_COOKIE, cookie)], 340 Json(serde_json::json!({"redirect_uri": redirect_url})), 341 ) 342 .into_response() 343 } else { 344 Json(serde_json::json!({"redirect_uri": redirect_url})).into_response() 345 } 346}