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::{
17 ClientAuthParams, GrantType, TokenGrant, TokenRequest, TokenResponse, ValidatedTokenRequest,
18};
19
20fn extract_client_ip(headers: &HeaderMap) -> String {
21 if let Some(forwarded) = headers.get("x-forwarded-for")
22 && let Ok(value) = forwarded.to_str()
23 && let Some(first_ip) = value.split(',').next()
24 {
25 return first_ip.trim().to_string();
26 }
27 if let Some(real_ip) = headers.get("x-real-ip")
28 && let Ok(value) = real_ip.to_str()
29 {
30 return value.trim().to_string();
31 }
32 "unknown".to_string()
33}
34
35pub async fn token_endpoint(
36 State(state): State<AppState>,
37 headers: HeaderMap,
38 body: Bytes,
39) -> Result<(HeaderMap, Json<TokenResponse>), OAuthError> {
40 let content_type = headers
41 .get("content-type")
42 .and_then(|v| v.to_str().ok())
43 .unwrap_or("");
44 let request: TokenRequest = if content_type.starts_with("application/json") {
45 serde_json::from_slice(&body)
46 .map_err(|e| OAuthError::InvalidRequest(format!("Invalid JSON: {}", e)))?
47 } else if content_type.starts_with("application/x-www-form-urlencoded") {
48 serde_urlencoded::from_bytes(&body)
49 .map_err(|e| OAuthError::InvalidRequest(format!("Invalid form data: {}", e)))?
50 } else {
51 return Err(OAuthError::InvalidRequest(
52 "Content-Type must be application/json or application/x-www-form-urlencoded"
53 .to_string(),
54 ));
55 };
56 let client_ip = extract_client_ip(&headers);
57 if !state
58 .check_rate_limit(RateLimitKind::OAuthToken, &client_ip)
59 .await
60 {
61 tracing::warn!(ip = %client_ip, "OAuth token rate limit exceeded");
62 return Err(OAuthError::InvalidRequest(
63 "Too many requests. Please try again later.".to_string(),
64 ));
65 }
66 let dpop_proof = headers
67 .get("DPoP")
68 .and_then(|v| v.to_str().ok())
69 .map(|s| s.to_string());
70 let validated = request.validate()?;
71 match validated.grant {
72 TokenGrant::AuthorizationCode { .. } => {
73 handle_authorization_code_grant(state, headers, validated, dpop_proof).await
74 }
75 TokenGrant::RefreshToken { .. } => {
76 handle_refresh_token_grant(state, headers, validated, dpop_proof).await
77 }
78 }
79}