zudoku 0.43.2 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/lib/authentication/components/CallbackHandler.js +5 -1
  2. package/dist/lib/authentication/components/CallbackHandler.js.map +1 -1
  3. package/dist/lib/authentication/providers/auth0.js +1 -1
  4. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  5. package/dist/lib/authentication/providers/clerk.js +13 -5
  6. package/dist/lib/authentication/providers/clerk.js.map +1 -1
  7. package/dist/lib/authentication/providers/openid.d.ts +6 -9
  8. package/dist/lib/authentication/providers/openid.js +17 -29
  9. package/dist/lib/authentication/providers/openid.js.map +1 -1
  10. package/dist/lib/plugins/openapi/playground/QueryParams.js +1 -1
  11. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  12. package/dist/lib/util/url.d.ts +4 -0
  13. package/dist/lib/util/url.js +13 -0
  14. package/dist/lib/util/url.js.map +1 -0
  15. package/dist/lib/util/url.test.d.ts +1 -0
  16. package/dist/lib/util/url.test.js +26 -0
  17. package/dist/lib/util/url.test.js.map +1 -0
  18. package/lib/{AuthenticationPlugin-BxoEZCSJ.js → AuthenticationPlugin-BlJsiGuX.js} +2 -2
  19. package/lib/{AuthenticationPlugin-BxoEZCSJ.js.map → AuthenticationPlugin-BlJsiGuX.js.map} +1 -1
  20. package/lib/{MdxPage-DUcuusMU.js → MdxPage-DlJaCSPf.js} +3 -3
  21. package/lib/{MdxPage-DUcuusMU.js.map → MdxPage-DlJaCSPf.js.map} +1 -1
  22. package/lib/{OasProvider-CjMm8pB7.js → OasProvider-DHPC9PnR.js} +2 -2
  23. package/lib/{OasProvider-CjMm8pB7.js.map → OasProvider-DHPC9PnR.js.map} +1 -1
  24. package/lib/{OperationList-BhJcPgGi.js → OperationList-C6Ky0zQa.js} +5 -5
  25. package/lib/{OperationList-BhJcPgGi.js.map → OperationList-C6Ky0zQa.js.map} +1 -1
  26. package/lib/{Pagination-BgQxwq5j.js → Pagination-C5Fi7z_v.js} +2 -2
  27. package/lib/{Pagination-BgQxwq5j.js.map → Pagination-C5Fi7z_v.js.map} +1 -1
  28. package/lib/{SchemaList-BexhT_Z0.js → SchemaList-Cu7rWQ_k.js} +3 -3
  29. package/lib/{SchemaList-BexhT_Z0.js.map → SchemaList-Cu7rWQ_k.js.map} +1 -1
  30. package/lib/{SchemaView-Dt_-u8rW.js → SchemaView-Ci_CnNlv.js} +2 -2
  31. package/lib/{SchemaView-Dt_-u8rW.js.map → SchemaView-Ci_CnNlv.js.map} +1 -1
  32. package/lib/{circular-BWEIet3w.js → circular-P9P1oxbQ.js} +2 -2
  33. package/lib/{circular-BWEIet3w.js.map → circular-P9P1oxbQ.js.map} +1 -1
  34. package/lib/{createServer-BQD3Eeqb.js → createServer-Iclzdx0h.js} +3 -3
  35. package/lib/{createServer-BQD3Eeqb.js.map → createServer-Iclzdx0h.js.map} +1 -1
  36. package/lib/{index-CFf9AN-y.js → index-C56xKbMM.js} +7 -7
  37. package/lib/{index-CFf9AN-y.js.map → index-C56xKbMM.js.map} +1 -1
  38. package/lib/{index-DGNSSXgR.js → index-CzUOM_vE.js} +3 -3
  39. package/lib/{index-DGNSSXgR.js.map → index-CzUOM_vE.js.map} +1 -1
  40. package/lib/zudoku.auth-auth0.js +7 -7
  41. package/lib/zudoku.auth-auth0.js.map +1 -1
  42. package/lib/zudoku.auth-clerk.js +26 -26
  43. package/lib/zudoku.auth-clerk.js.map +1 -1
  44. package/lib/zudoku.auth-openid.js +407 -405
  45. package/lib/zudoku.auth-openid.js.map +1 -1
  46. package/lib/zudoku.components.js +1 -1
  47. package/lib/zudoku.plugin-api-catalog.js +1 -1
  48. package/lib/zudoku.plugin-markdown.js +1 -1
  49. package/lib/zudoku.plugin-openapi.js +1 -1
  50. package/package.json +1 -1
  51. package/src/lib/authentication/components/CallbackHandler.tsx +11 -1
  52. package/src/lib/authentication/providers/auth0.tsx +1 -1
  53. package/src/lib/authentication/providers/clerk.tsx +14 -10
  54. package/src/lib/authentication/providers/openid.tsx +24 -42
  55. package/src/lib/plugins/openapi/playground/QueryParams.tsx +1 -1
  56. package/src/lib/util/url.test.ts +51 -0
  57. package/src/lib/util/url.ts +18 -0
