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 = 59 match crate::auth::validate_bearer_token_for_service_auth(&state.db, &token).await { 60 Ok(user) => user, 61 Err(e) => return ApiError::from(e).into_response(), 62 }; 63 let key_bytes = match &auth_user.key_bytes { 64 Some(kb) => kb.clone(), 65 None => { 66 return ApiError::AuthenticationFailedMsg( 67 "OAuth tokens cannot create service auth".into(), 68 ) 69 .into_response(); 70 } 71 }; 72 73 let lxm = params.lxm.as_deref(); 74 let lxm_for_token = lxm.unwrap_or("*"); 75 76 if let Some(method) = lxm { 77 if let Err(e) = crate::auth::scope_check::check_rpc_scope( 78 auth_user.is_oauth, 79 auth_user.scope.as_deref(), 80 &params.aud, 81 method, 82 ) { 83 return e; 84 } 85 } else if auth_user.is_oauth { 86 let permissions = auth_user.permissions(); 87 if !permissions.has_full_access() { 88 return ( 89 StatusCode::BAD_REQUEST, 90 Json(json!({ 91 "error": "InvalidRequest", 92 "message": "OAuth tokens with granular scopes must specify an lxm parameter" 93 })), 94 ) 95 .into_response(); 96 } 97 } 98 99 let user_status = sqlx::query!( 100 "SELECT takedown_ref FROM users WHERE did = $1", 101 auth_user.did 102 ) 103 .fetch_optional(&state.db) 104 .await; 105 106 let is_takendown = match user_status { 107 Ok(Some(row)) => row.takedown_ref.is_some(), 108 _ => false, 109 }; 110 111 if is_takendown && lxm != Some("com.atproto.server.createAccount") { 112 return ( 113 StatusCode::BAD_REQUEST, 114 Json(json!({ 115 "error": "InvalidToken", 116 "message": "Bad token scope" 117 })), 118 ) 119 .into_response(); 120 } 121 122 if let Some(method) = lxm 123 && PROTECTED_METHODS.contains(&method) 124 { 125 return ( 126 StatusCode::BAD_REQUEST, 127 Json(json!({ 128 "error": "InvalidRequest", 129 "message": format!("cannot request a service auth token for the following protected method: {}", method) 130 })), 131 ) 132 .into_response(); 133 } 134 135 if let Some(exp) = params.exp { 136 let now = chrono::Utc::now().timestamp(); 137 let diff = exp - now; 138 139 if diff < 0 { 140 return ( 141 StatusCode::BAD_REQUEST, 142 Json(json!({ 143 "error": "BadExpiration", 144 "message": "expiration is in past" 145 })), 146 ) 147 .into_response(); 148 } 149 150 if diff > HOUR_SECS { 151 return ( 152 StatusCode::BAD_REQUEST, 153 Json(json!({ 154 "error": "BadExpiration", 155 "message": "cannot request a token with an expiration more than an hour in the future" 156 })), 157 ) 158 .into_response(); 159 } 160 161 if lxm.is_none() && diff > MINUTE_SECS { 162 return ( 163 StatusCode::BAD_REQUEST, 164 Json(json!({ 165 "error": "BadExpiration", 166 "message": "cannot request a method-less token with an expiration more than a minute in the future" 167 })), 168 ) 169 .into_response(); 170 } 171 } 172 173 let service_token = match crate::auth::create_service_token( 174 &auth_user.did, 175 &params.aud, 176 lxm_for_token, 177 &key_bytes, 178 ) { 179 Ok(t) => t, 180 Err(e) => { 181 error!("Failed to create service token: {:?}", e); 182 return ( 183 StatusCode::INTERNAL_SERVER_ERROR, 184 Json(json!({"error": "InternalError"})), 185 ) 186 .into_response(); 187 } 188 }; 189 ( 190 StatusCode::OK, 191 Json(GetServiceAuthOutput { 192 token: service_token, 193 }), 194 ) 195 .into_response() 196}