Skip to main content

allowthem_server/
token_route.rs

1use axum::extract::State;
2use axum::http::header::AUTHORIZATION;
3use axum::http::{HeaderMap, StatusCode};
4use axum::response::{IntoResponse, Response};
5use axum::routing::post;
6use axum::{Form, Json, Router};
7use base64ct::{Base64, Encoding};
8use serde::Deserialize;
9use serde_json::json;
10
11use allowthem_core::password::verify_password;
12use allowthem_core::types::ClientId;
13use allowthem_core::{
14    AllowThem, AuthError, TokenError, exchange_authorization_code, exchange_refresh_token,
15};
16
17// ---------------------------------------------------------------------------
18// Request types
19// ---------------------------------------------------------------------------
20
21#[derive(Deserialize)]
22struct TokenParams {
23    grant_type: Option<String>,
24    // authorization_code grant
25    code: Option<String>,
26    redirect_uri: Option<String>,
27    code_verifier: Option<String>,
28    // refresh_token grant
29    refresh_token: Option<String>,
30    scope: Option<String>,
31    // client credentials (both grants)
32    client_id: Option<ClientId>,
33    client_secret: Option<String>,
34}
35
36// ---------------------------------------------------------------------------
37// Client credential extraction
38// ---------------------------------------------------------------------------
39
40fn extract_client_credentials(
41    headers: &HeaderMap,
42    params: &TokenParams,
43) -> Result<(ClientId, String), TokenError> {
44    if let Some(auth_header) = headers.get(AUTHORIZATION).and_then(|v| v.to_str().ok())
45        && let Some(encoded) = auth_header.strip_prefix("Basic ")
46    {
47        let decoded = Base64::decode_vec(encoded)
48            .map_err(|_| TokenError::InvalidClient("malformed Basic credentials".into()))?;
49        let decoded_str = String::from_utf8(decoded)
50            .map_err(|_| TokenError::InvalidClient("malformed Basic credentials".into()))?;
51        let (id_str, secret) = decoded_str
52            .split_once(':')
53            .ok_or_else(|| TokenError::InvalidClient("malformed Basic credentials".into()))?;
54        let client_id: ClientId =
55            serde_json::from_value::<ClientId>(serde_json::Value::String(id_str.to_string()))
56                .map_err(|_| {
57                    TokenError::InvalidClient("invalid client_id in Basic credentials".into())
58                })?;
59        return Ok((client_id, secret.to_string()));
60    }
61
62    let client_id = params
63        .client_id
64        .clone()
65        .ok_or_else(|| TokenError::InvalidClient("missing client credentials".into()))?;
66    let client_secret = params
67        .client_secret
68        .clone()
69        .ok_or_else(|| TokenError::InvalidClient("missing client credentials".into()))?;
70    Ok((client_id, client_secret))
71}
72
73// ---------------------------------------------------------------------------
74// Error response
75// ---------------------------------------------------------------------------
76
77fn token_error_response(error: &TokenError) -> Response {
78    let (status, error_code, description) = match error {
79        TokenError::InvalidClient(desc) => {
80            (StatusCode::UNAUTHORIZED, "invalid_client", desc.as_str())
81        }
82        TokenError::InvalidGrant(desc) => (StatusCode::BAD_REQUEST, "invalid_grant", desc.as_str()),
83        TokenError::InvalidRequest(desc) => {
84            (StatusCode::BAD_REQUEST, "invalid_request", desc.as_str())
85        }
86        TokenError::UnsupportedGrantType => (
87            StatusCode::BAD_REQUEST,
88            "unsupported_grant_type",
89            "unsupported grant_type",
90        ),
91        TokenError::ServerError(desc) => (
92            StatusCode::INTERNAL_SERVER_ERROR,
93            "server_error",
94            desc.as_str(),
95        ),
96    };
97
98    let body = json!({"error": error_code, "error_description": description});
99    let mut resp = (status, Json(body)).into_response();
100
101    if matches!(error, TokenError::InvalidClient(_)) {
102        resp.headers_mut().insert(
103            "WWW-Authenticate",
104            "Basic realm=\"allowthem\"".parse().expect("valid header"),
105        );
106    }
107
108    resp.headers_mut()
109        .insert("Cache-Control", "no-store".parse().expect("valid header"));
110    resp.headers_mut()
111        .insert("Pragma", "no-cache".parse().expect("valid header"));
112    resp
113}
114
115// ---------------------------------------------------------------------------
116// Handler
117// ---------------------------------------------------------------------------
118
119async fn token(
120    State(ath): State<AllowThem>,
121    headers: HeaderMap,
122    Form(params): Form<TokenParams>,
123) -> Response {
124    // 1. Extract client credentials
125    let (client_id, client_secret) = match extract_client_credentials(&headers, &params) {
126        Ok(creds) => creds,
127        Err(e) => return token_error_response(&e),
128    };
129
130    // 2. Authenticate client
131    let application = match ath.db().get_application_by_client_id(&client_id).await {
132        Ok(app) => app,
133        Err(AuthError::NotFound) => {
134            return token_error_response(&TokenError::InvalidClient("unknown client_id".into()));
135        }
136        Err(_) => return token_error_response(&TokenError::ServerError("internal error".into())),
137    };
138
139    match verify_password(&client_secret, &application.client_secret_hash) {
140        Ok(true) => {}
141        _ => {
142            return token_error_response(&TokenError::InvalidClient(
143                "invalid client_secret".into(),
144            ));
145        }
146    }
147
148    if !application.is_active {
149        return token_error_response(&TokenError::InvalidClient("application is inactive".into()));
150    }
151
152    // 3. Get signing key and issuer (shared by both grant types)
153    let (signing_key, private_key_pem) = match ath.get_decrypted_signing_key().await {
154        Ok(pair) => pair,
155        Err(AuthError::NotFound) => {
156            return token_error_response(&TokenError::ServerError("no active signing key".into()));
157        }
158        Err(e) => return token_error_response(&TokenError::ServerError(e.to_string())),
159    };
160
161    let issuer = match ath.base_url() {
162        Ok(url) => url,
163        Err(e) => return token_error_response(&TokenError::ServerError(e.to_string())),
164    };
165
166    // 4. Dispatch on grant_type
167    match params.grant_type.as_deref() {
168        Some("authorization_code") => {
169            handle_authorization_code(
170                ath.db(),
171                params,
172                signing_key,
173                private_key_pem,
174                &application,
175                issuer,
176            )
177            .await
178        }
179        Some("refresh_token") => {
180            handle_refresh_token(
181                ath.db(),
182                params,
183                signing_key,
184                private_key_pem,
185                &application,
186                issuer,
187            )
188            .await
189        }
190        _ => token_error_response(&TokenError::UnsupportedGrantType),
191    }
192}
193
194async fn handle_authorization_code(
195    db: &allowthem_core::db::Db,
196    params: TokenParams,
197    signing_key: allowthem_core::SigningKey,
198    private_key_pem: String,
199    application: &allowthem_core::applications::Application,
200    issuer: &str,
201) -> Response {
202    let code = match params.code.as_deref() {
203        Some(c) if !c.is_empty() => c,
204        _ => {
205            return token_error_response(&TokenError::InvalidRequest(
206                "missing code parameter".into(),
207            ));
208        }
209    };
210    let redirect_uri = match params.redirect_uri.as_deref() {
211        Some(r) if !r.is_empty() => r,
212        _ => {
213            return token_error_response(&TokenError::InvalidRequest(
214                "missing redirect_uri parameter".into(),
215            ));
216        }
217    };
218    let code_verifier = match params.code_verifier.as_deref() {
219        Some(v) if !v.is_empty() => v,
220        _ => {
221            return token_error_response(&TokenError::InvalidRequest(
222                "missing code_verifier parameter".into(),
223            ));
224        }
225    };
226
227    match exchange_authorization_code(
228        db,
229        code,
230        redirect_uri,
231        code_verifier,
232        application,
233        issuer,
234        &signing_key,
235        &private_key_pem,
236    )
237    .await
238    {
239        Ok(token_response) => token_success_response(token_response),
240        Err(e) => token_error_response(&e),
241    }
242}
243
244async fn handle_refresh_token(
245    db: &allowthem_core::db::Db,
246    params: TokenParams,
247    signing_key: allowthem_core::SigningKey,
248    private_key_pem: String,
249    application: &allowthem_core::applications::Application,
250    issuer: &str,
251) -> Response {
252    let raw_token = match params.refresh_token.as_deref() {
253        Some(t) if !t.is_empty() => t,
254        _ => {
255            return token_error_response(&TokenError::InvalidRequest(
256                "missing refresh_token parameter".into(),
257            ));
258        }
259    };
260
261    let requested_scopes = params.scope.as_deref();
262
263    match exchange_refresh_token(
264        db,
265        raw_token,
266        requested_scopes,
267        application,
268        issuer,
269        &signing_key,
270        &private_key_pem,
271    )
272    .await
273    {
274        Ok(token_response) => token_success_response(token_response),
275        Err(e) => token_error_response(&e),
276    }
277}
278
279fn token_success_response(token_response: allowthem_core::TokenResponse) -> Response {
280    let mut resp = (StatusCode::OK, Json(token_response)).into_response();
281    resp.headers_mut()
282        .insert("Cache-Control", "no-store".parse().expect("valid header"));
283    resp.headers_mut()
284        .insert("Pragma", "no-cache".parse().expect("valid header"));
285    resp
286}
287
288// ---------------------------------------------------------------------------
289// Router
290// ---------------------------------------------------------------------------
291
292pub fn token_route() -> Router<AllowThem> {
293    Router::new().route("/oauth/token", post(token))
294}
295
296// ---------------------------------------------------------------------------
297// Tests
298// ---------------------------------------------------------------------------
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303    use allowthem_core::authorization::{generate_authorization_code, hash_authorization_code};
304    use allowthem_core::handle::AllowThemBuilder;
305    use allowthem_core::types::Email;
306    use axum::body::Body;
307    use axum::http::Request;
308    use sha2::{Digest, Sha256};
309    use tower::ServiceExt;
310
311    const ENC_KEY: [u8; 32] = [0x42; 32];
312    const ISSUER: &str = "https://auth.example.com";
313
314    async fn test_app() -> (AllowThem, Router) {
315        let ath = AllowThemBuilder::new("sqlite::memory:")
316            .cookie_secure(false)
317            .signing_key(ENC_KEY)
318            .base_url(ISSUER)
319            .build()
320            .await
321            .unwrap();
322
323        let key = ath.db().create_signing_key(&ENC_KEY).await.unwrap();
324        ath.db().activate_signing_key(key.id).await.unwrap();
325
326        let routes = token_route();
327        let app = routes.with_state(ath.clone());
328        (ath, app)
329    }
330
331    async fn setup_code_exchange(
332        ath: &AllowThem,
333    ) -> (
334        allowthem_core::applications::Application,
335        String,
336        String,
337        String,
338        String,
339    ) {
340        let email = Email::new("token_test@example.com".into()).unwrap();
341        let user = ath
342            .db()
343            .create_user(email, "password123", None, None)
344            .await
345            .unwrap();
346
347        let (app, client_secret) = ath
348            .db()
349            .create_application(
350                "TokenTestApp".to_string(),
351                vec!["https://example.com/callback".to_string()],
352                false,
353                Some(user.id),
354                None,
355                None,
356            )
357            .await
358            .unwrap();
359        let raw_secret = client_secret.as_str().to_string();
360
361        let code_verifier = "test_verifier_with_enough_entropy_1234567890abcdef";
362        let digest = Sha256::digest(code_verifier.as_bytes());
363        let code_challenge = base64ct::Base64UrlUnpadded::encode_string(&digest);
364
365        let raw_code = generate_authorization_code();
366        let code_hash = hash_authorization_code(&raw_code);
367        ath.db()
368            .create_authorization_code(
369                app.id,
370                user.id,
371                &code_hash,
372                "https://example.com/callback",
373                &["openid".to_string(), "profile".to_string()],
374                &code_challenge,
375                "S256",
376                None,
377            )
378            .await
379            .unwrap();
380
381        (
382            app,
383            raw_secret,
384            raw_code,
385            code_verifier.to_string(),
386            "https://example.com/callback".to_string(),
387        )
388    }
389
390    fn build_token_body(
391        app: &allowthem_core::applications::Application,
392        secret: &str,
393        code: &str,
394        verifier: &str,
395        redirect_uri: &str,
396    ) -> String {
397        url::form_urlencoded::Serializer::new(String::new())
398            .append_pair("grant_type", "authorization_code")
399            .append_pair("code", code)
400            .append_pair("redirect_uri", redirect_uri)
401            .append_pair("code_verifier", verifier)
402            .append_pair("client_id", app.client_id.as_str())
403            .append_pair("client_secret", secret)
404            .finish()
405    }
406
407    async fn read_body(resp: axum::http::Response<Body>) -> serde_json::Value {
408        let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
409            .await
410            .unwrap();
411        serde_json::from_slice(&bytes).unwrap_or(serde_json::Value::Null)
412    }
413
414    #[tokio::test]
415    async fn valid_code_exchange_returns_200() {
416        let (ath, app) = test_app().await;
417        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
418        let body = build_token_body(&application, &secret, &code, &verifier, &redirect_uri);
419
420        let req = Request::builder()
421            .method("POST")
422            .uri("/oauth/token")
423            .header("content-type", "application/x-www-form-urlencoded")
424            .body(Body::from(body))
425            .unwrap();
426        let resp = app.oneshot(req).await.unwrap();
427        assert_eq!(resp.status(), StatusCode::OK);
428
429        let cache_control = resp
430            .headers()
431            .get("cache-control")
432            .unwrap()
433            .to_str()
434            .unwrap();
435        assert_eq!(cache_control, "no-store");
436
437        let body = read_body(resp).await;
438        assert_eq!(body["token_type"], "Bearer");
439        assert_eq!(body["expires_in"], 3600);
440        assert!(body["access_token"].is_string());
441        assert!(body["refresh_token"].is_string());
442        assert!(body["id_token"].is_string());
443    }
444
445    #[tokio::test]
446    async fn missing_grant_type_returns_400() {
447        let (ath, app) = test_app().await;
448        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
449
450        let body = url::form_urlencoded::Serializer::new(String::new())
451            .append_pair("code", &code)
452            .append_pair("redirect_uri", &redirect_uri)
453            .append_pair("code_verifier", &verifier)
454            .append_pair("client_id", application.client_id.as_str())
455            .append_pair("client_secret", &secret)
456            .finish();
457
458        let req = Request::builder()
459            .method("POST")
460            .uri("/oauth/token")
461            .header("content-type", "application/x-www-form-urlencoded")
462            .body(Body::from(body))
463            .unwrap();
464        let resp = app.oneshot(req).await.unwrap();
465        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
466        let body = read_body(resp).await;
467        assert_eq!(body["error"], "unsupported_grant_type");
468    }
469
470    #[tokio::test]
471    async fn invalid_client_id_returns_401_with_www_authenticate() {
472        let (_ath, app) = test_app().await;
473
474        let body = url::form_urlencoded::Serializer::new(String::new())
475            .append_pair("grant_type", "authorization_code")
476            .append_pair("code", "test")
477            .append_pair("redirect_uri", "https://example.com/callback")
478            .append_pair("code_verifier", "test")
479            .append_pair("client_id", "ath_nonexistent")
480            .append_pair("client_secret", "wrong")
481            .finish();
482
483        let req = Request::builder()
484            .method("POST")
485            .uri("/oauth/token")
486            .header("content-type", "application/x-www-form-urlencoded")
487            .body(Body::from(body))
488            .unwrap();
489        let resp = app.oneshot(req).await.unwrap();
490        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
491
492        let www_auth = resp
493            .headers()
494            .get("www-authenticate")
495            .unwrap()
496            .to_str()
497            .unwrap();
498        assert!(www_auth.contains("Basic"));
499
500        let body = read_body(resp).await;
501        assert_eq!(body["error"], "invalid_client");
502    }
503
504    #[tokio::test]
505    async fn wrong_client_secret_returns_401() {
506        let (ath, app) = test_app().await;
507        let (application, _, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
508
509        let body = build_token_body(
510            &application,
511            "wrong_secret",
512            &code,
513            &verifier,
514            &redirect_uri,
515        );
516        let req = Request::builder()
517            .method("POST")
518            .uri("/oauth/token")
519            .header("content-type", "application/x-www-form-urlencoded")
520            .body(Body::from(body))
521            .unwrap();
522        let resp = app.oneshot(req).await.unwrap();
523        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
524    }
525
526    #[tokio::test]
527    async fn basic_auth_valid() {
528        let (ath, app) = test_app().await;
529        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
530
531        let credentials = format!("{}:{}", application.client_id.as_str(), secret);
532        let encoded = Base64::encode_string(credentials.as_bytes());
533
534        let body = url::form_urlencoded::Serializer::new(String::new())
535            .append_pair("grant_type", "authorization_code")
536            .append_pair("code", &code)
537            .append_pair("redirect_uri", &redirect_uri)
538            .append_pair("code_verifier", &verifier)
539            .finish();
540
541        let req = Request::builder()
542            .method("POST")
543            .uri("/oauth/token")
544            .header("content-type", "application/x-www-form-urlencoded")
545            .header("authorization", format!("Basic {encoded}"))
546            .body(Body::from(body))
547            .unwrap();
548        let resp = app.oneshot(req).await.unwrap();
549        assert_eq!(resp.status(), StatusCode::OK);
550    }
551
552    #[tokio::test]
553    async fn basic_auth_malformed_returns_401() {
554        let (_ath, app) = test_app().await;
555
556        let body = url::form_urlencoded::Serializer::new(String::new())
557            .append_pair("grant_type", "authorization_code")
558            .append_pair("code", "test")
559            .append_pair("redirect_uri", "https://example.com/callback")
560            .append_pair("code_verifier", "test")
561            .finish();
562
563        let req = Request::builder()
564            .method("POST")
565            .uri("/oauth/token")
566            .header("content-type", "application/x-www-form-urlencoded")
567            .header("authorization", "Basic not-valid-base64!!!")
568            .body(Body::from(body))
569            .unwrap();
570        let resp = app.oneshot(req).await.unwrap();
571        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
572    }
573
574    #[tokio::test]
575    async fn wrong_code_verifier_returns_400() {
576        let (ath, app) = test_app().await;
577        let (application, secret, code, _, redirect_uri) = setup_code_exchange(&ath).await;
578
579        let body = build_token_body(
580            &application,
581            &secret,
582            &code,
583            "wrong_verifier",
584            &redirect_uri,
585        );
586        let req = Request::builder()
587            .method("POST")
588            .uri("/oauth/token")
589            .header("content-type", "application/x-www-form-urlencoded")
590            .body(Body::from(body))
591            .unwrap();
592        let resp = app.oneshot(req).await.unwrap();
593        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
594        let body = read_body(resp).await;
595        assert_eq!(body["error"], "invalid_grant");
596    }
597
598    #[tokio::test]
599    async fn wrong_redirect_uri_returns_400() {
600        let (ath, app) = test_app().await;
601        let (application, secret, code, verifier, _) = setup_code_exchange(&ath).await;
602
603        let body = build_token_body(
604            &application,
605            &secret,
606            &code,
607            &verifier,
608            "https://evil.example.com/callback",
609        );
610        let req = Request::builder()
611            .method("POST")
612            .uri("/oauth/token")
613            .header("content-type", "application/x-www-form-urlencoded")
614            .body(Body::from(body))
615            .unwrap();
616        let resp = app.oneshot(req).await.unwrap();
617        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
618        let body = read_body(resp).await;
619        assert_eq!(body["error"], "invalid_grant");
620    }
621
622    #[tokio::test]
623    async fn wrong_grant_type_returns_400() {
624        let (ath, app) = test_app().await;
625        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
626
627        let body = url::form_urlencoded::Serializer::new(String::new())
628            .append_pair("grant_type", "client_credentials")
629            .append_pair("code", &code)
630            .append_pair("redirect_uri", &redirect_uri)
631            .append_pair("code_verifier", &verifier)
632            .append_pair("client_id", application.client_id.as_str())
633            .append_pair("client_secret", &secret)
634            .finish();
635
636        let req = Request::builder()
637            .method("POST")
638            .uri("/oauth/token")
639            .header("content-type", "application/x-www-form-urlencoded")
640            .body(Body::from(body))
641            .unwrap();
642        let resp = app.oneshot(req).await.unwrap();
643        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
644        let body = read_body(resp).await;
645        assert_eq!(body["error"], "unsupported_grant_type");
646    }
647
648    #[tokio::test]
649    async fn missing_code_returns_400() {
650        let (ath, app) = test_app().await;
651        let (application, secret, _, verifier, redirect_uri) = setup_code_exchange(&ath).await;
652
653        let body = url::form_urlencoded::Serializer::new(String::new())
654            .append_pair("grant_type", "authorization_code")
655            .append_pair("redirect_uri", &redirect_uri)
656            .append_pair("code_verifier", &verifier)
657            .append_pair("client_id", application.client_id.as_str())
658            .append_pair("client_secret", &secret)
659            .finish();
660
661        let req = Request::builder()
662            .method("POST")
663            .uri("/oauth/token")
664            .header("content-type", "application/x-www-form-urlencoded")
665            .body(Body::from(body))
666            .unwrap();
667        let resp = app.oneshot(req).await.unwrap();
668        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
669        let body = read_body(resp).await;
670        assert_eq!(body["error"], "invalid_request");
671    }
672
673    #[tokio::test]
674    async fn missing_redirect_uri_returns_400() {
675        let (ath, app) = test_app().await;
676        let (application, secret, code, verifier, _) = setup_code_exchange(&ath).await;
677
678        let body = url::form_urlencoded::Serializer::new(String::new())
679            .append_pair("grant_type", "authorization_code")
680            .append_pair("code", &code)
681            .append_pair("code_verifier", &verifier)
682            .append_pair("client_id", application.client_id.as_str())
683            .append_pair("client_secret", &secret)
684            .finish();
685
686        let req = Request::builder()
687            .method("POST")
688            .uri("/oauth/token")
689            .header("content-type", "application/x-www-form-urlencoded")
690            .body(Body::from(body))
691            .unwrap();
692        let resp = app.oneshot(req).await.unwrap();
693        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
694        let body = read_body(resp).await;
695        assert_eq!(body["error"], "invalid_request");
696    }
697
698    #[tokio::test]
699    async fn missing_code_verifier_returns_400() {
700        let (ath, app) = test_app().await;
701        let (application, secret, code, _, redirect_uri) = setup_code_exchange(&ath).await;
702
703        let body = url::form_urlencoded::Serializer::new(String::new())
704            .append_pair("grant_type", "authorization_code")
705            .append_pair("code", &code)
706            .append_pair("redirect_uri", &redirect_uri)
707            .append_pair("client_id", application.client_id.as_str())
708            .append_pair("client_secret", &secret)
709            .finish();
710
711        let req = Request::builder()
712            .method("POST")
713            .uri("/oauth/token")
714            .header("content-type", "application/x-www-form-urlencoded")
715            .body(Body::from(body))
716            .unwrap();
717        let resp = app.oneshot(req).await.unwrap();
718        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
719        let body = read_body(resp).await;
720        assert_eq!(body["error"], "invalid_request");
721    }
722
723    #[tokio::test]
724    async fn missing_client_credentials_returns_401() {
725        let (_ath, app) = test_app().await;
726
727        let body = url::form_urlencoded::Serializer::new(String::new())
728            .append_pair("grant_type", "authorization_code")
729            .append_pair("code", "test")
730            .append_pair("redirect_uri", "https://example.com/callback")
731            .append_pair("code_verifier", "test")
732            .finish();
733
734        let req = Request::builder()
735            .method("POST")
736            .uri("/oauth/token")
737            .header("content-type", "application/x-www-form-urlencoded")
738            .body(Body::from(body))
739            .unwrap();
740        let resp = app.oneshot(req).await.unwrap();
741        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
742        let body = read_body(resp).await;
743        assert_eq!(body["error"], "invalid_client");
744    }
745
746    #[tokio::test]
747    async fn success_response_has_pragma_no_cache() {
748        let (ath, app) = test_app().await;
749        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
750        let body = build_token_body(&application, &secret, &code, &verifier, &redirect_uri);
751
752        let req = Request::builder()
753            .method("POST")
754            .uri("/oauth/token")
755            .header("content-type", "application/x-www-form-urlencoded")
756            .body(Body::from(body))
757            .unwrap();
758        let resp = app.oneshot(req).await.unwrap();
759        assert_eq!(resp.status(), StatusCode::OK);
760
761        let pragma = resp.headers().get("pragma").unwrap().to_str().unwrap();
762        assert_eq!(pragma, "no-cache");
763    }
764
765    fn build_refresh_body(
766        application: &allowthem_core::applications::Application,
767        secret: &str,
768        refresh_token: &str,
769    ) -> String {
770        url::form_urlencoded::Serializer::new(String::new())
771            .append_pair("grant_type", "refresh_token")
772            .append_pair("refresh_token", refresh_token)
773            .append_pair("client_id", application.client_id.as_str())
774            .append_pair("client_secret", secret)
775            .finish()
776    }
777
778    #[tokio::test]
779    async fn refresh_token_grant_returns_200() {
780        let (ath, app) = test_app().await;
781        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
782
783        // First: exchange authorization code
784        let body = build_token_body(&application, &secret, &code, &verifier, &redirect_uri);
785        let req = Request::builder()
786            .method("POST")
787            .uri("/oauth/token")
788            .header("content-type", "application/x-www-form-urlencoded")
789            .body(Body::from(body))
790            .unwrap();
791        let resp = app.clone().oneshot(req).await.unwrap();
792        let initial = read_body(resp).await;
793        let refresh_token = initial["refresh_token"].as_str().unwrap().to_string();
794
795        // Second: use the refresh token
796        let body = build_refresh_body(&application, &secret, &refresh_token);
797        let req = Request::builder()
798            .method("POST")
799            .uri("/oauth/token")
800            .header("content-type", "application/x-www-form-urlencoded")
801            .body(Body::from(body))
802            .unwrap();
803        let resp = app.oneshot(req).await.unwrap();
804        assert_eq!(resp.status(), StatusCode::OK);
805
806        let cache_control = resp
807            .headers()
808            .get("cache-control")
809            .unwrap()
810            .to_str()
811            .unwrap();
812        assert_eq!(cache_control, "no-store");
813
814        let json = read_body(resp).await;
815        assert_eq!(json["token_type"], "Bearer");
816        assert_eq!(json["expires_in"], 3600);
817        assert!(json["access_token"].is_string());
818        assert!(json["refresh_token"].is_string());
819        assert!(json["id_token"].is_string());
820    }
821
822    #[tokio::test]
823    async fn refresh_token_new_token_differs_from_old() {
824        let (ath, app) = test_app().await;
825        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
826
827        let body = build_token_body(&application, &secret, &code, &verifier, &redirect_uri);
828        let req = Request::builder()
829            .method("POST")
830            .uri("/oauth/token")
831            .header("content-type", "application/x-www-form-urlencoded")
832            .body(Body::from(body))
833            .unwrap();
834        let resp = app.clone().oneshot(req).await.unwrap();
835        let first = read_body(resp).await;
836        let first_refresh = first["refresh_token"].as_str().unwrap().to_string();
837
838        let body = build_refresh_body(&application, &secret, &first_refresh);
839        let req = Request::builder()
840            .method("POST")
841            .uri("/oauth/token")
842            .header("content-type", "application/x-www-form-urlencoded")
843            .body(Body::from(body))
844            .unwrap();
845        let resp = app.oneshot(req).await.unwrap();
846        let second = read_body(resp).await;
847        let second_refresh = second["refresh_token"].as_str().unwrap().to_string();
848
849        assert_ne!(
850            first_refresh, second_refresh,
851            "rotated refresh token must differ"
852        );
853    }
854
855    #[tokio::test]
856    async fn refresh_token_missing_returns_400() {
857        let (ath, app) = test_app().await;
858        let (application, secret, _, _, _) = setup_code_exchange(&ath).await;
859
860        let body = url::form_urlencoded::Serializer::new(String::new())
861            .append_pair("grant_type", "refresh_token")
862            .append_pair("client_id", application.client_id.as_str())
863            .append_pair("client_secret", &secret)
864            .finish();
865
866        let req = Request::builder()
867            .method("POST")
868            .uri("/oauth/token")
869            .header("content-type", "application/x-www-form-urlencoded")
870            .body(Body::from(body))
871            .unwrap();
872        let resp = app.oneshot(req).await.unwrap();
873        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
874        let json = read_body(resp).await;
875        assert_eq!(json["error"], "invalid_request");
876    }
877
878    #[tokio::test]
879    async fn refresh_token_reuse_returns_400() {
880        let (ath, app) = test_app().await;
881        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
882
883        let body = build_token_body(&application, &secret, &code, &verifier, &redirect_uri);
884        let req = Request::builder()
885            .method("POST")
886            .uri("/oauth/token")
887            .header("content-type", "application/x-www-form-urlencoded")
888            .body(Body::from(body))
889            .unwrap();
890        let resp = app.clone().oneshot(req).await.unwrap();
891        let initial = read_body(resp).await;
892        let refresh_token = initial["refresh_token"].as_str().unwrap().to_string();
893
894        // First use — succeeds, revokes old token
895        let body = build_refresh_body(&application, &secret, &refresh_token);
896        let req = Request::builder()
897            .method("POST")
898            .uri("/oauth/token")
899            .header("content-type", "application/x-www-form-urlencoded")
900            .body(Body::from(body))
901            .unwrap();
902        let _ = app.clone().oneshot(req).await.unwrap();
903
904        // Second use — fails, token was revoked
905        let body = build_refresh_body(&application, &secret, &refresh_token);
906        let req = Request::builder()
907            .method("POST")
908            .uri("/oauth/token")
909            .header("content-type", "application/x-www-form-urlencoded")
910            .body(Body::from(body))
911            .unwrap();
912        let resp = app.oneshot(req).await.unwrap();
913        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
914        let json = read_body(resp).await;
915        assert_eq!(json["error"], "invalid_grant");
916    }
917
918    #[tokio::test]
919    async fn refresh_token_invalid_token_returns_400() {
920        let (ath, app) = test_app().await;
921        let (application, secret, _, _, _) = setup_code_exchange(&ath).await;
922
923        let body = build_refresh_body(&application, &secret, "totally_garbage_token");
924        let req = Request::builder()
925            .method("POST")
926            .uri("/oauth/token")
927            .header("content-type", "application/x-www-form-urlencoded")
928            .body(Body::from(body))
929            .unwrap();
930        let resp = app.oneshot(req).await.unwrap();
931        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
932        let json = read_body(resp).await;
933        assert_eq!(json["error"], "invalid_grant");
934    }
935
936    #[tokio::test]
937    async fn refresh_token_wrong_client_returns_400() {
938        let (ath, app) = test_app().await;
939        let (application, secret, code, verifier, redirect_uri) = setup_code_exchange(&ath).await;
940
941        // Obtain refresh token for app_a
942        let body = build_token_body(&application, &secret, &code, &verifier, &redirect_uri);
943        let req = Request::builder()
944            .method("POST")
945            .uri("/oauth/token")
946            .header("content-type", "application/x-www-form-urlencoded")
947            .body(Body::from(body))
948            .unwrap();
949        let resp = app.clone().oneshot(req).await.unwrap();
950        let initial = read_body(resp).await;
951        let refresh_token = initial["refresh_token"].as_str().unwrap().to_string();
952
953        // Create app_b
954        let email_b = allowthem_core::types::Email::new("other_http@example.com".into()).unwrap();
955        let user_b = ath
956            .db()
957            .create_user(email_b, "password123", None, None)
958            .await
959            .unwrap();
960        let (app_b, secret_b) = ath
961            .db()
962            .create_application(
963                "OtherApp".to_string(),
964                vec!["https://other.example.com/callback".to_string()],
965                false,
966                Some(user_b.id),
967                None,
968                None,
969            )
970            .await
971            .unwrap();
972        let raw_secret_b = secret_b.as_str().to_string();
973
974        // Try to use app_a's refresh token as app_b
975        let body = build_refresh_body(&app_b, &raw_secret_b, &refresh_token);
976        let req = Request::builder()
977            .method("POST")
978            .uri("/oauth/token")
979            .header("content-type", "application/x-www-form-urlencoded")
980            .body(Body::from(body))
981            .unwrap();
982        let resp = app.oneshot(req).await.unwrap();
983        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
984        let json = read_body(resp).await;
985        assert_eq!(json["error"], "invalid_grant");
986    }
987
988    #[tokio::test]
989    async fn unsupported_grant_type_returns_400() {
990        let (ath, app) = test_app().await;
991        let (application, secret, _, _, _) = setup_code_exchange(&ath).await;
992
993        let body = url::form_urlencoded::Serializer::new(String::new())
994            .append_pair("grant_type", "client_credentials")
995            .append_pair("client_id", application.client_id.as_str())
996            .append_pair("client_secret", &secret)
997            .finish();
998
999        let req = Request::builder()
1000            .method("POST")
1001            .uri("/oauth/token")
1002            .header("content-type", "application/x-www-form-urlencoded")
1003            .body(Body::from(body))
1004            .unwrap();
1005        let resp = app.oneshot(req).await.unwrap();
1006        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1007        let json = read_body(resp).await;
1008        assert_eq!(json["error"], "unsupported_grant_type");
1009    }
1010}