1use std::sync::{Arc, LazyLock};
9
10use axum::{Json, extract::State, response::IntoResponse};
11use axum_extra::typed_header::TypedHeader;
12use chrono::Duration;
13use headers::{CacheControl, HeaderMap, HeaderMapExt, Pragma};
14use hyper::StatusCode;
15use mas_axum_utils::{
16 RecordAsRequester,
17 client_authorization::{ClientAuthorization, CredentialsVerificationError},
18 record_error,
19};
20use mas_data_model::{
21 AuthorizationGrantStage, BoxClock, BoxRng, Client, Clock, Device, DeviceCodeGrantState,
22 SiteConfig, TokenType,
23};
24use mas_i18n::DataLocale;
25use mas_keystore::{Encrypter, Keystore};
26use mas_matrix::HomeserverConnection;
27use mas_oidc_client::types::scope::ScopeToken;
28use mas_policy::Policy;
29use mas_router::UrlBuilder;
30use mas_storage::{
31 BoxRepository, RepositoryAccess,
32 oauth2::{
33 OAuth2AccessTokenRepository, OAuth2AuthorizationGrantRepository,
34 OAuth2RefreshTokenRepository, OAuth2SessionRepository,
35 },
36 user::BrowserSessionRepository,
37};
38use mas_templates::{DeviceNameContext, TemplateContext, Templates};
39use oauth2_types::{
40 errors::{ClientError, ClientErrorCode},
41 pkce::CodeChallengeError,
42 requests::{
43 AccessTokenRequest, AccessTokenResponse, AuthorizationCodeGrant, ClientCredentialsGrant,
44 DeviceCodeGrant, GrantType, RefreshTokenGrant,
45 },
46 scope,
47};
48use opentelemetry::{Key, KeyValue, metrics::Counter};
49use thiserror::Error;
50use tracing::{debug, info, warn};
51use ulid::Ulid;
52
53use super::{generate_id_token, generate_token_pair};
54use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
55
56static TOKEN_REQUEST_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
57 METER
58 .u64_counter("mas.oauth2.token_request")
59 .with_description("How many OAuth 2.0 token requests have gone through")
60 .with_unit("{request}")
61 .build()
62});
63const GRANT_TYPE: Key = Key::from_static_str("grant_type");
64const RESULT: Key = Key::from_static_str("successful");
65
66#[derive(Debug, Error)]
67pub(crate) enum RouteError {
68 #[error(transparent)]
69 Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
70
71 #[error("bad request")]
72 BadRequest,
73
74 #[error("pkce verification failed")]
75 PkceVerification(#[from] CodeChallengeError),
76
77 #[error("client not found")]
78 ClientNotFound,
79
80 #[error("client not allowed to use the token endpoint: {0}")]
81 ClientNotAllowed(Ulid),
82
83 #[error("invalid client credentials for client {client_id}")]
84 InvalidClientCredentials {
85 client_id: Ulid,
86 #[source]
87 source: CredentialsVerificationError,
88 },
89
90 #[error("could not verify client credentials for client {client_id}")]
91 ClientCredentialsVerification {
92 client_id: Ulid,
93 #[source]
94 source: CredentialsVerificationError,
95 },
96
97 #[error("grant not found")]
98 GrantNotFound,
99
100 #[error("invalid grant {0}")]
101 InvalidGrant(Ulid),
102
103 #[error("refresh token not found")]
104 RefreshTokenNotFound,
105
106 #[error("refresh token {0} is invalid")]
107 RefreshTokenInvalid(Ulid),
108
109 #[error("session {0} is invalid")]
110 SessionInvalid(Ulid),
111
112 #[error("client id mismatch: expected {expected}, got {actual}")]
113 ClientIDMismatch { expected: Ulid, actual: Ulid },
114
115 #[error("policy denied the request: {0}")]
116 DeniedByPolicy(mas_policy::EvaluationResult),
117
118 #[error("unsupported grant type")]
119 UnsupportedGrantType,
120
121 #[error("client {0} is not authorized to use this grant type")]
122 UnauthorizedClient(Ulid),
123
124 #[error("unexpected client {was} (expected {expected})")]
125 UnexptectedClient { was: Ulid, expected: Ulid },
126
127 #[error("failed to load browser session {0}")]
128 NoSuchBrowserSession(Ulid),
129
130 #[error("failed to load oauth session {0}")]
131 NoSuchOAuthSession(Ulid),
132
133 #[error(
134 "failed to load the next refresh token ({next:?}) from the previous one ({previous:?})"
135 )]
136 NoSuchNextRefreshToken { next: Ulid, previous: Ulid },
137
138 #[error(
139 "failed to load the access token ({access_token:?}) associated with the next refresh token ({refresh_token:?})"
140 )]
141 NoSuchNextAccessToken {
142 access_token: Ulid,
143 refresh_token: Ulid,
144 },
145
146 #[error("device code grant expired")]
147 DeviceCodeExpired,
148
149 #[error("device code grant is still pending")]
150 DeviceCodePending,
151
152 #[error("device code grant was rejected")]
153 DeviceCodeRejected,
154
155 #[error("device code grant was already exchanged")]
156 DeviceCodeExchanged,
157
158 #[error("failed to provision device")]
159 ProvisionDeviceFailed(#[source] anyhow::Error),
160}
161
162impl IntoResponse for RouteError {
163 fn into_response(self) -> axum::response::Response {
164 let sentry_event_id = record_error!(
165 self,
166 Self::Internal(_)
167 | Self::ClientCredentialsVerification { .. }
168 | Self::NoSuchBrowserSession(_)
169 | Self::NoSuchOAuthSession(_)
170 | Self::ProvisionDeviceFailed(_)
171 | Self::NoSuchNextRefreshToken { .. }
172 | Self::NoSuchNextAccessToken { .. }
173 );
174
175 TOKEN_REQUEST_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
176
177 let response = match self {
178 Self::Internal(_)
179 | Self::ClientCredentialsVerification { .. }
180 | Self::NoSuchBrowserSession(_)
181 | Self::NoSuchOAuthSession(_)
182 | Self::ProvisionDeviceFailed(_)
183 | Self::NoSuchNextRefreshToken { .. }
184 | Self::NoSuchNextAccessToken { .. } => (
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_i18n::DataError);
265impl_from_error_for_route!(mas_i18n::ParseError);
266impl_from_error_for_route!(mas_templates::TemplateError);
267impl_from_error_for_route!(mas_storage::RepositoryError);
268impl_from_error_for_route!(mas_policy::EvaluationError);
269impl_from_error_for_route!(super::IdTokenSignatureError);
270
271#[tracing::instrument(
272 name = "handlers.oauth2.token.post",
273 fields(client.id = client_authorization.client_id()),
274 skip_all,
275)]
276pub(crate) async fn post(
277 mut rng: BoxRng,
278 clock: BoxClock,
279 State(http_client): State<reqwest::Client>,
280 State(key_store): State<Keystore>,
281 State(url_builder): State<UrlBuilder>,
282 activity_tracker: BoundActivityTracker,
283 mut repo: BoxRepository,
284 State(homeserver): State<Arc<dyn HomeserverConnection>>,
285 State(site_config): State<SiteConfig>,
286 State(encrypter): State<Encrypter>,
287 State(templates): State<Templates>,
288 policy: Policy,
289 user_agent: Option<TypedHeader<headers::UserAgent>>,
290 client_authorization: ClientAuthorization<AccessTokenRequest>,
291) -> Result<impl IntoResponse, RouteError> {
292 let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
293 let client = client_authorization
294 .credentials
295 .fetch(&mut repo)
296 .await?
297 .ok_or(RouteError::ClientNotFound)?;
298
299 let method = client
300 .token_endpoint_auth_method
301 .as_ref()
302 .ok_or(RouteError::ClientNotAllowed(client.id))?;
303
304 client_authorization
305 .credentials
306 .verify(&http_client, &encrypter, method, &client)
307 .await
308 .map_err(|err| {
309 if err.is_internal() {
312 RouteError::ClientCredentialsVerification {
313 client_id: client.id,
314 source: err,
315 }
316 } else {
317 RouteError::InvalidClientCredentials {
318 client_id: client.id,
319 source: err,
320 }
321 }
322 })?;
323
324 client.maybe_record_as_requester();
327
328 let form = client_authorization.form.ok_or(RouteError::BadRequest)?;
329
330 let grant_type = form.grant_type();
331
332 let (reply, repo) = match form {
333 AccessTokenRequest::AuthorizationCode(grant) => {
334 authorization_code_grant(
335 &mut rng,
336 &clock,
337 &activity_tracker,
338 &grant,
339 &client,
340 &key_store,
341 &url_builder,
342 &site_config,
343 repo,
344 &homeserver,
345 &templates,
346 user_agent,
347 )
348 .await?
349 }
350 AccessTokenRequest::RefreshToken(grant) => {
351 refresh_token_grant(
352 &mut rng,
353 &clock,
354 &activity_tracker,
355 &grant,
356 &client,
357 &site_config,
358 repo,
359 user_agent,
360 )
361 .await?
362 }
363 AccessTokenRequest::ClientCredentials(grant) => {
364 client_credentials_grant(
365 &mut rng,
366 &clock,
367 &activity_tracker,
368 &grant,
369 &client,
370 &site_config,
371 repo,
372 policy,
373 user_agent,
374 )
375 .await?
376 }
377 AccessTokenRequest::DeviceCode(grant) => {
378 device_code_grant(
379 &mut rng,
380 &clock,
381 &activity_tracker,
382 &grant,
383 &client,
384 &key_store,
385 &url_builder,
386 &templates,
387 &site_config,
388 repo,
389 &homeserver,
390 user_agent,
391 )
392 .await?
393 }
394 _ => {
395 return Err(RouteError::UnsupportedGrantType);
396 }
397 };
398
399 repo.save().await?;
400
401 TOKEN_REQUEST_COUNTER.add(
402 1,
403 &[
404 KeyValue::new(GRANT_TYPE, grant_type),
405 KeyValue::new(RESULT, "success"),
406 ],
407 );
408
409 let mut headers = HeaderMap::new();
410 headers.typed_insert(CacheControl::new().with_no_store());
411 headers.typed_insert(Pragma::no_cache());
412
413 Ok((headers, Json(reply)))
414}
415
416async fn authorization_code_grant(
417 mut rng: &mut BoxRng,
418 clock: &impl Clock,
419 activity_tracker: &BoundActivityTracker,
420 grant: &AuthorizationCodeGrant,
421 client: &Client,
422 key_store: &Keystore,
423 url_builder: &UrlBuilder,
424 site_config: &SiteConfig,
425 mut repo: BoxRepository,
426 homeserver: &Arc<dyn HomeserverConnection>,
427 templates: &Templates,
428 user_agent: Option<String>,
429) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
430 if !client.grant_types.contains(&GrantType::AuthorizationCode) {
432 return Err(RouteError::UnauthorizedClient(client.id));
433 }
434
435 let authz_grant = repo
436 .oauth2_authorization_grant()
437 .find_by_code(&grant.code)
438 .await?
439 .ok_or(RouteError::GrantNotFound)?;
440
441 let now = clock.now();
442
443 let browser_session_id = match authz_grant.stage {
444 AuthorizationGrantStage::Cancelled { cancelled_at } => {
445 debug!(%cancelled_at, "Authorization grant was cancelled");
446 return Err(RouteError::InvalidGrant(authz_grant.id));
447 }
448 AuthorizationGrantStage::Exchanged {
449 exchanged_at,
450 fulfilled_at,
451 session_id,
452 ..
453 } => {
454 warn!(%exchanged_at, %fulfilled_at, "Authorization code was already exchanged");
455
456 if now - exchanged_at > Duration::microseconds(20 * 1000 * 1000) {
458 warn!(oauth_session.id = %session_id, "Ending potentially compromised session");
459 let session = repo
460 .oauth2_session()
461 .lookup(session_id)
462 .await?
463 .ok_or(RouteError::NoSuchOAuthSession(session_id))?;
464
465 repo.oauth2_session().finish(clock, session).await?;
467 repo.save().await?;
468 }
470
471 return Err(RouteError::InvalidGrant(authz_grant.id));
472 }
473 AuthorizationGrantStage::Pending => {
474 warn!("Authorization grant has not been fulfilled yet");
475 return Err(RouteError::InvalidGrant(authz_grant.id));
476 }
477 AuthorizationGrantStage::Fulfilled {
478 browser_session_id,
479 fulfilled_at,
480 } => {
481 if now - fulfilled_at > Duration::microseconds(10 * 60 * 1000 * 1000) {
482 warn!("Code exchange took more than 10 minutes");
483 return Err(RouteError::InvalidGrant(authz_grant.id));
484 }
485
486 browser_session_id
487 }
488 };
489
490 if client.id != authz_grant.client_id {
491 return Err(RouteError::UnexptectedClient {
492 was: client.id,
493 expected: authz_grant.client_id,
494 });
495 }
496
497 let code = authz_grant
499 .code
500 .as_ref()
501 .ok_or(RouteError::InvalidGrant(authz_grant.id))?;
502
503 match (code.pkce.as_ref(), grant.code_verifier.as_ref()) {
505 (None, None) => {}
506 (Some(_), None) | (None, Some(_)) => return Err(RouteError::BadRequest),
508 (Some(pkce), Some(verifier)) => {
510 pkce.verify(verifier)?;
511 }
512 }
513
514 let browser_session = repo
516 .browser_session()
517 .lookup(browser_session_id)
518 .await?
519 .ok_or(RouteError::NoSuchBrowserSession(browser_session_id))?;
520
521 if !browser_session.active() {
522 return Err(RouteError::InvalidGrant(authz_grant.id));
523 }
524
525 let lang: DataLocale = authz_grant.locale.as_deref().unwrap_or("en").parse()?;
526 let ctx = DeviceNameContext::new(client.clone(), user_agent.clone()).with_language(lang);
527 let device_name = templates.render_device_name(&ctx)?;
528
529 let mut session = repo
531 .oauth2_session()
532 .add_from_browser_session(
533 &mut rng,
534 clock,
535 client,
536 &browser_session,
537 authz_grant.scope.clone(),
538 )
539 .await?;
540
541 if let Some(user_agent) = user_agent {
542 session = repo
543 .oauth2_session()
544 .record_user_agent(session, user_agent)
545 .await?;
546 }
547
548 let last_authentication = repo
549 .browser_session()
550 .get_last_authentication(&browser_session)
551 .await?;
552
553 let ttl = site_config.access_token_ttl;
554 let (access_token, refresh_token) =
555 generate_token_pair(&mut rng, clock, &mut repo, &session, ttl).await?;
556
557 let id_token = if session.scope.contains(&scope::OPENID) {
558 Some(generate_id_token(
559 &mut rng,
560 clock,
561 url_builder,
562 key_store,
563 client,
564 Some(&authz_grant),
565 &browser_session,
566 Some(&access_token),
567 last_authentication.as_ref(),
568 )?)
569 } else {
570 None
571 };
572
573 let mut params = AccessTokenResponse::new(access_token.access_token)
574 .with_expires_in(ttl)
575 .with_refresh_token(refresh_token.refresh_token)
576 .with_scope(session.scope.clone());
577
578 if let Some(id_token) = id_token {
579 params = params.with_id_token(id_token);
580 }
581
582 repo.user()
584 .acquire_lock_for_sync(&browser_session.user)
585 .await?;
586
587 for scope in &*session.scope {
589 if let Some(device) = Device::from_scope_token(scope) {
590 homeserver
600 .upsert_device(
601 &browser_session.user.username,
602 device.as_str(),
603 Some(&device_name),
604 )
605 .await
606 .map_err(RouteError::ProvisionDeviceFailed)?;
607 }
608 }
609
610 repo.oauth2_authorization_grant()
611 .exchange(clock, authz_grant, &session)
612 .await?;
613
614 activity_tracker
618 .record_oauth2_session(clock, &session)
619 .await;
620
621 Ok((params, repo))
622}
623
624async fn refresh_token_grant(
625 rng: &mut BoxRng,
626 clock: &impl Clock,
627 activity_tracker: &BoundActivityTracker,
628 grant: &RefreshTokenGrant,
629 client: &Client,
630 site_config: &SiteConfig,
631 mut repo: BoxRepository,
632 user_agent: Option<String>,
633) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
634 if !client.grant_types.contains(&GrantType::RefreshToken) {
636 return Err(RouteError::UnauthorizedClient(client.id));
637 }
638
639 let refresh_token = repo
640 .oauth2_refresh_token()
641 .find_by_token(&grant.refresh_token)
642 .await?
643 .ok_or(RouteError::RefreshTokenNotFound)?;
644
645 let mut session = repo
646 .oauth2_session()
647 .lookup(refresh_token.session_id)
648 .await?
649 .ok_or(RouteError::NoSuchOAuthSession(refresh_token.session_id))?;
650
651 if let Some(user_agent) = user_agent {
654 session = repo
655 .oauth2_session()
656 .record_user_agent(session, user_agent)
657 .await?;
658 }
659
660 if !session.is_valid() {
661 return Err(RouteError::SessionInvalid(session.id));
662 }
663
664 if client.id != session.client_id {
665 return Err(RouteError::ClientIDMismatch {
667 expected: session.client_id,
668 actual: client.id,
669 });
670 }
671
672 if !refresh_token.is_valid() {
673 let Some(next_refresh_token_id) = refresh_token.next_refresh_token_id() else {
678 return Err(RouteError::RefreshTokenInvalid(refresh_token.id));
681 };
682
683 let Some(next_refresh_token) = repo
684 .oauth2_refresh_token()
685 .lookup(next_refresh_token_id)
686 .await?
687 else {
688 return Err(RouteError::NoSuchNextRefreshToken {
689 next: next_refresh_token_id,
690 previous: refresh_token.id,
691 });
692 };
693
694 if !next_refresh_token.is_valid() {
696 return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
698 }
699
700 if let Some(access_token_id) = next_refresh_token.access_token_id {
727 let next_access_token = repo
729 .oauth2_access_token()
730 .lookup(access_token_id)
731 .await?
732 .ok_or(RouteError::NoSuchNextAccessToken {
733 access_token: access_token_id,
734 refresh_token: next_refresh_token_id,
735 })?;
736
737 if next_access_token.is_used() {
738 return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
740 }
741
742 repo.oauth2_access_token()
744 .revoke(clock, next_access_token)
745 .await?;
746 }
747
748 info!(
752 oauth2_session.id = %session.id,
753 oauth2_client.id = %client.id,
754 %refresh_token.id,
755 "Refresh token already used, but issued refresh and access tokens are unused. Assuming those were lost; revoking those and reissuing new ones."
756 );
757
758 repo.oauth2_refresh_token()
759 .revoke(clock, next_refresh_token)
760 .await?;
761 }
762
763 activity_tracker
764 .record_oauth2_session(clock, &session)
765 .await;
766
767 let ttl = site_config.access_token_ttl;
768 let (new_access_token, new_refresh_token) =
769 generate_token_pair(rng, clock, &mut repo, &session, ttl).await?;
770
771 let refresh_token = repo
772 .oauth2_refresh_token()
773 .consume(clock, refresh_token, &new_refresh_token)
774 .await?;
775
776 if let Some(access_token_id) = refresh_token.access_token_id {
777 let access_token = repo.oauth2_access_token().lookup(access_token_id).await?;
778 if let Some(access_token) = access_token {
779 if !access_token.state.is_revoked() {
781 repo.oauth2_access_token()
782 .revoke(clock, access_token)
783 .await?;
784 }
785 }
786 }
787
788 let params = AccessTokenResponse::new(new_access_token.access_token)
789 .with_expires_in(ttl)
790 .with_refresh_token(new_refresh_token.refresh_token)
791 .with_scope(session.scope);
792
793 Ok((params, repo))
794}
795
796async fn client_credentials_grant(
797 rng: &mut BoxRng,
798 clock: &impl Clock,
799 activity_tracker: &BoundActivityTracker,
800 grant: &ClientCredentialsGrant,
801 client: &Client,
802 site_config: &SiteConfig,
803 mut repo: BoxRepository,
804 mut policy: Policy,
805 user_agent: Option<String>,
806) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
807 if !client.grant_types.contains(&GrantType::ClientCredentials) {
809 return Err(RouteError::UnauthorizedClient(client.id));
810 }
811
812 let scope = grant
814 .scope
815 .clone()
816 .unwrap_or_else(|| std::iter::empty::<ScopeToken>().collect());
817
818 let res = policy
820 .evaluate_authorization_grant(mas_policy::AuthorizationGrantInput {
821 user: None,
822 client,
823 session_counts: None,
824 scope: &scope,
825 grant_type: mas_policy::GrantType::ClientCredentials,
826 requester: mas_policy::Requester {
827 ip_address: activity_tracker.ip(),
828 user_agent: user_agent.clone(),
829 },
830 })
831 .await?;
832 if !res.valid() {
833 return Err(RouteError::DeniedByPolicy(res));
834 }
835
836 let mut session = repo
838 .oauth2_session()
839 .add_from_client_credentials(rng, clock, client, scope)
840 .await?;
841
842 if let Some(user_agent) = user_agent {
843 session = repo
844 .oauth2_session()
845 .record_user_agent(session, user_agent)
846 .await?;
847 }
848
849 let ttl = site_config.access_token_ttl;
850 let access_token_str = TokenType::AccessToken.generate(rng);
851
852 let access_token = repo
853 .oauth2_access_token()
854 .add(rng, clock, &session, access_token_str, Some(ttl))
855 .await?;
856
857 let mut params = AccessTokenResponse::new(access_token.access_token).with_expires_in(ttl);
858
859 activity_tracker
863 .record_oauth2_session(clock, &session)
864 .await;
865
866 if !session.scope.is_empty() {
867 params = params.with_scope(session.scope);
869 }
870
871 Ok((params, repo))
872}
873
874async fn device_code_grant(
875 rng: &mut BoxRng,
876 clock: &impl Clock,
877 activity_tracker: &BoundActivityTracker,
878 grant: &DeviceCodeGrant,
879 client: &Client,
880 key_store: &Keystore,
881 url_builder: &UrlBuilder,
882 templates: &Templates,
883 site_config: &SiteConfig,
884 mut repo: BoxRepository,
885 homeserver: &Arc<dyn HomeserverConnection>,
886 user_agent: Option<String>,
887) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
888 if !site_config.device_code_grant_enabled {
890 return Err(RouteError::UnsupportedGrantType);
891 }
892
893 if !client.grant_types.contains(&GrantType::DeviceCode) {
895 return Err(RouteError::UnauthorizedClient(client.id));
896 }
897
898 let grant = repo
899 .oauth2_device_code_grant()
900 .find_by_device_code(&grant.device_code)
901 .await?
902 .ok_or(RouteError::GrantNotFound)?;
903
904 if client.id != grant.client_id {
906 return Err(RouteError::ClientIDMismatch {
907 expected: grant.client_id,
908 actual: client.id,
909 });
910 }
911
912 if grant.expires_at < clock.now() {
913 return Err(RouteError::DeviceCodeExpired);
914 }
915
916 let browser_session_id = match &grant.state {
917 DeviceCodeGrantState::Pending => {
918 return Err(RouteError::DeviceCodePending);
919 }
920 DeviceCodeGrantState::Rejected { .. } => {
921 return Err(RouteError::DeviceCodeRejected);
922 }
923 DeviceCodeGrantState::Exchanged { .. } => {
924 return Err(RouteError::DeviceCodeExchanged);
925 }
926 DeviceCodeGrantState::Fulfilled {
927 browser_session_id, ..
928 } => *browser_session_id,
929 };
930
931 let browser_session = repo
932 .browser_session()
933 .lookup(browser_session_id)
934 .await?
935 .ok_or(RouteError::NoSuchBrowserSession(browser_session_id))?;
936
937 let lang: DataLocale = grant.locale.as_deref().unwrap_or("en").parse()?;
940 let ctx = DeviceNameContext::new(client.clone(), user_agent.clone()).with_language(lang);
941 let device_name = templates.render_device_name(&ctx)?;
942
943 let mut session = repo
945 .oauth2_session()
946 .add_from_browser_session(rng, clock, client, &browser_session, grant.scope.clone())
947 .await?;
948
949 repo.oauth2_device_code_grant()
950 .exchange(clock, grant, &session)
951 .await?;
952
953 if let Some(user_agent) = user_agent {
955 session = repo
956 .oauth2_session()
957 .record_user_agent(session, user_agent)
958 .await?;
959 }
960
961 let ttl = site_config.access_token_ttl;
962 let access_token_str = TokenType::AccessToken.generate(rng);
963
964 let access_token = repo
965 .oauth2_access_token()
966 .add(rng, clock, &session, access_token_str, Some(ttl))
967 .await?;
968
969 let mut params =
970 AccessTokenResponse::new(access_token.access_token.clone()).with_expires_in(ttl);
971
972 if client.grant_types.contains(&GrantType::RefreshToken) {
975 let refresh_token_str = TokenType::RefreshToken.generate(rng);
976
977 let refresh_token = repo
978 .oauth2_refresh_token()
979 .add(rng, clock, &session, &access_token, refresh_token_str)
980 .await?;
981
982 params = params.with_refresh_token(refresh_token.refresh_token);
983 }
984
985 if session.scope.contains(&scope::OPENID) {
987 let id_token = generate_id_token(
988 rng,
989 clock,
990 url_builder,
991 key_store,
992 client,
993 None,
994 &browser_session,
995 Some(&access_token),
996 None,
997 )?;
998
999 params = params.with_id_token(id_token);
1000 }
1001
1002 repo.user()
1004 .acquire_lock_for_sync(&browser_session.user)
1005 .await?;
1006
1007 for scope in &*session.scope {
1009 if let Some(device) = Device::from_scope_token(scope) {
1010 homeserver
1020 .upsert_device(
1021 &browser_session.user.username,
1022 device.as_str(),
1023 Some(&device_name),
1024 )
1025 .await
1026 .map_err(RouteError::ProvisionDeviceFailed)?;
1027 }
1028 }
1029
1030 activity_tracker
1034 .record_oauth2_session(clock, &session)
1035 .await;
1036
1037 if !session.scope.is_empty() {
1038 params = params.with_scope(session.scope);
1040 }
1041
1042 Ok((params, repo))
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047 use hyper::Request;
1048 use mas_data_model::{AccessToken, AuthorizationCode, RefreshToken};
1049 use mas_router::SimpleRoute;
1050 use oauth2_types::{
1051 registration::ClientRegistrationResponse,
1052 requests::{DeviceAuthorizationResponse, ResponseMode},
1053 scope::{OPENID, Scope},
1054 };
1055 use sqlx::PgPool;
1056
1057 use super::*;
1058 use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup, test_site_config};
1059
1060 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1061 async fn test_auth_code_grant(pool: PgPool) {
1062 setup();
1063 let state = TestState::from_pool(pool).await.unwrap();
1064
1065 let request =
1067 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1068 "client_uri": "https://example.com/",
1069 "redirect_uris": ["https://example.com/callback"],
1070 "token_endpoint_auth_method": "none",
1071 "response_types": ["code"],
1072 "grant_types": ["authorization_code"],
1073 }));
1074
1075 let response = state.request(request).await;
1076 response.assert_status(StatusCode::CREATED);
1077
1078 let ClientRegistrationResponse { client_id, .. } = response.json();
1079
1080 let mut repo = state.repository().await.unwrap();
1083
1084 let user = repo
1085 .user()
1086 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1087 .await
1088 .unwrap();
1089
1090 let browser_session = repo
1091 .browser_session()
1092 .add(&mut state.rng(), &state.clock, &user, None)
1093 .await
1094 .unwrap();
1095
1096 let client = repo
1098 .oauth2_client()
1099 .find_by_client_id(&client_id)
1100 .await
1101 .unwrap()
1102 .unwrap();
1103
1104 let code = "thisisaverysecurecode";
1106 let grant = repo
1107 .oauth2_authorization_grant()
1108 .add(
1109 &mut state.rng(),
1110 &state.clock,
1111 &client,
1112 "https://example.com/redirect".parse().unwrap(),
1113 Scope::from_iter([OPENID]),
1114 Some(AuthorizationCode {
1115 code: code.to_owned(),
1116 pkce: None,
1117 }),
1118 Some("state".to_owned()),
1119 Some("nonce".to_owned()),
1120 ResponseMode::Query,
1121 false,
1122 None,
1123 None,
1124 std::collections::BTreeMap::new(),
1125 )
1126 .await
1127 .unwrap();
1128
1129 let grant = repo
1131 .oauth2_authorization_grant()
1132 .fulfill(&state.clock, &browser_session, grant)
1133 .await
1134 .unwrap();
1135
1136 repo.save().await.unwrap();
1137
1138 let request =
1140 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1141 "grant_type": "authorization_code",
1142 "code": code,
1143 "redirect_uri": grant.redirect_uri,
1144 "client_id": client.client_id,
1145 }));
1146
1147 let response = state.request(request).await;
1148 response.assert_status(StatusCode::OK);
1149
1150 let AccessTokenResponse { access_token, .. } = response.json();
1151
1152 assert!(state.is_access_token_valid(&access_token).await);
1154
1155 let request =
1157 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1158 "grant_type": "authorization_code",
1159 "code": code,
1160 "redirect_uri": grant.redirect_uri,
1161 "client_id": client.client_id,
1162 }));
1163
1164 let response = state.request(request).await;
1165 response.assert_status(StatusCode::BAD_REQUEST);
1166 let error: ClientError = response.json();
1167 assert_eq!(error.error, ClientErrorCode::InvalidGrant);
1168
1169 assert!(state.is_access_token_valid(&access_token).await);
1171
1172 state.clock.advance(Duration::try_minutes(1).unwrap());
1174
1175 let request =
1177 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1178 "grant_type": "authorization_code",
1179 "code": code,
1180 "redirect_uri": grant.redirect_uri,
1181 "client_id": client.client_id,
1182 }));
1183
1184 let response = state.request(request).await;
1185 response.assert_status(StatusCode::BAD_REQUEST);
1186 let error: ClientError = response.json();
1187 assert_eq!(error.error, ClientErrorCode::InvalidGrant);
1188
1189 assert!(!state.is_access_token_valid(&access_token).await);
1191
1192 let mut repo = state.repository().await.unwrap();
1194 let code = "thisisanothercode";
1195 let grant = repo
1196 .oauth2_authorization_grant()
1197 .add(
1198 &mut state.rng(),
1199 &state.clock,
1200 &client,
1201 "https://example.com/redirect".parse().unwrap(),
1202 Scope::from_iter([OPENID]),
1203 Some(AuthorizationCode {
1204 code: code.to_owned(),
1205 pkce: None,
1206 }),
1207 Some("state".to_owned()),
1208 Some("nonce".to_owned()),
1209 ResponseMode::Query,
1210 false,
1211 None,
1212 None,
1213 std::collections::BTreeMap::new(),
1214 )
1215 .await
1216 .unwrap();
1217
1218 let grant = repo
1220 .oauth2_authorization_grant()
1221 .fulfill(&state.clock, &browser_session, grant)
1222 .await
1223 .unwrap();
1224
1225 repo.save().await.unwrap();
1226
1227 state
1229 .clock
1230 .advance(Duration::microseconds(15 * 60 * 1000 * 1000));
1231
1232 let request =
1234 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1235 "grant_type": "authorization_code",
1236 "code": code,
1237 "redirect_uri": grant.redirect_uri,
1238 "client_id": client.client_id,
1239 }));
1240
1241 let response = state.request(request).await;
1242 response.assert_status(StatusCode::BAD_REQUEST);
1243 let ClientError { error, .. } = response.json();
1244 assert_eq!(error, ClientErrorCode::InvalidGrant);
1245 }
1246
1247 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1248 async fn test_refresh_token_grant(pool: PgPool) {
1249 setup();
1250 let state = TestState::from_pool(pool).await.unwrap();
1251
1252 let request =
1254 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1255 "client_uri": "https://example.com/",
1256 "redirect_uris": ["https://example.com/callback"],
1257 "token_endpoint_auth_method": "none",
1258 "response_types": ["code"],
1259 "grant_types": ["authorization_code", "refresh_token"],
1260 }));
1261
1262 let response = state.request(request).await;
1263 response.assert_status(StatusCode::CREATED);
1264
1265 let ClientRegistrationResponse { client_id, .. } = response.json();
1266
1267 let mut repo = state.repository().await.unwrap();
1270
1271 let user = repo
1272 .user()
1273 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1274 .await
1275 .unwrap();
1276
1277 let browser_session = repo
1278 .browser_session()
1279 .add(&mut state.rng(), &state.clock, &user, None)
1280 .await
1281 .unwrap();
1282
1283 let client = repo
1285 .oauth2_client()
1286 .find_by_client_id(&client_id)
1287 .await
1288 .unwrap()
1289 .unwrap();
1290
1291 let session = repo
1293 .oauth2_session()
1294 .add_from_browser_session(
1295 &mut state.rng(),
1296 &state.clock,
1297 &client,
1298 &browser_session,
1299 Scope::from_iter([OPENID]),
1300 )
1301 .await
1302 .unwrap();
1303
1304 let (AccessToken { access_token, .. }, RefreshToken { refresh_token, .. }) =
1305 generate_token_pair(
1306 &mut state.rng(),
1307 &state.clock,
1308 &mut repo,
1309 &session,
1310 Duration::microseconds(5 * 60 * 1000 * 1000),
1311 )
1312 .await
1313 .unwrap();
1314
1315 repo.save().await.unwrap();
1316
1317 assert!(state.is_access_token_valid(&access_token).await);
1319
1320 let request =
1322 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1323 "grant_type": "refresh_token",
1324 "refresh_token": refresh_token,
1325 "client_id": client.client_id,
1326 }));
1327
1328 let response = state.request(request).await;
1329 response.assert_status(StatusCode::OK);
1330
1331 let old_access_token = access_token;
1332 let old_refresh_token = refresh_token;
1333 let response: AccessTokenResponse = response.json();
1334 let access_token = response.access_token;
1335 let refresh_token = response.refresh_token.expect("to have a refresh token");
1336
1337 assert!(state.is_access_token_valid(&access_token).await);
1339
1340 assert!(!state.is_access_token_valid(&old_access_token).await);
1342
1343 let request =
1345 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1346 "grant_type": "refresh_token",
1347 "refresh_token": old_refresh_token,
1348 "client_id": client.client_id,
1349 }));
1350
1351 let response = state.request(request).await;
1352 response.assert_status(StatusCode::BAD_REQUEST);
1353 let ClientError { error, .. } = response.json();
1354 assert_eq!(error, ClientErrorCode::InvalidGrant);
1355
1356 let request =
1358 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1359 "grant_type": "refresh_token",
1360 "refresh_token": refresh_token,
1361 "client_id": client.client_id,
1362 }));
1363
1364 let response = state.request(request).await;
1365 response.assert_status(StatusCode::OK);
1366 let _: AccessTokenResponse = response.json();
1367 }
1368
1369 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1370 async fn test_double_refresh(pool: PgPool) {
1371 setup();
1372 let state = TestState::from_pool(pool).await.unwrap();
1373
1374 let request =
1376 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1377 "client_uri": "https://example.com/",
1378 "redirect_uris": ["https://example.com/callback"],
1379 "token_endpoint_auth_method": "none",
1380 "response_types": ["code"],
1381 "grant_types": ["authorization_code", "refresh_token"],
1382 }));
1383
1384 let response = state.request(request).await;
1385 response.assert_status(StatusCode::CREATED);
1386
1387 let ClientRegistrationResponse { client_id, .. } = response.json();
1388
1389 let mut repo = state.repository().await.unwrap();
1392
1393 let user = repo
1394 .user()
1395 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1396 .await
1397 .unwrap();
1398
1399 let browser_session = repo
1400 .browser_session()
1401 .add(&mut state.rng(), &state.clock, &user, None)
1402 .await
1403 .unwrap();
1404
1405 let client = repo
1407 .oauth2_client()
1408 .find_by_client_id(&client_id)
1409 .await
1410 .unwrap()
1411 .unwrap();
1412
1413 let session = repo
1415 .oauth2_session()
1416 .add_from_browser_session(
1417 &mut state.rng(),
1418 &state.clock,
1419 &client,
1420 &browser_session,
1421 Scope::from_iter([OPENID]),
1422 )
1423 .await
1424 .unwrap();
1425
1426 let (AccessToken { access_token, .. }, RefreshToken { refresh_token, .. }) =
1427 generate_token_pair(
1428 &mut state.rng(),
1429 &state.clock,
1430 &mut repo,
1431 &session,
1432 Duration::microseconds(5 * 60 * 1000 * 1000),
1433 )
1434 .await
1435 .unwrap();
1436
1437 repo.save().await.unwrap();
1438
1439 assert!(state.is_access_token_valid(&access_token).await);
1441
1442 let request =
1444 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1445 "grant_type": "refresh_token",
1446 "refresh_token": refresh_token,
1447 "client_id": client.client_id,
1448 }));
1449
1450 let first_response = state.request(request).await;
1451 first_response.assert_status(StatusCode::OK);
1452 let first_response: AccessTokenResponse = first_response.json();
1453
1454 let request =
1457 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1458 "grant_type": "refresh_token",
1459 "refresh_token": refresh_token,
1460 "client_id": client.client_id,
1461 }));
1462
1463 let second_response = state.request(request).await;
1464 second_response.assert_status(StatusCode::OK);
1465 let second_response: AccessTokenResponse = second_response.json();
1466
1467 assert_ne!(first_response.access_token, second_response.access_token);
1469 assert_ne!(first_response.refresh_token, second_response.refresh_token);
1470
1471 assert!(
1473 !state
1474 .is_access_token_valid(&first_response.access_token)
1475 .await
1476 );
1477
1478 assert!(
1480 state
1481 .is_access_token_valid(&second_response.access_token)
1482 .await
1483 );
1484
1485 let request =
1488 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1489 "grant_type": "refresh_token",
1490 "refresh_token": refresh_token,
1491 "client_id": client.client_id,
1492 }));
1493
1494 let third_response = state.request(request).await;
1495 third_response.assert_status(StatusCode::BAD_REQUEST);
1496
1497 let request =
1503 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1504 "grant_type": "refresh_token",
1505 "refresh_token": second_response.refresh_token,
1506 "client_id": client.client_id,
1507 }));
1508
1509 let fourth_response = state.request(request).await;
1511 fourth_response.assert_status(StatusCode::OK);
1512 let fourth_response: AccessTokenResponse = fourth_response.json();
1513
1514 let request =
1516 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1517 "grant_type": "refresh_token",
1518 "refresh_token": fourth_response.refresh_token,
1519 "client_id": client.client_id,
1520 }));
1521
1522 let fifth_response = state.request(request).await;
1523 fifth_response.assert_status(StatusCode::OK);
1524 let fifth_response: AccessTokenResponse = fifth_response.json();
1525
1526 let request =
1529 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1530 "grant_type": "refresh_token",
1531 "refresh_token": second_response.refresh_token,
1532 "client_id": client.client_id,
1533 }));
1534
1535 let sixth_response = state.request(request).await;
1536 sixth_response.assert_status(StatusCode::BAD_REQUEST);
1537
1538 assert!(
1545 state
1546 .is_access_token_valid(&fifth_response.access_token)
1547 .await
1548 );
1549
1550 state.run_jobs_in_queue().await;
1554 state.clock.advance(Duration::days(31));
1555 state.run_jobs_in_queue().await;
1556
1557 let request =
1560 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1561 "grant_type": "refresh_token",
1562 "refresh_token": fourth_response.refresh_token,
1563 "client_id": client.client_id,
1564 }));
1565
1566 let seventh_response = state.request(request).await;
1567 seventh_response.assert_status(StatusCode::OK);
1568
1569 let request =
1571 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1572 "grant_type": "refresh_token",
1573 "refresh_token": fifth_response.refresh_token,
1574 "client_id": client.client_id,
1575 }));
1576
1577 let eighth_response = state.request(request).await;
1578 eighth_response.assert_status(StatusCode::BAD_REQUEST);
1579 }
1580
1581 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1582 async fn test_client_credentials(pool: PgPool) {
1583 setup();
1584 let state = TestState::from_pool(pool).await.unwrap();
1585
1586 let request =
1588 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1589 "client_uri": "https://example.com/",
1590 "token_endpoint_auth_method": "client_secret_post",
1591 "grant_types": ["client_credentials"],
1592 }));
1593
1594 let response = state.request(request).await;
1595 response.assert_status(StatusCode::CREATED);
1596
1597 let response: ClientRegistrationResponse = response.json();
1598 let client_id = response.client_id;
1599 let client_secret = response.client_secret.expect("to have a client secret");
1600
1601 let request =
1603 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1604 "grant_type": "client_credentials",
1605 "client_id": client_id,
1606 "client_secret": client_secret,
1607 }));
1608
1609 let response = state.request(request).await;
1610 response.assert_status(StatusCode::OK);
1611
1612 let response: AccessTokenResponse = response.json();
1613 assert!(response.refresh_token.is_none());
1614 assert!(response.expires_in.is_some());
1615 assert!(response.scope.is_none());
1616
1617 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1619 "token": response.access_token,
1620 "client_id": client_id,
1621 "client_secret": client_secret,
1622 }));
1623
1624 let response = state.request(request).await;
1625 response.assert_status(StatusCode::OK);
1626
1627 let request =
1629 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1630 "grant_type": "client_credentials",
1631 "client_id": client_id,
1632 "client_secret": client_secret,
1633 "scope": "urn:mas:graphql:*"
1634 }));
1635
1636 let response = state.request(request).await;
1637 response.assert_status(StatusCode::OK);
1638
1639 let response: AccessTokenResponse = response.json();
1640 assert!(response.refresh_token.is_none());
1641 assert!(response.expires_in.is_some());
1642 assert_eq!(response.scope, Some("urn:mas:graphql:*".parse().unwrap()));
1643
1644 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1646 "token": response.access_token,
1647 "client_id": client_id,
1648 "client_secret": client_secret,
1649 }));
1650
1651 let response = state.request(request).await;
1652 response.assert_status(StatusCode::OK);
1653
1654 let request =
1656 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1657 "grant_type": "client_credentials",
1658 "client_id": client_id,
1659 "client_secret": client_secret,
1660 "scope": "urn:mas:admin"
1661 }));
1662
1663 let response = state.request(request).await;
1664 response.assert_status(StatusCode::FORBIDDEN);
1665
1666 let ClientError { error, .. } = response.json();
1667 assert_eq!(error, ClientErrorCode::InvalidScope);
1668
1669 let state = {
1671 let mut state = state;
1672 state.policy_factory = crate::test_utils::policy_factory(
1673 mas_policy::BaseData {
1674 server_name: "example.com".to_owned(),
1675 session_limit: None,
1676 },
1677 serde_json::json!({
1678 "admin_clients": [client_id]
1679 }),
1680 )
1681 .await
1682 .unwrap();
1683 state
1684 };
1685
1686 let request =
1687 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1688 "grant_type": "client_credentials",
1689 "client_id": client_id,
1690 "client_secret": client_secret,
1691 "scope": "urn:mas:admin"
1692 }));
1693
1694 let response = state.request(request).await;
1695 response.assert_status(StatusCode::OK);
1696
1697 let response: AccessTokenResponse = response.json();
1698 assert!(response.refresh_token.is_none());
1699 assert!(response.expires_in.is_some());
1700 assert_eq!(response.scope, Some("urn:mas:admin".parse().unwrap()));
1701
1702 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1704 "token": response.access_token,
1705 "client_id": client_id,
1706 "client_secret": client_secret,
1707 }));
1708
1709 let response = state.request(request).await;
1710 response.assert_status(StatusCode::OK);
1711 }
1712
1713 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1714 async fn test_device_code_grant(pool: PgPool) {
1715 setup();
1716 let state = TestState::from_pool(pool).await.unwrap();
1717
1718 let request =
1720 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1721 "client_uri": "https://example.com/",
1722 "token_endpoint_auth_method": "none",
1723 "grant_types": ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"],
1724 "response_types": [],
1725 }));
1726
1727 let response = state.request(request).await;
1728 response.assert_status(StatusCode::CREATED);
1729
1730 let response: ClientRegistrationResponse = response.json();
1731 let client_id = response.client_id;
1732
1733 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1735 serde_json::json!({
1736 "client_id": client_id,
1737 "scope": "openid",
1738 }),
1739 );
1740 let response = state.request(request).await;
1741 response.assert_status(StatusCode::OK);
1742
1743 let device_grant: DeviceAuthorizationResponse = response.json();
1744
1745 let request =
1747 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1748 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1749 "device_code": device_grant.device_code,
1750 "client_id": client_id,
1751 }));
1752 let response = state.request(request).await;
1753 response.assert_status(StatusCode::FORBIDDEN);
1754
1755 let ClientError { error, .. } = response.json();
1756 assert_eq!(error, ClientErrorCode::AuthorizationPending);
1757
1758 let mut repo = state.repository().await.unwrap();
1762
1763 let user = repo
1764 .user()
1765 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1766 .await
1767 .unwrap();
1768
1769 let browser_session = repo
1770 .browser_session()
1771 .add(&mut state.rng(), &state.clock, &user, None)
1772 .await
1773 .unwrap();
1774
1775 let grant = repo
1777 .oauth2_device_code_grant()
1778 .find_by_user_code(&device_grant.user_code)
1779 .await
1780 .unwrap()
1781 .unwrap();
1782
1783 let grant = repo
1785 .oauth2_device_code_grant()
1786 .fulfill(&state.clock, grant, &browser_session, Some("en".to_owned()))
1787 .await
1788 .unwrap();
1789
1790 repo.save().await.unwrap();
1791
1792 let request =
1794 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1795 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1796 "device_code": grant.device_code,
1797 "client_id": client_id,
1798 }));
1799
1800 let response = state.request(request).await;
1801 response.assert_status(StatusCode::OK);
1802
1803 let response: AccessTokenResponse = response.json();
1804
1805 assert!(state.is_access_token_valid(&response.access_token).await);
1807 assert!(response.refresh_token.is_some());
1809 assert!(response.id_token.is_some());
1811
1812 let request =
1814 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1815 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1816 "device_code": grant.device_code,
1817 "client_id": client_id,
1818 }));
1819 let response = state.request(request).await;
1820 response.assert_status(StatusCode::BAD_REQUEST);
1821
1822 let ClientError { error, .. } = response.json();
1823 assert_eq!(error, ClientErrorCode::InvalidGrant);
1824
1825 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1827 serde_json::json!({
1828 "client_id": client_id,
1829 "scope": "openid",
1830 }),
1831 );
1832 let response = state.request(request).await;
1833 response.assert_status(StatusCode::OK);
1834
1835 let device_grant: DeviceAuthorizationResponse = response.json();
1836
1837 let request =
1839 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1840 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1841 "device_code": device_grant.device_code,
1842 "client_id": client_id,
1843 }));
1844 let response = state.request(request).await;
1845 response.assert_status(StatusCode::FORBIDDEN);
1846
1847 let ClientError { error, .. } = response.json();
1848 assert_eq!(error, ClientErrorCode::AuthorizationPending);
1849
1850 state.clock.advance(Duration::try_hours(1).unwrap());
1851
1852 let request =
1854 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1855 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1856 "device_code": device_grant.device_code,
1857 "client_id": client_id,
1858 }));
1859 let response = state.request(request).await;
1860 response.assert_status(StatusCode::FORBIDDEN);
1861
1862 let ClientError { error, .. } = response.json();
1863 assert_eq!(error, ClientErrorCode::ExpiredToken);
1864
1865 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1867 serde_json::json!({
1868 "client_id": client_id,
1869 "scope": "openid",
1870 }),
1871 );
1872 let response = state.request(request).await;
1873 response.assert_status(StatusCode::OK);
1874
1875 let device_grant: DeviceAuthorizationResponse = response.json();
1876
1877 let mut repo = state.repository().await.unwrap();
1879
1880 let grant = repo
1882 .oauth2_device_code_grant()
1883 .find_by_user_code(&device_grant.user_code)
1884 .await
1885 .unwrap()
1886 .unwrap();
1887
1888 let grant = repo
1890 .oauth2_device_code_grant()
1891 .reject(&state.clock, grant, &browser_session)
1892 .await
1893 .unwrap();
1894
1895 repo.save().await.unwrap();
1896
1897 let request =
1899 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1900 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1901 "device_code": grant.device_code,
1902 "client_id": client_id,
1903 }));
1904 let response = state.request(request).await;
1905 response.assert_status(StatusCode::FORBIDDEN);
1906
1907 let ClientError { error, .. } = response.json();
1908 assert_eq!(error, ClientErrorCode::AccessDenied);
1909 }
1910
1911 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1912 async fn test_unsupported_grant(pool: PgPool) {
1913 setup();
1914 let state = TestState::from_pool(pool).await.unwrap();
1915
1916 let request =
1918 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1919 "client_uri": "https://example.com/",
1920 "redirect_uris": ["https://example.com/callback"],
1921 "token_endpoint_auth_method": "client_secret_post",
1922 "grant_types": ["password"],
1923 "response_types": [],
1924 }));
1925
1926 let response = state.request(request).await;
1927 response.assert_status(StatusCode::CREATED);
1928
1929 let response: ClientRegistrationResponse = response.json();
1930 let client_id = response.client_id;
1931 let client_secret = response.client_secret.expect("to have a client secret");
1932
1933 let request =
1935 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1936 "grant_type": "password",
1937 "client_id": client_id,
1938 "client_secret": client_secret,
1939 "username": "john",
1940 "password": "hunter2",
1941 }));
1942
1943 let response = state.request(request).await;
1944 response.assert_status(StatusCode::BAD_REQUEST);
1945 let ClientError { error, .. } = response.json();
1946 assert_eq!(error, ClientErrorCode::UnsupportedGrantType);
1947 }
1948
1949 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1950 async fn test_device_code_grant_disabled(pool: PgPool) {
1951 setup();
1952 let state = TestState::from_pool_with_site_config(
1953 pool,
1954 SiteConfig {
1955 device_code_grant_enabled: false,
1956 ..test_site_config()
1957 },
1958 )
1959 .await
1960 .unwrap();
1961
1962 let request =
1964 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1965 "client_uri": "https://example.com/",
1966 "redirect_uris": ["https://example.com/callback"],
1967 "token_endpoint_auth_method": "none",
1968 "response_types": ["code"],
1969 "grant_types": ["authorization_code"],
1970 }));
1971
1972 let response = state.request(request).await;
1973 response.assert_status(StatusCode::CREATED);
1974
1975 let response: ClientRegistrationResponse = response.json();
1976 let client_id = response.client_id;
1977
1978 let request =
1980 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1981 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1982 "device_code": "fake-device-code",
1983 "client_id": client_id,
1984 }));
1985
1986 let response = state.request(request).await;
1987 response.assert_status(StatusCode::BAD_REQUEST);
1988 let ClientError { error, .. } = response.json();
1989 assert_eq!(error, ClientErrorCode::UnsupportedGrantType);
1990 }
1991}