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