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 query: RegisterQuery,
278}
279
280#[derive(Default, Debug, Clone, Serialize)]
282pub struct RegisterQuery {
283 #[serde(skip_serializing_if = "Option::is_none")]
285 token: Option<String>,
286
287 #[serde(flatten)]
288 post_auth_action: Option<PostAuthAction>,
289}
290
291impl Register {
292 #[must_use]
293 pub fn and_then(action: PostAuthAction) -> Self {
294 Self {
295 query: RegisterQuery {
296 token: None,
297 post_auth_action: Some(action),
298 },
299 }
300 }
301
302 #[must_use]
303 pub fn and_continue_grant(data: Ulid) -> Self {
304 Self::and_then(PostAuthAction::continue_grant(data))
305 }
306
307 #[must_use]
308 pub fn and_continue_compat_sso_login(data: Ulid) -> Self {
309 Self::and_then(PostAuthAction::continue_compat_sso_login(data))
310 }
311
312 #[must_use]
314 pub fn with_token(mut self, token: String) -> Self {
315 self.query.token = Some(token);
316 self
317 }
318
319 #[must_use]
321 pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
322 self.query.post_auth_action.as_ref()
323 }
324
325 pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
326 match &self.query.post_auth_action {
327 Some(action) => action.go_next(url_builder),
328 None => url_builder.redirect(&Index),
329 }
330 }
331}
332
333impl Route for Register {
334 type Query = RegisterQuery;
335
336 fn route() -> &'static str {
337 "/register"
338 }
339
340 fn query(&self) -> Option<&Self::Query> {
341 Some(&self.query)
342 }
343}
344
345impl From<Option<PostAuthAction>> for Register {
346 fn from(post_auth_action: Option<PostAuthAction>) -> Self {
347 Self {
348 query: RegisterQuery {
349 token: None,
350 post_auth_action,
351 },
352 }
353 }
354}
355
356#[derive(Debug, Clone)]
358pub struct RegisterToken {
359 id: Ulid,
360}
361
362impl RegisterToken {
363 #[must_use]
364 pub fn new(id: Ulid) -> Self {
365 Self { id }
366 }
367}
368
369impl Route for RegisterToken {
370 type Query = ();
371 fn route() -> &'static str {
372 "/register/steps/{id}/token"
373 }
374
375 fn path(&self) -> std::borrow::Cow<'static, str> {
376 format!("/register/steps/{}/token", self.id).into()
377 }
378}
379
380#[derive(Debug, Clone)]
382pub struct RegisterDisplayName {
383 id: Ulid,
384}
385
386impl RegisterDisplayName {
387 #[must_use]
388 pub fn new(id: Ulid) -> Self {
389 Self { id }
390 }
391}
392
393impl Route for RegisterDisplayName {
394 type Query = ();
395 fn route() -> &'static str {
396 "/register/steps/{id}/display-name"
397 }
398
399 fn path(&self) -> std::borrow::Cow<'static, str> {
400 format!("/register/steps/{}/display-name", self.id).into()
401 }
402}
403
404#[derive(Debug, Clone)]
406pub struct RegisterVerifyEmail {
407 id: Ulid,
408}
409
410impl RegisterVerifyEmail {
411 #[must_use]
412 pub fn new(id: Ulid) -> Self {
413 Self { id }
414 }
415}
416
417impl Route for RegisterVerifyEmail {
418 type Query = ();
419 fn route() -> &'static str {
420 "/register/steps/{id}/verify-email"
421 }
422
423 fn path(&self) -> std::borrow::Cow<'static, str> {
424 format!("/register/steps/{}/verify-email", self.id).into()
425 }
426}
427
428#[derive(Debug, Clone)]
430pub struct RegisterFinish {
431 id: Ulid,
432}
433
434impl RegisterFinish {
435 #[must_use]
436 pub const fn new(id: Ulid) -> Self {
437 Self { id }
438 }
439}
440
441impl Route for RegisterFinish {
442 type Query = ();
443 fn route() -> &'static str {
444 "/register/steps/{id}/finish"
445 }
446
447 fn path(&self) -> std::borrow::Cow<'static, str> {
448 format!("/register/steps/{}/finish", self.id).into()
449 }
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(tag = "action")]
455pub enum AccountAction {
456 #[serde(rename = "org.matrix.profile")]
457 OrgMatrixProfile,
458 #[serde(rename = "profile")]
460 Profile,
461
462 #[serde(rename = "org.matrix.devices_list")]
463 OrgMatrixDevicesList,
464 #[serde(rename = "org.matrix.sessions_list")]
466 OrgMatrixSessionsList,
467 #[serde(rename = "sessions_list")]
469 SessionsList,
470
471 #[serde(rename = "org.matrix.device_view")]
472 OrgMatrixDeviceView { device_id: String },
473 #[serde(rename = "org.matrix.session_view")]
475 OrgMatrixSessionView { device_id: String },
476 #[serde(rename = "session_view")]
478 SessionView { device_id: String },
479
480 #[serde(rename = "org.matrix.device_delete")]
481 OrgMatrixDeviceDelete { device_id: String },
482 #[serde(rename = "org.matrix.session_end")]
484 OrgMatrixSessionEnd { device_id: String },
485 #[serde(rename = "session_end")]
487 SessionEnd { device_id: String },
488
489 #[serde(rename = "org.matrix.cross_signing_reset")]
490 OrgMatrixCrossSigningReset,
491}
492
493#[derive(Default, Debug, Clone)]
495pub struct Account {
496 action: Option<AccountAction>,
497}
498
499impl Route for Account {
500 type Query = AccountAction;
501
502 fn route() -> &'static str {
503 "/account/"
504 }
505
506 fn query(&self) -> Option<&Self::Query> {
507 self.action.as_ref()
508 }
509}
510
511#[derive(Default, Debug, Clone)]
513pub struct AccountWildcard;
514
515impl SimpleRoute for AccountWildcard {
516 const PATH: &'static str = "/account/{*rest}";
517}
518
519#[derive(Default, Debug, Clone)]
524pub struct AccountPasswordChange;
525
526impl SimpleRoute for AccountPasswordChange {
527 const PATH: &'static str = "/account/password/change";
528}
529
530#[derive(Debug, Clone)]
532pub struct Consent(pub Ulid);
533
534impl Route for Consent {
535 type Query = ();
536 fn route() -> &'static str {
537 "/consent/{grant_id}"
538 }
539
540 fn path(&self) -> std::borrow::Cow<'static, str> {
541 format!("/consent/{}", self.0).into()
542 }
543}
544
545pub struct CompatLogin;
547
548impl SimpleRoute for CompatLogin {
549 const PATH: &'static str = "/_matrix/client/{version}/login";
550}
551
552pub struct CompatLogout;
554
555impl SimpleRoute for CompatLogout {
556 const PATH: &'static str = "/_matrix/client/{version}/logout";
557}
558
559pub struct CompatLogoutAll;
561
562impl SimpleRoute for CompatLogoutAll {
563 const PATH: &'static str = "/_matrix/client/{version}/logout/all";
564}
565
566pub struct CompatRefresh;
568
569impl SimpleRoute for CompatRefresh {
570 const PATH: &'static str = "/_matrix/client/{version}/refresh";
571}
572
573pub struct CompatLoginSsoRedirect;
575
576impl SimpleRoute for CompatLoginSsoRedirect {
577 const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect";
578}
579
580pub struct CompatLoginSsoRedirectSlash;
585
586impl SimpleRoute for CompatLoginSsoRedirectSlash {
587 const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/";
588}
589
590pub struct CompatLoginSsoRedirectIdp;
592
593impl SimpleRoute for CompatLoginSsoRedirectIdp {
594 const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/{idp}";
595}
596
597#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
598#[serde(rename_all = "lowercase")]
599pub enum CompatLoginSsoAction {
600 Login,
601 Register,
602 #[serde(other)]
603 Unknown,
604}
605
606impl CompatLoginSsoAction {
607 #[must_use]
609 pub fn is_known(&self) -> bool {
610 !matches!(self, Self::Unknown)
611 }
612}
613
614#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
615pub struct CompatLoginSsoActionParams {
616 action: CompatLoginSsoAction,
617 #[serde(rename = "org.matrix.msc3824.action")]
620 unstable_action: CompatLoginSsoAction,
621}
622
623pub struct CompatLoginSsoComplete {
625 id: Ulid,
626 query: Option<CompatLoginSsoActionParams>,
627}
628
629impl CompatLoginSsoComplete {
630 #[must_use]
631 pub fn new(id: Ulid, action: Option<CompatLoginSsoAction>) -> Self {
632 Self {
633 id,
634 query: action.map(|action| CompatLoginSsoActionParams {
635 action,
636 unstable_action: action,
637 }),
638 }
639 }
640}
641
642impl Route for CompatLoginSsoComplete {
643 type Query = CompatLoginSsoActionParams;
644
645 fn query(&self) -> Option<&Self::Query> {
646 self.query.as_ref()
647 }
648
649 fn route() -> &'static str {
650 "/complete-compat-sso/{grant_id}"
651 }
652
653 fn path(&self) -> std::borrow::Cow<'static, str> {
654 format!("/complete-compat-sso/{}", self.id).into()
655 }
656}
657
658pub struct UpstreamOAuth2Authorize {
660 id: Ulid,
661 post_auth_action: Option<PostAuthAction>,
662}
663
664impl UpstreamOAuth2Authorize {
665 #[must_use]
666 pub const fn new(id: Ulid) -> Self {
667 Self {
668 id,
669 post_auth_action: None,
670 }
671 }
672
673 #[must_use]
674 pub fn and_then(mut self, action: PostAuthAction) -> Self {
675 self.post_auth_action = Some(action);
676 self
677 }
678}
679
680impl Route for UpstreamOAuth2Authorize {
681 type Query = PostAuthAction;
682 fn route() -> &'static str {
683 "/upstream/authorize/{provider_id}"
684 }
685
686 fn path(&self) -> std::borrow::Cow<'static, str> {
687 format!("/upstream/authorize/{}", self.id).into()
688 }
689
690 fn query(&self) -> Option<&Self::Query> {
691 self.post_auth_action.as_ref()
692 }
693}
694
695pub struct UpstreamOAuth2Callback {
697 id: Ulid,
698}
699
700impl UpstreamOAuth2Callback {
701 #[must_use]
702 pub const fn new(id: Ulid) -> Self {
703 Self { id }
704 }
705}
706
707impl Route for UpstreamOAuth2Callback {
708 type Query = ();
709 fn route() -> &'static str {
710 "/upstream/callback/{provider_id}"
711 }
712
713 fn path(&self) -> std::borrow::Cow<'static, str> {
714 format!("/upstream/callback/{}", self.id).into()
715 }
716}
717
718pub struct UpstreamOAuth2Link {
720 id: Ulid,
721}
722
723impl UpstreamOAuth2Link {
724 #[must_use]
725 pub const fn new(id: Ulid) -> Self {
726 Self { id }
727 }
728}
729
730impl Route for UpstreamOAuth2Link {
731 type Query = ();
732 fn route() -> &'static str {
733 "/upstream/link/{link_id}"
734 }
735
736 fn path(&self) -> std::borrow::Cow<'static, str> {
737 format!("/upstream/link/{}", self.id).into()
738 }
739}
740
741pub struct UpstreamOAuth2BackchannelLogout {
743 id: Ulid,
744}
745
746impl UpstreamOAuth2BackchannelLogout {
747 #[must_use]
748 pub const fn new(id: Ulid) -> Self {
749 Self { id }
750 }
751}
752
753impl Route for UpstreamOAuth2BackchannelLogout {
754 type Query = ();
755 fn route() -> &'static str {
756 "/upstream/backchannel-logout/{provider_id}"
757 }
758
759 fn path(&self) -> std::borrow::Cow<'static, str> {
760 format!("/upstream/backchannel-logout/{}", self.id).into()
761 }
762}
763
764#[derive(Default, Serialize, Deserialize, Debug, Clone)]
766pub struct DeviceCodeLink {
767 code: Option<String>,
768}
769
770impl DeviceCodeLink {
771 #[must_use]
772 pub fn with_code(code: String) -> Self {
773 Self { code: Some(code) }
774 }
775}
776
777impl Route for DeviceCodeLink {
778 type Query = DeviceCodeLink;
779 fn route() -> &'static str {
780 "/link"
781 }
782
783 fn query(&self) -> Option<&Self::Query> {
784 Some(self)
785 }
786}
787
788#[derive(Default, Serialize, Deserialize, Debug, Clone)]
790pub struct DeviceCodeConsent {
791 id: Ulid,
792}
793
794impl Route for DeviceCodeConsent {
795 type Query = ();
796 fn route() -> &'static str {
797 "/device/{device_code_id}"
798 }
799
800 fn path(&self) -> std::borrow::Cow<'static, str> {
801 format!("/device/{}", self.id).into()
802 }
803}
804
805impl DeviceCodeConsent {
806 #[must_use]
807 pub fn new(id: Ulid) -> Self {
808 Self { id }
809 }
810}
811
812#[derive(Default, Serialize, Deserialize, Debug, Clone)]
814pub struct OAuth2DeviceAuthorizationEndpoint;
815
816impl SimpleRoute for OAuth2DeviceAuthorizationEndpoint {
817 const PATH: &'static str = "/oauth2/device";
818}
819
820#[derive(Default, Serialize, Deserialize, Debug, Clone)]
822pub struct AccountRecoveryStart;
823
824impl SimpleRoute for AccountRecoveryStart {
825 const PATH: &'static str = "/recover";
826}
827
828#[derive(Default, Serialize, Deserialize, Debug, Clone)]
830pub struct AccountRecoveryProgress {
831 session_id: Ulid,
832}
833
834impl AccountRecoveryProgress {
835 #[must_use]
836 pub fn new(session_id: Ulid) -> Self {
837 Self { session_id }
838 }
839}
840
841impl Route for AccountRecoveryProgress {
842 type Query = ();
843 fn route() -> &'static str {
844 "/recover/progress/{session_id}"
845 }
846
847 fn path(&self) -> std::borrow::Cow<'static, str> {
848 format!("/recover/progress/{}", self.session_id).into()
849 }
850}
851
852#[derive(Default, Serialize, Deserialize, Debug, Clone)]
855pub struct AccountRecoveryFinish {
856 ticket: String,
857}
858
859impl AccountRecoveryFinish {
860 #[must_use]
861 pub fn new(ticket: String) -> Self {
862 Self { ticket }
863 }
864}
865
866impl Route for AccountRecoveryFinish {
867 type Query = AccountRecoveryFinish;
868
869 fn route() -> &'static str {
870 "/account/password/recovery"
871 }
872
873 fn query(&self) -> Option<&Self::Query> {
874 Some(self)
875 }
876}
877
878pub struct StaticAsset {
880 path: String,
881}
882
883impl StaticAsset {
884 #[must_use]
885 pub fn new(path: String) -> Self {
886 Self { path }
887 }
888}
889
890impl Route for StaticAsset {
891 type Query = ();
892 fn route() -> &'static str {
893 "/assets/"
894 }
895
896 fn path(&self) -> std::borrow::Cow<'static, str> {
897 format!("/assets/{}", self.path).into()
898 }
899}
900
901pub struct GraphQL;
903
904impl SimpleRoute for GraphQL {
905 const PATH: &'static str = "/graphql";
906}
907
908pub struct ApiSpec;
910
911impl SimpleRoute for ApiSpec {
912 const PATH: &'static str = "/api/spec.json";
913}
914
915pub struct ApiDoc;
917
918impl SimpleRoute for ApiDoc {
919 const PATH: &'static str = "/api/doc/";
920}
921
922pub struct ApiDocCallback;
924
925impl SimpleRoute for ApiDocCallback {
926 const PATH: &'static str = "/api/doc/oauth2-callback";
927}