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