this repo has no description
1mod grants;
2mod helpers;
3mod introspect;
4mod types;
5
6use crate::oauth::OAuthError;
7use crate::state::{AppState, RateLimitKind};
8use axum::body::Bytes;
9use axum::{Json, extract::State, http::HeaderMap};
10
11pub use grants::{handle_authorization_code_grant, handle_refresh_token_grant};
12pub use helpers::{TokenClaims, create_access_token, extract_token_claims, verify_pkce};
13pub use introspect::{
14 IntrospectRequest, IntrospectResponse, RevokeRequest, introspect_token, revoke_token,
15};
16pub use types::{TokenRequest, TokenResponse};
17
18fn extract_client_ip(headers: &HeaderMap) -> String {
19 if let Some(forwarded) = headers.get("x-forwarded-for")
20 && let Ok(value) = forwarded.to_str()
21 && let Some(first_ip) = value.split(',').next()
22 {
23 return first_ip.trim().to_string();
24 }
25 if let Some(real_ip) = headers.get("x-real-ip")
26 && let Ok(value) = real_ip.to_str()
27 {
28 return value.trim().to_string();
29 }
30 "unknown".to_string()
31}
32
33pub async fn token_endpoint(
34 State(state): State<AppState>,
35 headers: HeaderMap,
36 body: Bytes,
37) -> Result<(HeaderMap, Json<TokenResponse>), OAuthError> {
38 let content_type = headers
39 .get("content-type")
40 .and_then(|v| v.to_str().ok())
41 .unwrap_or("");
42 let request: TokenRequest = if content_type.starts_with("application/json") {
43 serde_json::from_slice(&body)
44 .map_err(|e| OAuthError::InvalidRequest(format!("Invalid JSON: {}", e)))?
45 } else if content_type.starts_with("application/x-www-form-urlencoded") {
46 serde_urlencoded::from_bytes(&body)
47 .map_err(|e| OAuthError::InvalidRequest(format!("Invalid form data: {}", e)))?
48 } else {
49 return Err(OAuthError::InvalidRequest(
50 "Content-Type must be application/json or application/x-www-form-urlencoded"
51 .to_string(),
52 ));
53 };
54 let client_ip = extract_client_ip(&headers);
55 if !state
56 .check_rate_limit(RateLimitKind::OAuthToken, &client_ip)
57 .await
58 {
59 tracing::warn!(ip = %client_ip, "OAuth token rate limit exceeded");
60 return Err(OAuthError::InvalidRequest(
61 "Too many requests. Please try again later.".to_string(),
62 ));
63 }
64 let dpop_proof = headers
65 .get("DPoP")
66 .and_then(|v| v.to_str().ok())
67 .map(|s| s.to_string());
68 match request.grant_type.as_str() {
69 "authorization_code" => {
70 handle_authorization_code_grant(state, headers, request, dpop_proof).await
71 }
72 "refresh_token" => handle_refresh_token_grant(state, headers, request, dpop_proof).await,
73 _ => Err(OAuthError::UnsupportedGrantType(format!(
74 "Unsupported grant_type: {}",
75 request.grant_type
76 ))),
77 }
78}