@@ -3,7 +3,7 @@ import "./index-DwT-v3zK.js";
3
3
  import "./chunk-BAXFHI7N-BLTsN6tl.js";
4
4
  import "./hook-8GM2HXNM.js";
5
5
  import "./SlotletProvider-wWbHYqWf.js";
6
- import { e as d, f as k, n as l, B as S, C as h, j as B, l as c, H as E, d as H, L, M, g as R, R as Z, S as f, k as g, i as y, Z as A, a as b, h as j, c as v, m as w, b as x } from "./index-DGNSSXgR.js";
6
+ import { e as d, f as k, n as l, B as S, C as h, j as B, l as c, H as E, d as H, L, M, g as R, R as Z, S as f, k as g, i as y, Z as A, b, h as j, c as v, m as w, a as x } from "./index-CzUOM_vE.js";
7
7
  import "./ui/Button.js";
8
8
  import "./ui/Callout.js";
9
9
  import "./ClientOnly-E7hGysn1.js";
@@ -3,7 +3,7 @@ import { s as h } from "./index-LNp6rxyU.js";
3
3
  import { d as b, m as x } from "./chunk-BAXFHI7N-BLTsN6tl.js";
4
4
  import { u as j, d as y, f as p } from "./hook-8GM2HXNM.js";
5
5
  import { H as v } from "./RouteGuard-D2gX29iI.js";
6
- import { L as N } from "./index-DGNSSXgR.js";
6
+ import { L as N } from "./index-CzUOM_vE.js";
7
7
  import { H as S, M as w } from "./Markdown-DvdVn1O7.js";
