mas_handlers/oauth2/
token.rs

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