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#[derive(Deserialize)]
23struct TokenParams {
24 grant_type: Option<String>,
25 code: Option<String>,
27 redirect_uri: Option<String>,
28 code_verifier: Option<String>,
29 refresh_token: Option<String>,
31 scope: Option<String>,
32 client_id: Option<ClientId>,
34 client_secret: Option<String>,
35}
36
37fn 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
80fn 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
122async fn token(
127 Extension(ath): Extension<AllowThem>,
128 headers: HeaderMap,
129 Form(params): Form<TokenParams>,
130) -> Response {
131 let (client_id, maybe_secret) = match extract_client_credentials(&headers, ¶ms) {
133 Ok(creds) => creds,
134 Err(e) => return token_error_response(&e),
135 };
136
137 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 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 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 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 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
319pub fn token_route() -> Router<()> {
324 Router::new().route("/oauth/token", post(token))
325}
326
327#[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 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 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 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 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 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 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 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}