forked from
tranquil.farm/tranquil-pds
Our Personal Data Server from scratch!
16 kB
523 lines
1use super::*;
2
3#[derive(Debug, Serialize)]
4pub struct ScopeInfo {
5 pub scope: String,
6 pub category: String,
7 pub required: bool,
8 pub description: String,
9 pub display_name: String,
10 pub granted: Option<bool>,
11}
12
13#[derive(Debug, Serialize)]
14pub struct ConsentResponse {
15 pub request_uri: String,
16 pub client_id: ClientId,
17 pub client_name: Option<String>,
18 pub client_uri: Option<String>,
19 pub logo_uri: Option<String>,
20 pub scopes: Vec<ScopeInfo>,
21 pub show_consent: bool,
22 pub did: Did,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub handle: Option<String>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub is_delegation: Option<bool>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub controller_did: Option<String>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub controller_handle: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub delegation_level: Option<String>,
33}
34
35#[derive(Debug, Deserialize)]
36pub struct ConsentQuery {
37 pub request_uri: String,
38}
39
40#[derive(Debug, Deserialize)]
41pub struct ConsentSubmit {
42 pub request_uri: String,
43 pub approved_scopes: Vec<String>,
44 pub remember: bool,
45}
46
47pub async fn consent_get(
48 State(state): State<AppState>,
49 Query(query): Query<ConsentQuery>,
50) -> Response {
51 let consent_request_id = RequestId::from(query.request_uri.clone());
52 let request_data = match state
53 .repos
54 .oauth
55 .get_authorization_request(&consent_request_id)
56 .await
57 {
58 Ok(Some(data)) => data,
59 Ok(None) => {
60 return json_error(
61 StatusCode::BAD_REQUEST,
62 "invalid_request",
63 "Invalid or expired request_uri",
64 );
65 }
66 Err(e) => {
67 return json_error(
68 StatusCode::INTERNAL_SERVER_ERROR,
69 "server_error",
70 &format!("Database error: {:?}", e),
71 );
72 }
73 };
74 let flow_with_user = match AuthFlow::from_request_data(request_data.clone()) {
75 Ok(flow) => match flow.require_user() {
76 Ok(u) => u,
77 Err(_) => {
78 return json_error(StatusCode::FORBIDDEN, "access_denied", "Not authenticated");
79 }
80 },
81 Err(_) => {
82 return json_error(
83 StatusCode::BAD_REQUEST,
84 "expired_request",
85 "Authorization request has expired",
86 );
87 }
88 };
89
90 let did = flow_with_user.did().clone();
91 let client_cache = ClientMetadataCache::new(3600);
92 let client_metadata = client_cache
93 .get(&request_data.parameters.client_id)
94 .await
95 .ok();
96 let requested_scope_str = request_data
97 .parameters
98 .scope
99 .as_deref()
100 .filter(|s| !s.trim().is_empty())
101 .unwrap_or("atproto");
102
103 let controller_did_parsed: Option<Did> = request_data
104 .controller_did
105 .as_ref()
106 .and_then(|s| s.parse().ok());
107 let delegation_grant = if let Some(ref ctrl_did) = controller_did_parsed {
108 state
109 .repos
110 .delegation
111 .get_delegation(&did, ctrl_did)
112 .await
113 .ok()
114 .flatten()
115 } else {
116 None
117 };
118
119 let effective_scope_str = if let Some(ref grant) = delegation_grant {
120 tranquil_pds::delegation::intersect_scopes(
121 requested_scope_str,
122 grant.granted_scopes.as_str(),
123 )
124 } else {
125 requested_scope_str.to_string()
126 };
127
128 let expanded_scope_str = match expand_include_scopes(&effective_scope_str).await {
129 Ok(s) => s,
130 Err(e) => {
131 return json_error(
132 StatusCode::BAD_REQUEST,
133 "invalid_scope",
134 &format!("Failed to expand permission set: {e}"),
135 );
136 }
137 };
138 let requested_scopes: Vec<&str> = expanded_scope_str.split_whitespace().collect();
139 let preferences = state
140 .repos
141 .oauth
142 .get_scope_preferences(&did, &request_data.parameters.client_id)
143 .await
144 .unwrap_or_default();
145 let pref_map: std::collections::HashMap<_, _> = preferences
146 .iter()
147 .map(|p| (p.scope.as_str(), p.granted))
148 .collect();
149 let requested_scope_strings: Vec<String> =
150 requested_scopes.iter().map(|s| s.to_string()).collect();
151 let show_consent = should_show_consent(
152 state.repos.oauth.as_ref(),
153 &did,
154 &request_data.parameters.client_id,
155 &requested_scope_strings,
156 )
157 .await
158 .unwrap_or(true);
159 let has_granular_scopes = requested_scopes.iter().any(|s| is_granular_scope(s));
160 let scopes: Vec<ScopeInfo> = requested_scopes
161 .iter()
162 .map(|scope| {
163 let (category, required, description, display_name) = if let Some(def) =
164 tranquil_pds::oauth::scopes::SCOPE_DEFINITIONS.get(*scope)
165 {
166 let desc = if *scope == "atproto" && has_granular_scopes {
167 "AT Protocol baseline scope (permissions determined by selected options below)"
168 .to_string()
169 } else {
170 def.description.to_string()
171 };
172 let name = if *scope == "atproto" && has_granular_scopes {
173 "AT Protocol Access".to_string()
174 } else {
175 def.display_name.to_string()
176 };
177 (
178 def.category.display_name().to_string(),
179 def.required,
180 desc,
181 name,
182 )
183 } else if scope.starts_with("ref:") {
184 (
185 "Reference".to_string(),
186 false,
187 "Referenced scope".to_string(),
188 scope.to_string(),
189 )
190 } else {
191 (
192 "Other".to_string(),
193 false,
194 format!("Access to {}", scope),
195 scope.to_string(),
196 )
197 };
198 let granted = pref_map.get(*scope).copied();
199 ScopeInfo {
200 scope: scope.to_string(),
201 category,
202 required,
203 description,
204 display_name,
205 granted,
206 }
207 })
208 .collect();
209
210 let account_handle = state
211 .repos
212 .user
213 .get_handle_by_did(&did)
214 .await
215 .ok()
216 .flatten()
217 .map(|h| h.to_string());
218
219 let (is_delegation, controller_did_resp, controller_handle, delegation_level) =
220 if let Some(ref ctrl_did) = controller_did_parsed {
221 let ctrl_handle = state
222 .repos
223 .user
224 .get_handle_by_did(ctrl_did)
225 .await
226 .ok()
227 .flatten()
228 .map(|h| h.to_string());
229
230 let level = if let Some(ref grant) = delegation_grant {
231 let preset = tranquil_pds::delegation::SCOPE_PRESETS
232 .iter()
233 .find(|p| p.scopes == grant.granted_scopes.as_str());
234 preset
235 .map(|p| p.label.to_string())
236 .unwrap_or_else(|| "Custom".to_string())
237 } else {
238 "Unknown".to_string()
239 };
240
241 (
242 Some(true),
243 Some(ctrl_did.to_string()),
244 ctrl_handle,
245 Some(level),
246 )
247 } else {
248 (None, None, None, None)
249 };
250
251 Json(ConsentResponse {
252 request_uri: query.request_uri.clone(),
253 client_id: request_data.parameters.client_id.clone(),
254 client_name: client_metadata.as_ref().and_then(|m| m.client_name.clone()),
255 client_uri: client_metadata.as_ref().and_then(|m| m.client_uri.clone()),
256 logo_uri: client_metadata.as_ref().and_then(|m| m.logo_uri.clone()),
257 scopes,
258 show_consent,
259 did: did.clone(),
260 handle: account_handle,
261 is_delegation,
262 controller_did: controller_did_resp,
263 controller_handle,
264 delegation_level,
265 })
266 .into_response()
267}
268
269pub async fn consent_post(
270 State(state): State<AppState>,
271 Json(form): Json<ConsentSubmit>,
272) -> Response {
273 tracing::info!(
274 "consent_post: approved_scopes={:?}, remember={}",
275 form.approved_scopes,
276 form.remember
277 );
278 let consent_post_request_id = RequestId::from(form.request_uri.clone());
279 let request_data = match state
280 .repos
281 .oauth
282 .get_authorization_request(&consent_post_request_id)
283 .await
284 {
285 Ok(Some(data)) => data,
286 Ok(None) => {
287 return json_error(
288 StatusCode::BAD_REQUEST,
289 "invalid_request",
290 "Invalid or expired request_uri",
291 );
292 }
293 Err(e) => {
294 return json_error(
295 StatusCode::INTERNAL_SERVER_ERROR,
296 "server_error",
297 &format!("Database error: {:?}", e),
298 );
299 }
300 };
301 let flow_with_user = match AuthFlow::from_request_data(request_data.clone()) {
302 Ok(flow) => match flow.require_user() {
303 Ok(u) => u,
304 Err(_) => {
305 return json_error(StatusCode::FORBIDDEN, "access_denied", "Not authenticated");
306 }
307 },
308 Err(_) => {
309 let _ = state
310 .repos
311 .oauth
312 .delete_authorization_request(&consent_post_request_id)
313 .await;
314 return json_error(
315 StatusCode::BAD_REQUEST,
316 "invalid_request",
317 "Authorization request has expired",
318 );
319 }
320 };
321
322 let did = flow_with_user.did().clone();
323 let original_scope_str = request_data
324 .parameters
325 .scope
326 .as_deref()
327 .unwrap_or("atproto");
328
329 let controller_did_parsed: Option<Did> = request_data
330 .controller_did
331 .as_ref()
332 .and_then(|s| s.parse().ok());
333
334 let delegation_grant = match controller_did_parsed.as_ref() {
335 Some(ctrl_did) => state
336 .repos
337 .delegation
338 .get_delegation(&did, ctrl_did)
339 .await
340 .ok()
341 .flatten(),
342 None => None,
343 };
344
345 let effective_scope_str = if let Some(ref grant) = delegation_grant {
346 tranquil_pds::delegation::intersect_scopes(
347 original_scope_str,
348 grant.granted_scopes.as_str(),
349 )
350 } else {
351 original_scope_str.to_string()
352 };
353 let requested_scopes: Vec<&str> = effective_scope_str.split_whitespace().collect();
354 let atproto_was_requested = requested_scopes.contains(&"atproto");
355 if atproto_was_requested && !form.approved_scopes.contains(&"atproto".to_string()) {
356 return json_error(
357 StatusCode::BAD_REQUEST,
358 "invalid_request",
359 "The atproto scope was requested and must be approved",
360 );
361 }
362 let final_approved: Vec<String> = form.approved_scopes.clone();
363 if final_approved.is_empty() {
364 return json_error(
365 StatusCode::BAD_REQUEST,
366 "invalid_request",
367 "At least one scope must be approved",
368 );
369 }
370 let approved_scope_str = final_approved.join(" ");
371 let has_valid_scope = final_approved.iter().all(|s| is_valid_scope(s));
372 if !has_valid_scope {
373 return json_error(
374 StatusCode::BAD_REQUEST,
375 "invalid_request",
376 "Invalid scope format",
377 );
378 }
379 if form.remember {
380 let preferences: Vec<ScopePreference> = requested_scopes
381 .iter()
382 .map(|s| ScopePreference {
383 scope: s.to_string(),
384 granted: form.approved_scopes.contains(&s.to_string()),
385 })
386 .collect();
387 let _ = state
388 .repos
389 .oauth
390 .upsert_scope_preferences(&did, &request_data.parameters.client_id, &preferences)
391 .await;
392 }
393 if let Err(e) = state
394 .repos
395 .oauth
396 .update_request_scope(&consent_post_request_id, &approved_scope_str)
397 .await
398 {
399 tracing::warn!("Failed to update request scope: {:?}", e);
400 }
401 let code = AuthorizationCode::generate();
402 if state
403 .repos
404 .oauth
405 .update_authorization_request(
406 &consent_post_request_id,
407 &did,
408 request_data.device_id.as_ref(),
409 &code,
410 )
411 .await
412 .is_err()
413 {
414 return json_error(
415 StatusCode::INTERNAL_SERVER_ERROR,
416 "server_error",
417 "Failed to complete authorization",
418 );
419 }
420 let redirect_uri = &request_data.parameters.redirect_uri;
421 let intermediate_url = build_intermediate_redirect_url(
422 redirect_uri,
423 code.as_str(),
424 request_data.parameters.state.as_deref(),
425 request_data.parameters.response_mode.map(|m| m.as_str()),
426 );
427 tracing::info!(
428 intermediate_url = %intermediate_url,
429 client_redirect = %redirect_uri,
430 "consent_post returning JSON with intermediate URL (for 303 redirect)"
431 );
432 Json(serde_json::json!({ "redirect_uri": intermediate_url })).into_response()
433}
434
435#[derive(Debug, Deserialize)]
436pub struct RenewRequest {
437 pub request_uri: String,
438}
439
440pub async fn authorize_renew(
441 State(state): State<AppState>,
442 _rate_limit: OAuthRateLimited<OAuthAuthorizeLimit>,
443 Json(form): Json<RenewRequest>,
444) -> Response {
445 let request_id = RequestId::from(form.request_uri.clone());
446 let request_data = match state
447 .repos
448 .oauth
449 .get_authorization_request(&request_id)
450 .await
451 {
452 Ok(Some(data)) => data,
453 Ok(None) => {
454 return json_error(
455 StatusCode::BAD_REQUEST,
456 "invalid_request",
457 "Unknown authorization request",
458 );
459 }
460 Err(_) => {
461 return json_error(
462 StatusCode::INTERNAL_SERVER_ERROR,
463 "server_error",
464 "Database error",
465 );
466 }
467 };
468
469 if request_data.did.is_none() {
470 return json_error(
471 StatusCode::BAD_REQUEST,
472 "invalid_request",
473 "Authorization request not yet authenticated",
474 );
475 }
476
477 let now = Utc::now();
478 if request_data.expires_at >= now {
479 return Json(serde_json::json!({
480 "request_uri": form.request_uri,
481 "renewed": false
482 }))
483 .into_response();
484 }
485
486 let staleness = now - request_data.expires_at;
487 if staleness.num_seconds() > MAX_RENEWAL_STALENESS_SECONDS {
488 let _ = state
489 .repos
490 .oauth
491 .delete_authorization_request(&request_id)
492 .await;
493 return json_error(
494 StatusCode::BAD_REQUEST,
495 "invalid_request",
496 "Authorization request expired too long ago to renew",
497 );
498 }
499
500 let new_expires_at = now + chrono::Duration::seconds(RENEW_EXPIRY_SECONDS);
501 match state
502 .repos
503 .oauth
504 .extend_authorization_request_expiry(&request_id, new_expires_at)
505 .await
506 {
507 Ok(true) => Json(serde_json::json!({
508 "request_uri": form.request_uri,
509 "renewed": true
510 }))
511 .into_response(),
512 Ok(false) => json_error(
513 StatusCode::BAD_REQUEST,
514 "invalid_request",
515 "Authorization request could not be renewed",
516 ),
517 Err(_) => json_error(
518 StatusCode::INTERNAL_SERVER_ERROR,
519 "server_error",
520 "Database error",
521 ),
522 }
523}