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#[derive(Deserialize)]
22struct TokenParams {
23 grant_type: Option<String>,
24 code: Option<String>,
26 redirect_uri: Option<String>,
27 code_verifier: Option<String>,
28 refresh_token: Option<String>,
30 scope: Option<String>,
31 client_id: Option<ClientId>,
33 client_secret: Option<String>,
34}
35
36fn 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
79fn 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
121async fn token(
126 Extension(ath): Extension<AllowThem>,
127 headers: HeaderMap,
128 Form(params): Form<TokenParams>,
129) -> Response {
130 let (client_id, maybe_secret) = match extract_client_credentials(&headers, ¶ms) {
132 Ok(creds) => creds,
133 Err(e) => return token_error_response(&e),
134 };
135
136 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 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 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 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 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
315pub fn token_route() -> Router<()> {
320 Router::new().route("/oauth/token", post(token))
321}
322
323#[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 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 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 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 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 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 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 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}