Skip to main content

mas_handlers/oauth2/
token.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8use 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            // Classify the error differntly, depending on whether it's an 'internal' error,
310            // or just because the client presented invalid credentials.
311            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    // The authenticated client is the entity making this request, regardless of
325    // the grant type or whether a user session is later minted.
326    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    // Check that the client is allowed to use this grant type
431    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            // Ending the session if the token was already exchanged more than 20s ago
457            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                //if !session.is_finished() {
466                repo.oauth2_session().finish(clock, session).await?;
467                repo.save().await?;
468                //}
469            }
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    // This should never happen, since we looked up in the database using the code
498    let code = authz_grant
499        .code
500        .as_ref()
501        .ok_or(RouteError::InvalidGrant(authz_grant.id))?;
502
503    // verify the PKCE challenge before creating any session
504    match (code.pkce.as_ref(), grant.code_verifier.as_ref()) {
505        (None, None) => {}
506        // We have a challenge but no verifier (or vice-versa)? Bad request.
507        (Some(_), None) | (None, Some(_)) => return Err(RouteError::BadRequest),
508        // If we have both, we need to check the code validity
509        (Some(pkce), Some(verifier)) => {
510            pkce.verify(verifier)?;
511        }
512    }
513
514    // look up the browser session that approved this grant
515    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    // start the OAuth2 session now at token exchange time
530    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    // Lock the user sync to make sure we don't get into a race condition
583    repo.user()
584        .acquire_lock_for_sync(&browser_session.user)
585        .await?;
586
587    // Look for device to provision
588    for scope in &*session.scope {
589        if let Some(device) = Device::from_scope_token(scope) {
590            // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we
591            // want the device to be created synchronously on the homeserver, so
592            // that when we respond, the access token works completely. If the
593            // device doesn't exist on the homeserver side, token introspection
594            // from Synapse to MAS will work but Synapse will return a 401
595            // because it doesn't see the device.
596            //
597            // We're using an upsert so if the device already exists for some reason
598            // (like when a concurrent device sync happening) it won't have any effect.
599            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    // XXX: there is a potential (but unlikely) race here, where the activity for
615    // the session is recorded before the transaction is committed. We would have to
616    // save the repository here to fix that.
617    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    // Check that the client is allowed to use this grant type
635    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    // Let's for now record the user agent on each refresh, that should be
652    // responsive enough and not too much of a burden on the database.
653    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        // As per https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
666        return Err(RouteError::ClientIDMismatch {
667            expected: session.client_id,
668            actual: client.id,
669        });
670    }
671
672    if !refresh_token.is_valid() {
673        // We're seing a refresh token that already has been consumed, this might be a
674        // double-refresh or a replay attack
675
676        // First, get the next refresh token
677        let Some(next_refresh_token_id) = refresh_token.next_refresh_token_id() else {
678            // If we don't have a 'next' refresh token, it may just be because this was
679            // before we were recording those. Let's just treat it as a replay.
680            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        // Check if the next refresh token was already consumed or not
695        if !next_refresh_token.is_valid() {
696            // XXX: This is a replay, we *may* want to invalidate the session
697            return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
698        }
699
700        // Check if the associated access token was already used.
701        //
702        // If the access token is no longer present, we assume it was *not* used.
703        // Tokens can disappear for two main reasons:
704        //
705        //  - revoked access tokens are deleted after 1 hour
706        //  - expired access tokens are deleted after 30 days
707        //
708        // Revoked tokens are not an issue, as the associated refresh token is also
709        // revoked. For expired tokens, however, we are effectively losing the
710        // ability to prevent the client from performing a bad double-refresh.
711        // This measure is intended to enhance security when a refresh token
712        // leaks. However, the primary goal is to ensure that we do not maintain
713        // two active branches of the refresh token tree.
714        //
715        // Consider these two scenarios:
716        //
717        //   - Refresh token A is consumed, issuing refresh token B and access token C.
718        //   - The client uses access token C.
719        //   - Access token C expires after some time.
720        //   - If the client then attempts to use refresh token A again:
721        //      - If access token C is still present, the refresh will be rightfully
722        //        declined, as we have proof that it received the new set of tokens.
723        //      - If access token C was cleaned up, the refresh will succeed, issuing
724        //        new tokens but invalidating refresh token B and the original access
725        //        token C.
726        if let Some(access_token_id) = next_refresh_token.access_token_id {
727            // Load it
728            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                // XXX: This is a replay, we *may* want to invalidate the session
739                return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
740            }
741
742            // This could be a double-refresh, see below
743            repo.oauth2_access_token()
744                .revoke(clock, next_access_token)
745                .await?;
746        }
747
748        // Looks like it's a double-refresh, client lost their refresh token on
749        // the way back. Let's revoke the unused access and refresh tokens, and
750        // issue new ones
751        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 it is a double-refresh, it might already be revoked
780            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    // Check that the client is allowed to use this grant type
808    if !client.grant_types.contains(&GrantType::ClientCredentials) {
809        return Err(RouteError::UnauthorizedClient(client.id));
810    }
811
812    // Default to an empty scope if none is provided
813    let scope = grant
814        .scope
815        .clone()
816        .unwrap_or_else(|| std::iter::empty::<ScopeToken>().collect());
817
818    // Make the request go through the policy engine
819    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    // Start the session
837    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    // XXX: there is a potential (but unlikely) race here, where the activity for
860    // the session is recorded before the transaction is committed. We would have to
861    // save the repository here to fix that.
862    activity_tracker
863        .record_oauth2_session(clock, &session)
864        .await;
865
866    if !session.scope.is_empty() {
867        // We only return the scope if it's not empty
868        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    // Check that the Device Authorization Grant is enabled on this server
889    if !site_config.device_code_grant_enabled {
890        return Err(RouteError::UnsupportedGrantType);
891    }
892
893    // Check that the client is allowed to use this grant type
894    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    // Check that the client match
905    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    // Generate a device name, using the locale captured from the browser which
938    // fulfilled the grant
939    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    // Start the session
944    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    // XXX: should we get the user agent from the device code grant instead?
954    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 the client uses the refresh token grant type, we also generate a refresh
973    // token
974    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 the client asked for an ID token, we generate one
986    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    // Lock the user sync to make sure we don't get into a race condition
1003    repo.user()
1004        .acquire_lock_for_sync(&browser_session.user)
1005        .await?;
1006
1007    // Look for device to provision
1008    for scope in &*session.scope {
1009        if let Some(device) = Device::from_scope_token(scope) {
1010            // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we
1011            // want the device to be created synchronously on the homeserver, so
1012            // that when we respond, the access token works completely. If the
1013            // device doesn't exist on the homeserver side, token introspection
1014            // from Synapse to MAS will work but Synapse will return a 401
1015            // because it doesn't see the device.
1016            //
1017            // We're using an upsert so if the device already exists for some reason
1018            // (like when a concurrent device sync happening) it won't have any effect.
1019            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    // XXX: there is a potential (but unlikely) race here, where the activity for
1031    // the session is recorded before the transaction is committed. We would have to
1032    // save the repository here to fix that.
1033    activity_tracker
1034        .record_oauth2_session(clock, &session)
1035        .await;
1036
1037    if !session.scope.is_empty() {
1038        // We only return the scope if it's not empty
1039        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        // Provision a client
1066        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's provision a user and create a session for them. This part is hard to
1081        // test with just HTTP requests, so we'll use the repository directly.
1082        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        // Lookup the client in the database.
1097        let client = repo
1098            .oauth2_client()
1099            .find_by_client_id(&client_id)
1100            .await
1101            .unwrap()
1102            .unwrap();
1103
1104        // Start a grant
1105        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        // fulfill the grant with the browser session (no OAuth2 session yet)
1130        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        // Now call the token endpoint to get an access token.
1139        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        // Check that the token is valid
1153        assert!(state.is_access_token_valid(&access_token).await);
1154
1155        // Exchange it again, this it should fail
1156        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        // The token should still be valid
1170        assert!(state.is_access_token_valid(&access_token).await);
1171
1172        // Now wait a bit
1173        state.clock.advance(Duration::try_minutes(1).unwrap());
1174
1175        // Exchange it again, this it should fail
1176        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        // And it should have revoked the token we got
1190        assert!(!state.is_access_token_valid(&access_token).await);
1191
1192        // Try another one and wait for too long before exchanging it
1193        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        // fulfill the grant with the browser session
1219        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        // Now wait a bit
1228        state
1229            .clock
1230            .advance(Duration::microseconds(15 * 60 * 1000 * 1000));
1231
1232        // Exchange it, it should fail
1233        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        // Provision a client
1253        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's provision a user and create a session for them. This part is hard to
1268        // test with just HTTP requests, so we'll use the repository directly.
1269        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        // Lookup the client in the database.
1284        let client = repo
1285            .oauth2_client()
1286            .find_by_client_id(&client_id)
1287            .await
1288            .unwrap()
1289            .unwrap();
1290
1291        // Get a token pair
1292        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        // First check that the token is valid
1318        assert!(state.is_access_token_valid(&access_token).await);
1319
1320        // Now call the token endpoint to get an access token.
1321        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        // Check that the new token is valid
1338        assert!(state.is_access_token_valid(&access_token).await);
1339
1340        // Check that the old token is no longer valid
1341        assert!(!state.is_access_token_valid(&old_access_token).await);
1342
1343        // Call it again with the old token, it should fail
1344        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        // Call it again with the new token, it should work
1357        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        // Provision a client
1375        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's provision a user and create a session for them. This part is hard to
1390        // test with just HTTP requests, so we'll use the repository directly.
1391        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        // Lookup the client in the database.
1406        let client = repo
1407            .oauth2_client()
1408            .find_by_client_id(&client_id)
1409            .await
1410            .unwrap()
1411            .unwrap();
1412
1413        // Get a token pair
1414        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        // First check that the token is valid
1440        assert!(state.is_access_token_valid(&access_token).await);
1441
1442        // Now call the token endpoint to get an access token.
1443        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        // Call a second time, it should work, as we haven't done anything yet with the
1455        // token
1456        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        // Check that we got new tokens
1468        assert_ne!(first_response.access_token, second_response.access_token);
1469        assert_ne!(first_response.refresh_token, second_response.refresh_token);
1470
1471        // Check that the old-new token is invalid
1472        assert!(
1473            !state
1474                .is_access_token_valid(&first_response.access_token)
1475                .await
1476        );
1477
1478        // Check that the new-new token is valid
1479        assert!(
1480            state
1481                .is_access_token_valid(&second_response.access_token)
1482                .await
1483        );
1484
1485        // Do a third refresh, this one should not work, as we've used the new
1486        // access token
1487        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        // The other reason we consider a new refresh token to be 'used' is if
1498        // it was already used in a refresh
1499        // So, if we do a refresh with the second_response.refresh_token, then
1500        // another refresh with the result, redoing one with
1501        // second_response.refresh_token again should fail
1502        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        // This one is fine
1510        let fourth_response = state.request(request).await;
1511        fourth_response.assert_status(StatusCode::OK);
1512        let fourth_response: AccessTokenResponse = fourth_response.json();
1513
1514        // Do another one, it should be fine as well
1515        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        // But now, if we re-do with the second_response.refresh_token, it should
1527        // fail
1528        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        // One edge-case scenario: after 30 days, expired access tokens are
1539        // deleted, so we can't track accurately if the refresh successful or
1540        // not. In this case we chose to allow the refresh to succeed to avoid
1541        // spuriously logging out the user.
1542
1543        // Make sure to mark the fifth access token as used
1544        assert!(
1545            state
1546                .is_access_token_valid(&fifth_response.access_token)
1547                .await
1548        );
1549
1550        // Make sure to run all the cleanup tasks
1551        // We run the job queue once before advancing the clock to make sure the
1552        // scheduled jobs get scheduled to a time before we advanced the clock
1553        state.run_jobs_in_queue().await;
1554        state.clock.advance(Duration::days(31));
1555        state.run_jobs_in_queue().await;
1556
1557        // We're not supposed to be able to use the fourth refresh token, but here we
1558        // are
1559        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        // And the refresh token we had on the fifth response should now be invalid
1570        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        // Provision a client
1587        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        // Call the token endpoint with an empty scope
1602        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        // Revoke the token
1618        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        // We should be allowed to ask for the GraphQL API scope
1628        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        // Revoke the token
1645        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        // We should be NOT allowed to ask for the MAS admin scope
1655        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        // Now, if we add the client to the admin list in the policy, it should work
1670        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        // Revoke the token
1703        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        // Provision a client
1719        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        // Start a device code grant
1734        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        // Poll the token endpoint, it should be pending
1746        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's provision a user and create a browser session for them. This part is
1759        // hard to test with just HTTP requests, so we'll use the repository
1760        // directly.
1761        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        // Find the grant
1776        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        // And fulfill it
1784        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        // Now call the token endpoint to get an access token.
1793        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        // Check that the token is valid
1806        assert!(state.is_access_token_valid(&response.access_token).await);
1807        // We advertised the refresh token grant type, so we should have a refresh token
1808        assert!(response.refresh_token.is_some());
1809        // We asked for the openid scope, so we should have an ID token
1810        assert!(response.id_token.is_some());
1811
1812        // Calling it again should fail
1813        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        // Do another grant and make it expire
1826        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        // Poll the token endpoint, it should be pending
1838        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        // Poll again, it should be expired
1853        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        // Do another grant and reject it
1866        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        // Find the grant and reject it
1878        let mut repo = state.repository().await.unwrap();
1879
1880        // Find the grant
1881        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        // And reject it
1889        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        // Poll the token endpoint, it should be rejected
1898        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        // Provision a client
1917        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        // Call the token endpoint with an unsupported grant type
1934        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        // Provision a client (without device_code grant, since registration rejects it)
1963        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        // Attempt to use the device_code grant type at the token endpoint
1979        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}