8
8
  const H = ({
9
9
  items: r,
@@ -53,7 +53,7 @@ const P = (e) => ({
53
53
  const u = {
54
54
  path: r,
55
55
  lazy: async () => {
56
- const { MdxPage: p } = await import("./MdxPage-DUcuusMU.js"), { default: f, ...l } = await i();
56
+ const { MdxPage: p } = await import("./MdxPage-DlJaCSPf.js"), { default: f, ...l } = await i();
57
57
  return {
58
58
  element: /* @__PURE__ */ d.jsx(
59
59
  p,
@@ -3,7 +3,7 @@ import "lucide-react";
3
3
  import "./chunk-BAXFHI7N-BLTsN6tl.js";
4
4
  import "./hook-8GM2HXNM.js";
5
5
  import "./ui/Button.js";
6
- import { z as a, U as s, A } from "./index-CFf9AN-y.js";
6
+ import { z as a, U as s, A } from "./index-C56xKbMM.js";
7
7
  export {
8
8
  a as GetSidebarOperationsQuery,
9
9
  s as UNTAGGED_PATH,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.43.2",
3
+ "version": "0.44.0",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -1,18 +1,28 @@
1
1
  import { useSuspenseQuery } from "@tanstack/react-query";
2
2
  import { Navigate } from "react-router";
3
+ import { useZudoku } from "zudoku/components";
3
4
  import { ZudokuError } from "../../util/invariant.js";
5
+ import { joinUrl } from "../../util/joinUrl.js";
6
+ import { normalizeRedirectUrl } from "../../util/url.js";
4
7
 
5
8
  export function CallbackHandler({
6
9
  handleCallback,
7
10
  }: {
8
11
  handleCallback: () => Promise<string>;
9
12
  }) {
13
+ const { options } = useZudoku();
10
14
  const executeCallback = useSuspenseQuery({
11
15
  retry: false,
12
16
  queryKey: ["oauth-callback"],
13
17
  queryFn: async () => {
14
18
  try {
15
- return await handleCallback();
19
+ return joinUrl(
20
+ normalizeRedirectUrl(
21
+ await handleCallback(),
22
+ window.location.origin,
23
+ options.basePath,
24
+ ),
25
+ );
16
26
  } catch (error) {
17
27
  throw new ZudokuError("Could not validate user", {
18
28
  cause: error,
@@ -37,7 +37,7 @@ class Auth0AuthenticationProvider extends OpenIDAuthenticationProvider {
37
37
  });
38
38
 
39
39
  const redirectUrl = new URL(window.location.origin);
40
- redirectUrl.pathname = this.logoutRedirectUrlPath;
40
+ redirectUrl.pathname = this.redirectToAfterSignOut;
41
41
 
42
42
  // SEE: https://auth0.com/docs/authenticate/login/logout/log-users-out-of-auth0
43
43
  // For Auth0 tenants created on or after 14 November 2023, RP-Initiated
@@ -50,8 +50,8 @@ const clerkAuth: AuthenticationProviderInitializer<
50
50
  > = ({
51
51
  clerkPubKey,
52
52
  redirectToAfterSignOut = "/",
53
- redirectToAfterSignUp = "/",
54
- redirectToAfterSignIn = "/",
53
+ redirectToAfterSignUp,
54
+ redirectToAfterSignIn,
55
55
  }) => {
56
56
  let clerkApi: Clerk | undefined;
57
57
  const ensureLoaded = (async () => {
@@ -129,19 +129,23 @@ const clerkAuth: AuthenticationProviderInitializer<
129
129
  signIn: async ({ redirectTo }: { redirectTo?: string } = {}) => {
130
130
  await ensureLoaded;
131
131
  await clerkApi?.redirectToSignIn({
132
- signInForceRedirectUrl:
133
- redirectTo ?? window.location.origin + redirectToAfterSignIn,
134
- signUpForceRedirectUrl:
135
- redirectTo ?? window.location.origin + redirectToAfterSignUp,
132
+ signInForceRedirectUrl: redirectToAfterSignIn
133
+ ? window.location.origin + redirectToAfterSignIn
134
+ : redirectTo,
135
+ signUpForceRedirectUrl: redirectToAfterSignUp
136
+ ? window.location.origin + redirectToAfterSignUp
137
+ : redirectTo,
136
138
  });
137
139
  },
138
140
  signUp: async ({ redirectTo }: { redirectTo?: string } = {}) => {
139
141
  await ensureLoaded;
140
142
  await clerkApi?.redirectToSignUp({
141
- signInForceRedirectUrl:
142
- redirectTo ?? window.location.origin + redirectToAfterSignIn,
143
- signUpForceRedirectUrl:
144
- redirectTo ?? window.location.origin + redirectToAfterSignUp,
143
+ signInForceRedirectUrl: redirectToAfterSignIn
144
+ ? window.location.origin + redirectToAfterSignIn
145
+ : redirectTo,
146
+ signUpForceRedirectUrl: redirectToAfterSignUp
147
+ ? window.location.origin + redirectToAfterSignUp
148
+ : redirectTo,
145
149
  });
146
150
  },
147
151
  getAuthenticationPlugin() {
@@ -23,18 +23,17 @@ export interface OpenIdProviderData {
23
23
  tokenType: string;
24
24
  }
25
25
 
26
+ export const OPENID_CALLBACK_PATH = "/oauth/callback";
27
+
26
28
  class OpenIdAuthPlugin extends AuthenticationPlugin {
27
- constructor(
28
- private callbackUrlPath: string,
29
- private handleCallback: () => Promise<string>,
30
- ) {
29
+ constructor(private handleCallback: () => Promise<string>) {
31
30
  super();
32
31
  }
33
32
  getRoutes() {
34
33
  return [
35
34
  ...super.getRoutes(),
36
35
  {
37
- path: this.callbackUrlPath,
36
+ path: OPENID_CALLBACK_PATH,
38
37
  element: (
39
38
  <ClientOnly>
40
39
  <CallbackHandler handleCallback={this.handleCallback} />
@@ -48,29 +47,28 @@ class OpenIdAuthPlugin extends AuthenticationPlugin {
48
47
  export class OpenIDAuthenticationProvider implements AuthenticationProvider {
49
48
  protected client: oauth.Client;
50
49
  protected issuer: string;
51
-
52
50
  protected authorizationServer: oauth.AuthorizationServer | undefined;
53
51
 
54
- protected absoluteCallbackUrlPath: string;
55
- protected relativeCallbackUrlPath: string;
56
- protected logoutRedirectUrlPath: string;
52
+ protected callbackUrlPath: string;
53
+
57
54
  protected onAuthorizationUrl?: (
58
55
  authorizationUrl: URL,
59
56
  options: { isSignIn: boolean; isSignUp: boolean },
60
57
  ) => void;
61
- private readonly redirectToAfterSignUp: string;
62
- private readonly redirectToAfterSignIn: string;
63
- private readonly redirectToAfterSignOut: string;
58
+
59
+ protected readonly redirectToAfterSignUp: string | undefined;
60
+ protected readonly redirectToAfterSignIn: string | undefined;
61
+ protected readonly redirectToAfterSignOut: string;
64
62
  private readonly audience?: string;
65
63
  private readonly scopes: string[];
66
- private readonly root: string;
64
+
67
65
  constructor({
68
66
  issuer,
69
67
  audience,
70
68
  clientId,
71
69
  redirectToAfterSignUp,
72
70
  redirectToAfterSignIn,
73
- redirectToAfterSignOut,
71
+ redirectToAfterSignOut = "/",
74
72
  basePath,
75
73
  scopes,
76
74
  }: OpenIDAuthenticationConfig) {
@@ -80,19 +78,13 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
80
78
  };
81
79
  this.audience = audience;
82
80
  this.issuer = issuer;
83
- this.relativeCallbackUrlPath = "/oauth/callback";
84
- this.absoluteCallbackUrlPath = joinUrl(
85
- basePath,
86
- this.relativeCallbackUrlPath,
87
- );
81
+ // This is the callback URL for the OAuth provider. So it needs the base path.
82
+ this.callbackUrlPath = joinUrl(basePath, OPENID_CALLBACK_PATH);
88
83
  this.scopes = scopes ?? ["openid", "profile", "email"];
89
84
 
90
- this.root = joinUrl(basePath, "/");
91
-
92
- this.logoutRedirectUrlPath = this.root;
93
- this.redirectToAfterSignUp = redirectToAfterSignUp ?? this.root;
94
- this.redirectToAfterSignIn = redirectToAfterSignIn ?? this.root;
95
- this.redirectToAfterSignOut = redirectToAfterSignOut ?? this.root;
85
+ this.redirectToAfterSignUp = redirectToAfterSignUp;
86
+ this.redirectToAfterSignIn = redirectToAfterSignIn;
87
+ this.redirectToAfterSignOut = redirectToAfterSignOut;
96
88
  }
97
89
 
98
90
  protected async getAuthServer() {
@@ -138,14 +130,14 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
138
130
 
139
131
  async signUp({ redirectTo }: { redirectTo?: string } = {}) {
140
132
  return this.authorize({
141
- redirectTo: redirectTo ?? this.redirectToAfterSignUp,
133
+ redirectTo: this.redirectToAfterSignUp ?? redirectTo ?? "/",
142
134
  isSignUp: true,
143
135
  });
144
136
  }
145
137
 
146
138
  async signIn({ redirectTo }: { redirectTo?: string } = {}) {
147
139
  return this.authorize({
148
- redirectTo: redirectTo ?? this.redirectToAfterSignIn,
140
+ redirectTo: this.redirectToAfterSignIn ?? redirectTo ?? "/",
149
141
  });
150
142
  }
151
143
 
@@ -178,17 +170,10 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
178
170
  authorizationServer.authorization_endpoint,
179
171
  );
180
172
 
181
- const finalRedirect = redirectTo.startsWith(window.location.origin)
182
- ? this.root !== "/" &&
183
- redirectTo.startsWith(window.location.origin + this.root)
184
- ? redirectTo.slice(window.location.origin.length + this.root.length)
185
- : redirectTo.slice(window.location.origin.length)
186
- : redirectTo;
187
-
188
- sessionStorage.setItem("redirect-to", finalRedirect);
173
+ sessionStorage.setItem("redirect-to", redirectTo);
189
174
 
190
175
  const redirectUrl = new URL(window.location.origin);
191
- redirectUrl.pathname = this.absoluteCallbackUrlPath;
176
+ redirectUrl.pathname = this.callbackUrlPath;
192
177
  redirectUrl.search = "";
193
178
 
194
179
  authorizationUrl.searchParams.set("client_id", this.client.client_id);
@@ -282,7 +267,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
282
267
  const redirectUrl = new URL(
283
268
  window.location.origin + this.redirectToAfterSignOut,
284
269
  );
285
- redirectUrl.pathname = this.logoutRedirectUrlPath;
270
+ redirectUrl.pathname = this.callbackUrlPath;
286
271
 
287
272
  let logoutUrl: URL;
288
273
  // The endSessionEndpoint is set, the IdP supports some form of logout,
@@ -338,7 +323,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
338
323
  }
339
324
 
340
325
  const redirectUrl = new URL(url);
341
- redirectUrl.pathname = this.absoluteCallbackUrlPath;
326
+ redirectUrl.pathname = this.callbackUrlPath;
342
327
  redirectUrl.search = "";
343
328
 
344
329
  const response = await oauth.authorizationCodeGrantRequest(
@@ -396,10 +381,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
396
381
  getAuthenticationPlugin() {
397
382
  // TODO: This API is a bit messy, we need to refactor auth plugins/providers
398
383
  // to remove the extra layers of abstraction.
399
- return new OpenIdAuthPlugin(
400
- this.relativeCallbackUrlPath,
401
- this.handleCallback,
402
- );
384
+ return new OpenIdAuthPlugin(this.handleCallback);
403
385
  }
404
386
  }
405
387
 
@@ -111,7 +111,7 @@ export const QueryParams = ({
111
111
  field.onChange(e);
112
112
  form.setValue(`queryParams.${i}.active`, true);
113
113
  }}
114
- className="font-mono text-xs border-0 ring-1 ring-ring"
114
+ className="font-mono text-xs border-0 shadow-none"
115
115
  />
116
116
  );
117
117
  }}
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { normalizeRedirectUrl } from "./url.js";
3
+
4
+ describe("normalizeRedirectUrl", () => {
5
+ const mockOrigin = "https://example.com";
6
+
7
+ it("should return original URL if it doesn't start with origin", () => {
8
+ const result = normalizeRedirectUrl(
9
+ "https://other.com/path",
10
+ mockOrigin,
11
+ "/",
12
+ );
13
+ expect(result).toBe("https://other.com/path");
14
+ });
15
+
16
+ it("should remove origin when root is /", () => {
17
+ const result = normalizeRedirectUrl(
18
+ "https://example.com/some/path",
19
+ mockOrigin,
20
+ "/",
21
+ );
22
+ expect(result).toBe("/some/path");
23
+ });
24
+
25
+ it("should remove origin and root path when root is not /", () => {
26
+ const result = normalizeRedirectUrl(
27
+ "https://example.com/docs/some/path",
28
+ mockOrigin,
29
+ "/docs",
30
+ );
31
+ expect(result).toBe("/some/path");
32
+ });
33
+
34
+ it("should only remove origin when URL doesn't match root path", () => {
35
+ const result = normalizeRedirectUrl(
36
+ "https://example.com/other/path",
37
+ mockOrigin,
38
+ "/docs",
39
+ );
40
+ expect(result).toBe("/other/path");
41
+ });
42
+
43
+ it("should handle empty root path", () => {
44
+ const result = normalizeRedirectUrl(
45
+ "https://example.com/path",
46
+ mockOrigin,
47
+ "",
48
+ );
49
+ expect(result).toBe("/path");
50
+ });
51
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Normalizes a redirect URL by removing the origin and optionally the root path
3
+ */
4
+ export function normalizeRedirectUrl(
5
+ redirectTo: string,
6
+ origin: string,
7
+ basePath: string = "/",
8
+ ): string {
9
+ if (!redirectTo.startsWith(origin)) {
10
+ return redirectTo;
11
+ }
12
+
13
+ if (basePath !== "/" && redirectTo.startsWith(origin + basePath)) {
14
+ return redirectTo.slice(origin.length + basePath.length);
15
+ }
16
+
17
+ return redirectTo.slice(origin.length);
18
+ }