zudoku 0.46.3 → 0.47.1
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.
- package/dist/app/main.js +2 -2
- package/dist/app/main.js.map +1 -1
- package/dist/config/config.d.ts +11 -0
- package/dist/config/validators/InputSidebarSchema.d.ts +1 -1
- package/dist/config/validators/validate.d.ts +167 -47
- package/dist/config/validators/validate.js +22 -1
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/config/validators/validate.test.d.ts +1 -0
- package/dist/config/validators/validate.test.js +80 -0
- package/dist/config/validators/validate.test.js.map +1 -0
- package/dist/lib/auth/issuer.d.ts +2 -0
- package/dist/lib/auth/issuer.js +37 -0
- package/dist/lib/auth/issuer.js.map +1 -0
- package/dist/lib/auth/issuer.test.d.ts +1 -0
- package/dist/lib/auth/issuer.test.js +94 -0
- package/dist/lib/auth/issuer.test.js.map +1 -0
- package/dist/lib/authentication/hook.d.ts +6 -0
- package/dist/lib/authentication/providers/auth0.js +1 -1
- package/dist/lib/authentication/providers/auth0.js.map +1 -1
- package/dist/lib/authentication/providers/azureb2c.d.ts +28 -0
- package/dist/lib/authentication/providers/azureb2c.js +145 -0
- package/dist/lib/authentication/providers/azureb2c.js.map +1 -0
- package/dist/lib/authentication/providers/clerk.js +3 -12
- package/dist/lib/authentication/providers/clerk.js.map +1 -1
- package/dist/lib/authentication/providers/openid.d.ts +1 -0
- package/dist/lib/authentication/providers/openid.js +38 -0
- package/dist/lib/authentication/providers/openid.js.map +1 -1
- package/dist/lib/authentication/providers/supabase.js +2 -9
- package/dist/lib/authentication/providers/supabase.js.map +1 -1
- package/dist/lib/authentication/state.d.ts +6 -5
- package/dist/lib/authentication/state.js +19 -6
- package/dist/lib/authentication/state.js.map +1 -1
- package/dist/lib/components/context/ZudokuProvider.d.ts +1 -1
- package/dist/lib/components/index.d.ts +6 -0
- package/dist/lib/hooks/index.d.ts +6 -0
- package/dist/lib/plugins/api-keys/index.js +1 -3
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.d.ts +1 -1
- package/dist/lib/plugins/openapi/playground/Headers.d.ts +2 -2
- package/dist/lib/plugins/openapi/playground/fileUtils.js +1 -1
- package/dist/lib/plugins/openapi/playground/fileUtils.js.map +1 -1
- package/dist/lib/util/MdxComponents.js +1 -1
- package/dist/lib/util/MdxComponents.js.map +1 -1
- package/dist/vite/build.js +1 -28
- package/dist/vite/build.js.map +1 -1
- package/lib/Drawer-BzkOKwgC.js.map +1 -1
- package/lib/{Markdown-D81l28Ib.js → Markdown-r4buN85T.js} +37 -37
- package/lib/{Markdown-D81l28Ib.js.map → Markdown-r4buN85T.js.map} +1 -1
- package/lib/{MdxPage-S_CxlNmX.js → MdxPage-DYKsTerz.js} +4 -4
- package/lib/{MdxPage-S_CxlNmX.js.map → MdxPage-DYKsTerz.js.map} +1 -1
- package/lib/{OasProvider-D5rt6WMb.js → OasProvider-8vNiLpIG.js} +2 -2
- package/lib/{OasProvider-D5rt6WMb.js.map → OasProvider-8vNiLpIG.js.map} +1 -1
- package/lib/{OperationList-CNhg654C.js → OperationList-BCVHtZNK.js} +6 -6
- package/lib/OperationList-BCVHtZNK.js.map +1 -0
- package/lib/{RouteGuard-CZ_uLv3g.js → RouteGuard-B7GVW4oL.js} +2 -2
- package/lib/{RouteGuard-CZ_uLv3g.js.map → RouteGuard-B7GVW4oL.js.map} +1 -1
- package/lib/{SchemaList-BvzCrTYg.js → SchemaList-1oJKvBxh.js} +6 -6
- package/lib/{SchemaList-BvzCrTYg.js.map → SchemaList-1oJKvBxh.js.map} +1 -1
- package/lib/{SchemaView-Br1RupCp.js → SchemaView-CTqaB-79.js} +3 -3
- package/lib/{SchemaView-Br1RupCp.js.map → SchemaView-CTqaB-79.js.map} +1 -1
- package/lib/{SignUp-B-1Pvc-8.js → SignUp-CRIKdWt9.js} +2 -2
- package/lib/{SignUp-B-1Pvc-8.js.map → SignUp-CRIKdWt9.js.map} +1 -1
- package/lib/{Slot-T8NJUkm4.js → Slot-B5qSAnwR.js} +4 -4
- package/lib/{Slot-T8NJUkm4.js.map → Slot-B5qSAnwR.js.map} +1 -1
- package/lib/{SyntaxHighlight-Cz6Me7-F.js → SyntaxHighlight-CqKHkyEy.js} +1291 -1265
- package/lib/SyntaxHighlight-CqKHkyEy.js.map +1 -0
- package/lib/{Toc-PA-j0gEu.js → Toc-lxYQEOzX.js} +2 -2
- package/lib/{Toc-PA-j0gEu.js.map → Toc-lxYQEOzX.js.map} +1 -1
- package/lib/{circular-5FeDWJOn.js → circular-ZGGPtwMq.js} +2 -2
- package/lib/{circular-5FeDWJOn.js.map → circular-ZGGPtwMq.js.map} +1 -1
- package/lib/clerk-yAKDC3Qz.js +24812 -0
- package/lib/clerk-yAKDC3Qz.js.map +1 -0
- package/lib/{createServer-BC2RZgmW.js → createServer-DUBpXfvA.js} +2627 -2617
- package/lib/createServer-DUBpXfvA.js.map +1 -0
- package/lib/errors-D27ZTQgx.js +78 -0
- package/lib/errors-D27ZTQgx.js.map +1 -0
- package/lib/{hook-k7PfUIsj.js → hook-7wZANGJP.js} +53 -35
- package/lib/{hook-k7PfUIsj.js.map → hook-7wZANGJP.js.map} +1 -1
- package/lib/index-CrcNWbel.js.map +1 -1
- package/lib/{index-CJZthJSj.js → index-Cucjfk3D.js} +10 -10
- package/lib/index-Cucjfk3D.js.map +1 -0
- package/lib/{index-zddirpDj.js → index-DmNq2fbN.js} +226 -221
- package/lib/index-DmNq2fbN.js.map +1 -0
- package/lib/{mutation-BSeQ8pEK.js → mutation-C1XCQTQL.js} +2 -2
- package/lib/{mutation-BSeQ8pEK.js.map → mutation-C1XCQTQL.js.map} +1 -1
- package/lib/ui/SyntaxHighlight.js +2 -2
- package/lib/{useMutation-CZSmsIGW.js → useMutation-BKvPttRn.js} +3 -3
- package/lib/{useMutation-CZSmsIGW.js.map → useMutation-BKvPttRn.js.map} +1 -1
- package/lib/zudoku.auth-auth0.js +2 -2
- package/lib/zudoku.auth-auth0.js.map +1 -1
- package/lib/zudoku.auth-azureb2c.js +9974 -0
- package/lib/zudoku.auth-azureb2c.js.map +1 -0
- package/lib/zudoku.auth-clerk.js +39 -48
- package/lib/zudoku.auth-clerk.js.map +1 -1
- package/lib/zudoku.auth-openid.js +291 -316
- package/lib/zudoku.auth-openid.js.map +1 -1
- package/lib/zudoku.components.js +1222 -1594
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.hooks.js +2 -2
- package/lib/zudoku.plugin-api-catalog.js +2 -2
- package/lib/zudoku.plugin-api-keys.js +19 -21
- package/lib/zudoku.plugin-api-keys.js.map +1 -1
- package/lib/zudoku.plugin-custom-pages.js +1 -1
- package/lib/zudoku.plugin-markdown.js +1 -1
- package/lib/zudoku.plugin-openapi.js +2 -2
- package/lib/zudoku.plugin-search-pagefind.js +2 -2
- package/package.json +40 -22
- package/src/app/main.tsx +2 -2
- package/src/lib/auth/issuer.test.ts +120 -0
- package/src/lib/auth/issuer.ts +41 -0
- package/src/lib/authentication/providers/auth0.tsx +1 -1
- package/src/lib/authentication/providers/azureb2c.tsx +196 -0
- package/src/lib/authentication/providers/clerk.tsx +3 -12
- package/src/lib/authentication/providers/openid.tsx +53 -0
- package/src/lib/authentication/providers/supabase.tsx +2 -9
- package/src/lib/authentication/state.ts +37 -7
- package/src/lib/components/context/ZudokuProvider.tsx +1 -1
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +1 -1
- package/src/lib/plugins/api-keys/index.tsx +1 -3
- package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +1 -1
- package/src/lib/plugins/openapi/playground/Headers.tsx +2 -2
- package/src/lib/plugins/openapi/playground/fileUtils.ts +1 -1
- package/src/lib/util/MdxComponents.tsx +1 -1
- package/lib/OperationList-CNhg654C.js.map +0 -1
- package/lib/SyntaxHighlight-Cz6Me7-F.js.map +0 -1
- package/lib/createServer-BC2RZgmW.js.map +0 -1
- package/lib/index-CJZthJSj.js.map +0 -1
- package/lib/index-zddirpDj.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zudoku",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://zudoku.dev",
|
|
6
6
|
"repository": {
|
|
@@ -36,10 +36,6 @@
|
|
|
36
36
|
"import": "./lib/ui/*.js",
|
|
37
37
|
"types": "./dist/lib/ui/*.d.ts"
|
|
38
38
|
},
|
|
39
|
-
"./hooks": {
|
|
40
|
-
"import": "./lib/hooks/index.js",
|
|
41
|
-
"types": "./dist/lib/hooks/index.d.ts"
|
|
42
|
-
},
|
|
43
39
|
"./client": {
|
|
44
40
|
"types": "./client.d.ts"
|
|
45
41
|
},
|
|
@@ -59,6 +55,10 @@
|
|
|
59
55
|
"import": "./lib/zudoku.auth-supabase.js",
|
|
60
56
|
"types": "./dist/lib/authentication/providers/supabase.d.ts"
|
|
61
57
|
},
|
|
58
|
+
"./auth/azureb2c": {
|
|
59
|
+
"import": "./lib/zudoku.auth-azureb2c.js",
|
|
60
|
+
"types": "./dist/lib/authentication/providers/azureb2c.d.ts"
|
|
61
|
+
},
|
|
62
62
|
"./plugins": {
|
|
63
63
|
"import": "./lib/zudoku.plugins.js",
|
|
64
64
|
"types": "./dist/lib/core/plugins.d.ts"
|
|
@@ -95,6 +95,10 @@
|
|
|
95
95
|
"import": "./lib/zudoku.plugin-search-pagefind.js",
|
|
96
96
|
"types": "./dist/lib/plugins/search-pagefind/index.d.ts"
|
|
97
97
|
},
|
|
98
|
+
"./hooks": {
|
|
99
|
+
"import": "./lib/zudoku.hooks.js",
|
|
100
|
+
"types": "./dist/lib/hooks/index.d.ts"
|
|
101
|
+
},
|
|
98
102
|
"./components": {
|
|
99
103
|
"import": "./lib/zudoku.components.js",
|
|
100
104
|
"types": "./dist/lib/components/index.d.ts"
|
|
@@ -159,11 +163,11 @@
|
|
|
159
163
|
"@radix-ui/react-tooltip": "1.2.7",
|
|
160
164
|
"@radix-ui/react-visually-hidden": "1.2.3",
|
|
161
165
|
"@scalar/openapi-parser": "0.10.17",
|
|
162
|
-
"@sentry/node": "9.
|
|
163
|
-
"@shikijs/langs": "3.
|
|
164
|
-
"@shikijs/rehype": "3.
|
|
165
|
-
"@shikijs/themes": "3.
|
|
166
|
-
"@shikijs/transformers": "3.
|
|
166
|
+
"@sentry/node": "9.26.0",
|
|
167
|
+
"@shikijs/langs": "3.5.0",
|
|
168
|
+
"@shikijs/rehype": "3.5.0",
|
|
169
|
+
"@shikijs/themes": "3.5.0",
|
|
170
|
+
"@shikijs/transformers": "3.5.0",
|
|
167
171
|
"@sindresorhus/slugify": "2.2.1",
|
|
168
172
|
"@stefanprobst/rehype-extract-toc": "3.0.0",
|
|
169
173
|
"@tailwindcss/typography": "0.5.16",
|
|
@@ -171,7 +175,7 @@
|
|
|
171
175
|
"@tanem/react-nprogress": "5.0.55",
|
|
172
176
|
"@tanstack/react-query": "5.74.3",
|
|
173
177
|
"@types/react": "19.1.1",
|
|
174
|
-
"@types/react-dom": "19.1.
|
|
178
|
+
"@types/react-dom": "19.1.6",
|
|
175
179
|
"@vitejs/plugin-react": "4.4.1",
|
|
176
180
|
"@zudoku/httpsnippet": "10.0.9",
|
|
177
181
|
"@zudoku/react-helmet-async": "2.0.5",
|
|
@@ -183,14 +187,14 @@
|
|
|
183
187
|
"devlop": "^1.1.0",
|
|
184
188
|
"dotenv": "16.5.0",
|
|
185
189
|
"embla-carousel-react": "8.6.0",
|
|
186
|
-
"estree-util-value-to-estree": "3.
|
|
190
|
+
"estree-util-value-to-estree": "3.4.0",
|
|
187
191
|
"express": "5.1.0",
|
|
188
192
|
"fast-equals": "5.2.2",
|
|
189
193
|
"framer-motion": "^12.12.2",
|
|
190
|
-
"glob": "11.0.
|
|
194
|
+
"glob": "11.0.2",
|
|
191
195
|
"graphql": "16.11.0",
|
|
192
196
|
"graphql-type-json": "0.3.2",
|
|
193
|
-
"graphql-yoga": "5.13.
|
|
197
|
+
"graphql-yoga": "5.13.5",
|
|
194
198
|
"gray-matter": "4.0.3",
|
|
195
199
|
"hast-util-to-jsx-runtime": "^2.3.6",
|
|
196
200
|
"hast-util-to-string": "3.0.1",
|
|
@@ -227,7 +231,7 @@
|
|
|
227
231
|
"remark-rehype": "^11.1.2",
|
|
228
232
|
"rollup": "4.41.1",
|
|
229
233
|
"semver": "7.7.1",
|
|
230
|
-
"shiki": "3.
|
|
234
|
+
"shiki": "3.5.0",
|
|
231
235
|
"sitemap": "8.0.0",
|
|
232
236
|
"spin-delay": "2.0.1",
|
|
233
237
|
"strip-ansi": "7.1.0",
|
|
@@ -239,11 +243,11 @@
|
|
|
239
243
|
"vaul": "1.1.2",
|
|
240
244
|
"vfile": "6.0.3",
|
|
241
245
|
"vite": "6.3.5",
|
|
242
|
-
"yaml": "2.
|
|
246
|
+
"yaml": "2.8.0",
|
|
243
247
|
"yargs": "17.7.2",
|
|
244
|
-
"zod": "3.
|
|
248
|
+
"zod": "3.25.51",
|
|
245
249
|
"zod-validation-error": "3.4.1",
|
|
246
|
-
"zustand": "5.0.
|
|
250
|
+
"zustand": "5.0.5"
|
|
247
251
|
},
|
|
248
252
|
"devDependencies": {
|
|
249
253
|
"@graphql-codegen/cli": "5.0.6",
|
|
@@ -264,7 +268,7 @@
|
|
|
264
268
|
"@types/semver": "7.7.0",
|
|
265
269
|
"@types/unist": "^3.0.3",
|
|
266
270
|
"@types/yargs": "17.0.33",
|
|
267
|
-
"@vitest/coverage-v8": "3.
|
|
271
|
+
"@vitest/coverage-v8": "3.2.1",
|
|
268
272
|
"esbuild": "0.25.1",
|
|
269
273
|
"happy-dom": "17.5.6",
|
|
270
274
|
"mdast-util-mdx": "3.0.0",
|
|
@@ -276,13 +280,27 @@
|
|
|
276
280
|
},
|
|
277
281
|
"peerDependencies": {
|
|
278
282
|
"react": ">=19",
|
|
279
|
-
"react-dom": ">=19"
|
|
280
|
-
|
|
281
|
-
"optionalDependencies": {
|
|
283
|
+
"react-dom": ">=19",
|
|
284
|
+
"@azure/msal-browser": "^4.13.0",
|
|
282
285
|
"@clerk/clerk-js": "^5.63.1",
|
|
283
286
|
"@sentry/react": "^9.12.0",
|
|
284
287
|
"@supabase/supabase-js": "^2.49.4"
|
|
285
288
|
},
|
|
289
|
+
"optionalDependencies": {},
|
|
290
|
+
"peerDependenciesMeta": {
|
|
291
|
+
"@azure/msal-browser": {
|
|
292
|
+
"optional": true
|
|
293
|
+
},
|
|
294
|
+
"@clerk/clerk-js": {
|
|
295
|
+
"optional": true
|
|
296
|
+
},
|
|
297
|
+
"@sentry/react": {
|
|
298
|
+
"optional": true
|
|
299
|
+
},
|
|
300
|
+
"@supabase/supabase-js": {
|
|
301
|
+
"optional": true
|
|
302
|
+
}
|
|
303
|
+
},
|
|
286
304
|
"scripts": {
|
|
287
305
|
"build": "tsc --project tsconfig.app.json",
|
|
288
306
|
"build:dev": "esbuild './src/**/*.ts' --format=esm --platform=node --target=node22 --outdir=dist --splitting --log-level=warning",
|
package/src/app/main.tsx
CHANGED
|
@@ -71,7 +71,7 @@ export const convertZudokuConfigToOptions = (
|
|
|
71
71
|
|
|
72
72
|
export const getRoutesByOptions = (
|
|
73
73
|
options: ZudokuContextOptions,
|
|
74
|
-
enableStatusPages =
|
|
74
|
+
enableStatusPages = true,
|
|
75
75
|
) => {
|
|
76
76
|
const allPlugins = [
|
|
77
77
|
...(options.plugins ?? []),
|
|
@@ -84,7 +84,7 @@ export const getRoutesByOptions = (
|
|
|
84
84
|
enableStatusPages
|
|
85
85
|
? [400, 403, 404, 405, 414, 416, 500, 501, 502, 503, 504].map(
|
|
86
86
|
(statusCode) => ({
|
|
87
|
-
path:
|
|
87
|
+
path: `/${statusCode}`,
|
|
88
88
|
element: <StatusPage statusCode={statusCode} />,
|
|
89
89
|
}),
|
|
90
90
|
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { ZudokuConfig } from "../../config/validators/validate.js";
|
|
3
|
+
import { getIssuer } from "./issuer.js";
|
|
4
|
+
|
|
5
|
+
describe("getIssuer", () => {
|
|
6
|
+
it("should return clerk frontend API for clerk authentication", async () => {
|
|
7
|
+
// Using a valid base64 encoded string: "example.example$test" -> "ZXhhbXBsZS5leGFtcGxlJHRlc3Q="
|
|
8
|
+
const config: ZudokuConfig = {
|
|
9
|
+
authentication: {
|
|
10
|
+
type: "clerk",
|
|
11
|
+
clerkPubKey:
|
|
12
|
+
"pk_test_dG9sZXJhbnQtaG9ybmV0LTQ2LmNsZXJrLmFjY291bnRzLmRldiQ",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const result = await getIssuer(config);
|
|
17
|
+
expect(result).toBe("tolerant-hornet-46.clerk.accounts.dev");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should throw error for invalid clerk public key format", async () => {
|
|
21
|
+
const config: ZudokuConfig = {
|
|
22
|
+
authentication: {
|
|
23
|
+
type: "clerk",
|
|
24
|
+
clerkPubKey: "pk_test_invalid",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await expect(getIssuer(config)).rejects.toThrow(
|
|
29
|
+
"Clerk public key is invalid",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should throw error for clerk key with invalid base64", async () => {
|
|
34
|
+
const config: ZudokuConfig = {
|
|
35
|
+
authentication: {
|
|
36
|
+
type: "clerk",
|
|
37
|
+
clerkPubKey: "pk_test_invalid_base64",
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
await expect(getIssuer(config)).rejects.toThrow(
|
|
42
|
+
"Clerk public key is invalid",
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return auth0 issuer URL for auth0 authentication", async () => {
|
|
47
|
+
const config: ZudokuConfig = {
|
|
48
|
+
authentication: {
|
|
49
|
+
type: "auth0",
|
|
50
|
+
domain: "example.auth0.com",
|
|
51
|
+
clientId: "test-client-id",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const result = await getIssuer(config);
|
|
56
|
+
expect(result).toBe("https://example.auth0.com/");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should return openid issuer for openid authentication", async () => {
|
|
60
|
+
const config: ZudokuConfig = {
|
|
61
|
+
authentication: {
|
|
62
|
+
type: "openid",
|
|
63
|
+
issuer: "https://example.com/auth",
|
|
64
|
+
clientId: "test-client-id",
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const result = await getIssuer(config);
|
|
69
|
+
expect(result).toBe("https://example.com/auth");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should return azureb2c issuer for azureb2c authentication", async () => {
|
|
73
|
+
const config: ZudokuConfig = {
|
|
74
|
+
authentication: {
|
|
75
|
+
type: "azureb2c",
|
|
76
|
+
tenantName: "example",
|
|
77
|
+
policyName: "B2C_1_SignUpSignIn",
|
|
78
|
+
issuer: "https://example.b2clogin.com/example.onmicrosoft.com/v2.0/",
|
|
79
|
+
clientId: "test-client-id",
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
const result = await getIssuer(config);
|
|
83
|
+
expect(result).toBe(
|
|
84
|
+
"https://example.b2clogin.com/example.onmicrosoft.com/v2.0/",
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should return supabase URL for supabase authentication", async () => {
|
|
89
|
+
const config: ZudokuConfig = {
|
|
90
|
+
authentication: {
|
|
91
|
+
type: "supabase",
|
|
92
|
+
supabaseUrl: "https://project.supabase.co",
|
|
93
|
+
supabaseKey: "test-anon-key",
|
|
94
|
+
provider: "github",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const result = await getIssuer(config);
|
|
99
|
+
expect(result).toBe("https://project.supabase.co");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should return undefined for no authentication", async () => {
|
|
103
|
+
const config: ZudokuConfig = {};
|
|
104
|
+
|
|
105
|
+
const result = await getIssuer(config);
|
|
106
|
+
expect(result).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should throw error for unsupported authentication type", async () => {
|
|
110
|
+
const config = {
|
|
111
|
+
authentication: {
|
|
112
|
+
type: "unsupported" as any,
|
|
113
|
+
},
|
|
114
|
+
} as ZudokuConfig;
|
|
115
|
+
|
|
116
|
+
await expect(getIssuer(config)).rejects.toThrow(
|
|
117
|
+
"Unsupported authentication type",
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ZudokuConfig } from "../../config/validators/validate.js";
|
|
2
|
+
import invariant from "../util/invariant.js";
|
|
3
|
+
|
|
4
|
+
export const getIssuer = async (config: ZudokuConfig) => {
|
|
5
|
+
switch (config.authentication?.type) {
|
|
6
|
+
case "clerk": {
|
|
7
|
+
const frontendApiEncoded = config.authentication.clerkPubKey
|
|
8
|
+
.split("_")
|
|
9
|
+
.at(-1);
|
|
10
|
+
invariant(frontendApiEncoded, "Clerk public key is invalid");
|
|
11
|
+
const frontendApiParts = atob(frontendApiEncoded).split("$");
|
|
12
|
+
|
|
13
|
+
if (frontendApiParts.length !== 2) {
|
|
14
|
+
throw new Error("Clerk public key is invalid");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const frontendApi = frontendApiParts.at(0);
|
|
18
|
+
invariant(frontendApi, "Clerk public key is invalid");
|
|
19
|
+
|
|
20
|
+
return frontendApi;
|
|
21
|
+
}
|
|
22
|
+
case "auth0": {
|
|
23
|
+
return `https://${config.authentication.domain}/`;
|
|
24
|
+
}
|
|
25
|
+
case "openid": {
|
|
26
|
+
return config.authentication.issuer;
|
|
27
|
+
}
|
|
28
|
+
case "supabase": {
|
|
29
|
+
return config.authentication.supabaseUrl;
|
|
30
|
+
}
|
|
31
|
+
case "azureb2c": {
|
|
32
|
+
return config.authentication.issuer;
|
|
33
|
+
}
|
|
34
|
+
case undefined: {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
default: {
|
|
38
|
+
throw new Error(`Unsupported authentication type`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { AuthenticationResult, EventMessage } from "@azure/msal-browser";
|
|
2
|
+
import { EventType, PublicClientApplication } from "@azure/msal-browser";
|
|
3
|
+
import { type AzureB2CAuthenticationConfig } from "../../../config/config.js";
|
|
4
|
+
import { ClientOnly } from "../../components/ClientOnly.js";
|
|
5
|
+
import { joinUrl } from "../../util/joinUrl.js";
|
|
6
|
+
import {
|
|
7
|
+
type AuthenticationPlugin,
|
|
8
|
+
type AuthenticationProviderInitializer,
|
|
9
|
+
} from "../authentication.js";
|
|
10
|
+
import { CallbackHandler } from "../components/CallbackHandler.js";
|
|
11
|
+
import { AuthorizationError } from "../errors.js";
|
|
12
|
+
import { useAuthState } from "../state.js";
|
|
13
|
+
|
|
14
|
+
import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
|
|
15
|
+
|
|
16
|
+
const AZUREB2C_CALLBACK_PATH = "/oauth/callback";
|
|
17
|
+
|
|
18
|
+
export class AzureB2CAuthPlugin
|
|
19
|
+
extends CoreAuthenticationPlugin
|
|
20
|
+
implements AuthenticationPlugin
|
|
21
|
+
{
|
|
22
|
+
private msalInstance: PublicClientApplication;
|
|
23
|
+
private readonly scopes: string[];
|
|
24
|
+
private readonly redirectToAfterSignUp?: string;
|
|
25
|
+
private readonly redirectToAfterSignIn?: string;
|
|
26
|
+
private readonly redirectToAfterSignOut: string;
|
|
27
|
+
|
|
28
|
+
constructor({
|
|
29
|
+
clientId,
|
|
30
|
+
tenantName,
|
|
31
|
+
policyName,
|
|
32
|
+
scopes,
|
|
33
|
+
redirectToAfterSignUp,
|
|
34
|
+
redirectToAfterSignIn,
|
|
35
|
+
redirectToAfterSignOut = "/",
|
|
36
|
+
basePath = "",
|
|
37
|
+
}: AzureB2CAuthenticationConfig) {
|
|
38
|
+
super();
|
|
39
|
+
this.scopes = scopes ?? ["openid", "profile", "email"];
|
|
40
|
+
this.redirectToAfterSignUp = redirectToAfterSignUp;
|
|
41
|
+
this.redirectToAfterSignIn = redirectToAfterSignIn;
|
|
42
|
+
this.redirectToAfterSignOut = redirectToAfterSignOut;
|
|
43
|
+
|
|
44
|
+
const authority = `https://${tenantName}.b2clogin.com/${tenantName}.onmicrosoft.com/${policyName}`;
|
|
45
|
+
const redirectUri = joinUrl(basePath, AZUREB2C_CALLBACK_PATH);
|
|
46
|
+
|
|
47
|
+
this.msalInstance = new PublicClientApplication({
|
|
48
|
+
auth: {
|
|
49
|
+
clientId,
|
|
50
|
+
authority,
|
|
51
|
+
redirectUri,
|
|
52
|
+
knownAuthorities: [`${tenantName}.b2clogin.com`],
|
|
53
|
+
},
|
|
54
|
+
cache: {
|
|
55
|
+
cacheLocation: "sessionStorage",
|
|
56
|
+
storeAuthStateInCookie: false,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
void this.msalInstance.initialize().then(async () => {
|
|
61
|
+
void this.msalInstance
|
|
62
|
+
.handleRedirectPromise()
|
|
63
|
+
.then((response: AuthenticationResult | null) => {
|
|
64
|
+
if (response) {
|
|
65
|
+
this.handleAuthResponse(response);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Add event callback
|
|
70
|
+
void this.msalInstance.addEventCallback((event: EventMessage) => {
|
|
71
|
+
if (event.eventType === EventType.LOGIN_SUCCESS) {
|
|
72
|
+
this.handleAuthResponse(event.payload as AuthenticationResult);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private handleAuthResponse(response: AuthenticationResult) {
|
|
79
|
+
const { accessToken, idToken, scopes, account } = response;
|
|
80
|
+
|
|
81
|
+
if (!account) {
|
|
82
|
+
throw new AuthorizationError("No account information in response");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Get the user's name from Azure B2C claims
|
|
86
|
+
const name =
|
|
87
|
+
[account.idTokenClaims?.given_name, account.idTokenClaims?.family_name]
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.join(" ") || account.username;
|
|
90
|
+
|
|
91
|
+
useAuthState.getState().setLoggedIn({
|
|
92
|
+
providerData: {
|
|
93
|
+
accessToken,
|
|
94
|
+
idToken,
|
|
95
|
+
scopes,
|
|
96
|
+
account,
|
|
97
|
+
},
|
|
98
|
+
profile: {
|
|
99
|
+
sub: account.localAccountId,
|
|
100
|
+
email: account.username,
|
|
101
|
+
name,
|
|
102
|
+
emailVerified: true, // Azure B2C emails are verified
|
|
103
|
+
pictureUrl: undefined, // Azure B2C doesn't provide profile pictures by default
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async signUp({ redirectTo }: { redirectTo?: string } = {}) {
|
|
109
|
+
const redirectUri = this.redirectToAfterSignUp ?? redirectTo ?? "/";
|
|
110
|
+
sessionStorage.setItem("redirect-to", redirectUri);
|
|
111
|
+
|
|
112
|
+
await this.msalInstance.loginRedirect({
|
|
113
|
+
scopes: this.scopes,
|
|
114
|
+
prompt: "select_account",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async signIn({ redirectTo }: { redirectTo?: string } = {}) {
|
|
119
|
+
const redirectUri = this.redirectToAfterSignIn ?? redirectTo ?? "/";
|
|
120
|
+
sessionStorage.setItem("redirect-to", redirectUri);
|
|
121
|
+
|
|
122
|
+
await this.msalInstance.loginRedirect({
|
|
123
|
+
scopes: this.scopes,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async getAccessToken(): Promise<string> {
|
|
128
|
+
const account = this.msalInstance.getAllAccounts()[0];
|
|
129
|
+
if (!account) {
|
|
130
|
+
throw new AuthorizationError("No active account");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const response = await this.msalInstance.acquireTokenSilent({
|
|
135
|
+
scopes: this.scopes,
|
|
136
|
+
account,
|
|
137
|
+
});
|
|
138
|
+
return response.accessToken;
|
|
139
|
+
} catch {
|
|
140
|
+
// If silent token acquisition fails, try interactive
|
|
141
|
+
await this.msalInstance.acquireTokenRedirect({
|
|
142
|
+
scopes: this.scopes,
|
|
143
|
+
account,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
throw new AuthorizationError(
|
|
147
|
+
"Token acquisition failed after interactive attempt",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
signRequest = async (request: Request): Promise<Request> => {
|
|
153
|
+
const accessToken = await this.getAccessToken();
|
|
154
|
+
request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
155
|
+
return request;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
signOut = async () => {
|
|
159
|
+
const account = this.msalInstance.getAllAccounts()[0];
|
|
160
|
+
if (account) {
|
|
161
|
+
await this.msalInstance.logoutRedirect({
|
|
162
|
+
account,
|
|
163
|
+
postLogoutRedirectUri:
|
|
164
|
+
window.location.origin + this.redirectToAfterSignOut,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
useAuthState.getState().setLoggedOut();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
handleCallback = async () => {
|
|
172
|
+
const redirectTo = sessionStorage.getItem("redirect-to") ?? "/";
|
|
173
|
+
sessionStorage.removeItem("redirect-to");
|
|
174
|
+
return redirectTo;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
getRoutes() {
|
|
178
|
+
return [
|
|
179
|
+
...super.getRoutes(),
|
|
180
|
+
{
|
|
181
|
+
path: AZUREB2C_CALLBACK_PATH,
|
|
182
|
+
element: (
|
|
183
|
+
<ClientOnly>
|
|
184
|
+
<CallbackHandler handleCallback={this.handleCallback} />
|
|
185
|
+
</ClientOnly>
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const azureB2CAuth: AuthenticationProviderInitializer<
|
|
193
|
+
AzureB2CAuthenticationConfig
|
|
194
|
+
> = (options) => new AzureB2CAuthPlugin(options);
|
|
195
|
+
|
|
196
|
+
export default azureB2CAuth;
|
|
@@ -31,9 +31,7 @@ const clerkAuth: AuthenticationProviderInitializer<
|
|
|
31
31
|
const verifiedEmail = clerkApi.user.emailAddresses.find(
|
|
32
32
|
(email) => email.verification.status === "verified",
|
|
33
33
|
);
|
|
34
|
-
useAuthState.
|
|
35
|
-
isAuthenticated: true,
|
|
36
|
-
isPending: false,
|
|
34
|
+
useAuthState.getState().setLoggedIn({
|
|
37
35
|
profile: {
|
|
38
36
|
sub: clerkApi.user.id,
|
|
39
37
|
name: clerkApi.user.fullName ?? undefined,
|
|
@@ -115,9 +113,7 @@ const clerkAuth: AuthenticationProviderInitializer<
|
|
|
115
113
|
const verifiedEmail = clerk.session.user.emailAddresses.find(
|
|
116
114
|
(email) => email.verification.status === "verified",
|
|
117
115
|
);
|
|
118
|
-
useAuthState.
|
|
119
|
-
isAuthenticated: true,
|
|
120
|
-
isPending: false,
|
|
116
|
+
useAuthState.getState().setLoggedIn({
|
|
121
117
|
profile: {
|
|
122
118
|
sub: clerk.session.user.id,
|
|
123
119
|
name: clerk.session.user.fullName ?? undefined,
|
|
@@ -146,12 +142,7 @@ const clerkAuth: AuthenticationProviderInitializer<
|
|
|
146
142
|
await clerkApi?.signOut({
|
|
147
143
|
redirectUrl: window.location.origin + redirectToAfterSignOut,
|
|
148
144
|
});
|
|
149
|
-
useAuthState.
|
|
150
|
-
isAuthenticated: false,
|
|
151
|
-
isPending: false,
|
|
152
|
-
profile: null,
|
|
153
|
-
providerData: null,
|
|
154
|
-
});
|
|
145
|
+
useAuthState.getState().setLoggedOut();
|
|
155
146
|
},
|
|
156
147
|
signIn: async ({ redirectTo }: { redirectTo?: string } = {}) => {
|
|
157
148
|
await ensureLoaded;
|
|
@@ -273,6 +273,59 @@ export class OpenIDAuthenticationProvider
|
|
|
273
273
|
}
|
|
274
274
|
};
|
|
275
275
|
|
|
276
|
+
onPageLoad = async () => {
|
|
277
|
+
const { providerData } = useAuthState.getState();
|
|
278
|
+
|
|
279
|
+
if (!providerData) {
|
|
280
|
+
useAuthState.setState({ isPending: false });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const tokenState = providerData as OpenIdProviderData;
|
|
285
|
+
|
|
286
|
+
if (new Date(tokenState.expiresOn) < new Date()) {
|
|
287
|
+
if (!tokenState.refreshToken) {
|
|
288
|
+
useAuthState.setState({
|
|
289
|
+
isAuthenticated: false,
|
|
290
|
+
isPending: false,
|
|
291
|
+
profile: null,
|
|
292
|
+
providerData: null,
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const as = await this.getAuthServer();
|
|
299
|
+
const request = await oauth.refreshTokenGrantRequest(
|
|
300
|
+
as,
|
|
301
|
+
this.client,
|
|
302
|
+
tokenState.refreshToken,
|
|
303
|
+
);
|
|
304
|
+
const response = await oauth.processRefreshTokenResponse(
|
|
305
|
+
as,
|
|
306
|
+
this.client,
|
|
307
|
+
request,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
if (!response.access_token) {
|
|
311
|
+
throw new AuthorizationError("No access token in response");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this.setTokensFromResponse(response);
|
|
315
|
+
} catch {
|
|
316
|
+
useAuthState.setState({
|
|
317
|
+
isAuthenticated: false,
|
|
318
|
+
isPending: false,
|
|
319
|
+
profile: null,
|
|
320
|
+
providerData: null,
|
|
321
|
+
});
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
useAuthState.setState({ isPending: false });
|
|
327
|
+
};
|
|
328
|
+
|
|
276
329
|
handleCallback = async () => {
|
|
277
330
|
const url = new URL(window.location.href);
|
|
278
331
|
const state = url.searchParams.get("state");
|
|
@@ -51,12 +51,7 @@ class SupabaseAuthenticationProvider
|
|
|
51
51
|
if (session && (event === "SIGNED_IN" || event === "TOKEN_REFRESHED")) {
|
|
52
52
|
await this.updateUserState(session);
|
|
53
53
|
} else if (event === "SIGNED_OUT") {
|
|
54
|
-
useAuthState.
|
|
55
|
-
isAuthenticated: false,
|
|
56
|
-
isPending: false,
|
|
57
|
-
profile: undefined,
|
|
58
|
-
providerData: undefined,
|
|
59
|
-
});
|
|
54
|
+
useAuthState.getState().setLoggedOut();
|
|
60
55
|
}
|
|
61
56
|
});
|
|
62
57
|
}
|
|
@@ -72,9 +67,7 @@ class SupabaseAuthenticationProvider
|
|
|
72
67
|
pictureUrl: user.user_metadata.avatar_url,
|
|
73
68
|
};
|
|
74
69
|
|
|
75
|
-
useAuthState.
|
|
76
|
-
isAuthenticated: true,
|
|
77
|
-
isPending: false,
|
|
70
|
+
useAuthState.getState().setLoggedIn({
|
|
78
71
|
profile,
|
|
79
72
|
providerData: { session },
|
|
80
73
|
});
|