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, Serialize, Deserialize)]
176pub struct Login {
177    #[serde(flatten)]
178    post_auth_action: Option<PostAuthAction>,
179
180    login_hint: Option<String>,
181}
182
183impl Route for Login {
184    type Query = Self;
185
186    fn route() -> &'static str {
187        "/login"
188    }
189
190    fn query(&self) -> Option<&Self::Query> {
191        Some(self)
192    }
193}
194
195impl Login {
196    #[must_use]
197    pub const fn and_then(action: PostAuthAction) -> Self {
198        Self {
199            post_auth_action: Some(action),
200            login_hint: None,
201        }
202    }
203
204    #[must_use]
205    pub const fn and_continue_grant(id: Ulid) -> Self {
206        Self {
207            post_auth_action: Some(PostAuthAction::continue_grant(id)),
208            login_hint: None,
209        }
210    }
211
212    #[must_use]
213    pub const fn and_continue_device_code_grant(id: Ulid) -> Self {
214        Self {
215            post_auth_action: Some(PostAuthAction::continue_device_code_grant(id)),
216            login_hint: None,
217        }
218    }
219
220    #[must_use]
221    pub const fn and_continue_compat_sso_login(id: Ulid) -> Self {
222        Self {
223            post_auth_action: Some(PostAuthAction::continue_compat_sso_login(id)),
224            login_hint: None,
225        }
226    }
227
228    #[must_use]
229    pub const fn and_link_upstream(id: Ulid) -> Self {
230        Self {
231            post_auth_action: Some(PostAuthAction::link_upstream(id)),
232            login_hint: None,
233        }
234    }
235
236    #[must_use]
237    pub fn with_login_hint(mut self, login_hint: String) -> Self {
238        self.login_hint = Some(login_hint);
239        self
240    }
241
242    /// Get a reference to the login's post auth action.
243    #[must_use]
244    pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
245        self.post_auth_action.as_ref()
246    }
247
248    pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
249        match &self.post_auth_action {
250            Some(action) => action.go_next(url_builder),
251            None => url_builder.redirect(&Index),
252        }
253    }
254}
255
256impl From<Option<PostAuthAction>> for Login {
257    fn from(post_auth_action: Option<PostAuthAction>) -> Self {
258        Self {
259            post_auth_action,
260            login_hint: None,
261        }
262    }
263}
264
265/// `POST /logout`
266#[derive(Default, Debug, Clone)]
267pub struct Logout;
268
269impl SimpleRoute for Logout {
270    const PATH: &'static str = "/logout";
271}
272
273/// `POST /register`
274#[derive(Default, Debug, Clone)]
275pub struct Register {
276    post_auth_action: Option<PostAuthAction>,
277}
278
279impl Register {
280    #[must_use]
281    pub fn and_then(action: PostAuthAction) -> Self {
282        Self {
283            post_auth_action: Some(action),
284        }
285    }
286
287    #[must_use]
288    pub fn and_continue_grant(data: Ulid) -> Self {
289        Self {
290            post_auth_action: Some(PostAuthAction::continue_grant(data)),
291        }
292    }
293
294    #[must_use]
295    pub fn and_continue_compat_sso_login(data: Ulid) -> Self {
296        Self {
297            post_auth_action: Some(PostAuthAction::continue_compat_sso_login(data)),
298        }
299    }
300
301    /// Get a reference to the reauth's post auth action.
302    #[must_use]
303    pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
304        self.post_auth_action.as_ref()
305    }
306
307    pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
308        match &self.post_auth_action {
309            Some(action) => action.go_next(url_builder),
310            None => url_builder.redirect(&Index),
311        }
312    }
313}
314
315impl Route for Register {
316    type Query = PostAuthAction;
317
318    fn route() -> &'static str {
319        "/register"
320    }
321
322    fn query(&self) -> Option<&Self::Query> {
323        self.post_auth_action.as_ref()
324    }
325}
326
327impl From<Option<PostAuthAction>> for Register {
328    fn from(post_auth_action: Option<PostAuthAction>) -> Self {
329        Self { post_auth_action }
330    }
331}
332
333/// `GET|POST /register/password`
334#[derive(Default, Debug, Clone, Serialize, Deserialize)]
335pub struct PasswordRegister {
336    username: Option<String>,
337
338    #[serde(flatten)]
339    post_auth_action: Option<PostAuthAction>,
340}
341
342impl PasswordRegister {
343    #[must_use]
344    pub fn and_then(mut self, action: PostAuthAction) -> Self {
345        self.post_auth_action = Some(action);
346        self
347    }
348
349    #[must_use]
350    pub fn and_continue_grant(mut self, data: Ulid) -> Self {
351        self.post_auth_action = Some(PostAuthAction::continue_grant(data));
352        self
353    }
354
355    #[must_use]
356    pub fn and_continue_compat_sso_login(mut self, data: Ulid) -> Self {
357        self.post_auth_action = Some(PostAuthAction::continue_compat_sso_login(data));
358        self
359    }
360
361    /// Get a reference to the post auth action.
362    #[must_use]
363    pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
364        self.post_auth_action.as_ref()
365    }
366
367    /// Get a reference to the username chosen by the user.
368    #[must_use]
369    pub fn username(&self) -> Option<&str> {
370        self.username.as_deref()
371    }
372
373    pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
374        match &self.post_auth_action {
375            Some(action) => action.go_next(url_builder),
376            None => url_builder.redirect(&Index),
377        }
378    }
379}
380
381impl Route for PasswordRegister {
382    type Query = Self;
383
384    fn route() -> &'static str {
385        "/register/password"
386    }
387
388    fn query(&self) -> Option<&Self::Query> {
389        Some(self)
390    }
391}
392
393impl From<Option<PostAuthAction>> for PasswordRegister {
394    fn from(post_auth_action: Option<PostAuthAction>) -> Self {
395        Self {
396            username: None,
397            post_auth_action,
398        }
399    }
400}
401
402/// `GET|POST /register/steps/{id}/token`
403#[derive(Debug, Clone)]
404pub struct RegisterToken {
405    id: Ulid,
406}
407
408impl RegisterToken {
409    #[must_use]
410    pub fn new(id: Ulid) -> Self {
411        Self { id }
412    }
413}
414
415impl Route for RegisterToken {
416    type Query = ();
417    fn route() -> &'static str {
418        "/register/steps/{id}/token"
419    }
420
421    fn path(&self) -> std::borrow::Cow<'static, str> {
422        format!("/register/steps/{}/token", self.id).into()
423    }
424}
425
426/// `GET|POST /register/steps/{id}/display-name`
427#[derive(Debug, Clone)]
428pub struct RegisterDisplayName {
429    id: Ulid,
430}
431
432impl RegisterDisplayName {
433    #[must_use]
434    pub fn new(id: Ulid) -> Self {
435        Self { id }
436    }
437}
438
439impl Route for RegisterDisplayName {
440    type Query = ();
441    fn route() -> &'static str {
442        "/register/steps/{id}/display-name"
443    }
444
445    fn path(&self) -> std::borrow::Cow<'static, str> {
446        format!("/register/steps/{}/display-name", self.id).into()
447    }
448}
449
450/// `GET|POST /register/steps/{id}/verify-email`
451#[derive(Debug, Clone)]
452pub struct RegisterVerifyEmail {
453    id: Ulid,
454}
455
456impl RegisterVerifyEmail {
457    #[must_use]
458    pub fn new(id: Ulid) -> Self {
459        Self { id }
460    }
461}
462
463impl Route for RegisterVerifyEmail {
464    type Query = ();
465    fn route() -> &'static str {
466        "/register/steps/{id}/verify-email"
467    }
468
469    fn path(&self) -> std::borrow::Cow<'static, str> {
470        format!("/register/steps/{}/verify-email", self.id).into()
471    }
472}
473
474/// `GET /register/steps/{id}/finish`
475#[derive(Debug, Clone)]
476pub struct RegisterFinish {
477    id: Ulid,
478}
479
480impl RegisterFinish {
481    #[must_use]
482    pub const fn new(id: Ulid) -> Self {
483        Self { id }
484    }
485}
486
487impl Route for RegisterFinish {
488    type Query = ();
489    fn route() -> &'static str {
490        "/register/steps/{id}/finish"
491    }
492
493    fn path(&self) -> std::borrow::Cow<'static, str> {
494        format!("/register/steps/{}/finish", self.id).into()
495    }
496}
497
498/// Actions parameters as defined by MSC4191
499#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(tag = "action")]
501pub enum AccountAction {
502    #[serde(rename = "org.matrix.profile")]
503    OrgMatrixProfile,
504    /// DEPRECATED: Use `OrgMatrixProfile` instead
505    #[serde(rename = "profile")]
506    Profile,
507
508    #[serde(rename = "org.matrix.devices_list")]
509    OrgMatrixDevicesList,
510    /// DEPRECATED: Use `OrgMatrixDevicesList` instead
511    #[serde(rename = "org.matrix.sessions_list")]
512    OrgMatrixSessionsList,
513    /// DEPRECATED: Use `OrgMatrixDevicesList` instead
514    #[serde(rename = "sessions_list")]
515    SessionsList,
516
517    #[serde(rename = "org.matrix.device_view")]
518    OrgMatrixDeviceView { device_id: String },
519    /// DEPRECATED: Use `OrgMatrixDeviceView` instead
520    #[serde(rename = "org.matrix.session_view")]
521    OrgMatrixSessionView { device_id: String },
522    /// DEPRECATED: Use `OrgMatrixDeviceView` instead
523    #[serde(rename = "session_view")]
524    SessionView { device_id: String },
525
526    #[serde(rename = "org.matrix.device_delete")]
527    OrgMatrixDeviceDelete { device_id: String },
528    /// DEPRECATED: Use `OrgMatrixDeviceDelete` instead
529    #[serde(rename = "org.matrix.session_end")]
530    OrgMatrixSessionEnd { device_id: String },
531    /// DEPRECATED: Use `OrgMatrixDeviceDelete` instead
532    #[serde(rename = "session_end")]
533    SessionEnd { device_id: String },
534
535    #[serde(rename = "org.matrix.cross_signing_reset")]
536    OrgMatrixCrossSigningReset,
537}
538
539/// `GET /account/`
540#[derive(Default, Debug, Clone)]
541pub struct Account {
542    action: Option<AccountAction>,
543}
544
545impl Route for Account {
546    type Query = AccountAction;
547
548    fn route() -> &'static str {
549        "/account/"
550    }
551
552    fn query(&self) -> Option<&Self::Query> {
553        self.action.as_ref()
554    }
555}
556
557/// `GET /account/*`
558#[derive(Default, Debug, Clone)]
559pub struct AccountWildcard;
560
561impl SimpleRoute for AccountWildcard {
562    const PATH: &'static str = "/account/{*rest}";
563}
564
565/// `GET /account/password/change`
566///
567/// Handled by the React frontend; this struct definition is purely for
568/// redirects.
569#[derive(Default, Debug, Clone)]
570pub struct AccountPasswordChange;
571
572impl SimpleRoute for AccountPasswordChange {
573    const PATH: &'static str = "/account/password/change";
574}
575
576/// `GET /consent/{grant_id}`
577#[derive(Debug, Clone)]
578pub struct Consent(pub Ulid);
579
580impl Route for Consent {
581    type Query = ();
582    fn route() -> &'static str {
583        "/consent/{grant_id}"
584    }
585
586    fn path(&self) -> std::borrow::Cow<'static, str> {
587        format!("/consent/{}", self.0).into()
588    }
589}
590
591/// `GET|POST /_matrix/client/v3/login`
592pub struct CompatLogin;
593
594impl SimpleRoute for CompatLogin {
595    const PATH: &'static str = "/_matrix/client/{version}/login";
596}
597
598/// `POST /_matrix/client/v3/logout`
599pub struct CompatLogout;
600
601impl SimpleRoute for CompatLogout {
602    const PATH: &'static str = "/_matrix/client/{version}/logout";
603}
604
605/// `POST /_matrix/client/v3/logout/all`
606pub struct CompatLogoutAll;
607
608impl SimpleRoute for CompatLogoutAll {
609    const PATH: &'static str = "/_matrix/client/{version}/logout/all";
610}
611
612/// `POST /_matrix/client/v3/refresh`
613pub struct CompatRefresh;
614
615impl SimpleRoute for CompatRefresh {
616    const PATH: &'static str = "/_matrix/client/{version}/refresh";
617}
618
619/// `GET /_matrix/client/v3/login/sso/redirect`
620pub struct CompatLoginSsoRedirect;
621
622impl SimpleRoute for CompatLoginSsoRedirect {
623    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect";
624}
625
626/// `GET /_matrix/client/v3/login/sso/redirect/`
627///
628/// This is a workaround for the fact some clients (Element iOS) sends a
629/// trailing slash, even though it's not in the spec.
630pub struct CompatLoginSsoRedirectSlash;
631
632impl SimpleRoute for CompatLoginSsoRedirectSlash {
633    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/";
634}
635
636/// `GET /_matrix/client/v3/login/sso/redirect/{idp}`
637pub struct CompatLoginSsoRedirectIdp;
638
639impl SimpleRoute for CompatLoginSsoRedirectIdp {
640    const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/{idp}";
641}
642
643#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
644#[serde(rename_all = "lowercase")]
645pub enum CompatLoginSsoAction {
646    Login,
647    Register,
648    #[serde(other)]
649    Unknown,
650}
651
652impl CompatLoginSsoAction {
653    /// Returns true if the action is a known action.
654    #[must_use]
655    pub fn is_known(&self) -> bool {
656        !matches!(self, Self::Unknown)
657    }
658}
659
660#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
661pub struct CompatLoginSsoActionParams {
662    action: CompatLoginSsoAction,
663    /// DEPRECATED: Use `action` instead. We will remove this once enough
664    /// clients support the stable name.
665    #[serde(rename = "org.matrix.msc3824.action")]
666    unstable_action: CompatLoginSsoAction,
667}
668
669/// `GET|POST /complete-compat-sso/{id}`
670pub struct CompatLoginSsoComplete {
671    id: Ulid,
672    query: Option<CompatLoginSsoActionParams>,
673}
674
675impl CompatLoginSsoComplete {
676    #[must_use]
677    pub fn new(id: Ulid, action: Option<CompatLoginSsoAction>) -> Self {
678        Self {
679            id,
680            query: action.map(|action| CompatLoginSsoActionParams {
681                action,
682                unstable_action: action,
683            }),
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/// `POST /upstream/backchannel-logout/{id}`
788pub struct UpstreamOAuth2BackchannelLogout {
789    id: Ulid,
790}
791
792impl UpstreamOAuth2BackchannelLogout {
793    #[must_use]
794    pub const fn new(id: Ulid) -> Self {
795        Self { id }
796    }
797}
798
799impl Route for UpstreamOAuth2BackchannelLogout {
800    type Query = ();
801    fn route() -> &'static str {
802        "/upstream/backchannel-logout/{provider_id}"
803    }
804
805    fn path(&self) -> std::borrow::Cow<'static, str> {
806        format!("/upstream/backchannel-logout/{}", self.id).into()
807    }
808}
809
810/// `GET|POST /link`
811#[derive(Default, Serialize, Deserialize, Debug, Clone)]
812pub struct DeviceCodeLink {
813    code: Option<String>,
814}
815
816impl DeviceCodeLink {
817    #[must_use]
818    pub fn with_code(code: String) -> Self {
819        Self { code: Some(code) }
820    }
821}
822
823impl Route for DeviceCodeLink {
824    type Query = DeviceCodeLink;
825    fn route() -> &'static str {
826        "/link"
827    }
828
829    fn query(&self) -> Option<&Self::Query> {
830        Some(self)
831    }
832}
833
834/// `GET|POST /device/{device_code_id}`
835#[derive(Default, Serialize, Deserialize, Debug, Clone)]
836pub struct DeviceCodeConsent {
837    id: Ulid,
838}
839
840impl Route for DeviceCodeConsent {
841    type Query = ();
842    fn route() -> &'static str {
843        "/device/{device_code_id}"
844    }
845
846    fn path(&self) -> std::borrow::Cow<'static, str> {
847        format!("/device/{}", self.id).into()
848    }
849}
850
851impl DeviceCodeConsent {
852    #[must_use]
853    pub fn new(id: Ulid) -> Self {
854        Self { id }
855    }
856}
857
858/// `POST /oauth2/device`
859#[derive(Default, Serialize, Deserialize, Debug, Clone)]
860pub struct OAuth2DeviceAuthorizationEndpoint;
861
862impl SimpleRoute for OAuth2DeviceAuthorizationEndpoint {
863    const PATH: &'static str = "/oauth2/device";
864}
865
866/// `GET|POST /recover`
867#[derive(Default, Serialize, Deserialize, Debug, Clone)]
868pub struct AccountRecoveryStart;
869
870impl SimpleRoute for AccountRecoveryStart {
871    const PATH: &'static str = "/recover";
872}
873
874/// `GET|POST /recover/progress/{session_id}`
875#[derive(Default, Serialize, Deserialize, Debug, Clone)]
876pub struct AccountRecoveryProgress {
877    session_id: Ulid,
878}
879
880impl AccountRecoveryProgress {
881    #[must_use]
882    pub fn new(session_id: Ulid) -> Self {
883        Self { session_id }
884    }
885}
886
887impl Route for AccountRecoveryProgress {
888    type Query = ();
889    fn route() -> &'static str {
890        "/recover/progress/{session_id}"
891    }
892
893    fn path(&self) -> std::borrow::Cow<'static, str> {
894        format!("/recover/progress/{}", self.session_id).into()
895    }
896}
897
898/// `GET /account/password/recovery?ticket=:ticket`
899/// Rendered by the React frontend
900#[derive(Default, Serialize, Deserialize, Debug, Clone)]
901pub struct AccountRecoveryFinish {
902    ticket: String,
903}
904
905impl AccountRecoveryFinish {
906    #[must_use]
907    pub fn new(ticket: String) -> Self {
908        Self { ticket }
909    }
910}
911
912impl Route for AccountRecoveryFinish {
913    type Query = AccountRecoveryFinish;
914
915    fn route() -> &'static str {
916        "/account/password/recovery"
917    }
918
919    fn query(&self) -> Option<&Self::Query> {
920        Some(self)
921    }
922}
923
924/// `GET /assets`
925pub struct StaticAsset {
926    path: String,
927}
928
929impl StaticAsset {
930    #[must_use]
931    pub fn new(path: String) -> Self {
932        Self { path }
933    }
934}
935
936impl Route for StaticAsset {
937    type Query = ();
938    fn route() -> &'static str {
939        "/assets/"
940    }
941
942    fn path(&self) -> std::borrow::Cow<'static, str> {
943        format!("/assets/{}", self.path).into()
944    }
945}
946
947/// `GET|POST /graphql`
948pub struct GraphQL;
949
950impl SimpleRoute for GraphQL {
951    const PATH: &'static str = "/graphql";
952}
953
954/// `GET /graphql/playground`
955pub struct GraphQLPlayground;
956
957impl SimpleRoute for GraphQLPlayground {
958    const PATH: &'static str = "/graphql/playground";
959}
960
961/// `GET /api/spec.json`
962pub struct ApiSpec;
963
964impl SimpleRoute for ApiSpec {
965    const PATH: &'static str = "/api/spec.json";
966}
967
968/// `GET /api/doc/`
969pub struct ApiDoc;
970
971impl SimpleRoute for ApiDoc {
972    const PATH: &'static str = "/api/doc/";
973}
974
975/// `GET /api/doc/oauth2-callback`
976pub struct ApiDocCallback;
977
978impl SimpleRoute for ApiDocCallback {
979    const PATH: &'static str = "/api/doc/oauth2-callback";
980}