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_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            // Classify the error differntly, depending on whether it's an 'internal' error,
309            // or just because the client presented invalid credentials.
310            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    // The authenticated client is the entity making this request, regardless of
324    // the grant type or whether a user session is later minted.
325    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    // Check that the client is allowed to use this grant type
430    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            // Ending the session if the token was already exchanged more than 20s ago
455            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                //if !session.is_finished() {
464                repo.oauth2_session().finish(clock, session).await?;
465                repo.save().await?;
466                //}
467            }
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    // Generate a device name
495    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    // This should never happen, since we looked up in the database using the code
507    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        // We have a challenge but no verifier (or vice-versa)? Bad request.
522        (Some(_), None) | (None, Some(_)) => return Err(RouteError::BadRequest),
523        // If we have both, we need to check the code validity
524        (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    // Lock the user sync to make sure we don't get into a race condition
575    repo.user()
576        .acquire_lock_for_sync(&browser_session.user)
577        .await?;
578
579    // Look for device to provision
580    for scope in &*session.scope {
581        if let Some(device) = Device::from_scope_token(scope) {
582            // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we
583            // want the device to be created synchronously on the homeserver, so
584            // that when we respond, the access token works completely. If the
585            // device doesn't exist on the homeserver side, token introspection
586            // from Synapse to MAS will work but Synapse will return a 401
587            // because it doesn't see the device.
588            //
589            // We're using an upsert so if the device already exists for some reason
590            // (like when a concurrent device sync happening) it won't have any effect.
591            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    // XXX: there is a potential (but unlikely) race here, where the activity for
607    // the session is recorded before the transaction is committed. We would have to
608    // save the repository here to fix that.
609    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    // Check that the client is allowed to use this grant type
627    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    // Let's for now record the user agent on each refresh, that should be
644    // responsive enough and not too much of a burden on the database.
645    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        // As per https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
658        return Err(RouteError::ClientIDMismatch {
659            expected: session.client_id,
660            actual: client.id,
661        });
662    }
663
664    if !refresh_token.is_valid() {
665        // We're seing a refresh token that already has been consumed, this might be a
666        // double-refresh or a replay attack
667
668        // First, get the next refresh token
669        let Some(next_refresh_token_id) = refresh_token.next_refresh_token_id() else {
670            // If we don't have a 'next' refresh token, it may just be because this was
671            // before we were recording those. Let's just treat it as a replay.
672            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        // Check if the next refresh token was already consumed or not
687        if !next_refresh_token.is_valid() {
688            // XXX: This is a replay, we *may* want to invalidate the session
689            return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
690        }
691
692        // Check if the associated access token was already used.
693        //
694        // If the access token is no longer present, we assume it was *not* used.
695        // Tokens can disappear for two main reasons:
696        //
697        //  - revoked access tokens are deleted after 1 hour
698        //  - expired access tokens are deleted after 30 days
699        //
700        // Revoked tokens are not an issue, as the associated refresh token is also
701        // revoked. For expired tokens, however, we are effectively losing the
702        // ability to prevent the client from performing a bad double-refresh.
703        // This measure is intended to enhance security when a refresh token
704        // leaks. However, the primary goal is to ensure that we do not maintain
705        // two active branches of the refresh token tree.
706        //
707        // Consider these two scenarios:
708        //
709        //   - Refresh token A is consumed, issuing refresh token B and access token C.
710        //   - The client uses access token C.
711        //   - Access token C expires after some time.
712        //   - If the client then attempts to use refresh token A again:
713        //      - If access token C is still present, the refresh will be rightfully
714        //        declined, as we have proof that it received the new set of tokens.
715        //      - If access token C was cleaned up, the refresh will succeed, issuing
716        //        new tokens but invalidating refresh token B and the original access
717        //        token C.
718        if let Some(access_token_id) = next_refresh_token.access_token_id {
719            // Load it
720            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                // XXX: This is a replay, we *may* want to invalidate the session
731                return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
732            }
733
734            // This could be a double-refresh, see below
735            repo.oauth2_access_token()
736                .revoke(clock, next_access_token)
737                .await?;
738        }
739
740        // Looks like it's a double-refresh, client lost their refresh token on
741        // the way back. Let's revoke the unused access and refresh tokens, and
742        // issue new ones
743        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 it is a double-refresh, it might already be revoked
772            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    // Check that the client is allowed to use this grant type
800    if !client.grant_types.contains(&GrantType::ClientCredentials) {
801        return Err(RouteError::UnauthorizedClient(client.id));
802    }
803
804    // Default to an empty scope if none is provided
805    let scope = grant
806        .scope
807        .clone()
808        .unwrap_or_else(|| std::iter::empty::<ScopeToken>().collect());
809
810    // Make the request go through the policy engine
811    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    // Start the session
829    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    // XXX: there is a potential (but unlikely) race here, where the activity for
852    // the session is recorded before the transaction is committed. We would have to
853    // save the repository here to fix that.
854    activity_tracker
855        .record_oauth2_session(clock, &session)
856        .await;
857
858    if !session.scope.is_empty() {
859        // We only return the scope if it's not empty
860        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    // Check that the Device Authorization Grant is enabled on this server
881    if !site_config.device_code_grant_enabled {
882        return Err(RouteError::UnsupportedGrantType);
883    }
884
885    // Check that the client is allowed to use this grant type
886    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    // Check that the client match
897    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    // Generate a device name, using the locale captured from the browser which
930    // fulfilled the grant
931    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    // Start the session
936    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    // XXX: should we get the user agent from the device code grant instead?
946    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 the client uses the refresh token grant type, we also generate a refresh
965    // token
966    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 the client asked for an ID token, we generate one
978    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    // Lock the user sync to make sure we don't get into a race condition
995    repo.user()
996        .acquire_lock_for_sync(&browser_session.user)
997        .await?;
998
999    // Look for device to provision
1000    for scope in &*session.scope {
1001        if let Some(device) = Device::from_scope_token(scope) {
1002            // Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we
1003            // want the device to be created synchronously on the homeserver, so
1004            // that when we respond, the access token works completely. If the
1005            // device doesn't exist on the homeserver side, token introspection
1006            // from Synapse to MAS will work but Synapse will return a 401
1007            // because it doesn't see the device.
1008            //
1009            // We're using an upsert so if the device already exists for some reason
1010            // (like when a concurrent device sync happening) it won't have any effect.
1011            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    // XXX: there is a potential (but unlikely) race here, where the activity for
1023    // the session is recorded before the transaction is committed. We would have to
1024    // save the repository here to fix that.
1025    activity_tracker
1026        .record_oauth2_session(clock, &session)
1027        .await;
1028
1029    if !session.scope.is_empty() {
1030        // We only return the scope if it's not empty
1031        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        // Provision a client
1058        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's provision a user and create a session for them. This part is hard to
1073        // test with just HTTP requests, so we'll use the repository directly.
1074        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        // Lookup the client in the database.
1089        let client = repo
1090            .oauth2_client()
1091            .find_by_client_id(&client_id)
1092            .await
1093            .unwrap()
1094            .unwrap();
1095
1096        // Start a grant
1097        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        // And fulfill it
1134        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        // Now call the token endpoint to get an access token.
1143        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        // Check that the token is valid
1157        assert!(state.is_access_token_valid(&access_token).await);
1158
1159        // Exchange it again, this it should fail
1160        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        // The token should still be valid
1174        assert!(state.is_access_token_valid(&access_token).await);
1175
1176        // Now wait a bit
1177        state.clock.advance(Duration::try_minutes(1).unwrap());
1178
1179        // Exchange it again, this it should fail
1180        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        // And it should have revoked the token we got
1194        assert!(!state.is_access_token_valid(&access_token).await);
1195
1196        // Try another one and wait for too long before exchanging it
1197        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        // And fulfill it
1235        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        // Now wait a bit
1244        state
1245            .clock
1246            .advance(Duration::microseconds(15 * 60 * 1000 * 1000));
1247
1248        // Exchange it, it should fail
1249        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        // Provision a client
1269        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's provision a user and create a session for them. This part is hard to
1284        // test with just HTTP requests, so we'll use the repository directly.
1285        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        // Lookup the client in the database.
1300        let client = repo
1301            .oauth2_client()
1302            .find_by_client_id(&client_id)
1303            .await
1304            .unwrap()
1305            .unwrap();
1306
1307        // Get a token pair
1308        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        // First check that the token is valid
1334        assert!(state.is_access_token_valid(&access_token).await);
1335
1336        // Now call the token endpoint to get an access token.
1337        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        // Check that the new token is valid
1354        assert!(state.is_access_token_valid(&access_token).await);
1355
1356        // Check that the old token is no longer valid
1357        assert!(!state.is_access_token_valid(&old_access_token).await);
1358
1359        // Call it again with the old token, it should fail
1360        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        // Call it again with the new token, it should work
1373        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        // Provision a client
1391        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's provision a user and create a session for them. This part is hard to
1406        // test with just HTTP requests, so we'll use the repository directly.
1407        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        // Lookup the client in the database.
1422        let client = repo
1423            .oauth2_client()
1424            .find_by_client_id(&client_id)
1425            .await
1426            .unwrap()
1427            .unwrap();
1428
1429        // Get a token pair
1430        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        // First check that the token is valid
1456        assert!(state.is_access_token_valid(&access_token).await);
1457
1458        // Now call the token endpoint to get an access token.
1459        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        // Call a second time, it should work, as we haven't done anything yet with the
1471        // token
1472        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        // Check that we got new tokens
1484        assert_ne!(first_response.access_token, second_response.access_token);
1485        assert_ne!(first_response.refresh_token, second_response.refresh_token);
1486
1487        // Check that the old-new token is invalid
1488        assert!(
1489            !state
1490                .is_access_token_valid(&first_response.access_token)
1491                .await
1492        );
1493
1494        // Check that the new-new token is valid
1495        assert!(
1496            state
1497                .is_access_token_valid(&second_response.access_token)
1498                .await
1499        );
1500
1501        // Do a third refresh, this one should not work, as we've used the new
1502        // access token
1503        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        // The other reason we consider a new refresh token to be 'used' is if
1514        // it was already used in a refresh
1515        // So, if we do a refresh with the second_response.refresh_token, then
1516        // another refresh with the result, redoing one with
1517        // second_response.refresh_token again should fail
1518        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        // This one is fine
1526        let fourth_response = state.request(request).await;
1527        fourth_response.assert_status(StatusCode::OK);
1528        let fourth_response: AccessTokenResponse = fourth_response.json();
1529
1530        // Do another one, it should be fine as well
1531        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        // But now, if we re-do with the second_response.refresh_token, it should
1543        // fail
1544        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        // One edge-case scenario: after 30 days, expired access tokens are
1555        // deleted, so we can't track accurately if the refresh successful or
1556        // not. In this case we chose to allow the refresh to succeed to avoid
1557        // spuriously logging out the user.
1558
1559        // Make sure to mark the fifth access token as used
1560        assert!(
1561            state
1562                .is_access_token_valid(&fifth_response.access_token)
1563                .await
1564        );
1565
1566        // Make sure to run all the cleanup tasks
1567        // We run the job queue once before advancing the clock to make sure the
1568        // scheduled jobs get scheduled to a time before we advanced the clock
1569        state.run_jobs_in_queue().await;
1570        state.clock.advance(Duration::days(31));
1571        state.run_jobs_in_queue().await;
1572
1573        // We're not supposed to be able to use the fourth refresh token, but here we
1574        // are
1575        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        // And the refresh token we had on the fifth response should now be invalid
1586        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        // Provision a client
1603        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        // Call the token endpoint with an empty scope
1618        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        // Revoke the token
1634        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        // We should be allowed to ask for the GraphQL API scope
1644        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        // Revoke the token
1661        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        // We should be NOT allowed to ask for the MAS admin scope
1671        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        // Now, if we add the client to the admin list in the policy, it should work
1686        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        // Revoke the token
1719        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        // Provision a client
1735        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        // Start a device code grant
1750        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        // Poll the token endpoint, it should be pending
1762        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's provision a user and create a browser session for them. This part is
1775        // hard to test with just HTTP requests, so we'll use the repository
1776        // directly.
1777        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        // Find the grant
1792        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        // And fulfill it
1800        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        // Now call the token endpoint to get an access token.
1809        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        // Check that the token is valid
1822        assert!(state.is_access_token_valid(&response.access_token).await);
1823        // We advertised the refresh token grant type, so we should have a refresh token
1824        assert!(response.refresh_token.is_some());
1825        // We asked for the openid scope, so we should have an ID token
1826        assert!(response.id_token.is_some());
1827
1828        // Calling it again should fail
1829        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        // Do another grant and make it expire
1842        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        // Poll the token endpoint, it should be pending
1854        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        // Poll again, it should be expired
1869        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        // Do another grant and reject it
1882        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        // Find the grant and reject it
1894        let mut repo = state.repository().await.unwrap();
1895
1896        // Find the grant
1897        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        // And reject it
1905        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        // Poll the token endpoint, it should be rejected
1914        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        // Provision a client
1933        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        // Call the token endpoint with an unsupported grant type
1950        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        // Provision a client (without device_code grant, since registration rejects it)
1979        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        // Attempt to use the device_code grant type at the token endpoint
1995        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}