mas_router/
endpoints.rs

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