Skip to main content

mas_router/
endpoints.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2022-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 serde::{Deserialize, Serialize};
9use ulid::Ulid;
10
11use crate::UrlBuilder;
12pub use crate::traits::*;
13
14#[derive(Deserialize, Serialize, Clone, Debug)]
15#[serde(rename_all = "snake_case", tag = "kind")]
16pub enum PostAuthAction {
17    ContinueAuthorizationGrant {
18        id: Ulid,
19    },
20    ContinueDeviceCodeGrant {
21        id: Ulid,
22    },
23    ContinueCompatSsoLogin {
24        id: Ulid,
25    },
26    ChangePassword,
27    LinkUpstream {
28        id: Ulid,
29    },
30    ManageAccount {
31        #[serde(flatten)]
32        action: Option<AccountAction>,
33    },
34}
35
36impl PostAuthAction {
37    #[must_use]
38    pub const fn continue_grant(id: Ulid) -> Self {
39        PostAuthAction::ContinueAuthorizationGrant { id }
40    }
41
42    #[must_use]
43    pub const fn continue_device_code_grant(id: Ulid) -> Self {
44        PostAuthAction::ContinueDeviceCodeGrant { id }
45    }
46
47    #[must_use]
48    pub const fn continue_compat_sso_login(id: Ulid) -> Self {
49        PostAuthAction::ContinueCompatSsoLogin { id }
50    }
51
52    #[must_use]
53    pub const fn link_upstream(id: Ulid) -> Self {
54        PostAuthAction::LinkUpstream { id }
55    }
56
57    #[must_use]
58    pub const fn manage_account(action: Option<AccountAction>) -> Self {
59        PostAuthAction::ManageAccount { action }
60    }
61
62    pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
63        match self {
64            Self::ContinueAuthorizationGrant { id } => url_builder.redirect(&Consent(*id)),
65            Self::ContinueDeviceCodeGrant { id } => {
66                url_builder.redirect(&DeviceCodeConsent::new(*id))
67            }
68            Self::ContinueCompatSsoLogin { id } => {
69                url_builder.redirect(&CompatLoginSsoComplete::new(*id, None))
70            }
71            Self::ChangePassword => url_builder.redirect(&AccountPasswordChange),
72            Self::LinkUpstream { id } => url_builder.redirect(&UpstreamOAuth2Link::new(*id)),
73            Self::ManageAccount { action } => url_builder.redirect(&Account {
74                action: action.clone(),
75            }),
76        }
77    }
78}
79
80/// `GET /.well-known/openid-configuration`
81#[derive(Default, Debug, Clone)]
82pub struct OidcConfiguration;
83
84impl SimpleRoute for OidcConfiguration {
85    const PATH: &'static str = "/.well-known/openid-configuration";
86}
87
88/// `GET /.well-known/webfinger`
89#[derive(Default, Debug, Clone)]
90pub struct Webfinger;
91
92impl SimpleRoute for Webfinger {
93    const PATH: &'static str = "/.well-known/webfinger";
94}
95
96/// `GET /.well-known/change-password`
97pub struct ChangePasswordDiscovery;
98
99impl SimpleRoute for ChangePasswordDiscovery {
100    const PATH: &'static str = "/.well-known/change-password";
101}
102
103/// `GET /oauth2/keys.json`
104#[derive(Default, Debug, Clone)]
105pub struct OAuth2Keys;
106
107impl SimpleRoute for OAuth2Keys {
108    const PATH: &'static str = "/oauth2/keys.json";
109}
110
111/// `GET /oauth2/userinfo`
112#[derive(Default, Debug, Clone)]
113pub struct OidcUserinfo;
114
115impl SimpleRoute for OidcUserinfo {
116    const PATH: &'static str = "/oauth2/userinfo";
117}
118
119/// `POST /oauth2/introspect`
120#[derive(Default, Debug, Clone)]
121pub struct OAuth2Introspection;
122
123impl SimpleRoute for OAuth2Introspection {
124    const PATH: &'static str = "/oauth2/introspect";
125}
126
127/// `POST /oauth2/revoke`
128#[derive(Default, Debug, Clone)]
129pub struct OAuth2Revocation;
130
131impl SimpleRoute for OAuth2Revocation {
132    const PATH: &'static str = "/oauth2/revoke";
133}
134
135/// `POST /oauth2/token`
136#[derive(Default, Debug, Clone)]
137pub struct OAuth2TokenEndpoint;
138
139impl SimpleRoute for OAuth2TokenEndpoint {
140    const PATH: &'static str = "/oauth2/token";
141}
142
143/// `POST /oauth2/registration`
144#[derive(Default, Debug, Clone)]
145pub struct OAuth2RegistrationEndpoint;
146
147impl SimpleRoute for OAuth2RegistrationEndpoint {
148    const PATH: &'static str = "/oauth2/registration";
149}
150
151/// `GET /authorize`
152#[derive(Default, Debug, Clone)]
153pub struct OAuth2AuthorizationEndpoint;
154
155impl SimpleRoute for OAuth2AuthorizationEndpoint {
156    const PATH: &'static str = "/authorize";
157}
158
159/// `GET /`
160#[derive(Default, Debug, Clone)]
161pub struct Index;
162
163impl SimpleRoute for Index {
164    const PATH: &'static str = "/";
165}
166
167/// `GET /health`
168#[derive(Default, Debug, Clone)]
169pub struct Healthcheck;
170
171impl SimpleRoute for Healthcheck {
172    const PATH: &'static str = "/health";
173}
174
175/// `GET|POST /login`
176#[derive(Default, Debug, Clone, Serialize, Deserialize)]
177pub struct Login {
178    #[serde(flatten)]
179    post_auth_action: Option<PostAuthAction>,
180
181    login_hint: Option<String>,
182}
183
184impl Route for Login {
185    type Query = Self;
186
187    fn route() -> &'static str {
188        "/login"
189    }
190
191    fn query(&self) -> Option<&Self::Query> {
192        Some(self)
193    }
194}
195
196impl Login {
197    #[must_use]
198    pub const fn and_then(action: PostAuthAction) -> Self {
199        Self {
200            post_auth_action: Some(action),
201            login_hint: None,
202        }
203    }
204
205    #[must_use]
206    pub const fn and_continue_grant(id: Ulid) -> Self {
207        Self {
208            post_auth_action: Some(PostAuthAction::continue_grant(id)),
209            login_hint: None,
210        }
211    }
212
213    #[must_use]
214    pub const fn and_continue_device_code_grant(id: Ulid) -> Self {
215        Self {
216            post_auth_action: Some(PostAuthAction::continue_device_code_grant(id)),
217            login_hint: None,
218        }
219    }
220
221    #[must_use]
222    pub const fn and_continue_compat_sso_login(id: Ulid) -> Self {
223        Self {
224            post_auth_action: Some(PostAuthAction::continue_compat_sso_login(id)),
225            login_hint: None,
226        }
227    }
228
229    #[must_use]
230    pub const fn and_link_upstream(id: Ulid) -> Self {
231        Self {
232            post_auth_action: Some(PostAuthAction::link_upstream(id)),
233            login_hint: None,
234        }
235    }
236
237    #[must_use]
238    pub fn with_login_hint(mut self, login_hint: String) -> Self {
239        self.login_hint = Some(login_hint);
240        self
241    }
242
243    /// Get a reference to the login's post auth action.
244    #[must_use]
245    pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
246        self.post_auth_action.as_ref()
247    }
248
249    pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
250        match &self.post_auth_action {
251            Some(action) => action.go_next(url_builder),
252            None => url_builder.redirect(&Index),
253        }
254    }
255}
256
257impl From<Option<PostAuthAction>> for Login {
258    fn from(post_auth_action: Option<PostAuthAction>) -> Self {
259        Self {
260            post_auth_action,
261            login_hint: None,
262        }
263    }
264}
265
266/// `POST /logout`
267#[derive(Default, Debug, Clone)]
268pub struct Logout;
269
270impl SimpleRoute for Logout {
271    const PATH: &'static str = "/logout";
272}
273
274/// `POST /register`
275#[derive(Default, Debug, Clone)]
276pub struct Register {
277    post_auth_action: Option<PostAuthAction>,
278}
279
280impl Register {
281    #[must_use]
282    pub fn and_then(action: PostAuthAction) -> Self {
283        Self {
284            post_auth_action: Some(action),
285        }
286    }
287
288    #[must_use]
289    pub fn and_continue_grant(data: Ulid) -> Self {
290        Self {
291            post_auth_action: Some(PostAuthAction::continue_grant(data)),
292        }
293    }
294
295    #[must_use]
296    pub fn and_continue_compat_sso_login(data: Ulid) -> Self {
297        Self {
298            post_auth_action: Some(PostAuthAction::continue_compat_sso_login(data)),
299        }
300    }
301
302    /// Get a reference to the reauth's post auth action.
303    #[must_use]
304    pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
305        self.post_auth_action.as_ref()
306    }
307
308    pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
309        match &self.post_auth_action {
310            Some(action) => action.go_next(url_builder),
311            None => url_builder.redirect(&Index),
312        }
313    }
314}
315
316impl Route for Register {
317    type Query = PostAuthAction;
318
319    fn route() -> &'static str {
320        "/register"
321    }
322
323    fn query(&self) -> Option<&Self::Query> {
324        self.post_auth_action.as_ref()
325    }
326}
327
328impl From<Option<PostAuthAction>> for Register {
329    fn from(post_auth_action: Option<PostAuthAction>) -> Self {
330        Self { post_auth_action }
331    }
332}
333
334/// `GET|POST /register/password`
335#[derive(Default, Debug, Clone, Serialize, Deserialize)]
336pub struct PasswordRegister {
337    username: Option<String>,
338
339    #[serde(flatten)]
340    post_auth_action: Option<PostAuthAction>,
341}
342
343impl PasswordRegister {
344    #[must_use]
345    pub fn and_then(mut self, action: PostAuthAction) -> Self {
346        self.post_auth_action = Some(action);
347        self
348    }
349
350    #[must_use]
351    pub fn and_continue_grant(mut self, data: Ulid) -> Self {
352        self.post_auth_action = Some(PostAuthAction::continue_grant(data));
353        self
354    }
355
356    #[must_use]
357    pub fn and_continue_compat_sso_login(mut self, data: Ulid) -> Self {
358        self.post_auth_action = Some(PostAuthAction::continue_compat_sso_login(data));
359        self
360    }
361
362    /// Get a reference to the post auth action.
363    #[must_use]
364    pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
365        self.post_auth_action.as_ref()
366    }
367
368    /// Get a reference to the username chosen by the user.
369    #[must_use]
370    pub fn username(&self) -> Option<&str> {
371        self.username.as_deref()
372    }
373
374    pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
375        match &self.post_auth_action {
376            Some(action) => action.go_next(url_builder),
377            None => url_builder.redirect(&Index),
378        }
379    }
380}
381
382impl Route for PasswordRegister {
383    type Query = Self;
384
385    fn route() -> &'static str {
386        "/register/password"
387    }
388
389    fn query(&self) -> Option<&Self::Query> {
390        Some(self)
391    }
392}
393
394impl From<Option<PostAuthAction>> for PasswordRegister {
395    fn from(post_auth_action: Option<PostAuthAction>) -> Self {
396        Self {
397            username: None,
398            post_auth_action,
399        }
400    }
401}
402
403/// `GET|POST /register/steps/{id}/token`
404#[derive(Debug, Clone)]
405pub struct RegisterToken {
406    id: Ulid,
407}
408
409impl RegisterToken {
410    #[must_use]
411    pub fn new(id: Ulid) -> Self {
412        Self { id }
413    }
414}
415
416impl Route for RegisterToken {
417    type Query = ();
418    fn route() -> &'static str {
419        "/register/steps/{id}/token"
420    }
421
422    fn path(&self) -> std::borrow::Cow<'static, str> {
423        format!("/register/steps/{}/token", self.id).into()
424    }
425}
426
427/// `GET|POST /register/steps/{id}/display-name`
428#[derive(Debug, Clone)]
429pub struct RegisterDisplayName {
430    id: Ulid,
431}
432
433impl RegisterDisplayName {
434    #[must_use]
435    pub fn new(id: Ulid) -> Self {
436        Self { id }
437    }
438}
439
440impl Route for RegisterDisplayName {
441    type Query = ();
442    fn route() -> &'static str {
443        "/register/steps/{id}/display-name"
444    }
445
446    fn path(&self) -> std::borrow::Cow<'static, str> {
447        format!("/register/steps/{}/display-name", self.id).into()
448    }
449}
450
451/// `GET|POST /register/steps/{id}/verify-email`
452#[derive(Debug, Clone)]
453pub struct RegisterVerifyEmail {
454    id: Ulid,
455}
456
457impl RegisterVerifyEmail {
458    #[must_use]
459    pub fn new(id: Ulid) -> Self {
460        Self { id }
461    }
462}
463
464impl Route for RegisterVerifyEmail {
465    type Query = ();
466    fn route() -> &'static str {
467        "/register/steps/{id}/verify-email"
468    }
469
470    fn path(&self) -> std::borrow::Cow<'static, str> {
471        format!("/register/steps/{}/verify-email", self.id).into()
472    }
473}
474
475/// `GET /register/steps/{id}/finish`
476#[derive(Debug, Clone)]
477pub struct RegisterFinish {
478    id: Ulid,
479}
480
481impl RegisterFinish {
482    #[must_use]
483    pub const fn new(id: Ulid) -> Self {
484        Self { id }
485    }
486}
487
488impl Route for RegisterFinish {
489    type Query = ();
490    fn route() -> &'static str {
491        "/register/steps/{id}/finish"
492    }
493
494    fn path(&self) -> std::borrow::Cow<'static, str> {
495        format!("/register/steps/{}/finish", self.id).into()
496    }
497}
498
499/// Actions parameters as defined by MSC4191
500#[derive(Debug, Clone, Serialize, Deserialize)]
501#[serde(tag = "action")]
502pub enum AccountAction {
503    #[serde(rename = "org.matrix.profile")]
504    OrgMatrixProfile,
505    /// DEPRECATED: Use `OrgMatrixProfile` instead
506    #[serde(rename = "profile")]
507    Profile,
508
509    #[serde(rename = "org.matrix.devices_list")]
510    OrgMatrixDevicesList,
511    /// DEPRECATED: Use `OrgMatrixDevicesList` instead
512    #[serde(rename = "org.matrix.sessions_list")]
513    OrgMatrixSessionsList,
514    /// DEPRECATED: Use `OrgMatrixDevicesList` instead
515    #[serde(rename = "sessions_list")]
516    SessionsList,
517
518    #[serde(rename = "org.matrix.device_view")]
519    OrgMatrixDeviceView { device_id: String },
520    /// DEPRECATED: Use `OrgMatrixDeviceView` instead
521    #[serde(rename = "org.matrix.session_view")]
522    OrgMatrixSessionView { device_id: String },
523    /// DEPRECATED: Use `OrgMatrixDeviceView` instead
524    #[serde(rename = "session_view")]
525    SessionView { device_id: String },
526
527    #[serde(rename = "org.matrix.device_delete")]
528    OrgMatrixDeviceDelete { device_id: String },
529    /// DEPRECATED: Use `OrgMatrixDeviceDelete` instead
530    #[serde(rename = "org.matrix.session_end")]
531    OrgMatrixSessionEnd { device_id: String },
532    /// DEPRECATED: Use `OrgMatrixDeviceDelete` instead
533    #[serde(rename = "session_end")]
534    SessionEnd { device_id: String },
535
536    #[serde(rename = "org.matrix.cross_signing_reset")]
537    OrgMatrixCrossSigningReset,
538}
539
540/// `GET /account/`
541#[derive(Default, Debug, Clone)]
542pub struct Account {
543    action: Option<AccountAction>,
544}
545
546impl Route for Account {
547    type Query = AccountAction;
548
549    fn route() -> &'static str {
550        "/account/"
551    }
552
553    fn query(&self) -> Option<&Self::Query> {
554        self.action.as_ref()
555    }
556}
557
558/// `GET /account/*`
559#[derive(Default, Debug, Clone)]
560pub struct AccountWildcard;
561
562impl SimpleRoute for AccountWildcard {
563    const PATH: &'static str = "/account/{*rest}";
564}
565
566/// `GET /account/password/change`
567///
568/// Handled by the React frontend; this struct definition is purely for
569/// redirects.
570#[derive(Default, Debug, Clone)]
571pub struct AccountPasswordChange;
572
573impl SimpleRoute for AccountPasswordChange {
574    const PATH: &'static str = "/account/password/change";
575}
576
577/// `GET /consent/{grant_id}`
578#[derive(Debug, Clone)]
579pub struct Consent(pub Ulid);
580
581impl Route for Consent {
582    type Query = ();
583    fn route() -> &'static str {
584        "/consent/{grant_id}"
585    }
586
587    fn path(&self) -> std::borrow::Cow<'static, str> {
588        format!("/consent/{}", self.0).into()
589    }
590}
591
592/// `GET|POST /_matrix/client/v3/login`
593pub struct CompatLogin;
594
595impl SimpleRoute for CompatLogin {
596    const PATH: &'static str = "/_matrix/client/{version}/login";
597}
598
599/// `POST /_matrix/client/v3/logout`
600pub struct CompatLogout;
601
602impl SimpleRoute for CompatLogout {
603    const PATH: &'static str = "/_matrix/client/{version}/logout";
604}
605
606/// `POST /_matrix/client/v3/logout/all`
607pub struct CompatLogoutAll;
608
609impl SimpleRoute for CompatLogoutAll {
610    const PATH: &'static str = "/_matrix/client/{version}/logout/all";
611}
612
613/// `POST /_matrix/client/v3/refresh`
614pub struct CompatRefresh;
615
616impl SimpleRoute for CompatRefresh {
617    const PATH: &'static str = "/_matrix/client/{version}/refresh";
618}
619
620/// `GET /_matrix/client/v3/login/sso/redirect`
621pub struct CompatLoginSsoRedirect;
622
623impl SimpleRoute for CompatLoginSsoRedirect {
624    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect";
625}
626
627/// `GET /_matrix/client/v3/login/sso/redirect/`
628///
629/// This is a workaround for the fact some clients (Element iOS) sends a
630/// trailing slash, even though it's not in the spec.
631pub struct CompatLoginSsoRedirectSlash;
632
633impl SimpleRoute for CompatLoginSsoRedirectSlash {
634    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/";
635}
636
637/// `GET /_matrix/client/v3/login/sso/redirect/{idp}`
638pub struct CompatLoginSsoRedirectIdp;
639
640impl SimpleRoute for CompatLoginSsoRedirectIdp {
641    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/{idp}";
642}
643
644#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
645#[serde(rename_all = "lowercase")]
646pub enum CompatLoginSsoAction {
647    Login,
648    Register,
649    #[serde(other)]
650    Unknown,
651}
652
653impl CompatLoginSsoAction {
654    /// Returns true if the action is a known action.
655    #[must_use]
656    pub fn is_known(&self) -> bool {
657        !matches!(self, Self::Unknown)
658    }
659}
660
661#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
662pub struct CompatLoginSsoActionParams {
663    action: CompatLoginSsoAction,
664    /// DEPRECATED: Use `action` instead. We will remove this once enough
665    /// clients support the stable name.
666    #[serde(rename = "org.matrix.msc3824.action")]
667    unstable_action: CompatLoginSsoAction,
668}
669
670/// `GET|POST /complete-compat-sso/{id}`
671pub struct CompatLoginSsoComplete {
672    id: Ulid,
673    query: Option<CompatLoginSsoActionParams>,
674}
675
676impl CompatLoginSsoComplete {
677    #[must_use]
678    pub fn new(id: Ulid, action: Option<CompatLoginSsoAction>) -> Self {
679        Self {
680            id,
681            query: action.map(|action| CompatLoginSsoActionParams {
682                action,
683                unstable_action: action,
684            }),
685        }
686    }
687}
688
689impl Route for CompatLoginSsoComplete {
690    type Query = CompatLoginSsoActionParams;
691
692    fn query(&self) -> Option<&Self::Query> {
693        self.query.as_ref()
694    }
695
696    fn route() -> &'static str {
697        "/complete-compat-sso/{grant_id}"
698    }
699
700    fn path(&self) -> std::borrow::Cow<'static, str> {
701        format!("/complete-compat-sso/{}", self.id).into()
702    }
703}
704
705/// `GET /upstream/authorize/{id}`
706pub struct UpstreamOAuth2Authorize {
707    id: Ulid,
708    post_auth_action: Option<PostAuthAction>,
709}
710
711impl UpstreamOAuth2Authorize {
712    #[must_use]
713    pub const fn new(id: Ulid) -> Self {
714        Self {
715            id,
716            post_auth_action: None,
717        }
718    }
719
720    #[must_use]
721    pub fn and_then(mut self, action: PostAuthAction) -> Self {
722        self.post_auth_action = Some(action);
723        self
724    }
725}
726
727impl Route for UpstreamOAuth2Authorize {
728    type Query = PostAuthAction;
729    fn route() -> &'static str {
730        "/upstream/authorize/{provider_id}"
731    }
732
733    fn path(&self) -> std::borrow::Cow<'static, str> {
734        format!("/upstream/authorize/{}", self.id).into()
735    }
736
737    fn query(&self) -> Option<&Self::Query> {
738        self.post_auth_action.as_ref()
739    }
740}
741
742/// `GET /upstream/callback/{id}`
743pub struct UpstreamOAuth2Callback {
744    id: Ulid,
745}
746
747impl UpstreamOAuth2Callback {
748    #[must_use]
749    pub const fn new(id: Ulid) -> Self {
750        Self { id }
751    }
752}
753
754impl Route for UpstreamOAuth2Callback {
755    type Query = ();
756    fn route() -> &'static str {
757        "/upstream/callback/{provider_id}"
758    }
759
760    fn path(&self) -> std::borrow::Cow<'static, str> {
761        format!("/upstream/callback/{}", self.id).into()
762    }
763}
764
765/// `GET /upstream/link/{id}`
766pub struct UpstreamOAuth2Link {
767    id: Ulid,
768}
769
770impl UpstreamOAuth2Link {
771    #[must_use]
772    pub const fn new(id: Ulid) -> Self {
773        Self { id }
774    }
775}
776
777impl Route for UpstreamOAuth2Link {
778    type Query = ();
779    fn route() -> &'static str {
780        "/upstream/link/{link_id}"
781    }
782
783    fn path(&self) -> std::borrow::Cow<'static, str> {
784        format!("/upstream/link/{}", self.id).into()
785    }
786}
787
788/// `POST /upstream/backchannel-logout/{id}`
789pub struct UpstreamOAuth2BackchannelLogout {
790    id: Ulid,
791}
792
793impl UpstreamOAuth2BackchannelLogout {
794    #[must_use]
795    pub const fn new(id: Ulid) -> Self {
796        Self { id }
797    }
798}
799
800impl Route for UpstreamOAuth2BackchannelLogout {
801    type Query = ();
802    fn route() -> &'static str {
803        "/upstream/backchannel-logout/{provider_id}"
804    }
805
806    fn path(&self) -> std::borrow::Cow<'static, str> {
807        format!("/upstream/backchannel-logout/{}", self.id).into()
808    }
809}
810
811/// `GET|POST /link`
812#[derive(Default, Serialize, Deserialize, Debug, Clone)]
813pub struct DeviceCodeLink {
814    code: Option<String>,
815}
816
817impl DeviceCodeLink {
818    #[must_use]
819    pub fn with_code(code: String) -> Self {
820        Self { code: Some(code) }
821    }
822}
823
824impl Route for DeviceCodeLink {
825    type Query = DeviceCodeLink;
826    fn route() -> &'static str {
827        "/link"
828    }
829
830    fn query(&self) -> Option<&Self::Query> {
831        Some(self)
832    }
833}
834
835/// `GET|POST /device/{device_code_id}`
836#[derive(Default, Serialize, Deserialize, Debug, Clone)]
837pub struct DeviceCodeConsent {
838    id: Ulid,
839}
840
841impl Route for DeviceCodeConsent {
842    type Query = ();
843    fn route() -> &'static str {
844        "/device/{device_code_id}"
845    }
846
847    fn path(&self) -> std::borrow::Cow<'static, str> {
848        format!("/device/{}", self.id).into()
849    }
850}
851
852impl DeviceCodeConsent {
853    #[must_use]
854    pub fn new(id: Ulid) -> Self {
855        Self { id }
856    }
857}
858
859/// `POST /oauth2/device`
860#[derive(Default, Serialize, Deserialize, Debug, Clone)]
861pub struct OAuth2DeviceAuthorizationEndpoint;
862
863impl SimpleRoute for OAuth2DeviceAuthorizationEndpoint {
864    const PATH: &'static str = "/oauth2/device";
865}
866
867/// `GET|POST /recover`
868#[derive(Default, Serialize, Deserialize, Debug, Clone)]
869pub struct AccountRecoveryStart;
870
871impl SimpleRoute for AccountRecoveryStart {
872    const PATH: &'static str = "/recover";
873}
874
875/// `GET|POST /recover/progress/{session_id}`
876#[derive(Default, Serialize, Deserialize, Debug, Clone)]
877pub struct AccountRecoveryProgress {
878    session_id: Ulid,
879}
880
881impl AccountRecoveryProgress {
882    #[must_use]
883    pub fn new(session_id: Ulid) -> Self {
884        Self { session_id }
885    }
886}
887
888impl Route for AccountRecoveryProgress {
889    type Query = ();
890    fn route() -> &'static str {
891        "/recover/progress/{session_id}"
892    }
893
894    fn path(&self) -> std::borrow::Cow<'static, str> {
895        format!("/recover/progress/{}", self.session_id).into()
896    }
897}
898
899/// `GET /account/password/recovery?ticket=:ticket`
900/// Rendered by the React frontend
901#[derive(Default, Serialize, Deserialize, Debug, Clone)]
902pub struct AccountRecoveryFinish {
903    ticket: String,
904}
905
906impl AccountRecoveryFinish {
907    #[must_use]
908    pub fn new(ticket: String) -> Self {
909        Self { ticket }
910    }
911}
912
913impl Route for AccountRecoveryFinish {
914    type Query = AccountRecoveryFinish;
915
916    fn route() -> &'static str {
917        "/account/password/recovery"
918    }
919
920    fn query(&self) -> Option<&Self::Query> {
921        Some(self)
922    }
923}
924
925/// `GET /assets`
926pub struct StaticAsset {
927    path: String,
928}
929
930impl StaticAsset {
931    #[must_use]
932    pub fn new(path: String) -> Self {
933        Self { path }
934    }
935}
936
937impl Route for StaticAsset {
938    type Query = ();
939    fn route() -> &'static str {
940        "/assets/"
941    }
942
943    fn path(&self) -> std::borrow::Cow<'static, str> {
944        format!("/assets/{}", self.path).into()
945    }
946}
947
948/// `GET|POST /graphql`
949pub struct GraphQL;
950
951impl SimpleRoute for GraphQL {
952    const PATH: &'static str = "/graphql";
953}
954
955/// `GET /api/spec.json`
956pub struct ApiSpec;
957
958impl SimpleRoute for ApiSpec {
959    const PATH: &'static str = "/api/spec.json";
960}
961
962/// `GET /api/doc/`
963pub struct ApiDoc;
964
965impl SimpleRoute for ApiDoc {
966    const PATH: &'static str = "/api/doc/";
967}
968
969/// `GET /api/doc/oauth2-callback`
970pub struct ApiDocCallback;
971
972impl SimpleRoute for ApiDocCallback {
973    const PATH: &'static str = "/api/doc/oauth2-callback";
974}