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/// `GET|POST /register`
275#[derive(Default, Debug, Clone)]
276pub struct Register {
277    query: RegisterQuery,
278}
279
280/// The query parameters of the registration page
281#[derive(Default, Debug, Clone, Serialize)]
282pub struct RegisterQuery {
283    /// The registration token an invite link deep-links with
284    #[serde(skip_serializing_if = "Option::is_none")]
285    token: Option<String>,
286
287    #[serde(flatten)]
288    post_auth_action: Option<PostAuthAction>,
289}
290
291impl Register {
292    #[must_use]
293    pub fn and_then(action: PostAuthAction) -> Self {
294        Self {
295            query: RegisterQuery {
296                token: None,
297                post_auth_action: Some(action),
298            },
299        }
300    }
301
302    #[must_use]
303    pub fn and_continue_grant(data: Ulid) -> Self {
304        Self::and_then(PostAuthAction::continue_grant(data))
305    }
306
307    #[must_use]
308    pub fn and_continue_compat_sso_login(data: Ulid) -> Self {
309        Self::and_then(PostAuthAction::continue_compat_sso_login(data))
310    }
311
312    /// Deep-link to the registration page with a registration token
313    #[must_use]
314    pub fn with_token(mut self, token: String) -> Self {
315        self.query.token = Some(token);
316        self
317    }
318
319    /// Get a reference to the reauth's post auth action.
320    #[must_use]
321    pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
322        self.query.post_auth_action.as_ref()
323    }
324
325    pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
326        match &self.query.post_auth_action {
327            Some(action) => action.go_next(url_builder),
328            None => url_builder.redirect(&Index),
329        }
330    }
331}
332
333impl Route for Register {
334    type Query = RegisterQuery;
335
336    fn route() -> &'static str {
337        "/register"
338    }
339
340    fn query(&self) -> Option<&Self::Query> {
341        Some(&self.query)
342    }
343}
344
345impl From<Option<PostAuthAction>> for Register {
346    fn from(post_auth_action: Option<PostAuthAction>) -> Self {
347        Self {
348            query: RegisterQuery {
349                token: None,
350                post_auth_action,
351            },
352        }
353    }
354}
355
356/// `GET|POST /register/steps/{id}/token`
357#[derive(Debug, Clone)]
358pub struct RegisterToken {
359    id: Ulid,
360}
361
362impl RegisterToken {
363    #[must_use]
364    pub fn new(id: Ulid) -> Self {
365        Self { id }
366    }
367}
368
369impl Route for RegisterToken {
370    type Query = ();
371    fn route() -> &'static str {
372        "/register/steps/{id}/token"
373    }
374
375    fn path(&self) -> std::borrow::Cow<'static, str> {
376        format!("/register/steps/{}/token", self.id).into()
377    }
378}
379
380/// `GET|POST /register/steps/{id}/display-name`
381#[derive(Debug, Clone)]
382pub struct RegisterDisplayName {
383    id: Ulid,
384}
385
386impl RegisterDisplayName {
387    #[must_use]
388    pub fn new(id: Ulid) -> Self {
389        Self { id }
390    }
391}
392
393impl Route for RegisterDisplayName {
394    type Query = ();
395    fn route() -> &'static str {
396        "/register/steps/{id}/display-name"
397    }
398
399    fn path(&self) -> std::borrow::Cow<'static, str> {
400        format!("/register/steps/{}/display-name", self.id).into()
401    }
402}
403
404/// `GET|POST /register/steps/{id}/verify-email`
405#[derive(Debug, Clone)]
406pub struct RegisterVerifyEmail {
407    id: Ulid,
408}
409
410impl RegisterVerifyEmail {
411    #[must_use]
412    pub fn new(id: Ulid) -> Self {
413        Self { id }
414    }
415}
416
417impl Route for RegisterVerifyEmail {
418    type Query = ();
419    fn route() -> &'static str {
420        "/register/steps/{id}/verify-email"
421    }
422
423    fn path(&self) -> std::borrow::Cow<'static, str> {
424        format!("/register/steps/{}/verify-email", self.id).into()
425    }
426}
427
428/// `GET /register/steps/{id}/finish`
429#[derive(Debug, Clone)]
430pub struct RegisterFinish {
431    id: Ulid,
432}
433
434impl RegisterFinish {
435    #[must_use]
436    pub const fn new(id: Ulid) -> Self {
437        Self { id }
438    }
439}
440
441impl Route for RegisterFinish {
442    type Query = ();
443    fn route() -> &'static str {
444        "/register/steps/{id}/finish"
445    }
446
447    fn path(&self) -> std::borrow::Cow<'static, str> {
448        format!("/register/steps/{}/finish", self.id).into()
449    }
450}
451
452/// Actions parameters as defined by MSC4191
453#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(tag = "action")]
455pub enum AccountAction {
456    #[serde(rename = "org.matrix.profile")]
457    OrgMatrixProfile,
458    /// DEPRECATED: Use `OrgMatrixProfile` instead
459    #[serde(rename = "profile")]
460    Profile,
461
462    #[serde(rename = "org.matrix.devices_list")]
463    OrgMatrixDevicesList,
464    /// DEPRECATED: Use `OrgMatrixDevicesList` instead
465    #[serde(rename = "org.matrix.sessions_list")]
466    OrgMatrixSessionsList,
467    /// DEPRECATED: Use `OrgMatrixDevicesList` instead
468    #[serde(rename = "sessions_list")]
469    SessionsList,
470
471    #[serde(rename = "org.matrix.device_view")]
472    OrgMatrixDeviceView { device_id: String },
473    /// DEPRECATED: Use `OrgMatrixDeviceView` instead
474    #[serde(rename = "org.matrix.session_view")]
475    OrgMatrixSessionView { device_id: String },
476    /// DEPRECATED: Use `OrgMatrixDeviceView` instead
477    #[serde(rename = "session_view")]
478    SessionView { device_id: String },
479
480    #[serde(rename = "org.matrix.device_delete")]
481    OrgMatrixDeviceDelete { device_id: String },
482    /// DEPRECATED: Use `OrgMatrixDeviceDelete` instead
483    #[serde(rename = "org.matrix.session_end")]
484    OrgMatrixSessionEnd { device_id: String },
485    /// DEPRECATED: Use `OrgMatrixDeviceDelete` instead
486    #[serde(rename = "session_end")]
487    SessionEnd { device_id: String },
488
489    #[serde(rename = "org.matrix.cross_signing_reset")]
490    OrgMatrixCrossSigningReset,
491}
492
493/// `GET /account/`
494#[derive(Default, Debug, Clone)]
495pub struct Account {
496    action: Option<AccountAction>,
497}
498
499impl Route for Account {
500    type Query = AccountAction;
501
502    fn route() -> &'static str {
503        "/account/"
504    }
505
506    fn query(&self) -> Option<&Self::Query> {
507        self.action.as_ref()
508    }
509}
510
511/// `GET /account/*`
512#[derive(Default, Debug, Clone)]
513pub struct AccountWildcard;
514
515impl SimpleRoute for AccountWildcard {
516    const PATH: &'static str = "/account/{*rest}";
517}
518
519/// `GET /account/password/change`
520///
521/// Handled by the React frontend; this struct definition is purely for
522/// redirects.
523#[derive(Default, Debug, Clone)]
524pub struct AccountPasswordChange;
525
526impl SimpleRoute for AccountPasswordChange {
527    const PATH: &'static str = "/account/password/change";
528}
529
530/// `GET /consent/{grant_id}`
531#[derive(Debug, Clone)]
532pub struct Consent(pub Ulid);
533
534impl Route for Consent {
535    type Query = ();
536    fn route() -> &'static str {
537        "/consent/{grant_id}"
538    }
539
540    fn path(&self) -> std::borrow::Cow<'static, str> {
541        format!("/consent/{}", self.0).into()
542    }
543}
544
545/// `GET|POST /_matrix/client/v3/login`
546pub struct CompatLogin;
547
548impl SimpleRoute for CompatLogin {
549    const PATH: &'static str = "/_matrix/client/{version}/login";
550}
551
552/// `POST /_matrix/client/v3/logout`
553pub struct CompatLogout;
554
555impl SimpleRoute for CompatLogout {
556    const PATH: &'static str = "/_matrix/client/{version}/logout";
557}
558
559/// `POST /_matrix/client/v3/logout/all`
560pub struct CompatLogoutAll;
561
562impl SimpleRoute for CompatLogoutAll {
563    const PATH: &'static str = "/_matrix/client/{version}/logout/all";
564}
565
566/// `POST /_matrix/client/v3/refresh`
567pub struct CompatRefresh;
568
569impl SimpleRoute for CompatRefresh {
570    const PATH: &'static str = "/_matrix/client/{version}/refresh";
571}
572
573/// `GET /_matrix/client/v3/login/sso/redirect`
574pub struct CompatLoginSsoRedirect;
575
576impl SimpleRoute for CompatLoginSsoRedirect {
577    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect";
578}
579
580/// `GET /_matrix/client/v3/login/sso/redirect/`
581///
582/// This is a workaround for the fact some clients (Element iOS) sends a
583/// trailing slash, even though it's not in the spec.
584pub struct CompatLoginSsoRedirectSlash;
585
586impl SimpleRoute for CompatLoginSsoRedirectSlash {
587    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/";
588}
589
590/// `GET /_matrix/client/v3/login/sso/redirect/{idp}`
591pub struct CompatLoginSsoRedirectIdp;
592
593impl SimpleRoute for CompatLoginSsoRedirectIdp {
594    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/{idp}";
595}
596
597#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
598#[serde(rename_all = "lowercase")]
599pub enum CompatLoginSsoAction {
600    Login,
601    Register,
602    #[serde(other)]
603    Unknown,
604}
605
606impl CompatLoginSsoAction {
607    /// Returns true if the action is a known action.
608    #[must_use]
609    pub fn is_known(&self) -> bool {
610        !matches!(self, Self::Unknown)
611    }
612}
613
614#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
615pub struct CompatLoginSsoActionParams {
616    action: CompatLoginSsoAction,
617    /// DEPRECATED: Use `action` instead. We will remove this once enough
618    /// clients support the stable name.
619    #[serde(rename = "org.matrix.msc3824.action")]
620    unstable_action: CompatLoginSsoAction,
621}
622
623/// `GET|POST /complete-compat-sso/{id}`
624pub struct CompatLoginSsoComplete {
625    id: Ulid,
626    query: Option<CompatLoginSsoActionParams>,
627}
628
629impl CompatLoginSsoComplete {
630    #[must_use]
631    pub fn new(id: Ulid, action: Option<CompatLoginSsoAction>) -> Self {
632        Self {
633            id,
634            query: action.map(|action| CompatLoginSsoActionParams {
635                action,
636                unstable_action: action,
637            }),
638        }
639    }
640}
641
642impl Route for CompatLoginSsoComplete {
643    type Query = CompatLoginSsoActionParams;
644
645    fn query(&self) -> Option<&Self::Query> {
646        self.query.as_ref()
647    }
648
649    fn route() -> &'static str {
650        "/complete-compat-sso/{grant_id}"
651    }
652
653    fn path(&self) -> std::borrow::Cow<'static, str> {
654        format!("/complete-compat-sso/{}", self.id).into()
655    }
656}
657
658/// `GET /upstream/authorize/{id}`
659pub struct UpstreamOAuth2Authorize {
660    id: Ulid,
661    post_auth_action: Option<PostAuthAction>,
662}
663
664impl UpstreamOAuth2Authorize {
665    #[must_use]
666    pub const fn new(id: Ulid) -> Self {
667        Self {
668            id,
669            post_auth_action: None,
670        }
671    }
672
673    #[must_use]
674    pub fn and_then(mut self, action: PostAuthAction) -> Self {
675        self.post_auth_action = Some(action);
676        self
677    }
678}
679
680impl Route for UpstreamOAuth2Authorize {
681    type Query = PostAuthAction;
682    fn route() -> &'static str {
683        "/upstream/authorize/{provider_id}"
684    }
685
686    fn path(&self) -> std::borrow::Cow<'static, str> {
687        format!("/upstream/authorize/{}", self.id).into()
688    }
689
690    fn query(&self) -> Option<&Self::Query> {
691        self.post_auth_action.as_ref()
692    }
693}
694
695/// `GET /upstream/callback/{id}`
696pub struct UpstreamOAuth2Callback {
697    id: Ulid,
698}
699
700impl UpstreamOAuth2Callback {
701    #[must_use]
702    pub const fn new(id: Ulid) -> Self {
703        Self { id }
704    }
705}
706
707impl Route for UpstreamOAuth2Callback {
708    type Query = ();
709    fn route() -> &'static str {
710        "/upstream/callback/{provider_id}"
711    }
712
713    fn path(&self) -> std::borrow::Cow<'static, str> {
714        format!("/upstream/callback/{}", self.id).into()
715    }
716}
717
718/// `GET /upstream/link/{id}`
719pub struct UpstreamOAuth2Link {
720    id: Ulid,
721}
722
723impl UpstreamOAuth2Link {
724    #[must_use]
725    pub const fn new(id: Ulid) -> Self {
726        Self { id }
727    }
728}
729
730impl Route for UpstreamOAuth2Link {
731    type Query = ();
732    fn route() -> &'static str {
733        "/upstream/link/{link_id}"
734    }
735
736    fn path(&self) -> std::borrow::Cow<'static, str> {
737        format!("/upstream/link/{}", self.id).into()
738    }
739}
740
741/// `POST /upstream/backchannel-logout/{id}`
742pub struct UpstreamOAuth2BackchannelLogout {
743    id: Ulid,
744}
745
746impl UpstreamOAuth2BackchannelLogout {
747    #[must_use]
748    pub const fn new(id: Ulid) -> Self {
749        Self { id }
750    }
751}
752
753impl Route for UpstreamOAuth2BackchannelLogout {
754    type Query = ();
755    fn route() -> &'static str {
756        "/upstream/backchannel-logout/{provider_id}"
757    }
758
759    fn path(&self) -> std::borrow::Cow<'static, str> {
760        format!("/upstream/backchannel-logout/{}", self.id).into()
761    }
762}
763
764/// `GET|POST /link`
765#[derive(Default, Serialize, Deserialize, Debug, Clone)]
766pub struct DeviceCodeLink {
767    code: Option<String>,
768}
769
770impl DeviceCodeLink {
771    #[must_use]
772    pub fn with_code(code: String) -> Self {
773        Self { code: Some(code) }
774    }
775}
776
777impl Route for DeviceCodeLink {
778    type Query = DeviceCodeLink;
779    fn route() -> &'static str {
780        "/link"
781    }
782
783    fn query(&self) -> Option<&Self::Query> {
784        Some(self)
785    }
786}
787
788/// `GET|POST /device/{device_code_id}`
789#[derive(Default, Serialize, Deserialize, Debug, Clone)]
790pub struct DeviceCodeConsent {
791    id: Ulid,
792}
793
794impl Route for DeviceCodeConsent {
795    type Query = ();
796    fn route() -> &'static str {
797        "/device/{device_code_id}"
798    }
799
800    fn path(&self) -> std::borrow::Cow<'static, str> {
801        format!("/device/{}", self.id).into()
802    }
803}
804
805impl DeviceCodeConsent {
806    #[must_use]
807    pub fn new(id: Ulid) -> Self {
808        Self { id }
809    }
810}
811
812/// `POST /oauth2/device`
813#[derive(Default, Serialize, Deserialize, Debug, Clone)]
814pub struct OAuth2DeviceAuthorizationEndpoint;
815
816impl SimpleRoute for OAuth2DeviceAuthorizationEndpoint {
817    const PATH: &'static str = "/oauth2/device";
818}
819
820/// `GET|POST /recover`
821#[derive(Default, Serialize, Deserialize, Debug, Clone)]
822pub struct AccountRecoveryStart;
823
824impl SimpleRoute for AccountRecoveryStart {
825    const PATH: &'static str = "/recover";
826}
827
828/// `GET|POST /recover/progress/{session_id}`
829#[derive(Default, Serialize, Deserialize, Debug, Clone)]
830pub struct AccountRecoveryProgress {
831    session_id: Ulid,
832}
833
834impl AccountRecoveryProgress {
835    #[must_use]
836    pub fn new(session_id: Ulid) -> Self {
837        Self { session_id }
838    }
839}
840
841impl Route for AccountRecoveryProgress {
842    type Query = ();
843    fn route() -> &'static str {
844        "/recover/progress/{session_id}"
845    }
846
847    fn path(&self) -> std::borrow::Cow<'static, str> {
848        format!("/recover/progress/{}", self.session_id).into()
849    }
850}
851
852/// `GET /account/password/recovery?ticket=:ticket`
853/// Rendered by the React frontend
854#[derive(Default, Serialize, Deserialize, Debug, Clone)]
855pub struct AccountRecoveryFinish {
856    ticket: String,
857}
858
859impl AccountRecoveryFinish {
860    #[must_use]
861    pub fn new(ticket: String) -> Self {
862        Self { ticket }
863    }
864}
865
866impl Route for AccountRecoveryFinish {
867    type Query = AccountRecoveryFinish;
868
869    fn route() -> &'static str {
870        "/account/password/recovery"
871    }
872
873    fn query(&self) -> Option<&Self::Query> {
874        Some(self)
875    }
876}
877
878/// `GET /assets`
879pub struct StaticAsset {
880    path: String,
881}
882
883impl StaticAsset {
884    #[must_use]
885    pub fn new(path: String) -> Self {
886        Self { path }
887    }
888}
889
890impl Route for StaticAsset {
891    type Query = ();
892    fn route() -> &'static str {
893        "/assets/"
894    }
895
896    fn path(&self) -> std::borrow::Cow<'static, str> {
897        format!("/assets/{}", self.path).into()
898    }
899}
900
901/// `GET|POST /graphql`
902pub struct GraphQL;
903
904impl SimpleRoute for GraphQL {
905    const PATH: &'static str = "/graphql";
906}
907
908/// `GET /api/spec.json`
909pub struct ApiSpec;
910
911impl SimpleRoute for ApiSpec {
912    const PATH: &'static str = "/api/spec.json";
913}
914
915/// `GET /api/doc/`
916pub struct ApiDoc;
917
918impl SimpleRoute for ApiDoc {
919    const PATH: &'static str = "/api/doc/";
920}
921
922/// `GET /api/doc/oauth2-callback`
923pub struct ApiDocCallback;
924
925impl SimpleRoute for ApiDocCallback {
926    const PATH: &'static str = "/api/doc/oauth2-callback";
927}