1use std::sync::{Arc, LazyLock};
8
9use axum::{Json, extract::State, response::IntoResponse};
10use axum_extra::typed_header::TypedHeader;
11use chrono::Duration;
12use headers::{CacheControl, HeaderMap, HeaderMapExt, Pragma};
13use hyper::StatusCode;
14use mas_axum_utils::{
15 client_authorization::{ClientAuthorization, CredentialsVerificationError},
16 record_error,
17};
18use mas_data_model::{
19 AuthorizationGrantStage, Client, Device, DeviceCodeGrantState, SiteConfig, TokenType,
20};
21use mas_keystore::{Encrypter, Keystore};
22use mas_matrix::HomeserverConnection;
23use mas_oidc_client::types::scope::ScopeToken;
24use mas_policy::Policy;
25use mas_router::UrlBuilder;
26use mas_storage::{
27 BoxClock, BoxRepository, BoxRng, Clock, RepositoryAccess,
28 oauth2::{
29 OAuth2AccessTokenRepository, OAuth2AuthorizationGrantRepository,
30 OAuth2RefreshTokenRepository, OAuth2SessionRepository,
31 },
32 user::BrowserSessionRepository,
33};
34use oauth2_types::{
35 errors::{ClientError, ClientErrorCode},
36 pkce::CodeChallengeError,
37 requests::{
38 AccessTokenRequest, AccessTokenResponse, AuthorizationCodeGrant, ClientCredentialsGrant,
39 DeviceCodeGrant, GrantType, RefreshTokenGrant,
40 },
41 scope,
42};
43use opentelemetry::{Key, KeyValue, metrics::Counter};
44use thiserror::Error;
45use tracing::{debug, info, warn};
46use ulid::Ulid;
47
48use super::{generate_id_token, generate_token_pair};
49use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
50
51static TOKEN_REQUEST_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
52 METER
53 .u64_counter("mas.oauth2.token_request")
54 .with_description("How many OAuth 2.0 token requests have gone through")
55 .with_unit("{request}")
56 .build()
57});
58const GRANT_TYPE: Key = Key::from_static_str("grant_type");
59const RESULT: Key = Key::from_static_str("successful");
60
61#[derive(Debug, Error)]
62pub(crate) enum RouteError {
63 #[error(transparent)]
64 Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
65
66 #[error("bad request")]
67 BadRequest,
68
69 #[error("pkce verification failed")]
70 PkceVerification(#[from] CodeChallengeError),
71
72 #[error("client not found")]
73 ClientNotFound,
74
75 #[error("client not allowed to use the token endpoint: {0}")]
76 ClientNotAllowed(Ulid),
77
78 #[error("invalid client credentials for client {client_id}")]
79 InvalidClientCredentials {
80 client_id: Ulid,
81 #[source]
82 source: CredentialsVerificationError,
83 },
84
85 #[error("could not verify client credentials for client {client_id}")]
86 ClientCredentialsVerification {
87 client_id: Ulid,
88 #[source]
89 source: CredentialsVerificationError,
90 },
91
92 #[error("grant not found")]
93 GrantNotFound,
94
95 #[error("invalid grant {0}")]
96 InvalidGrant(Ulid),
97
98 #[error("refresh token not found")]
99 RefreshTokenNotFound,
100
101 #[error("refresh token {0} is invalid")]
102 RefreshTokenInvalid(Ulid),
103
104 #[error("session {0} is invalid")]
105 SessionInvalid(Ulid),
106
107 #[error("client id mismatch: expected {expected}, got {actual}")]
108 ClientIDMismatch { expected: Ulid, actual: Ulid },
109
110 #[error("policy denied the request: {0}")]
111 DeniedByPolicy(mas_policy::EvaluationResult),
112
113 #[error("unsupported grant type")]
114 UnsupportedGrantType,
115
116 #[error("client {0} is not authorized to use this grant type")]
117 UnauthorizedClient(Ulid),
118
119 #[error("unexpected client {was} (expected {expected})")]
120 UnexptectedClient { was: Ulid, expected: Ulid },
121
122 #[error("failed to load browser session {0}")]
123 NoSuchBrowserSession(Ulid),
124
125 #[error("failed to load oauth session {0}")]
126 NoSuchOAuthSession(Ulid),
127
128 #[error(
129 "failed to load the next refresh token ({next:?}) from the previous one ({previous:?})"
130 )]
131 NoSuchNextRefreshToken { next: Ulid, previous: Ulid },
132
133 #[error(
134 "failed to load the access token ({access_token:?}) associated with the next refresh token ({refresh_token:?})"
135 )]
136 NoSuchNextAccessToken {
137 access_token: Ulid,
138 refresh_token: Ulid,
139 },
140
141 #[error("no access token associated with the refresh token {refresh_token:?}")]
142 NoAccessTokenOnRefreshToken { refresh_token: Ulid },
143
144 #[error("device code grant expired")]
145 DeviceCodeExpired,
146
147 #[error("device code grant is still pending")]
148 DeviceCodePending,
149
150 #[error("device code grant was rejected")]
151 DeviceCodeRejected,
152
153 #[error("device code grant was already exchanged")]
154 DeviceCodeExchanged,
155
156 #[error("failed to provision device")]
157 ProvisionDeviceFailed(#[source] anyhow::Error),
158}
159
160impl IntoResponse for RouteError {
161 fn into_response(self) -> axum::response::Response {
162 let sentry_event_id = record_error!(
163 self,
164 Self::Internal(_)
165 | Self::ClientCredentialsVerification { .. }
166 | Self::NoSuchBrowserSession(_)
167 | Self::NoSuchOAuthSession(_)
168 | Self::ProvisionDeviceFailed(_)
169 | Self::NoSuchNextRefreshToken { .. }
170 | Self::NoSuchNextAccessToken { .. }
171 | Self::NoAccessTokenOnRefreshToken { .. }
172 );
173
174 TOKEN_REQUEST_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
175
176 let response = match self {
177 Self::Internal(_)
178 | Self::ClientCredentialsVerification { .. }
179 | Self::NoSuchBrowserSession(_)
180 | Self::NoSuchOAuthSession(_)
181 | Self::ProvisionDeviceFailed(_)
182 | Self::NoSuchNextRefreshToken { .. }
183 | Self::NoSuchNextAccessToken { .. }
184 | Self::NoAccessTokenOnRefreshToken { .. } => (
185 StatusCode::INTERNAL_SERVER_ERROR,
186 Json(ClientError::from(ClientErrorCode::ServerError)),
187 ),
188
189 Self::BadRequest => (
190 StatusCode::BAD_REQUEST,
191 Json(ClientError::from(ClientErrorCode::InvalidRequest)),
192 ),
193
194 Self::PkceVerification(err) => (
195 StatusCode::BAD_REQUEST,
196 Json(
197 ClientError::from(ClientErrorCode::InvalidGrant)
198 .with_description(format!("PKCE verification failed: {err}")),
199 ),
200 ),
201
202 Self::ClientNotFound | Self::InvalidClientCredentials { .. } => (
203 StatusCode::UNAUTHORIZED,
204 Json(ClientError::from(ClientErrorCode::InvalidClient)),
205 ),
206
207 Self::ClientNotAllowed(_)
208 | Self::UnauthorizedClient(_)
209 | Self::UnexptectedClient { .. } => (
210 StatusCode::UNAUTHORIZED,
211 Json(ClientError::from(ClientErrorCode::UnauthorizedClient)),
212 ),
213
214 Self::DeniedByPolicy(evaluation) => (
215 StatusCode::FORBIDDEN,
216 Json(
217 ClientError::from(ClientErrorCode::InvalidScope).with_description(
218 evaluation
219 .violations
220 .into_iter()
221 .map(|violation| violation.msg)
222 .collect::<Vec<_>>()
223 .join(", "),
224 ),
225 ),
226 ),
227
228 Self::DeviceCodeRejected => (
229 StatusCode::FORBIDDEN,
230 Json(ClientError::from(ClientErrorCode::AccessDenied)),
231 ),
232
233 Self::DeviceCodeExpired => (
234 StatusCode::FORBIDDEN,
235 Json(ClientError::from(ClientErrorCode::ExpiredToken)),
236 ),
237
238 Self::DeviceCodePending => (
239 StatusCode::FORBIDDEN,
240 Json(ClientError::from(ClientErrorCode::AuthorizationPending)),
241 ),
242
243 Self::InvalidGrant(_)
244 | Self::DeviceCodeExchanged
245 | Self::RefreshTokenNotFound
246 | Self::RefreshTokenInvalid(_)
247 | Self::SessionInvalid(_)
248 | Self::ClientIDMismatch { .. }
249 | Self::GrantNotFound => (
250 StatusCode::BAD_REQUEST,
251 Json(ClientError::from(ClientErrorCode::InvalidGrant)),
252 ),
253
254 Self::UnsupportedGrantType => (
255 StatusCode::BAD_REQUEST,
256 Json(ClientError::from(ClientErrorCode::UnsupportedGrantType)),
257 ),
258 };
259
260 (sentry_event_id, response).into_response()
261 }
262}
263
264impl_from_error_for_route!(mas_storage::RepositoryError);
265impl_from_error_for_route!(mas_policy::EvaluationError);
266impl_from_error_for_route!(super::IdTokenSignatureError);
267
268#[tracing::instrument(
269 name = "handlers.oauth2.token.post",
270 fields(client.id = client_authorization.client_id()),
271 skip_all,
272)]
273pub(crate) async fn post(
274 mut rng: BoxRng,
275 clock: BoxClock,
276 State(http_client): State<reqwest::Client>,
277 State(key_store): State<Keystore>,
278 State(url_builder): State<UrlBuilder>,
279 activity_tracker: BoundActivityTracker,
280 mut repo: BoxRepository,
281 State(homeserver): State<Arc<dyn HomeserverConnection>>,
282 State(site_config): State<SiteConfig>,
283 State(encrypter): State<Encrypter>,
284 policy: Policy,
285 user_agent: Option<TypedHeader<headers::UserAgent>>,
286 client_authorization: ClientAuthorization<AccessTokenRequest>,
287) -> Result<impl IntoResponse, RouteError> {
288 let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
289 let client = client_authorization
290 .credentials
291 .fetch(&mut repo)
292 .await?
293 .ok_or(RouteError::ClientNotFound)?;
294
295 let method = client
296 .token_endpoint_auth_method
297 .as_ref()
298 .ok_or(RouteError::ClientNotAllowed(client.id))?;
299
300 client_authorization
301 .credentials
302 .verify(&http_client, &encrypter, method, &client)
303 .await
304 .map_err(|err| {
305 if err.is_internal() {
308 RouteError::ClientCredentialsVerification {
309 client_id: client.id,
310 source: err,
311 }
312 } else {
313 RouteError::InvalidClientCredentials {
314 client_id: client.id,
315 source: err,
316 }
317 }
318 })?;
319
320 let form = client_authorization.form.ok_or(RouteError::BadRequest)?;
321
322 let grant_type = form.grant_type();
323
324 let (reply, repo) = match form {
325 AccessTokenRequest::AuthorizationCode(grant) => {
326 authorization_code_grant(
327 &mut rng,
328 &clock,
329 &activity_tracker,
330 &grant,
331 &client,
332 &key_store,
333 &url_builder,
334 &site_config,
335 repo,
336 &homeserver,
337 user_agent,
338 )
339 .await?
340 }
341 AccessTokenRequest::RefreshToken(grant) => {
342 refresh_token_grant(
343 &mut rng,
344 &clock,
345 &activity_tracker,
346 &grant,
347 &client,
348 &site_config,
349 repo,
350 user_agent,
351 )
352 .await?
353 }
354 AccessTokenRequest::ClientCredentials(grant) => {
355 client_credentials_grant(
356 &mut rng,
357 &clock,
358 &activity_tracker,
359 &grant,
360 &client,
361 &site_config,
362 repo,
363 policy,
364 user_agent,
365 )
366 .await?
367 }
368 AccessTokenRequest::DeviceCode(grant) => {
369 device_code_grant(
370 &mut rng,
371 &clock,
372 &activity_tracker,
373 &grant,
374 &client,
375 &key_store,
376 &url_builder,
377 &site_config,
378 repo,
379 &homeserver,
380 user_agent,
381 )
382 .await?
383 }
384 _ => {
385 return Err(RouteError::UnsupportedGrantType);
386 }
387 };
388
389 repo.save().await?;
390
391 TOKEN_REQUEST_COUNTER.add(
392 1,
393 &[
394 KeyValue::new(GRANT_TYPE, grant_type),
395 KeyValue::new(RESULT, "success"),
396 ],
397 );
398
399 let mut headers = HeaderMap::new();
400 headers.typed_insert(CacheControl::new().with_no_store());
401 headers.typed_insert(Pragma::no_cache());
402
403 Ok((headers, Json(reply)))
404}
405
406#[allow(clippy::too_many_lines)] async fn authorization_code_grant(
408 mut rng: &mut BoxRng,
409 clock: &impl Clock,
410 activity_tracker: &BoundActivityTracker,
411 grant: &AuthorizationCodeGrant,
412 client: &Client,
413 key_store: &Keystore,
414 url_builder: &UrlBuilder,
415 site_config: &SiteConfig,
416 mut repo: BoxRepository,
417 homeserver: &Arc<dyn HomeserverConnection>,
418 user_agent: Option<String>,
419) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
420 if !client.grant_types.contains(&GrantType::AuthorizationCode) {
422 return Err(RouteError::UnauthorizedClient(client.id));
423 }
424
425 let authz_grant = repo
426 .oauth2_authorization_grant()
427 .find_by_code(&grant.code)
428 .await?
429 .ok_or(RouteError::GrantNotFound)?;
430
431 let now = clock.now();
432
433 let session_id = match authz_grant.stage {
434 AuthorizationGrantStage::Cancelled { cancelled_at } => {
435 debug!(%cancelled_at, "Authorization grant was cancelled");
436 return Err(RouteError::InvalidGrant(authz_grant.id));
437 }
438 AuthorizationGrantStage::Exchanged {
439 exchanged_at,
440 fulfilled_at,
441 session_id,
442 } => {
443 warn!(%exchanged_at, %fulfilled_at, "Authorization code was already exchanged");
444
445 if now - exchanged_at > Duration::microseconds(20 * 1000 * 1000) {
447 warn!(oauth_session.id = %session_id, "Ending potentially compromised session");
448 let session = repo
449 .oauth2_session()
450 .lookup(session_id)
451 .await?
452 .ok_or(RouteError::NoSuchOAuthSession(session_id))?;
453
454 repo.oauth2_session().finish(clock, session).await?;
456 repo.save().await?;
457 }
459
460 return Err(RouteError::InvalidGrant(authz_grant.id));
461 }
462 AuthorizationGrantStage::Pending => {
463 warn!("Authorization grant has not been fulfilled yet");
464 return Err(RouteError::InvalidGrant(authz_grant.id));
465 }
466 AuthorizationGrantStage::Fulfilled {
467 session_id,
468 fulfilled_at,
469 } => {
470 if now - fulfilled_at > Duration::microseconds(10 * 60 * 1000 * 1000) {
471 warn!("Code exchange took more than 10 minutes");
472 return Err(RouteError::InvalidGrant(authz_grant.id));
473 }
474
475 session_id
476 }
477 };
478
479 let mut session = repo
480 .oauth2_session()
481 .lookup(session_id)
482 .await?
483 .ok_or(RouteError::NoSuchOAuthSession(session_id))?;
484
485 if let Some(user_agent) = user_agent {
486 session = repo
487 .oauth2_session()
488 .record_user_agent(session, user_agent)
489 .await?;
490 }
491
492 let code = authz_grant
494 .code
495 .as_ref()
496 .ok_or(RouteError::InvalidGrant(authz_grant.id))?;
497
498 if client.id != session.client_id {
499 return Err(RouteError::UnexptectedClient {
500 was: client.id,
501 expected: session.client_id,
502 });
503 }
504
505 match (code.pkce.as_ref(), grant.code_verifier.as_ref()) {
506 (None, None) => {}
507 (Some(_), None) | (None, Some(_)) => return Err(RouteError::BadRequest),
509 (Some(pkce), Some(verifier)) => {
511 pkce.verify(verifier)?;
512 }
513 }
514
515 let Some(user_session_id) = session.user_session_id else {
516 tracing::warn!("No user session associated with this OAuth2 session");
517 return Err(RouteError::InvalidGrant(authz_grant.id));
518 };
519
520 let browser_session = repo
521 .browser_session()
522 .lookup(user_session_id)
523 .await?
524 .ok_or(RouteError::NoSuchBrowserSession(user_session_id))?;
525
526 let last_authentication = repo
527 .browser_session()
528 .get_last_authentication(&browser_session)
529 .await?;
530
531 let ttl = site_config.access_token_ttl;
532 let (access_token, refresh_token) =
533 generate_token_pair(&mut rng, clock, &mut repo, &session, ttl).await?;
534
535 let id_token = if session.scope.contains(&scope::OPENID) {
536 Some(generate_id_token(
537 &mut rng,
538 clock,
539 url_builder,
540 key_store,
541 client,
542 Some(&authz_grant),
543 &browser_session,
544 Some(&access_token),
545 last_authentication.as_ref(),
546 )?)
547 } else {
548 None
549 };
550
551 let mut params = AccessTokenResponse::new(access_token.access_token)
552 .with_expires_in(ttl)
553 .with_refresh_token(refresh_token.refresh_token)
554 .with_scope(session.scope.clone());
555
556 if let Some(id_token) = id_token {
557 params = params.with_id_token(id_token);
558 }
559
560 repo.user()
562 .acquire_lock_for_sync(&browser_session.user)
563 .await?;
564
565 let mxid = homeserver.mxid(&browser_session.user.username);
567 for scope in &*session.scope {
568 if let Some(device) = Device::from_scope_token(scope) {
569 homeserver
570 .create_device(&mxid, device.as_str())
571 .await
572 .map_err(RouteError::ProvisionDeviceFailed)?;
573 }
574 }
575
576 repo.oauth2_authorization_grant()
577 .exchange(clock, authz_grant)
578 .await?;
579
580 activity_tracker
584 .record_oauth2_session(clock, &session)
585 .await;
586
587 Ok((params, repo))
588}
589
590#[allow(clippy::too_many_lines)]
591async fn refresh_token_grant(
592 rng: &mut BoxRng,
593 clock: &impl Clock,
594 activity_tracker: &BoundActivityTracker,
595 grant: &RefreshTokenGrant,
596 client: &Client,
597 site_config: &SiteConfig,
598 mut repo: BoxRepository,
599 user_agent: Option<String>,
600) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
601 if !client.grant_types.contains(&GrantType::RefreshToken) {
603 return Err(RouteError::UnauthorizedClient(client.id));
604 }
605
606 let refresh_token = repo
607 .oauth2_refresh_token()
608 .find_by_token(&grant.refresh_token)
609 .await?
610 .ok_or(RouteError::RefreshTokenNotFound)?;
611
612 let mut session = repo
613 .oauth2_session()
614 .lookup(refresh_token.session_id)
615 .await?
616 .ok_or(RouteError::NoSuchOAuthSession(refresh_token.session_id))?;
617
618 if let Some(user_agent) = user_agent {
621 session = repo
622 .oauth2_session()
623 .record_user_agent(session, user_agent)
624 .await?;
625 }
626
627 if !session.is_valid() {
628 return Err(RouteError::SessionInvalid(session.id));
629 }
630
631 if client.id != session.client_id {
632 return Err(RouteError::ClientIDMismatch {
634 expected: session.client_id,
635 actual: client.id,
636 });
637 }
638
639 if !refresh_token.is_valid() {
640 let Some(next_refresh_token_id) = refresh_token.next_refresh_token_id() else {
645 return Err(RouteError::RefreshTokenInvalid(refresh_token.id));
648 };
649
650 let Some(next_refresh_token) = repo
651 .oauth2_refresh_token()
652 .lookup(next_refresh_token_id)
653 .await?
654 else {
655 return Err(RouteError::NoSuchNextRefreshToken {
656 next: next_refresh_token_id,
657 previous: refresh_token.id,
658 });
659 };
660
661 if !next_refresh_token.is_valid() {
663 return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
665 }
666
667 let Some(access_token_id) = next_refresh_token.access_token_id else {
669 return Err(RouteError::NoAccessTokenOnRefreshToken {
672 refresh_token: next_refresh_token.id,
673 });
674 };
675
676 let next_access_token = repo
678 .oauth2_access_token()
679 .lookup(access_token_id)
680 .await?
681 .ok_or(RouteError::NoSuchNextAccessToken {
682 access_token: access_token_id,
683 refresh_token: next_refresh_token_id,
684 })?;
685
686 if next_access_token.is_used() {
687 return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
689 }
690
691 info!(
695 oauth2_session.id = %session.id,
696 oauth2_client.id = %client.id,
697 %refresh_token.id,
698 "Refresh token already used, but issued refresh and access tokens are unused. Assuming those were lost; revoking those and reissuing new ones."
699 );
700
701 repo.oauth2_access_token()
702 .revoke(clock, next_access_token)
703 .await?;
704
705 repo.oauth2_refresh_token()
706 .revoke(clock, next_refresh_token)
707 .await?;
708 }
709
710 activity_tracker
711 .record_oauth2_session(clock, &session)
712 .await;
713
714 let ttl = site_config.access_token_ttl;
715 let (new_access_token, new_refresh_token) =
716 generate_token_pair(rng, clock, &mut repo, &session, ttl).await?;
717
718 let refresh_token = repo
719 .oauth2_refresh_token()
720 .consume(clock, refresh_token, &new_refresh_token)
721 .await?;
722
723 if let Some(access_token_id) = refresh_token.access_token_id {
724 let access_token = repo.oauth2_access_token().lookup(access_token_id).await?;
725 if let Some(access_token) = access_token {
726 if !access_token.state.is_revoked() {
728 repo.oauth2_access_token()
729 .revoke(clock, access_token)
730 .await?;
731 }
732 }
733 }
734
735 let params = AccessTokenResponse::new(new_access_token.access_token)
736 .with_expires_in(ttl)
737 .with_refresh_token(new_refresh_token.refresh_token)
738 .with_scope(session.scope);
739
740 Ok((params, repo))
741}
742
743async fn client_credentials_grant(
744 rng: &mut BoxRng,
745 clock: &impl Clock,
746 activity_tracker: &BoundActivityTracker,
747 grant: &ClientCredentialsGrant,
748 client: &Client,
749 site_config: &SiteConfig,
750 mut repo: BoxRepository,
751 mut policy: Policy,
752 user_agent: Option<String>,
753) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
754 if !client.grant_types.contains(&GrantType::ClientCredentials) {
756 return Err(RouteError::UnauthorizedClient(client.id));
757 }
758
759 let scope = grant
761 .scope
762 .clone()
763 .unwrap_or_else(|| std::iter::empty::<ScopeToken>().collect());
764
765 let res = policy
767 .evaluate_authorization_grant(mas_policy::AuthorizationGrantInput {
768 user: None,
769 client,
770 scope: &scope,
771 grant_type: mas_policy::GrantType::ClientCredentials,
772 requester: mas_policy::Requester {
773 ip_address: activity_tracker.ip(),
774 user_agent: user_agent.clone(),
775 },
776 })
777 .await?;
778 if !res.valid() {
779 return Err(RouteError::DeniedByPolicy(res));
780 }
781
782 let mut session = repo
784 .oauth2_session()
785 .add_from_client_credentials(rng, clock, client, scope)
786 .await?;
787
788 if let Some(user_agent) = user_agent {
789 session = repo
790 .oauth2_session()
791 .record_user_agent(session, user_agent)
792 .await?;
793 }
794
795 let ttl = site_config.access_token_ttl;
796 let access_token_str = TokenType::AccessToken.generate(rng);
797
798 let access_token = repo
799 .oauth2_access_token()
800 .add(rng, clock, &session, access_token_str, Some(ttl))
801 .await?;
802
803 let mut params = AccessTokenResponse::new(access_token.access_token).with_expires_in(ttl);
804
805 activity_tracker
809 .record_oauth2_session(clock, &session)
810 .await;
811
812 if !session.scope.is_empty() {
813 params = params.with_scope(session.scope);
815 }
816
817 Ok((params, repo))
818}
819
820async fn device_code_grant(
821 rng: &mut BoxRng,
822 clock: &impl Clock,
823 activity_tracker: &BoundActivityTracker,
824 grant: &DeviceCodeGrant,
825 client: &Client,
826 key_store: &Keystore,
827 url_builder: &UrlBuilder,
828 site_config: &SiteConfig,
829 mut repo: BoxRepository,
830 homeserver: &Arc<dyn HomeserverConnection>,
831 user_agent: Option<String>,
832) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
833 if !client.grant_types.contains(&GrantType::DeviceCode) {
835 return Err(RouteError::UnauthorizedClient(client.id));
836 }
837
838 let grant = repo
839 .oauth2_device_code_grant()
840 .find_by_device_code(&grant.device_code)
841 .await?
842 .ok_or(RouteError::GrantNotFound)?;
843
844 if client.id != grant.client_id {
846 return Err(RouteError::ClientIDMismatch {
847 expected: grant.client_id,
848 actual: client.id,
849 });
850 }
851
852 if grant.expires_at < clock.now() {
853 return Err(RouteError::DeviceCodeExpired);
854 }
855
856 let browser_session_id = match &grant.state {
857 DeviceCodeGrantState::Pending => {
858 return Err(RouteError::DeviceCodePending);
859 }
860 DeviceCodeGrantState::Rejected { .. } => {
861 return Err(RouteError::DeviceCodeRejected);
862 }
863 DeviceCodeGrantState::Exchanged { .. } => {
864 return Err(RouteError::DeviceCodeExchanged);
865 }
866 DeviceCodeGrantState::Fulfilled {
867 browser_session_id, ..
868 } => *browser_session_id,
869 };
870
871 let browser_session = repo
872 .browser_session()
873 .lookup(browser_session_id)
874 .await?
875 .ok_or(RouteError::NoSuchBrowserSession(browser_session_id))?;
876
877 let mut session = repo
879 .oauth2_session()
880 .add_from_browser_session(rng, clock, client, &browser_session, grant.scope.clone())
881 .await?;
882
883 repo.oauth2_device_code_grant()
884 .exchange(clock, grant, &session)
885 .await?;
886
887 if let Some(user_agent) = user_agent {
889 session = repo
890 .oauth2_session()
891 .record_user_agent(session, user_agent)
892 .await?;
893 }
894
895 let ttl = site_config.access_token_ttl;
896 let access_token_str = TokenType::AccessToken.generate(rng);
897
898 let access_token = repo
899 .oauth2_access_token()
900 .add(rng, clock, &session, access_token_str, Some(ttl))
901 .await?;
902
903 let mut params =
904 AccessTokenResponse::new(access_token.access_token.clone()).with_expires_in(ttl);
905
906 if client.grant_types.contains(&GrantType::RefreshToken) {
909 let refresh_token_str = TokenType::RefreshToken.generate(rng);
910
911 let refresh_token = repo
912 .oauth2_refresh_token()
913 .add(rng, clock, &session, &access_token, refresh_token_str)
914 .await?;
915
916 params = params.with_refresh_token(refresh_token.refresh_token);
917 }
918
919 if session.scope.contains(&scope::OPENID) {
921 let id_token = generate_id_token(
922 rng,
923 clock,
924 url_builder,
925 key_store,
926 client,
927 None,
928 &browser_session,
929 Some(&access_token),
930 None,
931 )?;
932
933 params = params.with_id_token(id_token);
934 }
935
936 repo.user()
938 .acquire_lock_for_sync(&browser_session.user)
939 .await?;
940
941 let mxid = homeserver.mxid(&browser_session.user.username);
943 for scope in &*session.scope {
944 if let Some(device) = Device::from_scope_token(scope) {
945 homeserver
946 .create_device(&mxid, device.as_str())
947 .await
948 .map_err(RouteError::ProvisionDeviceFailed)?;
949 }
950 }
951
952 activity_tracker
956 .record_oauth2_session(clock, &session)
957 .await;
958
959 if !session.scope.is_empty() {
960 params = params.with_scope(session.scope);
962 }
963
964 Ok((params, repo))
965}
966
967#[cfg(test)]
968mod tests {
969 use hyper::Request;
970 use mas_data_model::{AccessToken, AuthorizationCode, RefreshToken};
971 use mas_router::SimpleRoute;
972 use oauth2_types::{
973 registration::ClientRegistrationResponse,
974 requests::{DeviceAuthorizationResponse, ResponseMode},
975 scope::{OPENID, Scope},
976 };
977 use sqlx::PgPool;
978
979 use super::*;
980 use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
981
982 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
983 async fn test_auth_code_grant(pool: PgPool) {
984 setup();
985 let state = TestState::from_pool(pool).await.unwrap();
986
987 let request =
989 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
990 "client_uri": "https://example.com/",
991 "redirect_uris": ["https://example.com/callback"],
992 "token_endpoint_auth_method": "none",
993 "response_types": ["code"],
994 "grant_types": ["authorization_code"],
995 }));
996
997 let response = state.request(request).await;
998 response.assert_status(StatusCode::CREATED);
999
1000 let ClientRegistrationResponse { client_id, .. } = response.json();
1001
1002 let mut repo = state.repository().await.unwrap();
1005
1006 let user = repo
1007 .user()
1008 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1009 .await
1010 .unwrap();
1011
1012 let browser_session = repo
1013 .browser_session()
1014 .add(&mut state.rng(), &state.clock, &user, None)
1015 .await
1016 .unwrap();
1017
1018 let client = repo
1020 .oauth2_client()
1021 .find_by_client_id(&client_id)
1022 .await
1023 .unwrap()
1024 .unwrap();
1025
1026 let code = "thisisaverysecurecode";
1028 let grant = repo
1029 .oauth2_authorization_grant()
1030 .add(
1031 &mut state.rng(),
1032 &state.clock,
1033 &client,
1034 "https://example.com/redirect".parse().unwrap(),
1035 Scope::from_iter([OPENID]),
1036 Some(AuthorizationCode {
1037 code: code.to_owned(),
1038 pkce: None,
1039 }),
1040 Some("state".to_owned()),
1041 Some("nonce".to_owned()),
1042 ResponseMode::Query,
1043 false,
1044 None,
1045 )
1046 .await
1047 .unwrap();
1048
1049 let session = repo
1050 .oauth2_session()
1051 .add_from_browser_session(
1052 &mut state.rng(),
1053 &state.clock,
1054 &client,
1055 &browser_session,
1056 grant.scope.clone(),
1057 )
1058 .await
1059 .unwrap();
1060
1061 let grant = repo
1063 .oauth2_authorization_grant()
1064 .fulfill(&state.clock, &session, grant)
1065 .await
1066 .unwrap();
1067
1068 repo.save().await.unwrap();
1069
1070 let request =
1072 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1073 "grant_type": "authorization_code",
1074 "code": code,
1075 "redirect_uri": grant.redirect_uri,
1076 "client_id": client.client_id,
1077 }));
1078
1079 let response = state.request(request).await;
1080 response.assert_status(StatusCode::OK);
1081
1082 let AccessTokenResponse { access_token, .. } = response.json();
1083
1084 assert!(state.is_access_token_valid(&access_token).await);
1086
1087 let request =
1089 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1090 "grant_type": "authorization_code",
1091 "code": code,
1092 "redirect_uri": grant.redirect_uri,
1093 "client_id": client.client_id,
1094 }));
1095
1096 let response = state.request(request).await;
1097 response.assert_status(StatusCode::BAD_REQUEST);
1098 let error: ClientError = response.json();
1099 assert_eq!(error.error, ClientErrorCode::InvalidGrant);
1100
1101 assert!(state.is_access_token_valid(&access_token).await);
1103
1104 state.clock.advance(Duration::try_minutes(1).unwrap());
1106
1107 let request =
1109 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1110 "grant_type": "authorization_code",
1111 "code": code,
1112 "redirect_uri": grant.redirect_uri,
1113 "client_id": client.client_id,
1114 }));
1115
1116 let response = state.request(request).await;
1117 response.assert_status(StatusCode::BAD_REQUEST);
1118 let error: ClientError = response.json();
1119 assert_eq!(error.error, ClientErrorCode::InvalidGrant);
1120
1121 assert!(!state.is_access_token_valid(&access_token).await);
1123
1124 let mut repo = state.repository().await.unwrap();
1126 let code = "thisisanothercode";
1127 let grant = repo
1128 .oauth2_authorization_grant()
1129 .add(
1130 &mut state.rng(),
1131 &state.clock,
1132 &client,
1133 "https://example.com/redirect".parse().unwrap(),
1134 Scope::from_iter([OPENID]),
1135 Some(AuthorizationCode {
1136 code: code.to_owned(),
1137 pkce: None,
1138 }),
1139 Some("state".to_owned()),
1140 Some("nonce".to_owned()),
1141 ResponseMode::Query,
1142 false,
1143 None,
1144 )
1145 .await
1146 .unwrap();
1147
1148 let session = repo
1149 .oauth2_session()
1150 .add_from_browser_session(
1151 &mut state.rng(),
1152 &state.clock,
1153 &client,
1154 &browser_session,
1155 grant.scope.clone(),
1156 )
1157 .await
1158 .unwrap();
1159
1160 let grant = repo
1162 .oauth2_authorization_grant()
1163 .fulfill(&state.clock, &session, grant)
1164 .await
1165 .unwrap();
1166
1167 repo.save().await.unwrap();
1168
1169 state
1171 .clock
1172 .advance(Duration::microseconds(15 * 60 * 1000 * 1000));
1173
1174 let request =
1176 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1177 "grant_type": "authorization_code",
1178 "code": code,
1179 "redirect_uri": grant.redirect_uri,
1180 "client_id": client.client_id,
1181 }));
1182
1183 let response = state.request(request).await;
1184 response.assert_status(StatusCode::BAD_REQUEST);
1185 let ClientError { error, .. } = response.json();
1186 assert_eq!(error, ClientErrorCode::InvalidGrant);
1187 }
1188
1189 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1190 async fn test_refresh_token_grant(pool: PgPool) {
1191 setup();
1192 let state = TestState::from_pool(pool).await.unwrap();
1193
1194 let request =
1196 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1197 "client_uri": "https://example.com/",
1198 "redirect_uris": ["https://example.com/callback"],
1199 "token_endpoint_auth_method": "none",
1200 "response_types": ["code"],
1201 "grant_types": ["authorization_code", "refresh_token"],
1202 }));
1203
1204 let response = state.request(request).await;
1205 response.assert_status(StatusCode::CREATED);
1206
1207 let ClientRegistrationResponse { client_id, .. } = response.json();
1208
1209 let mut repo = state.repository().await.unwrap();
1212
1213 let user = repo
1214 .user()
1215 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1216 .await
1217 .unwrap();
1218
1219 let browser_session = repo
1220 .browser_session()
1221 .add(&mut state.rng(), &state.clock, &user, None)
1222 .await
1223 .unwrap();
1224
1225 let client = repo
1227 .oauth2_client()
1228 .find_by_client_id(&client_id)
1229 .await
1230 .unwrap()
1231 .unwrap();
1232
1233 let session = repo
1235 .oauth2_session()
1236 .add_from_browser_session(
1237 &mut state.rng(),
1238 &state.clock,
1239 &client,
1240 &browser_session,
1241 Scope::from_iter([OPENID]),
1242 )
1243 .await
1244 .unwrap();
1245
1246 let (AccessToken { access_token, .. }, RefreshToken { refresh_token, .. }) =
1247 generate_token_pair(
1248 &mut state.rng(),
1249 &state.clock,
1250 &mut repo,
1251 &session,
1252 Duration::microseconds(5 * 60 * 1000 * 1000),
1253 )
1254 .await
1255 .unwrap();
1256
1257 repo.save().await.unwrap();
1258
1259 assert!(state.is_access_token_valid(&access_token).await);
1261
1262 let request =
1264 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1265 "grant_type": "refresh_token",
1266 "refresh_token": refresh_token,
1267 "client_id": client.client_id,
1268 }));
1269
1270 let response = state.request(request).await;
1271 response.assert_status(StatusCode::OK);
1272
1273 let old_access_token = access_token;
1274 let old_refresh_token = refresh_token;
1275 let response: AccessTokenResponse = response.json();
1276 let access_token = response.access_token;
1277 let refresh_token = response.refresh_token.expect("to have a refresh token");
1278
1279 assert!(state.is_access_token_valid(&access_token).await);
1281
1282 assert!(!state.is_access_token_valid(&old_access_token).await);
1284
1285 let request =
1287 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1288 "grant_type": "refresh_token",
1289 "refresh_token": old_refresh_token,
1290 "client_id": client.client_id,
1291 }));
1292
1293 let response = state.request(request).await;
1294 response.assert_status(StatusCode::BAD_REQUEST);
1295 let ClientError { error, .. } = response.json();
1296 assert_eq!(error, ClientErrorCode::InvalidGrant);
1297
1298 let request =
1300 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1301 "grant_type": "refresh_token",
1302 "refresh_token": refresh_token,
1303 "client_id": client.client_id,
1304 }));
1305
1306 let response = state.request(request).await;
1307 response.assert_status(StatusCode::OK);
1308 let _: AccessTokenResponse = response.json();
1309 }
1310
1311 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1312 async fn test_double_refresh(pool: PgPool) {
1313 setup();
1314 let state = TestState::from_pool(pool).await.unwrap();
1315
1316 let request =
1318 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1319 "client_uri": "https://example.com/",
1320 "redirect_uris": ["https://example.com/callback"],
1321 "token_endpoint_auth_method": "none",
1322 "response_types": ["code"],
1323 "grant_types": ["authorization_code", "refresh_token"],
1324 }));
1325
1326 let response = state.request(request).await;
1327 response.assert_status(StatusCode::CREATED);
1328
1329 let ClientRegistrationResponse { client_id, .. } = response.json();
1330
1331 let mut repo = state.repository().await.unwrap();
1334
1335 let user = repo
1336 .user()
1337 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1338 .await
1339 .unwrap();
1340
1341 let browser_session = repo
1342 .browser_session()
1343 .add(&mut state.rng(), &state.clock, &user, None)
1344 .await
1345 .unwrap();
1346
1347 let client = repo
1349 .oauth2_client()
1350 .find_by_client_id(&client_id)
1351 .await
1352 .unwrap()
1353 .unwrap();
1354
1355 let session = repo
1357 .oauth2_session()
1358 .add_from_browser_session(
1359 &mut state.rng(),
1360 &state.clock,
1361 &client,
1362 &browser_session,
1363 Scope::from_iter([OPENID]),
1364 )
1365 .await
1366 .unwrap();
1367
1368 let (AccessToken { access_token, .. }, RefreshToken { refresh_token, .. }) =
1369 generate_token_pair(
1370 &mut state.rng(),
1371 &state.clock,
1372 &mut repo,
1373 &session,
1374 Duration::microseconds(5 * 60 * 1000 * 1000),
1375 )
1376 .await
1377 .unwrap();
1378
1379 repo.save().await.unwrap();
1380
1381 assert!(state.is_access_token_valid(&access_token).await);
1383
1384 let request =
1386 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1387 "grant_type": "refresh_token",
1388 "refresh_token": refresh_token,
1389 "client_id": client.client_id,
1390 }));
1391
1392 let first_response = state.request(request).await;
1393 first_response.assert_status(StatusCode::OK);
1394 let first_response: AccessTokenResponse = first_response.json();
1395
1396 let request =
1399 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1400 "grant_type": "refresh_token",
1401 "refresh_token": refresh_token,
1402 "client_id": client.client_id,
1403 }));
1404
1405 let second_response = state.request(request).await;
1406 second_response.assert_status(StatusCode::OK);
1407 let second_response: AccessTokenResponse = second_response.json();
1408
1409 assert_ne!(first_response.access_token, second_response.access_token);
1411 assert_ne!(first_response.refresh_token, second_response.refresh_token);
1412
1413 assert!(
1415 !state
1416 .is_access_token_valid(&first_response.access_token)
1417 .await
1418 );
1419
1420 assert!(
1422 state
1423 .is_access_token_valid(&second_response.access_token)
1424 .await
1425 );
1426
1427 let request =
1430 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1431 "grant_type": "refresh_token",
1432 "refresh_token": refresh_token,
1433 "client_id": client.client_id,
1434 }));
1435
1436 let third_response = state.request(request).await;
1437 third_response.assert_status(StatusCode::BAD_REQUEST);
1438
1439 let request =
1445 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1446 "grant_type": "refresh_token",
1447 "refresh_token": second_response.refresh_token,
1448 "client_id": client.client_id,
1449 }));
1450
1451 let fourth_response = state.request(request).await;
1453 fourth_response.assert_status(StatusCode::OK);
1454 let fourth_response: AccessTokenResponse = fourth_response.json();
1455
1456 let request =
1458 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1459 "grant_type": "refresh_token",
1460 "refresh_token": fourth_response.refresh_token,
1461 "client_id": client.client_id,
1462 }));
1463
1464 let fifth_response = state.request(request).await;
1465 fifth_response.assert_status(StatusCode::OK);
1466
1467 let request =
1470 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1471 "grant_type": "refresh_token",
1472 "refresh_token": second_response.refresh_token,
1473 "client_id": client.client_id,
1474 }));
1475
1476 let sixth_response = state.request(request).await;
1477 sixth_response.assert_status(StatusCode::BAD_REQUEST);
1478 }
1479
1480 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1481 async fn test_client_credentials(pool: PgPool) {
1482 setup();
1483 let state = TestState::from_pool(pool).await.unwrap();
1484
1485 let request =
1487 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1488 "client_uri": "https://example.com/",
1489 "token_endpoint_auth_method": "client_secret_post",
1490 "grant_types": ["client_credentials"],
1491 }));
1492
1493 let response = state.request(request).await;
1494 response.assert_status(StatusCode::CREATED);
1495
1496 let response: ClientRegistrationResponse = response.json();
1497 let client_id = response.client_id;
1498 let client_secret = response.client_secret.expect("to have a client secret");
1499
1500 let request =
1502 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1503 "grant_type": "client_credentials",
1504 "client_id": client_id,
1505 "client_secret": client_secret,
1506 }));
1507
1508 let response = state.request(request).await;
1509 response.assert_status(StatusCode::OK);
1510
1511 let response: AccessTokenResponse = response.json();
1512 assert!(response.refresh_token.is_none());
1513 assert!(response.expires_in.is_some());
1514 assert!(response.scope.is_none());
1515
1516 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1518 "token": response.access_token,
1519 "client_id": client_id,
1520 "client_secret": client_secret,
1521 }));
1522
1523 let response = state.request(request).await;
1524 response.assert_status(StatusCode::OK);
1525
1526 let request =
1528 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1529 "grant_type": "client_credentials",
1530 "client_id": client_id,
1531 "client_secret": client_secret,
1532 "scope": "urn:mas:graphql:*"
1533 }));
1534
1535 let response = state.request(request).await;
1536 response.assert_status(StatusCode::OK);
1537
1538 let response: AccessTokenResponse = response.json();
1539 assert!(response.refresh_token.is_none());
1540 assert!(response.expires_in.is_some());
1541 assert_eq!(response.scope, Some("urn:mas:graphql:*".parse().unwrap()));
1542
1543 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1545 "token": response.access_token,
1546 "client_id": client_id,
1547 "client_secret": client_secret,
1548 }));
1549
1550 let response = state.request(request).await;
1551 response.assert_status(StatusCode::OK);
1552
1553 let request =
1555 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1556 "grant_type": "client_credentials",
1557 "client_id": client_id,
1558 "client_secret": client_secret,
1559 "scope": "urn:mas:admin"
1560 }));
1561
1562 let response = state.request(request).await;
1563 response.assert_status(StatusCode::FORBIDDEN);
1564
1565 let ClientError { error, .. } = response.json();
1566 assert_eq!(error, ClientErrorCode::InvalidScope);
1567
1568 let state = {
1570 let mut state = state;
1571 state.policy_factory = crate::test_utils::policy_factory(
1572 "example.com",
1573 serde_json::json!({
1574 "admin_clients": [client_id]
1575 }),
1576 )
1577 .await
1578 .unwrap();
1579 state
1580 };
1581
1582 let request =
1583 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1584 "grant_type": "client_credentials",
1585 "client_id": client_id,
1586 "client_secret": client_secret,
1587 "scope": "urn:mas:admin"
1588 }));
1589
1590 let response = state.request(request).await;
1591 response.assert_status(StatusCode::OK);
1592
1593 let response: AccessTokenResponse = response.json();
1594 assert!(response.refresh_token.is_none());
1595 assert!(response.expires_in.is_some());
1596 assert_eq!(response.scope, Some("urn:mas:admin".parse().unwrap()));
1597
1598 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1600 "token": response.access_token,
1601 "client_id": client_id,
1602 "client_secret": client_secret,
1603 }));
1604
1605 let response = state.request(request).await;
1606 response.assert_status(StatusCode::OK);
1607 }
1608
1609 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1610 async fn test_device_code_grant(pool: PgPool) {
1611 setup();
1612 let state = TestState::from_pool(pool).await.unwrap();
1613
1614 let request =
1616 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1617 "client_uri": "https://example.com/",
1618 "token_endpoint_auth_method": "none",
1619 "grant_types": ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"],
1620 "response_types": [],
1621 }));
1622
1623 let response = state.request(request).await;
1624 response.assert_status(StatusCode::CREATED);
1625
1626 let response: ClientRegistrationResponse = response.json();
1627 let client_id = response.client_id;
1628
1629 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1631 serde_json::json!({
1632 "client_id": client_id,
1633 "scope": "openid",
1634 }),
1635 );
1636 let response = state.request(request).await;
1637 response.assert_status(StatusCode::OK);
1638
1639 let device_grant: DeviceAuthorizationResponse = response.json();
1640
1641 let request =
1643 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1644 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1645 "device_code": device_grant.device_code,
1646 "client_id": client_id,
1647 }));
1648 let response = state.request(request).await;
1649 response.assert_status(StatusCode::FORBIDDEN);
1650
1651 let ClientError { error, .. } = response.json();
1652 assert_eq!(error, ClientErrorCode::AuthorizationPending);
1653
1654 let mut repo = state.repository().await.unwrap();
1658
1659 let user = repo
1660 .user()
1661 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1662 .await
1663 .unwrap();
1664
1665 let browser_session = repo
1666 .browser_session()
1667 .add(&mut state.rng(), &state.clock, &user, None)
1668 .await
1669 .unwrap();
1670
1671 let grant = repo
1673 .oauth2_device_code_grant()
1674 .find_by_user_code(&device_grant.user_code)
1675 .await
1676 .unwrap()
1677 .unwrap();
1678
1679 let grant = repo
1681 .oauth2_device_code_grant()
1682 .fulfill(&state.clock, grant, &browser_session)
1683 .await
1684 .unwrap();
1685
1686 repo.save().await.unwrap();
1687
1688 let request =
1690 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1691 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1692 "device_code": grant.device_code,
1693 "client_id": client_id,
1694 }));
1695
1696 let response = state.request(request).await;
1697 response.assert_status(StatusCode::OK);
1698
1699 let response: AccessTokenResponse = response.json();
1700
1701 assert!(state.is_access_token_valid(&response.access_token).await);
1703 assert!(response.refresh_token.is_some());
1705 assert!(response.id_token.is_some());
1707
1708 let request =
1710 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1711 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1712 "device_code": grant.device_code,
1713 "client_id": client_id,
1714 }));
1715 let response = state.request(request).await;
1716 response.assert_status(StatusCode::BAD_REQUEST);
1717
1718 let ClientError { error, .. } = response.json();
1719 assert_eq!(error, ClientErrorCode::InvalidGrant);
1720
1721 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1723 serde_json::json!({
1724 "client_id": client_id,
1725 "scope": "openid",
1726 }),
1727 );
1728 let response = state.request(request).await;
1729 response.assert_status(StatusCode::OK);
1730
1731 let device_grant: DeviceAuthorizationResponse = response.json();
1732
1733 let request =
1735 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1736 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1737 "device_code": device_grant.device_code,
1738 "client_id": client_id,
1739 }));
1740 let response = state.request(request).await;
1741 response.assert_status(StatusCode::FORBIDDEN);
1742
1743 let ClientError { error, .. } = response.json();
1744 assert_eq!(error, ClientErrorCode::AuthorizationPending);
1745
1746 state.clock.advance(Duration::try_hours(1).unwrap());
1747
1748 let request =
1750 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1751 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1752 "device_code": device_grant.device_code,
1753 "client_id": client_id,
1754 }));
1755 let response = state.request(request).await;
1756 response.assert_status(StatusCode::FORBIDDEN);
1757
1758 let ClientError { error, .. } = response.json();
1759 assert_eq!(error, ClientErrorCode::ExpiredToken);
1760
1761 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1763 serde_json::json!({
1764 "client_id": client_id,
1765 "scope": "openid",
1766 }),
1767 );
1768 let response = state.request(request).await;
1769 response.assert_status(StatusCode::OK);
1770
1771 let device_grant: DeviceAuthorizationResponse = response.json();
1772
1773 let mut repo = state.repository().await.unwrap();
1775
1776 let grant = repo
1778 .oauth2_device_code_grant()
1779 .find_by_user_code(&device_grant.user_code)
1780 .await
1781 .unwrap()
1782 .unwrap();
1783
1784 let grant = repo
1786 .oauth2_device_code_grant()
1787 .reject(&state.clock, grant, &browser_session)
1788 .await
1789 .unwrap();
1790
1791 repo.save().await.unwrap();
1792
1793 let request =
1795 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1796 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1797 "device_code": grant.device_code,
1798 "client_id": client_id,
1799 }));
1800 let response = state.request(request).await;
1801 response.assert_status(StatusCode::FORBIDDEN);
1802
1803 let ClientError { error, .. } = response.json();
1804 assert_eq!(error, ClientErrorCode::AccessDenied);
1805 }
1806
1807 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1808 async fn test_unsupported_grant(pool: PgPool) {
1809 setup();
1810 let state = TestState::from_pool(pool).await.unwrap();
1811
1812 let request =
1814 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1815 "client_uri": "https://example.com/",
1816 "redirect_uris": ["https://example.com/callback"],
1817 "token_endpoint_auth_method": "client_secret_post",
1818 "grant_types": ["password"],
1819 "response_types": [],
1820 }));
1821
1822 let response = state.request(request).await;
1823 response.assert_status(StatusCode::CREATED);
1824
1825 let response: ClientRegistrationResponse = response.json();
1826 let client_id = response.client_id;
1827 let client_secret = response.client_secret.expect("to have a client secret");
1828
1829 let request =
1831 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1832 "grant_type": "password",
1833 "client_id": client_id,
1834 "client_secret": client_secret,
1835 "username": "john",
1836 "password": "hunter2",
1837 }));
1838
1839 let response = state.request(request).await;
1840 response.assert_status(StatusCode::BAD_REQUEST);
1841 let ClientError { error, .. } = response.json();
1842 assert_eq!(error, ClientErrorCode::UnsupportedGrantType);
1843 }
1844}