this repo has no description
1use crate::api::ApiError; 2use crate::state::AppState; 3use axum::{ 4 Json, 5 extract::{Query, State}, 6 http::StatusCode, 7 response::{IntoResponse, Response}, 8}; 9use serde::{Deserialize, Serialize}; 10use serde_json::json; 11use tracing::error; 12 13const HOUR_SECS: i64 = 3600; 14const MINUTE_SECS: i64 = 60; 15 16const PROTECTED_METHODS: &[&str] = &[ 17 "com.atproto.admin.sendEmail", 18 "com.atproto.identity.requestPlcOperationSignature", 19 "com.atproto.identity.signPlcOperation", 20 "com.atproto.identity.updateHandle", 21 "com.atproto.server.activateAccount", 22 "com.atproto.server.confirmEmail", 23 "com.atproto.server.createAppPassword", 24 "com.atproto.server.deactivateAccount", 25 "com.atproto.server.getAccountInviteCodes", 26 "com.atproto.server.getSession", 27 "com.atproto.server.listAppPasswords", 28 "com.atproto.server.requestAccountDelete", 29 "com.atproto.server.requestEmailConfirmation", 30 "com.atproto.server.requestEmailUpdate", 31 "com.atproto.server.revokeAppPassword", 32 "com.atproto.server.updateEmail", 33]; 34 35#[derive(Deserialize)] 36pub struct GetServiceAuthParams { 37 pub aud: String, 38 pub lxm: Option<String>, 39 pub exp: Option<i64>, 40} 41 42#[derive(Serialize)] 43pub struct GetServiceAuthOutput { 44 pub token: String, 45} 46 47pub async fn get_service_auth( 48 State(state): State<AppState>, 49 headers: axum::http::HeaderMap, 50 Query(params): Query<GetServiceAuthParams>, 51) -> Response { 52 let token = match crate::auth::extract_bearer_token_from_header( 53 headers.get("Authorization").and_then(|h| h.to_str().ok()), 54 ) { 55 Some(t) => t, 56 None => return ApiError::AuthenticationRequired.into_response(), 57 }; 58 let auth_user = match crate::auth::validate_bearer_token_for_service_auth(&state.db, &token).await { 59 Ok(user) => user, 60 Err(e) => return ApiError::from(e).into_response(), 61 }; 62 let key_bytes = match auth_user.key_bytes { 63 Some(kb) => kb, 64 None => { 65 return ApiError::AuthenticationFailedMsg( 66 "OAuth tokens cannot create service auth".into(), 67 ) 68 .into_response(); 69 } 70 }; 71 72 let lxm = params.lxm.as_deref(); 73 let lxm_for_token = lxm.unwrap_or("*"); 74 75 let user_status = sqlx::query!( 76 "SELECT takedown_ref FROM users WHERE did = $1", 77 auth_user.did 78 ) 79 .fetch_optional(&state.db) 80 .await; 81 82 let is_takendown = match user_status { 83 Ok(Some(row)) => row.takedown_ref.is_some(), 84 _ => false, 85 }; 86 87 if is_takendown && lxm != Some("com.atproto.server.createAccount") { 88 return ( 89 StatusCode::BAD_REQUEST, 90 Json(json!({ 91 "error": "InvalidToken", 92 "message": "Bad token scope" 93 })), 94 ) 95 .into_response(); 96 } 97 98 if let Some(method) = lxm { 99 if PROTECTED_METHODS.contains(&method) { 100 return ( 101 StatusCode::BAD_REQUEST, 102 Json(json!({ 103 "error": "InvalidRequest", 104 "message": format!("cannot request a service auth token for the following protected method: {}", method) 105 })), 106 ) 107 .into_response(); 108 } 109 } 110 111 if let Some(exp) = params.exp { 112 let now = chrono::Utc::now().timestamp(); 113 let diff = exp - now; 114 115 if diff < 0 { 116 return ( 117 StatusCode::BAD_REQUEST, 118 Json(json!({ 119 "error": "BadExpiration", 120 "message": "expiration is in past" 121 })), 122 ) 123 .into_response(); 124 } 125 126 if diff > HOUR_SECS { 127 return ( 128 StatusCode::BAD_REQUEST, 129 Json(json!({ 130 "error": "BadExpiration", 131 "message": "cannot request a token with an expiration more than an hour in the future" 132 })), 133 ) 134 .into_response(); 135 } 136 137 if lxm.is_none() && diff > MINUTE_SECS { 138 return ( 139 StatusCode::BAD_REQUEST, 140 Json(json!({ 141 "error": "BadExpiration", 142 "message": "cannot request a method-less token with an expiration more than a minute in the future" 143 })), 144 ) 145 .into_response(); 146 } 147 } 148 149 let service_token = 150 match crate::auth::create_service_token(&auth_user.did, &params.aud, lxm_for_token, &key_bytes) { 151 Ok(t) => t, 152 Err(e) => { 153 error!("Failed to create service token: {:?}", e); 154 return ( 155 StatusCode::INTERNAL_SERVER_ERROR, 156 Json(json!({"error": "InternalError"})), 157 ) 158 .into_response(); 159 } 160 }; 161 ( 162 StatusCode::OK, 163 Json(GetServiceAuthOutput { 164 token: service_token, 165 }), 166 ) 167 .into_response() 168}