Skip to main content

allowthem_server/
token_route.rs

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