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