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