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