1use 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#[derive(Default, Debug, Clone)]
82pub struct OidcConfiguration;
83
84impl SimpleRoute for OidcConfiguration {
85 const PATH: &'static str = "/.well-known/openid-configuration";
86}
87
88#[derive(Default, Debug, Clone)]
90pub struct Webfinger;
91
92impl SimpleRoute for Webfinger {
93 const PATH: &'static str = "/.well-known/webfinger";
94}
95
96pub struct ChangePasswordDiscovery;
98
99impl SimpleRoute for ChangePasswordDiscovery {
100 const PATH: &'static str = "/.well-known/change-password";
101}
102
103#[derive(Default, Debug, Clone)]
105pub struct OAuth2Keys;
106
107impl SimpleRoute for OAuth2Keys {
108 const PATH: &'static str = "/oauth2/keys.json";
109}
110
111#[derive(Default, Debug, Clone)]
113pub struct OidcUserinfo;
114
115impl SimpleRoute for OidcUserinfo {
116 const PATH: &'static str = "/oauth2/userinfo";
117}
118
119#[derive(Default, Debug, Clone)]
121pub struct OAuth2Introspection;
122
123impl SimpleRoute for OAuth2Introspection {
124 const PATH: &'static str = "/oauth2/introspect";
125}
126
127#[derive(Default, Debug, Clone)]
129pub struct OAuth2Revocation;
130
131impl SimpleRoute for OAuth2Revocation {
132 const PATH: &'static str = "/oauth2/revoke";
133}
134
135#[derive(Default, Debug, Clone)]
137pub struct OAuth2TokenEndpoint;
138
139impl SimpleRoute for OAuth2TokenEndpoint {
140 const PATH: &'static str = "/oauth2/token";
141}
142
143#[derive(Default, Debug, Clone)]
145pub struct OAuth2RegistrationEndpoint;
146
147impl SimpleRoute for OAuth2RegistrationEndpoint {
148 const PATH: &'static str = "/oauth2/registration";
149}
150
151#[derive(Default, Debug, Clone)]
153pub struct OAuth2AuthorizationEndpoint;
154
155impl SimpleRoute for OAuth2AuthorizationEndpoint {
156 const PATH: &'static str = "/authorize";
157}
158
159#[derive(Default, Debug, Clone)]
161pub struct Index;
162
163impl SimpleRoute for Index {
164 const PATH: &'static str = "/";
165}
166
167#[derive(Default, Debug, Clone)]
169pub struct Healthcheck;
170
171impl SimpleRoute for Healthcheck {
172 const PATH: &'static str = "/health";
173}
174
175#[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 #[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#[derive(Default, Debug, Clone)]
268pub struct Logout;
269
270impl SimpleRoute for Logout {
271 const PATH: &'static str = "/logout";
272}
273
274#[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 #[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#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
432#[serde(tag = "action")]
433pub enum AccountAction {
434 #[serde(rename = "org.matrix.profile")]
435 OrgMatrixProfile,
436 #[serde(rename = "profile")]
438 Profile,
439
440 #[serde(rename = "org.matrix.devices_list")]
441 OrgMatrixDevicesList,
442 #[serde(rename = "org.matrix.sessions_list")]
444 OrgMatrixSessionsList,
445 #[serde(rename = "sessions_list")]
447 SessionsList,
448
449 #[serde(rename = "org.matrix.device_view")]
450 OrgMatrixDeviceView { device_id: String },
451 #[serde(rename = "org.matrix.session_view")]
453 OrgMatrixSessionView { device_id: String },
454 #[serde(rename = "session_view")]
456 SessionView { device_id: String },
457
458 #[serde(rename = "org.matrix.device_delete")]
459 OrgMatrixDeviceDelete { device_id: String },
460 #[serde(rename = "org.matrix.session_end")]
462 OrgMatrixSessionEnd { device_id: String },
463 #[serde(rename = "session_end")]
465 SessionEnd { device_id: String },
466
467 #[serde(rename = "org.matrix.cross_signing_reset")]
468 OrgMatrixCrossSigningReset,
469}
470
471#[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#[derive(Default, Debug, Clone)]
491pub struct AccountWildcard;
492
493impl SimpleRoute for AccountWildcard {
494 const PATH: &'static str = "/account/{*rest}";
495}
496
497#[derive(Default, Debug, Clone)]
502pub struct AccountPasswordChange;
503
504impl SimpleRoute for AccountPasswordChange {
505 const PATH: &'static str = "/account/password/change";
506}
507
508#[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
523pub struct CompatLogin;
525
526impl SimpleRoute for CompatLogin {
527 const PATH: &'static str = "/_matrix/client/{version}/login";
528}
529
530pub struct CompatLogout;
532
533impl SimpleRoute for CompatLogout {
534 const PATH: &'static str = "/_matrix/client/{version}/logout";
535}
536
537pub struct CompatLogoutAll;
539
540impl SimpleRoute for CompatLogoutAll {
541 const PATH: &'static str = "/_matrix/client/{version}/logout/all";
542}
543
544pub struct CompatRefresh;
546
547impl SimpleRoute for CompatRefresh {
548 const PATH: &'static str = "/_matrix/client/{version}/refresh";
549}
550
551pub struct CompatLoginSsoRedirect;
553
554impl SimpleRoute for CompatLoginSsoRedirect {
555 const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect";
556}
557
558pub struct CompatLoginSsoRedirectSlash;
563
564impl SimpleRoute for CompatLoginSsoRedirectSlash {
565 const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/";
566}
567
568pub 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 #[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 #[serde(rename = "org.matrix.msc3824.action")]
598 unstable_action: CompatLoginSsoAction,
599}
600
601pub 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
636pub 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
673pub 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
696pub 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
719pub 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#[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#[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#[derive(Default, Serialize, Deserialize, Debug, Clone)]
792pub struct OAuth2DeviceAuthorizationEndpoint;
793
794impl SimpleRoute for OAuth2DeviceAuthorizationEndpoint {
795 const PATH: &'static str = "/oauth2/device";
796}
797
798#[derive(Default, Serialize, Deserialize, Debug, Clone)]
800pub struct AccountRecoveryStart;
801
802impl SimpleRoute for AccountRecoveryStart {
803 const PATH: &'static str = "/recover";
804}
805
806#[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#[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
856pub 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
879pub struct GraphQL;
881
882impl SimpleRoute for GraphQL {
883 const PATH: &'static str = "/graphql";
884}
885
886pub struct ApiSpec;
888
889impl SimpleRoute for ApiSpec {
890 const PATH: &'static str = "/api/spec.json";
891}
892
893pub struct ApiDoc;
895
896impl SimpleRoute for ApiDoc {
897 const PATH: &'static str = "/api/doc/";
898}
899
900pub struct ApiDocCallback;
902
903impl SimpleRoute for ApiDocCallback {
904 const PATH: &'static str = "/api/doc/oauth2-callback";
905}