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