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