zudoku 0.66.2 → 0.66.4
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/config/validators/validate.d.ts +32 -3
- package/dist/config/validators/validate.js +1 -1
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/flat-config.d.ts +1 -1
- package/dist/lib/auth/issuer.js +1 -1
- package/dist/lib/auth/issuer.js.map +1 -1
- package/dist/lib/authentication/authentication.d.ts +3 -2
- package/dist/lib/authentication/components/SignIn.js +4 -2
- package/dist/lib/authentication/components/SignIn.js.map +1 -1
- package/dist/lib/authentication/components/SignUp.js +4 -2
- package/dist/lib/authentication/components/SignUp.js.map +1 -1
- package/dist/lib/authentication/hook.d.ts +2 -0
- package/dist/lib/authentication/hook.js +10 -0
- package/dist/lib/authentication/hook.js.map +1 -1
- package/dist/lib/authentication/providers/firebase.js +67 -9
- package/dist/lib/authentication/providers/firebase.js.map +1 -1
- package/dist/lib/authentication/ui/EmailVerificationUi.d.ts +4 -0
- package/dist/lib/authentication/ui/EmailVerificationUi.js +34 -0
- package/dist/lib/authentication/ui/EmailVerificationUi.js.map +1 -0
- package/dist/lib/authentication/ui/ZudokuAuthUi.d.ts +7 -2
- package/dist/lib/authentication/ui/ZudokuAuthUi.js +43 -11
- package/dist/lib/authentication/ui/ZudokuAuthUi.js.map +1 -1
- package/dist/lib/authentication/utils/relativeRedirectUrl.d.ts +1 -0
- package/dist/lib/authentication/utils/relativeRedirectUrl.js +8 -0
- package/dist/lib/authentication/utils/relativeRedirectUrl.js.map +1 -0
- package/dist/lib/components/index.d.ts +2 -0
- package/dist/lib/errors/ErrorMessage.d.ts +3 -0
- package/dist/lib/errors/ErrorMessage.js +16 -0
- package/dist/lib/errors/ErrorMessage.js.map +1 -0
- package/dist/lib/hooks/index.d.ts +2 -0
- package/dist/lib/oas/graphql/index.js +7 -3
- package/dist/lib/oas/graphql/index.js.map +1 -1
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js +9 -172
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-keys/index.d.ts +4 -1
- package/dist/lib/plugins/api-keys/index.js +21 -17
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.d.ts +12 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js +133 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js.map +1 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.d.ts +4 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.js +30 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.js.map +1 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.d.ts +6 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.js +39 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.js.map +1 -0
- package/dist/lib/plugins/openapi/ParamInfos.js +1 -0
- package/dist/lib/plugins/openapi/ParamInfos.js.map +1 -1
- package/dist/lib/plugins/openapi/Sidecar.js +3 -2
- package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
- package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
- package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
- package/dist/lib/plugins/openapi/util/createHttpSnippet.js +24 -1
- package/dist/lib/plugins/openapi/util/createHttpSnippet.js.map +1 -1
- package/dist/lib/ui/Button.js +1 -1
- package/dist/lib/ui/Button.js.map +1 -1
- package/dist/vite/config.js +7 -0
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/plugin-api-keys.js +4 -1
- package/dist/vite/plugin-api-keys.js.map +1 -1
- package/dist/vite/plugin-markdown-export.js +1 -1
- package/dist/vite/plugin-markdown-export.js.map +1 -1
- package/dist/vite/prerender/worker.js +3 -0
- package/dist/vite/prerender/worker.js.map +1 -1
- package/dist/vite/zuplo.d.ts +13 -0
- package/dist/vite/zuplo.js +15 -0
- package/dist/vite/zuplo.js.map +1 -0
- package/lib/{ActionButton-DUgvSylL.js → ActionButton-BSM2oNHF.js} +2 -2
- package/lib/{ActionButton-DUgvSylL.js.map → ActionButton-BSM2oNHF.js.map} +1 -1
- package/lib/{Button-CynVW1JV.js → Button-IOAeVaIH.js} +7 -6
- package/lib/{Button-CynVW1JV.js.map → Button-IOAeVaIH.js.map} +1 -1
- package/lib/{ClaudeLogo-CGRfGTk2.js → ClaudeLogo-DgjivS8A.js} +3 -3
- package/lib/{ClaudeLogo-CGRfGTk2.js.map → ClaudeLogo-DgjivS8A.js.map} +1 -1
- package/lib/{Drawer-Ci7XwhqT.js → Drawer-BRMcpfuF.js} +6 -6
- package/lib/{Drawer-Ci7XwhqT.js.map → Drawer-BRMcpfuF.js.map} +1 -1
- package/lib/Frame-DxlznfVd.js +205 -0
- package/lib/Frame-DxlznfVd.js.map +1 -0
- package/lib/{IndexingDialog-B5zCiUKr.js → IndexingDialog-DZWj_3cU.js} +2 -2
- package/lib/{IndexingDialog-B5zCiUKr.js.map → IndexingDialog-DZWj_3cU.js.map} +1 -1
- package/lib/{useMutation-C6RqWmTS.js → Input-D-kqEQ5M.js} +41 -23
- package/lib/Input-D-kqEQ5M.js.map +1 -0
- package/lib/{MdxPage-Bjf72BP3.js → MdxPage-BL-HbZrv.js} +9 -9
- package/lib/{MdxPage-Bjf72BP3.js.map → MdxPage-BL-HbZrv.js.map} +1 -1
- package/lib/{Mermaid-D_VSX7_Q.js → Mermaid-BjSczjLW.js} +3 -3
- package/lib/{Mermaid-D_VSX7_Q.js.map → Mermaid-BjSczjLW.js.map} +1 -1
- package/lib/{OAuthErrorPage-1Ekji0PK.js → OAuthErrorPage-DQtg28Go.js} +20 -21
- package/lib/{OAuthErrorPage-1Ekji0PK.js.map → OAuthErrorPage-DQtg28Go.js.map} +1 -1
- package/lib/{OasProvider-BZxmTyMM.js → OasProvider--qcZwrKS.js} +4 -4
- package/lib/{OasProvider-BZxmTyMM.js.map → OasProvider--qcZwrKS.js.map} +1 -1
- package/lib/{OperationList-B7nPIFB8.js → OperationList-CSJYzxQY.js} +1101 -1087
- package/lib/{OperationList-B7nPIFB8.js.map → OperationList-CSJYzxQY.js.map} +1 -1
- package/lib/{RouteGuard-9wjejsKm.js → RouteGuard-D0f743SM.js} +5 -5
- package/lib/{RouteGuard-9wjejsKm.js.map → RouteGuard-D0f743SM.js.map} +1 -1
- package/lib/{SchemaList-16_obkku.js → SchemaList-DtyuDrQA.js} +8 -8
- package/lib/{SchemaList-16_obkku.js.map → SchemaList-DtyuDrQA.js.map} +1 -1
- package/lib/SchemaView-G-SVXxAG.js +435 -0
- package/lib/SchemaView-G-SVXxAG.js.map +1 -0
- package/lib/{Select-CkxXP5I7.js → Secret-BxGpIhDP.js} +121 -121
- package/lib/Secret-BxGpIhDP.js.map +1 -0
- package/lib/SignUp-CDl7bQj3.js +50 -0
- package/lib/SignUp-CDl7bQj3.js.map +1 -0
- package/lib/{SyntaxHighlight-j_HRSPCU.js → SyntaxHighlight-Dgd0AaaX.js} +2 -2
- package/lib/{SyntaxHighlight-j_HRSPCU.js.map → SyntaxHighlight-Dgd0AaaX.js.map} +1 -1
- package/lib/{Toc-z05x698-.js → Toc-D_Rj4jVx.js} +2 -2
- package/lib/{Toc-z05x698-.js.map → Toc-D_Rj4jVx.js.map} +1 -1
- package/lib/{ZudokuContext-BXldanA8.js → ZudokuContext-DNHMZfcP.js} +33 -33
- package/lib/{ZudokuContext-BXldanA8.js.map → ZudokuContext-DNHMZfcP.js.map} +1 -1
- package/lib/{chunk-PVWAREVJ-dLIqswPy.js → chunk-PVWAREVJ-ClM0m2aJ.js} +19 -19
- package/lib/{chunk-PVWAREVJ-dLIqswPy.js.map → chunk-PVWAREVJ-ClM0m2aJ.js.map} +1 -1
- package/lib/{circular-D5sYCIWL.js → circular-BxODTa7z.js} +2 -2
- package/lib/{circular-D5sYCIWL.js.map → circular-BxODTa7z.js.map} +1 -1
- package/lib/{createServer-BlwU7lIr.js → createServer-BpreIXp6.js} +10 -10
- package/lib/{createServer-BlwU7lIr.js.map → createServer-BpreIXp6.js.map} +1 -1
- package/lib/createVariantComponent-CQVt-H3r.js +18 -0
- package/lib/createVariantComponent-CQVt-H3r.js.map +1 -0
- package/lib/{errors-BtC4Kn2j.js → errors-DliW1dED.js} +2 -2
- package/lib/{errors-BtC4Kn2j.js.map → errors-DliW1dED.js.map} +1 -1
- package/lib/{firebase-Ibm_tv3G.js → firebase-D4tbaCYB.js} +1588 -1342
- package/lib/firebase-D4tbaCYB.js.map +1 -0
- package/lib/hook-CHw_R_xu.js +52 -0
- package/lib/hook-CHw_R_xu.js.map +1 -0
- package/lib/{index-eKVhlB94.js → index-1TbL0HXQ.js} +2 -2
- package/lib/{index-eKVhlB94.js.map → index-1TbL0HXQ.js.map} +1 -1
- package/lib/{index-CeVTNcfF.js → index-9MxNUgg4.js} +99 -100
- package/lib/{index-CeVTNcfF.js.map → index-9MxNUgg4.js.map} +1 -1
- package/lib/{ErrorAlert-BUlG32M9.js → index-CboxZOVW.js} +5373 -4335
- package/lib/index-CboxZOVW.js.map +1 -0
- package/lib/index-CrcNWbel.js.map +1 -1
- package/lib/{index-Css56y3F.js → index-DXXZDuSJ.js} +4 -4
- package/lib/{index-Css56y3F.js.map → index-DXXZDuSJ.js.map} +1 -1
- package/lib/index.esm-BYObtETB.js.map +1 -1
- package/lib/index.esm-DtzT_KoE.js.map +1 -1
- package/lib/{index.esm-BoKBnRoT.js → index.esm-ti5zvZS_.js} +16 -14
- package/lib/index.esm-ti5zvZS_.js.map +1 -0
- package/lib/jsx-runtime-BzflLqGi.js.map +1 -1
- package/lib/{mutation-BoVlx8yA.js → mutation-DMHWqmFp.js} +2 -2
- package/lib/{mutation-BoVlx8yA.js.map → mutation-DMHWqmFp.js.map} +1 -1
- package/lib/ui/ActionButton.js +1 -1
- package/lib/ui/Button.js +6 -5
- package/lib/ui/Button.js.map +1 -1
- package/lib/ui/Carousel.js.map +1 -1
- package/lib/ui/Drawer.js +2 -2
- package/lib/ui/SyntaxHighlight.js +2 -2
- package/lib/zudoku.__internal.js +507 -479
- package/lib/zudoku.__internal.js.map +1 -1
- package/lib/zudoku.auth-auth0.js +1 -1
- package/lib/zudoku.auth-azureb2c.js +4 -4
- package/lib/zudoku.auth-clerk.js +2 -2
- package/lib/zudoku.auth-firebase.js +6 -5
- package/lib/zudoku.auth-firebase.js.map +1 -1
- package/lib/zudoku.auth-openid.js +4 -4
- package/lib/zudoku.auth-supabase.js +5 -5
- package/lib/zudoku.components.js +20 -21
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.hooks.js +3 -3
- package/lib/zudoku.mermaid.js +3 -3
- package/lib/zudoku.plugin-api-catalog.js +8 -9
- package/lib/zudoku.plugin-api-catalog.js.map +1 -1
- package/lib/zudoku.plugin-api-keys.js +579 -544
- 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 +3 -3
- package/lib/zudoku.plugin-redirect.js +1 -1
- package/lib/zudoku.plugin-search-pagefind.js +5 -5
- package/lib/zudoku.router.js +2 -2
- package/lib/zudoku.router.js.map +1 -1
- package/package.json +7 -5
- package/src/lib/auth/issuer.ts +1 -1
- package/src/lib/authentication/authentication.ts +8 -2
- package/src/lib/authentication/components/SignIn.tsx +5 -2
- package/src/lib/authentication/components/SignUp.tsx +5 -2
- package/src/lib/authentication/hook.ts +16 -0
- package/src/lib/authentication/providers/firebase.tsx +98 -6
- package/src/lib/authentication/ui/EmailVerificationUi.tsx +129 -0
- package/src/lib/authentication/ui/ZudokuAuthUi.tsx +170 -38
- package/src/lib/authentication/utils/relativeRedirectUrl.ts +12 -0
- package/src/lib/errors/ErrorMessage.tsx +38 -0
- package/src/lib/oas/graphql/index.ts +7 -3
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +36 -476
- package/src/lib/plugins/api-keys/index.tsx +35 -21
- package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -0
- package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +64 -0
- package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +124 -0
- package/src/lib/plugins/openapi/ParamInfos.tsx +1 -0
- package/src/lib/plugins/openapi/Sidecar.tsx +3 -2
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +6 -4
- package/src/lib/plugins/openapi/util/createHttpSnippet.ts +29 -1
- package/src/lib/ui/Button.tsx +1 -0
- package/lib/ErrorAlert-BUlG32M9.js.map +0 -1
- package/lib/RouterError-DfTZblpv.js +0 -42
- package/lib/RouterError-DfTZblpv.js.map +0 -1
- package/lib/SchemaView-eyvR4bRt.js +0 -597
- package/lib/SchemaView-eyvR4bRt.js.map +0 -1
- package/lib/Select-CkxXP5I7.js.map +0 -1
- package/lib/SignUp-D54_QWFy.js +0 -50
- package/lib/SignUp-D54_QWFy.js.map +0 -1
- package/lib/createVariantComponent-B9_dVBvu.js +0 -35
- package/lib/createVariantComponent-B9_dVBvu.js.map +0 -1
- package/lib/firebase-Ibm_tv3G.js.map +0 -1
- package/lib/hook-BNxidGQq.js +0 -40
- package/lib/hook-BNxidGQq.js.map +0 -1
- package/lib/index-DSOi7zVM.js +0 -1059
- package/lib/index-DSOi7zVM.js.map +0 -1
- package/lib/index.esm-BoKBnRoT.js.map +0 -1
- package/lib/useMutation-C6RqWmTS.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zudoku",
|
|
3
|
-
"version": "0.66.
|
|
3
|
+
"version": "0.66.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://zudoku.dev",
|
|
6
6
|
"repository": {
|
|
@@ -198,9 +198,11 @@
|
|
|
198
198
|
"@zudoku/httpsnippet": "10.0.9",
|
|
199
199
|
"@zudoku/react-helmet-async": "2.0.5",
|
|
200
200
|
"@zuplo/mcp": "^0.0.22",
|
|
201
|
+
"bs58": "^6.0.0",
|
|
201
202
|
"class-variance-authority": "0.7.1",
|
|
202
203
|
"clsx": "2.1.1",
|
|
203
204
|
"cmdk": "1.1.1",
|
|
205
|
+
"dotenv": "^17.2.3",
|
|
204
206
|
"embla-carousel-react": "8.6.0",
|
|
205
207
|
"estree-util-value-to-estree": "3.4.1",
|
|
206
208
|
"express": "5.2.1",
|
|
@@ -229,7 +231,7 @@
|
|
|
229
231
|
"posthog-node": "5.14.1",
|
|
230
232
|
"react-error-boundary": "6.0.0",
|
|
231
233
|
"react-hook-form": "7.66.0",
|
|
232
|
-
"react-is": "19.2.
|
|
234
|
+
"react-is": "19.2.3",
|
|
233
235
|
"react-markdown": "10.1.0",
|
|
234
236
|
"react-router": "7.8.2",
|
|
235
237
|
"rehype-mdx-import-media": "1.2.0",
|
|
@@ -282,12 +284,12 @@
|
|
|
282
284
|
"esbuild": "0.27.0",
|
|
283
285
|
"happy-dom": "20.0.10",
|
|
284
286
|
"mdast-util-mdx": "3.0.0",
|
|
285
|
-
"react": "19.2.
|
|
286
|
-
"react-dom": "19.2.
|
|
287
|
+
"react": "19.2.3",
|
|
288
|
+
"react-dom": "19.2.3",
|
|
287
289
|
"rollup-plugin-visualizer": "6.0.5",
|
|
288
290
|
"tsx": "4.20.6",
|
|
289
291
|
"typescript": "5.9.3",
|
|
290
|
-
"vitest": "4.0.
|
|
292
|
+
"vitest": "4.0.15"
|
|
291
293
|
},
|
|
292
294
|
"peerDependencies": {
|
|
293
295
|
"@azure/msal-browser": "^4.13.0",
|
package/src/lib/auth/issuer.ts
CHANGED
|
@@ -32,7 +32,7 @@ export const getIssuer = async (config: ZudokuConfig) => {
|
|
|
32
32
|
return config.authentication.issuer;
|
|
33
33
|
}
|
|
34
34
|
case "firebase": {
|
|
35
|
-
return config.authentication.
|
|
35
|
+
return `https://securetoken.google.com/${config.authentication.projectId}`;
|
|
36
36
|
}
|
|
37
37
|
case undefined: {
|
|
38
38
|
return undefined;
|
|
@@ -6,6 +6,7 @@ export type AuthActionOptions = { redirectTo?: string; replace?: boolean };
|
|
|
6
6
|
|
|
7
7
|
export interface AuthenticationPlugin {
|
|
8
8
|
initialize?(context: ZudokuContext): Promise<void>;
|
|
9
|
+
onPageLoad?(): void;
|
|
9
10
|
setNavigate?(navigate: NavigateFunction): void;
|
|
10
11
|
|
|
11
12
|
signUp(
|
|
@@ -18,12 +19,17 @@ export interface AuthenticationPlugin {
|
|
|
18
19
|
): Promise<void>;
|
|
19
20
|
|
|
20
21
|
signOut({ navigate }: AuthActionContext): Promise<void>;
|
|
22
|
+
|
|
23
|
+
signRequest(request: Request): Promise<Request>;
|
|
24
|
+
requestEmailVerification?(
|
|
25
|
+
{ navigate }: AuthActionContext,
|
|
26
|
+
options?: AuthActionOptions,
|
|
27
|
+
): Promise<void>;
|
|
28
|
+
|
|
21
29
|
/**
|
|
22
30
|
* @deprecated use signRequest instead
|
|
23
31
|
*/
|
|
24
32
|
getAccessToken?(): Promise<string>;
|
|
25
|
-
onPageLoad?(): void;
|
|
26
|
-
signRequest(request: Request): Promise<Request>;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
export type AuthenticationProviderInitializer<TConfig> = (
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
CardHeader,
|
|
9
9
|
CardTitle,
|
|
10
10
|
} from "zudoku/ui/Card.js";
|
|
11
|
+
import { useLatest } from "../../util/useLatest.js";
|
|
11
12
|
import { useAuth } from "../hook.js";
|
|
12
13
|
|
|
13
14
|
export const SignIn = () => {
|
|
@@ -15,12 +16,14 @@ export const SignIn = () => {
|
|
|
15
16
|
const [search] = useSearchParams();
|
|
16
17
|
const redirectTo = search.get("redirect") ?? undefined;
|
|
17
18
|
|
|
19
|
+
const login = useLatest(auth.login);
|
|
20
|
+
|
|
18
21
|
useEffect(() => {
|
|
19
|
-
void
|
|
22
|
+
void login.current({
|
|
20
23
|
redirectTo,
|
|
21
24
|
replace: true,
|
|
22
25
|
});
|
|
23
|
-
}, [
|
|
26
|
+
}, [login, redirectTo]);
|
|
24
27
|
|
|
25
28
|
return (
|
|
26
29
|
<div className="flex items-center justify-center mt-8">
|
|
@@ -7,14 +7,17 @@ import {
|
|
|
7
7
|
CardHeader,
|
|
8
8
|
CardTitle,
|
|
9
9
|
} from "zudoku/ui/Card.js";
|
|
10
|
+
import { useLatest } from "../../util/useLatest.js";
|
|
10
11
|
import { useAuth } from "../hook.js";
|
|
11
12
|
|
|
12
13
|
export const SignUp = () => {
|
|
13
14
|
const auth = useAuth();
|
|
14
15
|
|
|
16
|
+
const signup = useLatest(auth.signup);
|
|
17
|
+
|
|
15
18
|
useEffect(() => {
|
|
16
|
-
void
|
|
17
|
-
}, [
|
|
19
|
+
void signup.current();
|
|
20
|
+
}, [signup]);
|
|
18
21
|
|
|
19
22
|
return (
|
|
20
23
|
<div className="flex items-center justify-center mt-8">
|
|
@@ -52,5 +52,21 @@ export const useAuth = () => {
|
|
|
52
52
|
},
|
|
53
53
|
);
|
|
54
54
|
},
|
|
55
|
+
supportsEmailVerification:
|
|
56
|
+
typeof authentication?.requestEmailVerification === "function",
|
|
57
|
+
|
|
58
|
+
requestEmailVerification: async (options?: AuthActionOptions) => {
|
|
59
|
+
if (!isAuthEnabled) {
|
|
60
|
+
throw new Error("Authentication is not enabled.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await authentication.requestEmailVerification?.(
|
|
64
|
+
{ navigate },
|
|
65
|
+
{
|
|
66
|
+
...options,
|
|
67
|
+
redirectTo: options?.redirectTo ?? window.location.href,
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
},
|
|
55
71
|
};
|
|
56
72
|
};
|
|
@@ -3,12 +3,15 @@ import {
|
|
|
3
3
|
type Auth,
|
|
4
4
|
createUserWithEmailAndPassword,
|
|
5
5
|
getAuth,
|
|
6
|
+
sendEmailVerification,
|
|
7
|
+
sendPasswordResetEmail,
|
|
6
8
|
signInWithEmailAndPassword,
|
|
7
9
|
signInWithPopup,
|
|
8
10
|
signOut,
|
|
9
11
|
type User,
|
|
10
12
|
} from "firebase/auth";
|
|
11
13
|
import type { FirebaseAuthenticationConfig } from "../../../config/config.js";
|
|
14
|
+
import { ZudokuError } from "../../util/invariant.js";
|
|
12
15
|
import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
|
|
13
16
|
import type {
|
|
14
17
|
AuthActionContext,
|
|
@@ -19,7 +22,12 @@ import type {
|
|
|
19
22
|
import { SignOut } from "../components/SignOut.js";
|
|
20
23
|
import { AuthorizationError } from "../errors.js";
|
|
21
24
|
import { useAuthState } from "../state.js";
|
|
22
|
-
import {
|
|
25
|
+
import { EmailVerificationUi } from "../ui/EmailVerificationUi.js";
|
|
26
|
+
import {
|
|
27
|
+
ZudokuPasswordResetUi,
|
|
28
|
+
ZudokuSignInUi,
|
|
29
|
+
ZudokuSignUpUi,
|
|
30
|
+
} from "../ui/ZudokuAuthUi.js";
|
|
23
31
|
|
|
24
32
|
class FirebaseAuthenticationProvider
|
|
25
33
|
extends CoreAuthenticationPlugin
|
|
@@ -28,6 +36,7 @@ class FirebaseAuthenticationProvider
|
|
|
28
36
|
private readonly app: FirebaseApp;
|
|
29
37
|
private readonly auth: Auth;
|
|
30
38
|
private readonly providers: string[];
|
|
39
|
+
private readonly enableUsernamePassword: boolean;
|
|
31
40
|
|
|
32
41
|
constructor(config: FirebaseAuthenticationConfig) {
|
|
33
42
|
super();
|
|
@@ -42,7 +51,13 @@ class FirebaseAuthenticationProvider
|
|
|
42
51
|
measurementId: config.measurementId,
|
|
43
52
|
});
|
|
44
53
|
this.auth = getAuth(this.app);
|
|
45
|
-
this.providers = config.providers ?? [];
|
|
54
|
+
this.providers = config.providers?.filter((p) => p !== "password") ?? [];
|
|
55
|
+
this.enableUsernamePassword =
|
|
56
|
+
config.providers?.includes("password") ?? false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async initialize() {
|
|
60
|
+
await this.auth.authStateReady();
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
async signRequest(request: Request): Promise<Request> {
|
|
@@ -76,13 +91,77 @@ class FirebaseAuthenticationProvider
|
|
|
76
91
|
);
|
|
77
92
|
};
|
|
78
93
|
|
|
94
|
+
requestEmailVerification = async (
|
|
95
|
+
{ navigate }: AuthActionContext,
|
|
96
|
+
{ redirectTo }: AuthActionOptions,
|
|
97
|
+
) => {
|
|
98
|
+
if (!this.auth.currentUser) {
|
|
99
|
+
throw new ZudokuError("User is not authenticated", {
|
|
100
|
+
title: "User not authenticated",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await sendEmailVerification(this.auth.currentUser);
|
|
105
|
+
void navigate(
|
|
106
|
+
redirectTo
|
|
107
|
+
? `/verify-email?redirectTo=${encodeURIComponent(redirectTo)}`
|
|
108
|
+
: `/verify-email`,
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
79
112
|
getRoutes = () => {
|
|
80
113
|
return [
|
|
114
|
+
{
|
|
115
|
+
path: "/verify-email",
|
|
116
|
+
element: (
|
|
117
|
+
<EmailVerificationUi
|
|
118
|
+
onResendVerification={async () => {
|
|
119
|
+
if (!this.auth.currentUser) {
|
|
120
|
+
throw new ZudokuError("User is not authenticated", {
|
|
121
|
+
title: "User not authenticated",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
await sendEmailVerification(this.auth.currentUser);
|
|
125
|
+
}}
|
|
126
|
+
onCheckVerification={async () => {
|
|
127
|
+
if (!this.auth.currentUser) {
|
|
128
|
+
throw new ZudokuError("User is not authenticated", {
|
|
129
|
+
title: "User not authenticated",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
await this.auth.currentUser.reload();
|
|
133
|
+
const isVerified = this.auth.currentUser.emailVerified;
|
|
134
|
+
|
|
135
|
+
if (isVerified) {
|
|
136
|
+
await this.auth.currentUser.getIdToken(true);
|
|
137
|
+
await this.setUserLoggedIn(this.auth.currentUser);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return isVerified;
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
path: "/reset-password",
|
|
147
|
+
element: (
|
|
148
|
+
<ZudokuPasswordResetUi
|
|
149
|
+
onPasswordReset={async (email: string) => {
|
|
150
|
+
try {
|
|
151
|
+
await sendPasswordResetEmail(this.auth, email);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw Error(getFirebaseErrorMessage(error), { cause: error });
|
|
154
|
+
}
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
),
|
|
158
|
+
},
|
|
81
159
|
{
|
|
82
160
|
path: "/signin",
|
|
83
161
|
element: (
|
|
84
162
|
<ZudokuSignInUi
|
|
85
163
|
providers={this.providers}
|
|
164
|
+
enableUsernamePassword={this.enableUsernamePassword}
|
|
86
165
|
onOAuthSignIn={async (providerId: string) => {
|
|
87
166
|
useAuthState.setState({ isPending: true });
|
|
88
167
|
const provider = await getProviderForId(providerId);
|
|
@@ -109,7 +188,13 @@ class FirebaseAuthenticationProvider
|
|
|
109
188
|
password: string,
|
|
110
189
|
) => {
|
|
111
190
|
try {
|
|
112
|
-
|
|
191
|
+
useAuthState.setState({ isPending: false });
|
|
192
|
+
const result = await signInWithEmailAndPassword(
|
|
193
|
+
this.auth,
|
|
194
|
+
email,
|
|
195
|
+
password,
|
|
196
|
+
);
|
|
197
|
+
await this.setUserLoggedIn(result.user);
|
|
113
198
|
} catch (error) {
|
|
114
199
|
throw Error(getFirebaseErrorMessage(error), { cause: error });
|
|
115
200
|
}
|
|
@@ -122,6 +207,7 @@ class FirebaseAuthenticationProvider
|
|
|
122
207
|
element: (
|
|
123
208
|
<ZudokuSignUpUi
|
|
124
209
|
providers={this.providers}
|
|
210
|
+
enableUsernamePassword={this.enableUsernamePassword}
|
|
125
211
|
onOAuthSignUp={async (providerId: string) => {
|
|
126
212
|
const provider = await getProviderForId(providerId);
|
|
127
213
|
if (!provider) {
|
|
@@ -135,7 +221,13 @@ class FirebaseAuthenticationProvider
|
|
|
135
221
|
email: string,
|
|
136
222
|
password: string,
|
|
137
223
|
) => {
|
|
138
|
-
|
|
224
|
+
useAuthState.setState({ isPending: true });
|
|
225
|
+
const createUser = await createUserWithEmailAndPassword(
|
|
226
|
+
this.auth,
|
|
227
|
+
email,
|
|
228
|
+
password,
|
|
229
|
+
);
|
|
230
|
+
await this.setUserLoggedIn(createUser.user);
|
|
139
231
|
}}
|
|
140
232
|
/>
|
|
141
233
|
),
|
|
@@ -162,13 +254,13 @@ class FirebaseAuthenticationProvider
|
|
|
162
254
|
const user = this.auth.currentUser;
|
|
163
255
|
|
|
164
256
|
if (user) {
|
|
165
|
-
await this.
|
|
257
|
+
await this.setUserLoggedIn(user);
|
|
166
258
|
} else {
|
|
167
259
|
useAuthState.setState({ isPending: false });
|
|
168
260
|
}
|
|
169
261
|
};
|
|
170
262
|
|
|
171
|
-
private async
|
|
263
|
+
private async setUserLoggedIn(user: User) {
|
|
172
264
|
useAuthState.getState().setLoggedIn({
|
|
173
265
|
profile: {
|
|
174
266
|
sub: user.uid,
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { CheckIcon, MailCheck, RefreshCw } from "lucide-react";
|
|
3
|
+
import { Navigate, useSearchParams } from "react-router";
|
|
4
|
+
import { ActionButton } from "zudoku/ui/ActionButton.js";
|
|
5
|
+
import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
|
|
6
|
+
import { Button } from "zudoku/ui/Button.js";
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
} from "zudoku/ui/Card.js";
|
|
14
|
+
import createVariantComponent from "../../util/createVariantComponent.js";
|
|
15
|
+
import { getRelativeRedirectUrl } from "../utils/relativeRedirectUrl.js";
|
|
16
|
+
|
|
17
|
+
export const EmailVerificationUi = ({
|
|
18
|
+
onResendVerification,
|
|
19
|
+
onCheckVerification,
|
|
20
|
+
}: {
|
|
21
|
+
onResendVerification: () => Promise<void>;
|
|
22
|
+
onCheckVerification: () => Promise<boolean>;
|
|
23
|
+
}) => {
|
|
24
|
+
const [searchParams] = useSearchParams();
|
|
25
|
+
const redirectTo = searchParams.get("redirectTo");
|
|
26
|
+
const relativeRedirectTo = getRelativeRedirectUrl(redirectTo);
|
|
27
|
+
|
|
28
|
+
const resendMutation = useMutation({
|
|
29
|
+
mutationFn: async () => {
|
|
30
|
+
await onResendVerification();
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const checkVerificationMutation = useQuery({
|
|
35
|
+
queryKey: ["check-verification"],
|
|
36
|
+
queryFn: async () => {
|
|
37
|
+
const isVerified = await onCheckVerification();
|
|
38
|
+
return isVerified;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const error = resendMutation.error ?? checkVerificationMutation.error ?? null;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<AuthCard>
|
|
46
|
+
{checkVerificationMutation.data === true && (
|
|
47
|
+
<Navigate to={relativeRedirectTo} />
|
|
48
|
+
)}
|
|
49
|
+
<CardHeader className="text-center">
|
|
50
|
+
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-primary/10">
|
|
51
|
+
<MailCheck className="h-8 w-8 text-primary" />
|
|
52
|
+
</div>
|
|
53
|
+
<CardTitle>Verify your email</CardTitle>
|
|
54
|
+
<CardDescription>We've sent a verification link</CardDescription>
|
|
55
|
+
</CardHeader>
|
|
56
|
+
<CardContent className="flex flex-col gap-4">
|
|
57
|
+
{error && (
|
|
58
|
+
<Alert variant="destructive">
|
|
59
|
+
<AlertTitle>Error</AlertTitle>
|
|
60
|
+
<AlertDescription>{error?.message}</AlertDescription>
|
|
61
|
+
</Alert>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{resendMutation.isSuccess && (
|
|
65
|
+
<Alert>
|
|
66
|
+
<AlertTitle>Email sent</AlertTitle>
|
|
67
|
+
<AlertDescription>
|
|
68
|
+
A new verification email has been sent. Please check your inbox.
|
|
69
|
+
</AlertDescription>
|
|
70
|
+
</Alert>
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{checkVerificationMutation.isSuccess &&
|
|
74
|
+
!checkVerificationMutation.data && (
|
|
75
|
+
<Alert>
|
|
76
|
+
<AlertDescription>
|
|
77
|
+
{checkVerificationMutation.isFetching
|
|
78
|
+
? "Checking verification..."
|
|
79
|
+
: "Your email hasn't been verified yet. Please check your inbox and click the verification link."}
|
|
80
|
+
</AlertDescription>
|
|
81
|
+
</Alert>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
<div className="space-y-4">
|
|
85
|
+
<ActionButton
|
|
86
|
+
onClick={() => void checkVerificationMutation.refetch()}
|
|
87
|
+
isPending={checkVerificationMutation.isFetching}
|
|
88
|
+
className="w-full"
|
|
89
|
+
>
|
|
90
|
+
<div className="flex items-center gap-2">
|
|
91
|
+
<CheckIcon className="h-4 w-4" /> Continue
|
|
92
|
+
</div>
|
|
93
|
+
</ActionButton>
|
|
94
|
+
|
|
95
|
+
<div className="relative">
|
|
96
|
+
<div className="absolute inset-0 flex items-center">
|
|
97
|
+
<span className="w-full border-t" />
|
|
98
|
+
</div>
|
|
99
|
+
<div className="relative flex justify-center text-sm">
|
|
100
|
+
<span className="bg-card px-2 text-muted-foreground">
|
|
101
|
+
Didn't receive the email?
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<Button
|
|
107
|
+
variant="outline"
|
|
108
|
+
onClick={() => void resendMutation.mutate()}
|
|
109
|
+
disabled={resendMutation.isPending}
|
|
110
|
+
className="w-full gap-2"
|
|
111
|
+
>
|
|
112
|
+
{resendMutation.isPending ? (
|
|
113
|
+
<RefreshCw className="h-4 w-4 animate-spin" />
|
|
114
|
+
) : (
|
|
115
|
+
<RefreshCw className="h-4 w-4" />
|
|
116
|
+
)}
|
|
117
|
+
Resend verification email
|
|
118
|
+
</Button>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<p className="text-center text-xs text-muted-foreground">
|
|
122
|
+
Make sure to check your spam folder if you don't see the email.
|
|
123
|
+
</p>
|
|
124
|
+
</CardContent>
|
|
125
|
+
</AuthCard>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const AuthCard = createVariantComponent(Card, "max-w-md w-full mt-10 mx-auto");
|