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_templates::TemplateError);
266impl_from_error_for_route!(mas_storage::RepositoryError);
267impl_from_error_for_route!(mas_policy::EvaluationError);
268impl_from_error_for_route!(super::IdTokenSignatureError);
269
270#[tracing::instrument(
271 name = "handlers.oauth2.token.post",
272 fields(client.id = client_authorization.client_id()),
273 skip_all,
274)]
275pub(crate) async fn post(
276 mut rng: BoxRng,
277 clock: BoxClock,
278 State(http_client): State<reqwest::Client>,
279 State(key_store): State<Keystore>,
280 State(url_builder): State<UrlBuilder>,
281 activity_tracker: BoundActivityTracker,
282 mut repo: BoxRepository,
283 State(homeserver): State<Arc<dyn HomeserverConnection>>,
284 State(site_config): State<SiteConfig>,
285 State(encrypter): State<Encrypter>,
286 State(templates): State<Templates>,
287 policy: Policy,
288 user_agent: Option<TypedHeader<headers::UserAgent>>,
289 client_authorization: ClientAuthorization<AccessTokenRequest>,
290) -> Result<impl IntoResponse, RouteError> {
291 let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
292 let client = client_authorization
293 .credentials
294 .fetch(&mut repo)
295 .await?
296 .ok_or(RouteError::ClientNotFound)?;
297
298 let method = client
299 .token_endpoint_auth_method
300 .as_ref()
301 .ok_or(RouteError::ClientNotAllowed(client.id))?;
302
303 client_authorization
304 .credentials
305 .verify(&http_client, &encrypter, method, &client)
306 .await
307 .map_err(|err| {
308 if err.is_internal() {
311 RouteError::ClientCredentialsVerification {
312 client_id: client.id,
313 source: err,
314 }
315 } else {
316 RouteError::InvalidClientCredentials {
317 client_id: client.id,
318 source: err,
319 }
320 }
321 })?;
322
323 client.maybe_record_as_requester();
326
327 let form = client_authorization.form.ok_or(RouteError::BadRequest)?;
328
329 let grant_type = form.grant_type();
330
331 let (reply, repo) = match form {
332 AccessTokenRequest::AuthorizationCode(grant) => {
333 authorization_code_grant(
334 &mut rng,
335 &clock,
336 &activity_tracker,
337 &grant,
338 &client,
339 &key_store,
340 &url_builder,
341 &site_config,
342 repo,
343 &homeserver,
344 &templates,
345 user_agent,
346 )
347 .await?
348 }
349 AccessTokenRequest::RefreshToken(grant) => {
350 refresh_token_grant(
351 &mut rng,
352 &clock,
353 &activity_tracker,
354 &grant,
355 &client,
356 &site_config,
357 repo,
358 user_agent,
359 )
360 .await?
361 }
362 AccessTokenRequest::ClientCredentials(grant) => {
363 client_credentials_grant(
364 &mut rng,
365 &clock,
366 &activity_tracker,
367 &grant,
368 &client,
369 &site_config,
370 repo,
371 policy,
372 user_agent,
373 )
374 .await?
375 }
376 AccessTokenRequest::DeviceCode(grant) => {
377 device_code_grant(
378 &mut rng,
379 &clock,
380 &activity_tracker,
381 &grant,
382 &client,
383 &key_store,
384 &url_builder,
385 &templates,
386 &site_config,
387 repo,
388 &homeserver,
389 user_agent,
390 )
391 .await?
392 }
393 _ => {
394 return Err(RouteError::UnsupportedGrantType);
395 }
396 };
397
398 repo.save().await?;
399
400 TOKEN_REQUEST_COUNTER.add(
401 1,
402 &[
403 KeyValue::new(GRANT_TYPE, grant_type),
404 KeyValue::new(RESULT, "success"),
405 ],
406 );
407
408 let mut headers = HeaderMap::new();
409 headers.typed_insert(CacheControl::new().with_no_store());
410 headers.typed_insert(Pragma::no_cache());
411
412 Ok((headers, Json(reply)))
413}
414
415async fn authorization_code_grant(
416 mut rng: &mut BoxRng,
417 clock: &impl Clock,
418 activity_tracker: &BoundActivityTracker,
419 grant: &AuthorizationCodeGrant,
420 client: &Client,
421 key_store: &Keystore,
422 url_builder: &UrlBuilder,
423 site_config: &SiteConfig,
424 mut repo: BoxRepository,
425 homeserver: &Arc<dyn HomeserverConnection>,
426 templates: &Templates,
427 user_agent: Option<String>,
428) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
429 if !client.grant_types.contains(&GrantType::AuthorizationCode) {
431 return Err(RouteError::UnauthorizedClient(client.id));
432 }
433
434 let authz_grant = repo
435 .oauth2_authorization_grant()
436 .find_by_code(&grant.code)
437 .await?
438 .ok_or(RouteError::GrantNotFound)?;
439
440 let now = clock.now();
441
442 let session_id = match authz_grant.stage {
443 AuthorizationGrantStage::Cancelled { cancelled_at } => {
444 debug!(%cancelled_at, "Authorization grant was cancelled");
445 return Err(RouteError::InvalidGrant(authz_grant.id));
446 }
447 AuthorizationGrantStage::Exchanged {
448 exchanged_at,
449 fulfilled_at,
450 session_id,
451 } => {
452 warn!(%exchanged_at, %fulfilled_at, "Authorization code was already exchanged");
453
454 if now - exchanged_at > Duration::microseconds(20 * 1000 * 1000) {
456 warn!(oauth_session.id = %session_id, "Ending potentially compromised session");
457 let session = repo
458 .oauth2_session()
459 .lookup(session_id)
460 .await?
461 .ok_or(RouteError::NoSuchOAuthSession(session_id))?;
462
463 repo.oauth2_session().finish(clock, session).await?;
465 repo.save().await?;
466 }
468
469 return Err(RouteError::InvalidGrant(authz_grant.id));
470 }
471 AuthorizationGrantStage::Pending => {
472 warn!("Authorization grant has not been fulfilled yet");
473 return Err(RouteError::InvalidGrant(authz_grant.id));
474 }
475 AuthorizationGrantStage::Fulfilled {
476 session_id,
477 fulfilled_at,
478 } => {
479 if now - fulfilled_at > Duration::microseconds(10 * 60 * 1000 * 1000) {
480 warn!("Code exchange took more than 10 minutes");
481 return Err(RouteError::InvalidGrant(authz_grant.id));
482 }
483
484 session_id
485 }
486 };
487
488 let mut session = repo
489 .oauth2_session()
490 .lookup(session_id)
491 .await?
492 .ok_or(RouteError::NoSuchOAuthSession(session_id))?;
493
494 let lang: DataLocale = authz_grant.locale.as_deref().unwrap_or("en").parse()?;
496 let ctx = DeviceNameContext::new(client.clone(), user_agent.clone()).with_language(lang);
497 let device_name = templates.render_device_name(&ctx)?;
498
499 if let Some(user_agent) = user_agent {
500 session = repo
501 .oauth2_session()
502 .record_user_agent(session, user_agent)
503 .await?;
504 }
505
506 let code = authz_grant
508 .code
509 .as_ref()
510 .ok_or(RouteError::InvalidGrant(authz_grant.id))?;
511
512 if client.id != session.client_id {
513 return Err(RouteError::UnexptectedClient {
514 was: client.id,
515 expected: session.client_id,
516 });
517 }
518
519 match (code.pkce.as_ref(), grant.code_verifier.as_ref()) {
520 (None, None) => {}
521 (Some(_), None) | (None, Some(_)) => return Err(RouteError::BadRequest),
523 (Some(pkce), Some(verifier)) => {
525 pkce.verify(verifier)?;
526 }
527 }
528
529 let Some(user_session_id) = session.user_session_id else {
530 tracing::warn!("No user session associated with this OAuth2 session");
531 return Err(RouteError::InvalidGrant(authz_grant.id));
532 };
533
534 let browser_session = repo
535 .browser_session()
536 .lookup(user_session_id)
537 .await?
538 .ok_or(RouteError::NoSuchBrowserSession(user_session_id))?;
539
540 let last_authentication = repo
541 .browser_session()
542 .get_last_authentication(&browser_session)
543 .await?;
544
545 let ttl = site_config.access_token_ttl;
546 let (access_token, refresh_token) =
547 generate_token_pair(&mut rng, clock, &mut repo, &session, ttl).await?;
548
549 let id_token = if session.scope.contains(&scope::OPENID) {
550 Some(generate_id_token(
551 &mut rng,
552 clock,
553 url_builder,
554 key_store,
555 client,
556 Some(&authz_grant),
557 &browser_session,
558 Some(&access_token),
559 last_authentication.as_ref(),
560 )?)
561 } else {
562 None
563 };
564
565 let mut params = AccessTokenResponse::new(access_token.access_token)
566 .with_expires_in(ttl)
567 .with_refresh_token(refresh_token.refresh_token)
568 .with_scope(session.scope.clone());
569
570 if let Some(id_token) = id_token {
571 params = params.with_id_token(id_token);
572 }
573
574 repo.user()
576 .acquire_lock_for_sync(&browser_session.user)
577 .await?;
578
579 for scope in &*session.scope {
581 if let Some(device) = Device::from_scope_token(scope) {
582 homeserver
592 .upsert_device(
593 &browser_session.user.username,
594 device.as_str(),
595 Some(&device_name),
596 )
597 .await
598 .map_err(RouteError::ProvisionDeviceFailed)?;
599 }
600 }
601
602 repo.oauth2_authorization_grant()
603 .exchange(clock, authz_grant)
604 .await?;
605
606 activity_tracker
610 .record_oauth2_session(clock, &session)
611 .await;
612
613 Ok((params, repo))
614}
615
616async fn refresh_token_grant(
617 rng: &mut BoxRng,
618 clock: &impl Clock,
619 activity_tracker: &BoundActivityTracker,
620 grant: &RefreshTokenGrant,
621 client: &Client,
622 site_config: &SiteConfig,
623 mut repo: BoxRepository,
624 user_agent: Option<String>,
625) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
626 if !client.grant_types.contains(&GrantType::RefreshToken) {
628 return Err(RouteError::UnauthorizedClient(client.id));
629 }
630
631 let refresh_token = repo
632 .oauth2_refresh_token()
633 .find_by_token(&grant.refresh_token)
634 .await?
635 .ok_or(RouteError::RefreshTokenNotFound)?;
636
637 let mut session = repo
638 .oauth2_session()
639 .lookup(refresh_token.session_id)
640 .await?
641 .ok_or(RouteError::NoSuchOAuthSession(refresh_token.session_id))?;
642
643 if let Some(user_agent) = user_agent {
646 session = repo
647 .oauth2_session()
648 .record_user_agent(session, user_agent)
649 .await?;
650 }
651
652 if !session.is_valid() {
653 return Err(RouteError::SessionInvalid(session.id));
654 }
655
656 if client.id != session.client_id {
657 return Err(RouteError::ClientIDMismatch {
659 expected: session.client_id,
660 actual: client.id,
661 });
662 }
663
664 if !refresh_token.is_valid() {
665 let Some(next_refresh_token_id) = refresh_token.next_refresh_token_id() else {
670 return Err(RouteError::RefreshTokenInvalid(refresh_token.id));
673 };
674
675 let Some(next_refresh_token) = repo
676 .oauth2_refresh_token()
677 .lookup(next_refresh_token_id)
678 .await?
679 else {
680 return Err(RouteError::NoSuchNextRefreshToken {
681 next: next_refresh_token_id,
682 previous: refresh_token.id,
683 });
684 };
685
686 if !next_refresh_token.is_valid() {
688 return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
690 }
691
692 if let Some(access_token_id) = next_refresh_token.access_token_id {
719 let next_access_token = repo
721 .oauth2_access_token()
722 .lookup(access_token_id)
723 .await?
724 .ok_or(RouteError::NoSuchNextAccessToken {
725 access_token: access_token_id,
726 refresh_token: next_refresh_token_id,
727 })?;
728
729 if next_access_token.is_used() {
730 return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
732 }
733
734 repo.oauth2_access_token()
736 .revoke(clock, next_access_token)
737 .await?;
738 }
739
740 info!(
744 oauth2_session.id = %session.id,
745 oauth2_client.id = %client.id,
746 %refresh_token.id,
747 "Refresh token already used, but issued refresh and access tokens are unused. Assuming those were lost; revoking those and reissuing new ones."
748 );
749
750 repo.oauth2_refresh_token()
751 .revoke(clock, next_refresh_token)
752 .await?;
753 }
754
755 activity_tracker
756 .record_oauth2_session(clock, &session)
757 .await;
758
759 let ttl = site_config.access_token_ttl;
760 let (new_access_token, new_refresh_token) =
761 generate_token_pair(rng, clock, &mut repo, &session, ttl).await?;
762
763 let refresh_token = repo
764 .oauth2_refresh_token()
765 .consume(clock, refresh_token, &new_refresh_token)
766 .await?;
767
768 if let Some(access_token_id) = refresh_token.access_token_id {
769 let access_token = repo.oauth2_access_token().lookup(access_token_id).await?;
770 if let Some(access_token) = access_token {
771 if !access_token.state.is_revoked() {
773 repo.oauth2_access_token()
774 .revoke(clock, access_token)
775 .await?;
776 }
777 }
778 }
779
780 let params = AccessTokenResponse::new(new_access_token.access_token)
781 .with_expires_in(ttl)
782 .with_refresh_token(new_refresh_token.refresh_token)
783 .with_scope(session.scope);
784
785 Ok((params, repo))
786}
787
788async fn client_credentials_grant(
789 rng: &mut BoxRng,
790 clock: &impl Clock,
791 activity_tracker: &BoundActivityTracker,
792 grant: &ClientCredentialsGrant,
793 client: &Client,
794 site_config: &SiteConfig,
795 mut repo: BoxRepository,
796 mut policy: Policy,
797 user_agent: Option<String>,
798) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
799 if !client.grant_types.contains(&GrantType::ClientCredentials) {
801 return Err(RouteError::UnauthorizedClient(client.id));
802 }
803
804 let scope = grant
806 .scope
807 .clone()
808 .unwrap_or_else(|| std::iter::empty::<ScopeToken>().collect());
809
810 let res = policy
812 .evaluate_authorization_grant(mas_policy::AuthorizationGrantInput {
813 user: None,
814 client,
815 session_counts: None,
816 scope: &scope,
817 grant_type: mas_policy::GrantType::ClientCredentials,
818 requester: mas_policy::Requester {
819 ip_address: activity_tracker.ip(),
820 user_agent: user_agent.clone(),
821 },
822 })
823 .await?;
824 if !res.valid() {
825 return Err(RouteError::DeniedByPolicy(res));
826 }
827
828 let mut session = repo
830 .oauth2_session()
831 .add_from_client_credentials(rng, clock, client, scope)
832 .await?;
833
834 if let Some(user_agent) = user_agent {
835 session = repo
836 .oauth2_session()
837 .record_user_agent(session, user_agent)
838 .await?;
839 }
840
841 let ttl = site_config.access_token_ttl;
842 let access_token_str = TokenType::AccessToken.generate(rng);
843
844 let access_token = repo
845 .oauth2_access_token()
846 .add(rng, clock, &session, access_token_str, Some(ttl))
847 .await?;
848
849 let mut params = AccessTokenResponse::new(access_token.access_token).with_expires_in(ttl);
850
851 activity_tracker
855 .record_oauth2_session(clock, &session)
856 .await;
857
858 if !session.scope.is_empty() {
859 params = params.with_scope(session.scope);
861 }
862
863 Ok((params, repo))
864}
865
866async fn device_code_grant(
867 rng: &mut BoxRng,
868 clock: &impl Clock,
869 activity_tracker: &BoundActivityTracker,
870 grant: &DeviceCodeGrant,
871 client: &Client,
872 key_store: &Keystore,
873 url_builder: &UrlBuilder,
874 templates: &Templates,
875 site_config: &SiteConfig,
876 mut repo: BoxRepository,
877 homeserver: &Arc<dyn HomeserverConnection>,
878 user_agent: Option<String>,
879) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
880 if !site_config.device_code_grant_enabled {
882 return Err(RouteError::UnsupportedGrantType);
883 }
884
885 if !client.grant_types.contains(&GrantType::DeviceCode) {
887 return Err(RouteError::UnauthorizedClient(client.id));
888 }
889
890 let grant = repo
891 .oauth2_device_code_grant()
892 .find_by_device_code(&grant.device_code)
893 .await?
894 .ok_or(RouteError::GrantNotFound)?;
895
896 if client.id != grant.client_id {
898 return Err(RouteError::ClientIDMismatch {
899 expected: grant.client_id,
900 actual: client.id,
901 });
902 }
903
904 if grant.expires_at < clock.now() {
905 return Err(RouteError::DeviceCodeExpired);
906 }
907
908 let browser_session_id = match &grant.state {
909 DeviceCodeGrantState::Pending => {
910 return Err(RouteError::DeviceCodePending);
911 }
912 DeviceCodeGrantState::Rejected { .. } => {
913 return Err(RouteError::DeviceCodeRejected);
914 }
915 DeviceCodeGrantState::Exchanged { .. } => {
916 return Err(RouteError::DeviceCodeExchanged);
917 }
918 DeviceCodeGrantState::Fulfilled {
919 browser_session_id, ..
920 } => *browser_session_id,
921 };
922
923 let browser_session = repo
924 .browser_session()
925 .lookup(browser_session_id)
926 .await?
927 .ok_or(RouteError::NoSuchBrowserSession(browser_session_id))?;
928
929 let lang: DataLocale = grant.locale.as_deref().unwrap_or("en").parse()?;
932 let ctx = DeviceNameContext::new(client.clone(), user_agent.clone()).with_language(lang);
933 let device_name = templates.render_device_name(&ctx)?;
934
935 let mut session = repo
937 .oauth2_session()
938 .add_from_browser_session(rng, clock, client, &browser_session, grant.scope.clone())
939 .await?;
940
941 repo.oauth2_device_code_grant()
942 .exchange(clock, grant, &session)
943 .await?;
944
945 if let Some(user_agent) = user_agent {
947 session = repo
948 .oauth2_session()
949 .record_user_agent(session, user_agent)
950 .await?;
951 }
952
953 let ttl = site_config.access_token_ttl;
954 let access_token_str = TokenType::AccessToken.generate(rng);
955
956 let access_token = repo
957 .oauth2_access_token()
958 .add(rng, clock, &session, access_token_str, Some(ttl))
959 .await?;
960
961 let mut params =
962 AccessTokenResponse::new(access_token.access_token.clone()).with_expires_in(ttl);
963
964 if client.grant_types.contains(&GrantType::RefreshToken) {
967 let refresh_token_str = TokenType::RefreshToken.generate(rng);
968
969 let refresh_token = repo
970 .oauth2_refresh_token()
971 .add(rng, clock, &session, &access_token, refresh_token_str)
972 .await?;
973
974 params = params.with_refresh_token(refresh_token.refresh_token);
975 }
976
977 if session.scope.contains(&scope::OPENID) {
979 let id_token = generate_id_token(
980 rng,
981 clock,
982 url_builder,
983 key_store,
984 client,
985 None,
986 &browser_session,
987 Some(&access_token),
988 None,
989 )?;
990
991 params = params.with_id_token(id_token);
992 }
993
994 repo.user()
996 .acquire_lock_for_sync(&browser_session.user)
997 .await?;
998
999 for scope in &*session.scope {
1001 if let Some(device) = Device::from_scope_token(scope) {
1002 homeserver
1012 .upsert_device(
1013 &browser_session.user.username,
1014 device.as_str(),
1015 Some(&device_name),
1016 )
1017 .await
1018 .map_err(RouteError::ProvisionDeviceFailed)?;
1019 }
1020 }
1021
1022 activity_tracker
1026 .record_oauth2_session(clock, &session)
1027 .await;
1028
1029 if !session.scope.is_empty() {
1030 params = params.with_scope(session.scope);
1032 }
1033
1034 Ok((params, repo))
1035}
1036
1037#[cfg(test)]
1038mod tests {
1039 use hyper::Request;
1040 use mas_data_model::{AccessToken, AuthorizationCode, RefreshToken};
1041 use mas_router::SimpleRoute;
1042 use oauth2_types::{
1043 registration::ClientRegistrationResponse,
1044 requests::{DeviceAuthorizationResponse, ResponseMode},
1045 scope::{OPENID, Scope},
1046 };
1047 use sqlx::PgPool;
1048
1049 use super::*;
1050 use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup, test_site_config};
1051
1052 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1053 async fn test_auth_code_grant(pool: PgPool) {
1054 setup();
1055 let state = TestState::from_pool(pool).await.unwrap();
1056
1057 let request =
1059 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1060 "client_uri": "https://example.com/",
1061 "redirect_uris": ["https://example.com/callback"],
1062 "token_endpoint_auth_method": "none",
1063 "response_types": ["code"],
1064 "grant_types": ["authorization_code"],
1065 }));
1066
1067 let response = state.request(request).await;
1068 response.assert_status(StatusCode::CREATED);
1069
1070 let ClientRegistrationResponse { client_id, .. } = response.json();
1071
1072 let mut repo = state.repository().await.unwrap();
1075
1076 let user = repo
1077 .user()
1078 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1079 .await
1080 .unwrap();
1081
1082 let browser_session = repo
1083 .browser_session()
1084 .add(&mut state.rng(), &state.clock, &user, None)
1085 .await
1086 .unwrap();
1087
1088 let client = repo
1090 .oauth2_client()
1091 .find_by_client_id(&client_id)
1092 .await
1093 .unwrap()
1094 .unwrap();
1095
1096 let code = "thisisaverysecurecode";
1098 let grant = repo
1099 .oauth2_authorization_grant()
1100 .add(
1101 &mut state.rng(),
1102 &state.clock,
1103 &client,
1104 "https://example.com/redirect".parse().unwrap(),
1105 Scope::from_iter([OPENID]),
1106 Some(AuthorizationCode {
1107 code: code.to_owned(),
1108 pkce: None,
1109 }),
1110 Some("state".to_owned()),
1111 Some("nonce".to_owned()),
1112 ResponseMode::Query,
1113 false,
1114 None,
1115 None,
1116 std::collections::BTreeMap::new(),
1117 )
1118 .await
1119 .unwrap();
1120
1121 let session = repo
1122 .oauth2_session()
1123 .add_from_browser_session(
1124 &mut state.rng(),
1125 &state.clock,
1126 &client,
1127 &browser_session,
1128 grant.scope.clone(),
1129 )
1130 .await
1131 .unwrap();
1132
1133 let grant = repo
1135 .oauth2_authorization_grant()
1136 .fulfill(&state.clock, &session, grant)
1137 .await
1138 .unwrap();
1139
1140 repo.save().await.unwrap();
1141
1142 let request =
1144 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1145 "grant_type": "authorization_code",
1146 "code": code,
1147 "redirect_uri": grant.redirect_uri,
1148 "client_id": client.client_id,
1149 }));
1150
1151 let response = state.request(request).await;
1152 response.assert_status(StatusCode::OK);
1153
1154 let AccessTokenResponse { access_token, .. } = response.json();
1155
1156 assert!(state.is_access_token_valid(&access_token).await);
1158
1159 let request =
1161 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1162 "grant_type": "authorization_code",
1163 "code": code,
1164 "redirect_uri": grant.redirect_uri,
1165 "client_id": client.client_id,
1166 }));
1167
1168 let response = state.request(request).await;
1169 response.assert_status(StatusCode::BAD_REQUEST);
1170 let error: ClientError = response.json();
1171 assert_eq!(error.error, ClientErrorCode::InvalidGrant);
1172
1173 assert!(state.is_access_token_valid(&access_token).await);
1175
1176 state.clock.advance(Duration::try_minutes(1).unwrap());
1178
1179 let request =
1181 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1182 "grant_type": "authorization_code",
1183 "code": code,
1184 "redirect_uri": grant.redirect_uri,
1185 "client_id": client.client_id,
1186 }));
1187
1188 let response = state.request(request).await;
1189 response.assert_status(StatusCode::BAD_REQUEST);
1190 let error: ClientError = response.json();
1191 assert_eq!(error.error, ClientErrorCode::InvalidGrant);
1192
1193 assert!(!state.is_access_token_valid(&access_token).await);
1195
1196 let mut repo = state.repository().await.unwrap();
1198 let code = "thisisanothercode";
1199 let grant = repo
1200 .oauth2_authorization_grant()
1201 .add(
1202 &mut state.rng(),
1203 &state.clock,
1204 &client,
1205 "https://example.com/redirect".parse().unwrap(),
1206 Scope::from_iter([OPENID]),
1207 Some(AuthorizationCode {
1208 code: code.to_owned(),
1209 pkce: None,
1210 }),
1211 Some("state".to_owned()),
1212 Some("nonce".to_owned()),
1213 ResponseMode::Query,
1214 false,
1215 None,
1216 None,
1217 std::collections::BTreeMap::new(),
1218 )
1219 .await
1220 .unwrap();
1221
1222 let session = repo
1223 .oauth2_session()
1224 .add_from_browser_session(
1225 &mut state.rng(),
1226 &state.clock,
1227 &client,
1228 &browser_session,
1229 grant.scope.clone(),
1230 )
1231 .await
1232 .unwrap();
1233
1234 let grant = repo
1236 .oauth2_authorization_grant()
1237 .fulfill(&state.clock, &session, grant)
1238 .await
1239 .unwrap();
1240
1241 repo.save().await.unwrap();
1242
1243 state
1245 .clock
1246 .advance(Duration::microseconds(15 * 60 * 1000 * 1000));
1247
1248 let request =
1250 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1251 "grant_type": "authorization_code",
1252 "code": code,
1253 "redirect_uri": grant.redirect_uri,
1254 "client_id": client.client_id,
1255 }));
1256
1257 let response = state.request(request).await;
1258 response.assert_status(StatusCode::BAD_REQUEST);
1259 let ClientError { error, .. } = response.json();
1260 assert_eq!(error, ClientErrorCode::InvalidGrant);
1261 }
1262
1263 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1264 async fn test_refresh_token_grant(pool: PgPool) {
1265 setup();
1266 let state = TestState::from_pool(pool).await.unwrap();
1267
1268 let request =
1270 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1271 "client_uri": "https://example.com/",
1272 "redirect_uris": ["https://example.com/callback"],
1273 "token_endpoint_auth_method": "none",
1274 "response_types": ["code"],
1275 "grant_types": ["authorization_code", "refresh_token"],
1276 }));
1277
1278 let response = state.request(request).await;
1279 response.assert_status(StatusCode::CREATED);
1280
1281 let ClientRegistrationResponse { client_id, .. } = response.json();
1282
1283 let mut repo = state.repository().await.unwrap();
1286
1287 let user = repo
1288 .user()
1289 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1290 .await
1291 .unwrap();
1292
1293 let browser_session = repo
1294 .browser_session()
1295 .add(&mut state.rng(), &state.clock, &user, None)
1296 .await
1297 .unwrap();
1298
1299 let client = repo
1301 .oauth2_client()
1302 .find_by_client_id(&client_id)
1303 .await
1304 .unwrap()
1305 .unwrap();
1306
1307 let session = repo
1309 .oauth2_session()
1310 .add_from_browser_session(
1311 &mut state.rng(),
1312 &state.clock,
1313 &client,
1314 &browser_session,
1315 Scope::from_iter([OPENID]),
1316 )
1317 .await
1318 .unwrap();
1319
1320 let (AccessToken { access_token, .. }, RefreshToken { refresh_token, .. }) =
1321 generate_token_pair(
1322 &mut state.rng(),
1323 &state.clock,
1324 &mut repo,
1325 &session,
1326 Duration::microseconds(5 * 60 * 1000 * 1000),
1327 )
1328 .await
1329 .unwrap();
1330
1331 repo.save().await.unwrap();
1332
1333 assert!(state.is_access_token_valid(&access_token).await);
1335
1336 let request =
1338 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1339 "grant_type": "refresh_token",
1340 "refresh_token": refresh_token,
1341 "client_id": client.client_id,
1342 }));
1343
1344 let response = state.request(request).await;
1345 response.assert_status(StatusCode::OK);
1346
1347 let old_access_token = access_token;
1348 let old_refresh_token = refresh_token;
1349 let response: AccessTokenResponse = response.json();
1350 let access_token = response.access_token;
1351 let refresh_token = response.refresh_token.expect("to have a refresh token");
1352
1353 assert!(state.is_access_token_valid(&access_token).await);
1355
1356 assert!(!state.is_access_token_valid(&old_access_token).await);
1358
1359 let request =
1361 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1362 "grant_type": "refresh_token",
1363 "refresh_token": old_refresh_token,
1364 "client_id": client.client_id,
1365 }));
1366
1367 let response = state.request(request).await;
1368 response.assert_status(StatusCode::BAD_REQUEST);
1369 let ClientError { error, .. } = response.json();
1370 assert_eq!(error, ClientErrorCode::InvalidGrant);
1371
1372 let request =
1374 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1375 "grant_type": "refresh_token",
1376 "refresh_token": refresh_token,
1377 "client_id": client.client_id,
1378 }));
1379
1380 let response = state.request(request).await;
1381 response.assert_status(StatusCode::OK);
1382 let _: AccessTokenResponse = response.json();
1383 }
1384
1385 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1386 async fn test_double_refresh(pool: PgPool) {
1387 setup();
1388 let state = TestState::from_pool(pool).await.unwrap();
1389
1390 let request =
1392 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1393 "client_uri": "https://example.com/",
1394 "redirect_uris": ["https://example.com/callback"],
1395 "token_endpoint_auth_method": "none",
1396 "response_types": ["code"],
1397 "grant_types": ["authorization_code", "refresh_token"],
1398 }));
1399
1400 let response = state.request(request).await;
1401 response.assert_status(StatusCode::CREATED);
1402
1403 let ClientRegistrationResponse { client_id, .. } = response.json();
1404
1405 let mut repo = state.repository().await.unwrap();
1408
1409 let user = repo
1410 .user()
1411 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1412 .await
1413 .unwrap();
1414
1415 let browser_session = repo
1416 .browser_session()
1417 .add(&mut state.rng(), &state.clock, &user, None)
1418 .await
1419 .unwrap();
1420
1421 let client = repo
1423 .oauth2_client()
1424 .find_by_client_id(&client_id)
1425 .await
1426 .unwrap()
1427 .unwrap();
1428
1429 let session = repo
1431 .oauth2_session()
1432 .add_from_browser_session(
1433 &mut state.rng(),
1434 &state.clock,
1435 &client,
1436 &browser_session,
1437 Scope::from_iter([OPENID]),
1438 )
1439 .await
1440 .unwrap();
1441
1442 let (AccessToken { access_token, .. }, RefreshToken { refresh_token, .. }) =
1443 generate_token_pair(
1444 &mut state.rng(),
1445 &state.clock,
1446 &mut repo,
1447 &session,
1448 Duration::microseconds(5 * 60 * 1000 * 1000),
1449 )
1450 .await
1451 .unwrap();
1452
1453 repo.save().await.unwrap();
1454
1455 assert!(state.is_access_token_valid(&access_token).await);
1457
1458 let request =
1460 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1461 "grant_type": "refresh_token",
1462 "refresh_token": refresh_token,
1463 "client_id": client.client_id,
1464 }));
1465
1466 let first_response = state.request(request).await;
1467 first_response.assert_status(StatusCode::OK);
1468 let first_response: AccessTokenResponse = first_response.json();
1469
1470 let request =
1473 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1474 "grant_type": "refresh_token",
1475 "refresh_token": refresh_token,
1476 "client_id": client.client_id,
1477 }));
1478
1479 let second_response = state.request(request).await;
1480 second_response.assert_status(StatusCode::OK);
1481 let second_response: AccessTokenResponse = second_response.json();
1482
1483 assert_ne!(first_response.access_token, second_response.access_token);
1485 assert_ne!(first_response.refresh_token, second_response.refresh_token);
1486
1487 assert!(
1489 !state
1490 .is_access_token_valid(&first_response.access_token)
1491 .await
1492 );
1493
1494 assert!(
1496 state
1497 .is_access_token_valid(&second_response.access_token)
1498 .await
1499 );
1500
1501 let request =
1504 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1505 "grant_type": "refresh_token",
1506 "refresh_token": refresh_token,
1507 "client_id": client.client_id,
1508 }));
1509
1510 let third_response = state.request(request).await;
1511 third_response.assert_status(StatusCode::BAD_REQUEST);
1512
1513 let request =
1519 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1520 "grant_type": "refresh_token",
1521 "refresh_token": second_response.refresh_token,
1522 "client_id": client.client_id,
1523 }));
1524
1525 let fourth_response = state.request(request).await;
1527 fourth_response.assert_status(StatusCode::OK);
1528 let fourth_response: AccessTokenResponse = fourth_response.json();
1529
1530 let request =
1532 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1533 "grant_type": "refresh_token",
1534 "refresh_token": fourth_response.refresh_token,
1535 "client_id": client.client_id,
1536 }));
1537
1538 let fifth_response = state.request(request).await;
1539 fifth_response.assert_status(StatusCode::OK);
1540 let fifth_response: AccessTokenResponse = fifth_response.json();
1541
1542 let request =
1545 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1546 "grant_type": "refresh_token",
1547 "refresh_token": second_response.refresh_token,
1548 "client_id": client.client_id,
1549 }));
1550
1551 let sixth_response = state.request(request).await;
1552 sixth_response.assert_status(StatusCode::BAD_REQUEST);
1553
1554 assert!(
1561 state
1562 .is_access_token_valid(&fifth_response.access_token)
1563 .await
1564 );
1565
1566 state.run_jobs_in_queue().await;
1570 state.clock.advance(Duration::days(31));
1571 state.run_jobs_in_queue().await;
1572
1573 let request =
1576 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1577 "grant_type": "refresh_token",
1578 "refresh_token": fourth_response.refresh_token,
1579 "client_id": client.client_id,
1580 }));
1581
1582 let seventh_response = state.request(request).await;
1583 seventh_response.assert_status(StatusCode::OK);
1584
1585 let request =
1587 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1588 "grant_type": "refresh_token",
1589 "refresh_token": fifth_response.refresh_token,
1590 "client_id": client.client_id,
1591 }));
1592
1593 let eighth_response = state.request(request).await;
1594 eighth_response.assert_status(StatusCode::BAD_REQUEST);
1595 }
1596
1597 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1598 async fn test_client_credentials(pool: PgPool) {
1599 setup();
1600 let state = TestState::from_pool(pool).await.unwrap();
1601
1602 let request =
1604 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1605 "client_uri": "https://example.com/",
1606 "token_endpoint_auth_method": "client_secret_post",
1607 "grant_types": ["client_credentials"],
1608 }));
1609
1610 let response = state.request(request).await;
1611 response.assert_status(StatusCode::CREATED);
1612
1613 let response: ClientRegistrationResponse = response.json();
1614 let client_id = response.client_id;
1615 let client_secret = response.client_secret.expect("to have a client secret");
1616
1617 let request =
1619 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1620 "grant_type": "client_credentials",
1621 "client_id": client_id,
1622 "client_secret": client_secret,
1623 }));
1624
1625 let response = state.request(request).await;
1626 response.assert_status(StatusCode::OK);
1627
1628 let response: AccessTokenResponse = response.json();
1629 assert!(response.refresh_token.is_none());
1630 assert!(response.expires_in.is_some());
1631 assert!(response.scope.is_none());
1632
1633 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1635 "token": response.access_token,
1636 "client_id": client_id,
1637 "client_secret": client_secret,
1638 }));
1639
1640 let response = state.request(request).await;
1641 response.assert_status(StatusCode::OK);
1642
1643 let request =
1645 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1646 "grant_type": "client_credentials",
1647 "client_id": client_id,
1648 "client_secret": client_secret,
1649 "scope": "urn:mas:graphql:*"
1650 }));
1651
1652 let response = state.request(request).await;
1653 response.assert_status(StatusCode::OK);
1654
1655 let response: AccessTokenResponse = response.json();
1656 assert!(response.refresh_token.is_none());
1657 assert!(response.expires_in.is_some());
1658 assert_eq!(response.scope, Some("urn:mas:graphql:*".parse().unwrap()));
1659
1660 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1662 "token": response.access_token,
1663 "client_id": client_id,
1664 "client_secret": client_secret,
1665 }));
1666
1667 let response = state.request(request).await;
1668 response.assert_status(StatusCode::OK);
1669
1670 let request =
1672 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1673 "grant_type": "client_credentials",
1674 "client_id": client_id,
1675 "client_secret": client_secret,
1676 "scope": "urn:mas:admin"
1677 }));
1678
1679 let response = state.request(request).await;
1680 response.assert_status(StatusCode::FORBIDDEN);
1681
1682 let ClientError { error, .. } = response.json();
1683 assert_eq!(error, ClientErrorCode::InvalidScope);
1684
1685 let state = {
1687 let mut state = state;
1688 state.policy_factory = crate::test_utils::policy_factory(
1689 mas_policy::BaseData {
1690 server_name: "example.com".to_owned(),
1691 session_limit: None,
1692 },
1693 serde_json::json!({
1694 "admin_clients": [client_id]
1695 }),
1696 )
1697 .await
1698 .unwrap();
1699 state
1700 };
1701
1702 let request =
1703 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1704 "grant_type": "client_credentials",
1705 "client_id": client_id,
1706 "client_secret": client_secret,
1707 "scope": "urn:mas:admin"
1708 }));
1709
1710 let response = state.request(request).await;
1711 response.assert_status(StatusCode::OK);
1712
1713 let response: AccessTokenResponse = response.json();
1714 assert!(response.refresh_token.is_none());
1715 assert!(response.expires_in.is_some());
1716 assert_eq!(response.scope, Some("urn:mas:admin".parse().unwrap()));
1717
1718 let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1720 "token": response.access_token,
1721 "client_id": client_id,
1722 "client_secret": client_secret,
1723 }));
1724
1725 let response = state.request(request).await;
1726 response.assert_status(StatusCode::OK);
1727 }
1728
1729 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1730 async fn test_device_code_grant(pool: PgPool) {
1731 setup();
1732 let state = TestState::from_pool(pool).await.unwrap();
1733
1734 let request =
1736 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1737 "client_uri": "https://example.com/",
1738 "token_endpoint_auth_method": "none",
1739 "grant_types": ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"],
1740 "response_types": [],
1741 }));
1742
1743 let response = state.request(request).await;
1744 response.assert_status(StatusCode::CREATED);
1745
1746 let response: ClientRegistrationResponse = response.json();
1747 let client_id = response.client_id;
1748
1749 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1751 serde_json::json!({
1752 "client_id": client_id,
1753 "scope": "openid",
1754 }),
1755 );
1756 let response = state.request(request).await;
1757 response.assert_status(StatusCode::OK);
1758
1759 let device_grant: DeviceAuthorizationResponse = response.json();
1760
1761 let request =
1763 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1764 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1765 "device_code": device_grant.device_code,
1766 "client_id": client_id,
1767 }));
1768 let response = state.request(request).await;
1769 response.assert_status(StatusCode::FORBIDDEN);
1770
1771 let ClientError { error, .. } = response.json();
1772 assert_eq!(error, ClientErrorCode::AuthorizationPending);
1773
1774 let mut repo = state.repository().await.unwrap();
1778
1779 let user = repo
1780 .user()
1781 .add(&mut state.rng(), &state.clock, "alice".to_owned())
1782 .await
1783 .unwrap();
1784
1785 let browser_session = repo
1786 .browser_session()
1787 .add(&mut state.rng(), &state.clock, &user, None)
1788 .await
1789 .unwrap();
1790
1791 let grant = repo
1793 .oauth2_device_code_grant()
1794 .find_by_user_code(&device_grant.user_code)
1795 .await
1796 .unwrap()
1797 .unwrap();
1798
1799 let grant = repo
1801 .oauth2_device_code_grant()
1802 .fulfill(&state.clock, grant, &browser_session, Some("en".to_owned()))
1803 .await
1804 .unwrap();
1805
1806 repo.save().await.unwrap();
1807
1808 let request =
1810 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1811 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1812 "device_code": grant.device_code,
1813 "client_id": client_id,
1814 }));
1815
1816 let response = state.request(request).await;
1817 response.assert_status(StatusCode::OK);
1818
1819 let response: AccessTokenResponse = response.json();
1820
1821 assert!(state.is_access_token_valid(&response.access_token).await);
1823 assert!(response.refresh_token.is_some());
1825 assert!(response.id_token.is_some());
1827
1828 let request =
1830 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1831 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1832 "device_code": grant.device_code,
1833 "client_id": client_id,
1834 }));
1835 let response = state.request(request).await;
1836 response.assert_status(StatusCode::BAD_REQUEST);
1837
1838 let ClientError { error, .. } = response.json();
1839 assert_eq!(error, ClientErrorCode::InvalidGrant);
1840
1841 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1843 serde_json::json!({
1844 "client_id": client_id,
1845 "scope": "openid",
1846 }),
1847 );
1848 let response = state.request(request).await;
1849 response.assert_status(StatusCode::OK);
1850
1851 let device_grant: DeviceAuthorizationResponse = response.json();
1852
1853 let request =
1855 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1856 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1857 "device_code": device_grant.device_code,
1858 "client_id": client_id,
1859 }));
1860 let response = state.request(request).await;
1861 response.assert_status(StatusCode::FORBIDDEN);
1862
1863 let ClientError { error, .. } = response.json();
1864 assert_eq!(error, ClientErrorCode::AuthorizationPending);
1865
1866 state.clock.advance(Duration::try_hours(1).unwrap());
1867
1868 let request =
1870 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1871 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1872 "device_code": device_grant.device_code,
1873 "client_id": client_id,
1874 }));
1875 let response = state.request(request).await;
1876 response.assert_status(StatusCode::FORBIDDEN);
1877
1878 let ClientError { error, .. } = response.json();
1879 assert_eq!(error, ClientErrorCode::ExpiredToken);
1880
1881 let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1883 serde_json::json!({
1884 "client_id": client_id,
1885 "scope": "openid",
1886 }),
1887 );
1888 let response = state.request(request).await;
1889 response.assert_status(StatusCode::OK);
1890
1891 let device_grant: DeviceAuthorizationResponse = response.json();
1892
1893 let mut repo = state.repository().await.unwrap();
1895
1896 let grant = repo
1898 .oauth2_device_code_grant()
1899 .find_by_user_code(&device_grant.user_code)
1900 .await
1901 .unwrap()
1902 .unwrap();
1903
1904 let grant = repo
1906 .oauth2_device_code_grant()
1907 .reject(&state.clock, grant, &browser_session)
1908 .await
1909 .unwrap();
1910
1911 repo.save().await.unwrap();
1912
1913 let request =
1915 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1916 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1917 "device_code": grant.device_code,
1918 "client_id": client_id,
1919 }));
1920 let response = state.request(request).await;
1921 response.assert_status(StatusCode::FORBIDDEN);
1922
1923 let ClientError { error, .. } = response.json();
1924 assert_eq!(error, ClientErrorCode::AccessDenied);
1925 }
1926
1927 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1928 async fn test_unsupported_grant(pool: PgPool) {
1929 setup();
1930 let state = TestState::from_pool(pool).await.unwrap();
1931
1932 let request =
1934 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1935 "client_uri": "https://example.com/",
1936 "redirect_uris": ["https://example.com/callback"],
1937 "token_endpoint_auth_method": "client_secret_post",
1938 "grant_types": ["password"],
1939 "response_types": [],
1940 }));
1941
1942 let response = state.request(request).await;
1943 response.assert_status(StatusCode::CREATED);
1944
1945 let response: ClientRegistrationResponse = response.json();
1946 let client_id = response.client_id;
1947 let client_secret = response.client_secret.expect("to have a client secret");
1948
1949 let request =
1951 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1952 "grant_type": "password",
1953 "client_id": client_id,
1954 "client_secret": client_secret,
1955 "username": "john",
1956 "password": "hunter2",
1957 }));
1958
1959 let response = state.request(request).await;
1960 response.assert_status(StatusCode::BAD_REQUEST);
1961 let ClientError { error, .. } = response.json();
1962 assert_eq!(error, ClientErrorCode::UnsupportedGrantType);
1963 }
1964
1965 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1966 async fn test_device_code_grant_disabled(pool: PgPool) {
1967 setup();
1968 let state = TestState::from_pool_with_site_config(
1969 pool,
1970 SiteConfig {
1971 device_code_grant_enabled: false,
1972 ..test_site_config()
1973 },
1974 )
1975 .await
1976 .unwrap();
1977
1978 let request =
1980 Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1981 "client_uri": "https://example.com/",
1982 "redirect_uris": ["https://example.com/callback"],
1983 "token_endpoint_auth_method": "none",
1984 "response_types": ["code"],
1985 "grant_types": ["authorization_code"],
1986 }));
1987
1988 let response = state.request(request).await;
1989 response.assert_status(StatusCode::CREATED);
1990
1991 let response: ClientRegistrationResponse = response.json();
1992 let client_id = response.client_id;
1993
1994 let request =
1996 Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1997 "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1998 "device_code": "fake-device-code",
1999 "client_id": client_id,
2000 }));
2001
2002 let response = state.request(request).await;
2003 response.assert_status(StatusCode::BAD_REQUEST);
2004 let ClientError { error, .. } = response.json();
2005 assert_eq!(error, ClientErrorCode::UnsupportedGrantType);
2006 }
2007}