this repo has no description
1use crate::auth::{extract_bearer_token_from_header, validate_bearer_token};
2use crate::state::AppState;
3use axum::{
4 Json,
5 extract::State,
6 http::{HeaderMap, StatusCode},
7 response::{IntoResponse, Response},
8};
9use cid::Cid;
10use jacquard_repo::storage::BlockStore;
11use serde::{Deserialize, Serialize};
12use serde_json::json;
13use std::str::FromStr;
14
15#[derive(Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct CheckSignupQueueOutput {
18 pub activated: bool,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub place_in_queue: Option<i64>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub estimated_time_ms: Option<i64>,
23}
24
25pub async fn check_signup_queue(State(state): State<AppState>, headers: HeaderMap) -> Response {
26 if let Some(token) =
27 extract_bearer_token_from_header(headers.get("Authorization").and_then(|h| h.to_str().ok()))
28 && let Ok(user) = validate_bearer_token(&state.db, &token).await
29 && user.is_oauth
30 {
31 return (
32 StatusCode::FORBIDDEN,
33 Json(json!({
34 "error": "Forbidden",
35 "message": "OAuth credentials are not supported for this endpoint"
36 })),
37 )
38 .into_response();
39 }
40 Json(CheckSignupQueueOutput {
41 activated: true,
42 place_in_queue: None,
43 estimated_time_ms: None,
44 })
45 .into_response()
46}
47
48#[derive(Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct DereferenceScopeInput {
51 pub scope: String,
52}
53
54#[derive(Serialize)]
55#[serde(rename_all = "camelCase")]
56pub struct DereferenceScopeOutput {
57 pub scope: String,
58}
59
60pub async fn dereference_scope(
61 State(state): State<AppState>,
62 headers: HeaderMap,
63 Json(input): Json<DereferenceScopeInput>,
64) -> Response {
65 let token = match extract_bearer_token_from_header(
66 headers.get("Authorization").and_then(|h| h.to_str().ok()),
67 ) {
68 Some(t) => t,
69 None => {
70 return (
71 StatusCode::UNAUTHORIZED,
72 Json(json!({"error": "AuthenticationRequired"})),
73 )
74 .into_response();
75 }
76 };
77
78 if validate_bearer_token(&state.db, &token).await.is_err() {
79 return (
80 StatusCode::UNAUTHORIZED,
81 Json(json!({"error": "AuthenticationFailed"})),
82 )
83 .into_response();
84 }
85
86 let scope_parts: Vec<&str> = input.scope.split_whitespace().collect();
87 let mut resolved_scopes: Vec<String> = Vec::new();
88
89 for part in scope_parts {
90 if let Some(cid_str) = part.strip_prefix("ref:") {
91 let cache_key = format!("scope_ref:{}", cid_str);
92 if let Some(cached) = state.cache.get(&cache_key).await {
93 for s in cached.split_whitespace() {
94 if !resolved_scopes.contains(&s.to_string()) {
95 resolved_scopes.push(s.to_string());
96 }
97 }
98 continue;
99 }
100
101 let cid = match Cid::from_str(cid_str) {
102 Ok(c) => c,
103 Err(_) => {
104 tracing::warn!("Invalid CID in scope ref: {}", cid_str);
105 continue;
106 }
107 };
108
109 let block_bytes = match state.block_store.get(&cid).await {
110 Ok(Some(b)) => b,
111 Ok(None) => {
112 tracing::warn!("Scope ref block not found: {}", cid_str);
113 continue;
114 }
115 Err(e) => {
116 tracing::warn!("Error fetching scope ref block {}: {:?}", cid_str, e);
117 continue;
118 }
119 };
120
121 let scope_record: serde_json::Value = match serde_ipld_dagcbor::from_slice(&block_bytes)
122 {
123 Ok(v) => v,
124 Err(e) => {
125 tracing::warn!("Failed to decode scope ref block {}: {:?}", cid_str, e);
126 continue;
127 }
128 };
129
130 if let Some(scope_value) = scope_record.get("scope").and_then(|v| v.as_str()) {
131 let _ = state
132 .cache
133 .set(
134 &cache_key,
135 scope_value,
136 std::time::Duration::from_secs(3600),
137 )
138 .await;
139 for s in scope_value.split_whitespace() {
140 if !resolved_scopes.contains(&s.to_string()) {
141 resolved_scopes.push(s.to_string());
142 }
143 }
144 }
145 } else if !resolved_scopes.contains(&part.to_string()) {
146 resolved_scopes.push(part.to_string());
147 }
148 }
149
150 Json(DereferenceScopeOutput {
151 scope: resolved_scopes.join(" "),
152 })
153 .into_response()
154}