this repo has no description
1use axum::http::HeaderMap;
2use axum::Json;
3use chrono::{Duration, Utc};
4use crate::config::AuthConfig;
5use crate::state::AppState;
6use crate::oauth::{
7 ClientAuth, OAuthError, RefreshToken, TokenData, TokenId,
8 client::{ClientMetadataCache, verify_client_auth},
9 db,
10 dpop::DPoPVerifier,
11};
12use super::types::{TokenRequest, TokenResponse};
13use super::helpers::{create_access_token, verify_pkce};
14const ACCESS_TOKEN_EXPIRY_SECONDS: i64 = 3600;
15const REFRESH_TOKEN_EXPIRY_DAYS: i64 = 60;
16pub async fn handle_authorization_code_grant(
17 state: AppState,
18 _headers: HeaderMap,
19 request: TokenRequest,
20 dpop_proof: Option<String>,
21) -> Result<(HeaderMap, Json<TokenResponse>), OAuthError> {
22 let code = request
23 .code
24 .ok_or_else(|| OAuthError::InvalidRequest("code is required".to_string()))?;
25 let code_verifier = request
26 .code_verifier
27 .ok_or_else(|| OAuthError::InvalidRequest("code_verifier is required".to_string()))?;
28 let auth_request = db::consume_authorization_request_by_code(&state.db, &code)
29 .await?
30 .ok_or_else(|| OAuthError::InvalidGrant("Invalid or expired code".to_string()))?;
31 if auth_request.expires_at < Utc::now() {
32 return Err(OAuthError::InvalidGrant("Authorization code has expired".to_string()));
33 }
34 if let Some(request_client_id) = &request.client_id {
35 if request_client_id != &auth_request.client_id {
36 return Err(OAuthError::InvalidGrant("client_id mismatch".to_string()));
37 }
38 }
39 let did = auth_request
40 .did
41 .ok_or_else(|| OAuthError::InvalidGrant("Authorization not completed".to_string()))?;
42 let client_metadata_cache = ClientMetadataCache::new(3600);
43 let client_metadata = client_metadata_cache
44 .get(&auth_request.client_id)
45 .await?;
46 let client_auth = auth_request.client_auth.clone().unwrap_or(ClientAuth::None);
47 verify_client_auth(&client_metadata_cache, &client_metadata, &client_auth).await?;
48 verify_pkce(&auth_request.parameters.code_challenge, &code_verifier)?;
49 if let Some(redirect_uri) = &request.redirect_uri {
50 if redirect_uri != &auth_request.parameters.redirect_uri {
51 return Err(OAuthError::InvalidGrant("redirect_uri mismatch".to_string()));
52 }
53 }
54 let dpop_jkt = if let Some(proof) = &dpop_proof {
55 let config = AuthConfig::get();
56 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes());
57 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
58 let token_endpoint = format!("https://{}/oauth/token", pds_hostname);
59 let result = verifier.verify_proof(proof, "POST", &token_endpoint, None)?;
60 if !db::check_and_record_dpop_jti(&state.db, &result.jti).await? {
61 return Err(OAuthError::InvalidDpopProof(
62 "DPoP proof has already been used".to_string(),
63 ));
64 }
65 if let Some(expected_jkt) = &auth_request.parameters.dpop_jkt {
66 if &result.jkt != expected_jkt {
67 return Err(OAuthError::InvalidDpopProof(
68 "DPoP key binding mismatch".to_string(),
69 ));
70 }
71 }
72 Some(result.jkt)
73 } else if auth_request.parameters.dpop_jkt.is_some() {
74 return Err(OAuthError::InvalidRequest(
75 "DPoP proof required for this authorization".to_string(),
76 ));
77 } else {
78 None
79 };
80 let token_id = TokenId::generate();
81 let refresh_token = RefreshToken::generate();
82 let now = Utc::now();
83 let access_token = create_access_token(&token_id.0, &did, dpop_jkt.as_deref())?;
84 let token_data = TokenData {
85 did: did.clone(),
86 token_id: token_id.0.clone(),
87 created_at: now,
88 updated_at: now,
89 expires_at: now + Duration::days(REFRESH_TOKEN_EXPIRY_DAYS),
90 client_id: auth_request.client_id.clone(),
91 client_auth: auth_request.client_auth.unwrap_or(ClientAuth::None),
92 device_id: auth_request.device_id,
93 parameters: auth_request.parameters.clone(),
94 details: None,
95 code: None,
96 current_refresh_token: Some(refresh_token.0.clone()),
97 scope: auth_request.parameters.scope.clone(),
98 };
99 db::create_token(&state.db, &token_data).await?;
100 tokio::spawn({
101 let pool = state.db.clone();
102 let did_clone = did.clone();
103 async move {
104 if let Err(e) = db::enforce_token_limit_for_user(&pool, &did_clone).await {
105 tracing::warn!("Failed to enforce token limit for user: {:?}", e);
106 }
107 }
108 });
109 let mut response_headers = HeaderMap::new();
110 let config = AuthConfig::get();
111 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes());
112 response_headers.insert(
113 "DPoP-Nonce",
114 verifier.generate_nonce().parse().unwrap(),
115 );
116 Ok((
117 response_headers,
118 Json(TokenResponse {
119 access_token,
120 token_type: if dpop_jkt.is_some() { "DPoP" } else { "Bearer" }.to_string(),
121 expires_in: ACCESS_TOKEN_EXPIRY_SECONDS as u64,
122 refresh_token: Some(refresh_token.0),
123 scope: auth_request.parameters.scope,
124 sub: Some(did),
125 }),
126 ))
127}
128pub async fn handle_refresh_token_grant(
129 state: AppState,
130 _headers: HeaderMap,
131 request: TokenRequest,
132 dpop_proof: Option<String>,
133) -> Result<(HeaderMap, Json<TokenResponse>), OAuthError> {
134 let refresh_token_str = request
135 .refresh_token
136 .ok_or_else(|| OAuthError::InvalidRequest("refresh_token is required".to_string()))?;
137 if let Some(token_id) = db::check_refresh_token_used(&state.db, &refresh_token_str).await? {
138 db::delete_token_family(&state.db, token_id).await?;
139 return Err(OAuthError::InvalidGrant(
140 "Refresh token reuse detected, token family revoked".to_string(),
141 ));
142 }
143 let (db_id, token_data) = db::get_token_by_refresh_token(&state.db, &refresh_token_str)
144 .await?
145 .ok_or_else(|| OAuthError::InvalidGrant("Invalid refresh token".to_string()))?;
146 if token_data.expires_at < Utc::now() {
147 db::delete_token_family(&state.db, db_id).await?;
148 return Err(OAuthError::InvalidGrant("Refresh token has expired".to_string()));
149 }
150 let dpop_jkt = if let Some(proof) = &dpop_proof {
151 let config = AuthConfig::get();
152 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes());
153 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
154 let token_endpoint = format!("https://{}/oauth/token", pds_hostname);
155 let result = verifier.verify_proof(proof, "POST", &token_endpoint, None)?;
156 if !db::check_and_record_dpop_jti(&state.db, &result.jti).await? {
157 return Err(OAuthError::InvalidDpopProof(
158 "DPoP proof has already been used".to_string(),
159 ));
160 }
161 if let Some(expected_jkt) = &token_data.parameters.dpop_jkt {
162 if &result.jkt != expected_jkt {
163 return Err(OAuthError::InvalidDpopProof(
164 "DPoP key binding mismatch".to_string(),
165 ));
166 }
167 }
168 Some(result.jkt)
169 } else if token_data.parameters.dpop_jkt.is_some() {
170 return Err(OAuthError::InvalidRequest(
171 "DPoP proof required".to_string(),
172 ));
173 } else {
174 None
175 };
176 let new_token_id = TokenId::generate();
177 let new_refresh_token = RefreshToken::generate();
178 let new_expires_at = Utc::now() + Duration::days(REFRESH_TOKEN_EXPIRY_DAYS);
179 db::rotate_token(
180 &state.db,
181 db_id,
182 &new_token_id.0,
183 &new_refresh_token.0,
184 new_expires_at,
185 )
186 .await?;
187 let access_token = create_access_token(&new_token_id.0, &token_data.did, dpop_jkt.as_deref())?;
188 let mut response_headers = HeaderMap::new();
189 let config = AuthConfig::get();
190 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes());
191 response_headers.insert(
192 "DPoP-Nonce",
193 verifier.generate_nonce().parse().unwrap(),
194 );
195 Ok((
196 response_headers,
197 Json(TokenResponse {
198 access_token,
199 token_type: if dpop_jkt.is_some() { "DPoP" } else { "Bearer" }.to_string(),
200 expires_in: ACCESS_TOKEN_EXPIRY_SECONDS as u64,
201 refresh_token: Some(new_refresh_token.0),
202 scope: token_data.scope,
203 sub: Some(token_data.did),
204 }),
205 ))
206}