this repo has no description
1use crate::delegation;
2use crate::oauth::db;
3use crate::state::{AppState, RateLimitKind};
4use crate::types::PlainPassword;
5use crate::util::extract_client_ip;
6use axum::{
7 Json,
8 extract::State,
9 http::{HeaderMap, StatusCode},
10 response::{IntoResponse, Response},
11};
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Deserialize)]
15pub struct DelegationAuthSubmit {
16 pub request_uri: String,
17 pub delegated_did: Option<String>,
18 pub controller_did: String,
19 pub password: PlainPassword,
20 #[serde(default)]
21 pub remember_device: bool,
22}
23
24#[derive(Debug, Serialize)]
25pub struct DelegationAuthResponse {
26 pub success: bool,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub needs_totp: Option<bool>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub redirect_uri: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub error: Option<String>,
33}
34
35pub async fn delegation_auth(
36 State(state): State<AppState>,
37 headers: HeaderMap,
38 Json(form): Json<DelegationAuthSubmit>,
39) -> Response {
40 let client_ip = extract_client_ip(&headers);
41 if !state
42 .check_rate_limit(RateLimitKind::Login, &client_ip)
43 .await
44 {
45 return (
46 StatusCode::TOO_MANY_REQUESTS,
47 Json(DelegationAuthResponse {
48 success: false,
49 needs_totp: None,
50 redirect_uri: None,
51 error: Some("Too many login attempts. Please try again later.".to_string()),
52 }),
53 )
54 .into_response();
55 }
56
57 let request = match db::get_authorization_request(&state.db, &form.request_uri).await {
58 Ok(Some(r)) => r,
59 Ok(None) => {
60 return Json(DelegationAuthResponse {
61 success: false,
62 needs_totp: None,
63 redirect_uri: None,
64 error: Some("Authorization request not found".to_string()),
65 })
66 .into_response();
67 }
68 Err(_) => {
69 return Json(DelegationAuthResponse {
70 success: false,
71 needs_totp: None,
72 redirect_uri: None,
73 error: Some("Server error".to_string()),
74 })
75 .into_response();
76 }
77 };
78
79 let delegated_did = match form.delegated_did.as_ref().or(request.did.as_ref()) {
80 Some(did) => did.clone(),
81 None => {
82 return Json(DelegationAuthResponse {
83 success: false,
84 needs_totp: None,
85 redirect_uri: None,
86 error: Some("No delegated account selected".to_string()),
87 })
88 .into_response();
89 }
90 };
91
92 if db::set_request_did(&state.db, &form.request_uri, &delegated_did)
93 .await
94 .is_err()
95 {
96 tracing::warn!("Failed to set delegated DID on authorization request");
97 }
98
99 let grant =
100 match delegation::get_delegation(&state.db, &delegated_did, &form.controller_did).await {
101 Ok(Some(g)) => g,
102 Ok(None) => {
103 return Json(DelegationAuthResponse {
104 success: false,
105 needs_totp: None,
106 redirect_uri: None,
107 error: Some("No delegation grant found for this controller".to_string()),
108 })
109 .into_response();
110 }
111 Err(_) => {
112 return Json(DelegationAuthResponse {
113 success: false,
114 needs_totp: None,
115 redirect_uri: None,
116 error: Some("Server error".to_string()),
117 })
118 .into_response();
119 }
120 };
121
122 let controller = match sqlx::query!(
123 r#"
124 SELECT id, did, password_hash, deactivated_at, takedown_ref,
125 email_verified, discord_verified, telegram_verified, signal_verified
126 FROM users
127 WHERE did = $1
128 "#,
129 form.controller_did
130 )
131 .fetch_optional(&state.db)
132 .await
133 {
134 Ok(Some(u)) => u,
135 Ok(None) => {
136 return Json(DelegationAuthResponse {
137 success: false,
138 needs_totp: None,
139 redirect_uri: None,
140 error: Some("Controller account not found".to_string()),
141 })
142 .into_response();
143 }
144 Err(_) => {
145 return Json(DelegationAuthResponse {
146 success: false,
147 needs_totp: None,
148 redirect_uri: None,
149 error: Some("Server error".to_string()),
150 })
151 .into_response();
152 }
153 };
154
155 if controller.deactivated_at.is_some() {
156 return Json(DelegationAuthResponse {
157 success: false,
158 needs_totp: None,
159 redirect_uri: None,
160 error: Some("Controller account is deactivated".to_string()),
161 })
162 .into_response();
163 }
164
165 if controller.takedown_ref.is_some() {
166 return Json(DelegationAuthResponse {
167 success: false,
168 needs_totp: None,
169 redirect_uri: None,
170 error: Some("Controller account has been taken down".to_string()),
171 })
172 .into_response();
173 }
174
175 let password_valid = controller
176 .password_hash
177 .as_ref()
178 .map(|hash| bcrypt::verify(&form.password, hash).unwrap_or_default())
179 .unwrap_or_default();
180
181 if !password_valid {
182 return Json(DelegationAuthResponse {
183 success: false,
184 needs_totp: None,
185 redirect_uri: None,
186 error: Some("Invalid password".to_string()),
187 })
188 .into_response();
189 }
190
191 if db::set_controller_did(&state.db, &form.request_uri, &form.controller_did)
192 .await
193 .is_err()
194 {
195 return Json(DelegationAuthResponse {
196 success: false,
197 needs_totp: None,
198 redirect_uri: None,
199 error: Some("Failed to update authorization request".to_string()),
200 })
201 .into_response();
202 }
203
204 let has_totp = crate::api::server::has_totp_enabled(&state, &form.controller_did).await;
205 if has_totp {
206 return Json(DelegationAuthResponse {
207 success: true,
208 needs_totp: Some(true),
209 redirect_uri: Some(format!(
210 "/app/oauth/delegation-totp?request_uri={}",
211 urlencoding::encode(&form.request_uri)
212 )),
213 error: None,
214 })
215 .into_response();
216 }
217
218 let ip = extract_client_ip(&headers);
219 let user_agent = headers
220 .get("user-agent")
221 .and_then(|v| v.to_str().ok())
222 .map(|s| s.to_string());
223
224 let _ = delegation::log_delegation_action(
225 &state.db,
226 &delegated_did,
227 &form.controller_did,
228 Some(&form.controller_did),
229 delegation::DelegationActionType::TokenIssued,
230 Some(serde_json::json!({
231 "client_id": request.client_id,
232 "granted_scopes": grant.granted_scopes
233 })),
234 Some(&ip),
235 user_agent.as_deref(),
236 )
237 .await;
238
239 Json(DelegationAuthResponse {
240 success: true,
241 needs_totp: None,
242 redirect_uri: Some(format!(
243 "/app/oauth/consent?request_uri={}",
244 urlencoding::encode(&form.request_uri)
245 )),
246 error: None,
247 })
248 .into_response()
249}
250
251#[derive(Debug, Deserialize)]
252pub struct DelegationTotpSubmit {
253 pub request_uri: String,
254 pub code: String,
255}
256
257pub async fn delegation_totp_verify(
258 State(state): State<AppState>,
259 headers: HeaderMap,
260 Json(form): Json<DelegationTotpSubmit>,
261) -> Response {
262 let client_ip = extract_client_ip(&headers);
263 if !state
264 .check_rate_limit(RateLimitKind::TotpVerify, &client_ip)
265 .await
266 {
267 return (
268 StatusCode::TOO_MANY_REQUESTS,
269 Json(DelegationAuthResponse {
270 success: false,
271 needs_totp: None,
272 redirect_uri: None,
273 error: Some("Too many verification attempts. Please try again later.".to_string()),
274 }),
275 )
276 .into_response();
277 }
278
279 let request = match db::get_authorization_request(&state.db, &form.request_uri).await {
280 Ok(Some(r)) => r,
281 Ok(None) => {
282 return Json(DelegationAuthResponse {
283 success: false,
284 needs_totp: None,
285 redirect_uri: None,
286 error: Some("Authorization request not found".to_string()),
287 })
288 .into_response();
289 }
290 Err(_) => {
291 return Json(DelegationAuthResponse {
292 success: false,
293 needs_totp: None,
294 redirect_uri: None,
295 error: Some("Server error".to_string()),
296 })
297 .into_response();
298 }
299 };
300
301 let controller_did = match &request.controller_did {
302 Some(did) => did.clone(),
303 None => {
304 return Json(DelegationAuthResponse {
305 success: false,
306 needs_totp: None,
307 redirect_uri: None,
308 error: Some("Controller not authenticated".to_string()),
309 })
310 .into_response();
311 }
312 };
313
314 let delegated_did = match &request.did {
315 Some(did) => did.clone(),
316 None => {
317 return Json(DelegationAuthResponse {
318 success: false,
319 needs_totp: None,
320 redirect_uri: None,
321 error: Some("No delegated account".to_string()),
322 })
323 .into_response();
324 }
325 };
326
327 let grant = match delegation::get_delegation(&state.db, &delegated_did, &controller_did).await {
328 Ok(Some(g)) => g,
329 _ => {
330 return Json(DelegationAuthResponse {
331 success: false,
332 needs_totp: None,
333 redirect_uri: None,
334 error: Some("Delegation grant not found".to_string()),
335 })
336 .into_response();
337 }
338 };
339
340 let totp_valid =
341 crate::api::server::verify_totp_or_backup_for_user(&state, &controller_did, &form.code)
342 .await;
343 if !totp_valid {
344 return Json(DelegationAuthResponse {
345 success: false,
346 needs_totp: Some(true),
347 redirect_uri: None,
348 error: Some("Invalid TOTP code".to_string()),
349 })
350 .into_response();
351 }
352
353 let ip = extract_client_ip(&headers);
354 let user_agent = headers
355 .get("user-agent")
356 .and_then(|v| v.to_str().ok())
357 .map(|s| s.to_string());
358
359 let _ = delegation::log_delegation_action(
360 &state.db,
361 &delegated_did,
362 &controller_did,
363 Some(&controller_did),
364 delegation::DelegationActionType::TokenIssued,
365 Some(serde_json::json!({
366 "client_id": request.client_id,
367 "granted_scopes": grant.granted_scopes
368 })),
369 Some(&ip),
370 user_agent.as_deref(),
371 )
372 .await;
373
374 Json(DelegationAuthResponse {
375 success: true,
376 needs_totp: None,
377 redirect_uri: Some(format!(
378 "/app/oauth/consent?request_uri={}",
379 urlencoding::encode(&form.request_uri)
380 )),
381 error: None,
382 })
383 .into_response()
384}