mas_router/
endpoints.rs

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