@robelest/convex-auth 0.0.4-preview.21 → 0.0.4-preview.23
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/authorization/index.d.ts +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/index.js.map +1 -1
- package/dist/client/index.d.ts +1 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +36 -39
- package/dist/client/index.js.map +1 -1
- package/dist/component/client/index.d.ts +1 -2
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/model.d.ts +5 -5
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/public/enterprise/audit.d.ts.map +1 -1
- package/dist/component/public/enterprise/audit.js.map +1 -1
- package/dist/component/public/enterprise/core.d.ts.map +1 -1
- package/dist/component/public/enterprise/core.js.map +1 -1
- package/dist/component/public/enterprise/domains.d.ts.map +1 -1
- package/dist/component/public/enterprise/domains.js.map +1 -1
- package/dist/component/public/enterprise/scim.d.ts.map +1 -1
- package/dist/component/public/enterprise/scim.js.map +1 -1
- package/dist/component/public/enterprise/secrets.d.ts.map +1 -1
- package/dist/component/public/enterprise/secrets.js.map +1 -1
- package/dist/component/public/enterprise/webhooks.d.ts.map +1 -1
- package/dist/component/public/enterprise/webhooks.js.map +1 -1
- package/dist/component/public/factors/devices.d.ts.map +1 -1
- package/dist/component/public/factors/devices.js.map +1 -1
- package/dist/component/public/factors/passkeys.d.ts.map +1 -1
- package/dist/component/public/factors/passkeys.js.map +1 -1
- package/dist/component/public/factors/totp.d.ts.map +1 -1
- package/dist/component/public/factors/totp.js.map +1 -1
- package/dist/component/public/groups/core.js.map +1 -1
- package/dist/component/public/groups/invites.d.ts.map +1 -1
- package/dist/component/public/groups/invites.js.map +1 -1
- package/dist/component/public/groups/members.d.ts.map +1 -1
- package/dist/component/public/groups/members.js.map +1 -1
- package/dist/component/public/identity/accounts.d.ts.map +1 -1
- package/dist/component/public/identity/accounts.js.map +1 -1
- package/dist/component/public/identity/codes.d.ts.map +1 -1
- package/dist/component/public/identity/codes.js.map +1 -1
- package/dist/component/public/identity/sessions.d.ts.map +1 -1
- package/dist/component/public/identity/sessions.js.map +1 -1
- package/dist/component/public/identity/tokens.d.ts.map +1 -1
- package/dist/component/public/identity/tokens.js.map +1 -1
- package/dist/component/public/identity/users.d.ts.map +1 -1
- package/dist/component/public/identity/users.js.map +1 -1
- package/dist/component/public/identity/verifiers.d.ts.map +1 -1
- package/dist/component/public/identity/verifiers.js.map +1 -1
- package/dist/component/public/security/keys.d.ts.map +1 -1
- package/dist/component/public/security/keys.js.map +1 -1
- package/dist/component/public/security/limits.d.ts.map +1 -1
- package/dist/component/public/security/limits.js.map +1 -1
- package/dist/component/schema.d.ts +39 -39
- package/dist/component/server/auth.d.ts +95 -52
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +63 -43
- package/dist/component/server/auth.js.map +1 -1
- package/dist/component/server/core.js +116 -235
- package/dist/component/server/core.js.map +1 -1
- package/dist/component/server/crypto.js +25 -7
- package/dist/component/server/crypto.js.map +1 -1
- package/dist/component/server/device.js +58 -15
- package/dist/component/server/device.js.map +1 -1
- package/dist/component/server/enterprise/domain.js +148 -59
- package/dist/component/server/enterprise/domain.js.map +1 -1
- package/dist/component/server/enterprise/http.js +36 -15
- package/dist/component/server/enterprise/http.js.map +1 -1
- package/dist/component/server/enterprise/oidc.js +1 -1
- package/dist/component/server/http.js +26 -21
- package/dist/component/server/http.js.map +1 -1
- package/dist/component/server/identity.js +5 -2
- package/dist/component/server/identity.js.map +1 -1
- package/dist/component/server/limits.js +21 -30
- package/dist/component/server/limits.js.map +1 -1
- package/dist/component/server/mutations/account.js +12 -10
- package/dist/component/server/mutations/account.js.map +1 -1
- package/dist/component/server/mutations/code.js +5 -2
- package/dist/component/server/mutations/code.js.map +1 -1
- package/dist/component/server/mutations/invalidate.js +1 -1
- package/dist/component/server/mutations/invalidate.js.map +1 -1
- package/dist/component/server/mutations/oauth.js +10 -4
- package/dist/component/server/mutations/oauth.js.map +1 -1
- package/dist/component/server/mutations/refresh.js +2 -2
- package/dist/component/server/mutations/refresh.js.map +1 -1
- package/dist/component/server/mutations/register.js +46 -42
- package/dist/component/server/mutations/register.js.map +1 -1
- package/dist/component/server/mutations/retrieve.js +21 -25
- package/dist/component/server/mutations/retrieve.js.map +1 -1
- package/dist/component/server/mutations/signature.js +10 -4
- package/dist/component/server/mutations/signature.js.map +1 -1
- package/dist/component/server/mutations/signout.js.map +1 -1
- package/dist/component/server/mutations/store.js +9 -24
- package/dist/component/server/mutations/store.js.map +1 -1
- package/dist/component/server/mutations/verifier.js.map +1 -1
- package/dist/component/server/mutations/verify.js +1 -1
- package/dist/component/server/mutations/verify.js.map +1 -1
- package/dist/component/server/oauth.js +53 -16
- package/dist/component/server/oauth.js.map +1 -1
- package/dist/component/server/passkey.js +115 -31
- package/dist/component/server/passkey.js.map +1 -1
- package/dist/component/server/redirects.js +9 -3
- package/dist/component/server/redirects.js.map +1 -1
- package/dist/component/server/refresh.js +10 -7
- package/dist/component/server/refresh.js.map +1 -1
- package/dist/component/server/runtime.d.ts +3 -3
- package/dist/component/server/runtime.d.ts.map +1 -1
- package/dist/component/server/runtime.js +62 -20
- package/dist/component/server/runtime.js.map +1 -1
- package/dist/component/server/signin.js +34 -10
- package/dist/component/server/signin.js.map +1 -1
- package/dist/component/server/totp.js +79 -19
- package/dist/component/server/totp.js.map +1 -1
- package/dist/component/server/types.d.ts +12 -20
- package/dist/component/server/types.d.ts.map +1 -1
- package/dist/component/server/types.js.map +1 -1
- package/dist/component/server/users.js +6 -3
- package/dist/component/server/users.js.map +1 -1
- package/dist/component/server/utils.js +10 -4
- package/dist/component/server/utils.js.map +1 -1
- package/dist/core/types.d.ts +14 -22
- package/dist/core/types.d.ts.map +1 -1
- package/dist/factors/device.js +8 -9
- package/dist/factors/device.js.map +1 -1
- package/dist/factors/passkey.js +18 -21
- package/dist/factors/passkey.js.map +1 -1
- package/dist/providers/password.js +66 -81
- package/dist/providers/password.js.map +1 -1
- package/dist/runtime/invite.js +2 -8
- package/dist/runtime/invite.js.map +1 -1
- package/dist/server/auth.d.ts +95 -52
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +63 -43
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core.d.ts +71 -159
- package/dist/server/core.d.ts.map +1 -1
- package/dist/server/core.js +116 -235
- package/dist/server/core.js.map +1 -1
- package/dist/server/crypto.d.ts.map +1 -1
- package/dist/server/crypto.js +25 -7
- package/dist/server/crypto.js.map +1 -1
- package/dist/server/device.js +58 -15
- package/dist/server/device.js.map +1 -1
- package/dist/server/enterprise/domain.d.ts +0 -8
- package/dist/server/enterprise/domain.d.ts.map +1 -1
- package/dist/server/enterprise/domain.js +148 -59
- package/dist/server/enterprise/domain.js.map +1 -1
- package/dist/server/enterprise/http.d.ts.map +1 -1
- package/dist/server/enterprise/http.js +35 -14
- package/dist/server/enterprise/http.js.map +1 -1
- package/dist/server/http.d.ts +2 -2
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +25 -20
- package/dist/server/http.js.map +1 -1
- package/dist/server/identity.js +5 -2
- package/dist/server/identity.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/limits.js +21 -30
- package/dist/server/limits.js.map +1 -1
- package/dist/server/mounts.d.ts +26 -64
- package/dist/server/mounts.d.ts.map +1 -1
- package/dist/server/mounts.js +45 -106
- package/dist/server/mounts.js.map +1 -1
- package/dist/server/mutations/account.d.ts +8 -9
- package/dist/server/mutations/account.d.ts.map +1 -1
- package/dist/server/mutations/account.js +11 -9
- package/dist/server/mutations/account.js.map +1 -1
- package/dist/server/mutations/code.d.ts +13 -13
- package/dist/server/mutations/code.d.ts.map +1 -1
- package/dist/server/mutations/code.js +5 -2
- package/dist/server/mutations/code.js.map +1 -1
- package/dist/server/mutations/invalidate.d.ts +4 -4
- package/dist/server/mutations/invalidate.d.ts.map +1 -1
- package/dist/server/mutations/invalidate.js.map +1 -1
- package/dist/server/mutations/oauth.d.ts +12 -10
- package/dist/server/mutations/oauth.d.ts.map +1 -1
- package/dist/server/mutations/oauth.js +9 -3
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/refresh.d.ts +3 -3
- package/dist/server/mutations/refresh.d.ts.map +1 -1
- package/dist/server/mutations/refresh.js +1 -1
- package/dist/server/mutations/refresh.js.map +1 -1
- package/dist/server/mutations/register.d.ts +11 -11
- package/dist/server/mutations/register.d.ts.map +1 -1
- package/dist/server/mutations/register.js +45 -41
- package/dist/server/mutations/register.js.map +1 -1
- package/dist/server/mutations/retrieve.d.ts +6 -6
- package/dist/server/mutations/retrieve.d.ts.map +1 -1
- package/dist/server/mutations/retrieve.js +20 -24
- package/dist/server/mutations/retrieve.js.map +1 -1
- package/dist/server/mutations/signature.d.ts +6 -7
- package/dist/server/mutations/signature.d.ts.map +1 -1
- package/dist/server/mutations/signature.js +9 -3
- package/dist/server/mutations/signature.js.map +1 -1
- package/dist/server/mutations/signin.d.ts +5 -5
- package/dist/server/mutations/signin.d.ts.map +1 -1
- package/dist/server/mutations/signout.js.map +1 -1
- package/dist/server/mutations/store.d.ts +97 -97
- package/dist/server/mutations/store.d.ts.map +1 -1
- package/dist/server/mutations/store.js +8 -23
- package/dist/server/mutations/store.js.map +1 -1
- package/dist/server/mutations/verifier.js.map +1 -1
- package/dist/server/mutations/verify.d.ts +10 -10
- package/dist/server/mutations/verify.d.ts.map +1 -1
- package/dist/server/mutations/verify.js.map +1 -1
- package/dist/server/oauth.js +53 -16
- package/dist/server/oauth.js.map +1 -1
- package/dist/server/passkey.d.ts +2 -2
- package/dist/server/passkey.d.ts.map +1 -1
- package/dist/server/passkey.js +114 -30
- package/dist/server/passkey.js.map +1 -1
- package/dist/server/redirects.js +9 -3
- package/dist/server/redirects.js.map +1 -1
- package/dist/server/refresh.js +10 -7
- package/dist/server/refresh.js.map +1 -1
- package/dist/server/runtime.d.ts +14 -14
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +61 -19
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/signin.js +34 -10
- package/dist/server/signin.js.map +1 -1
- package/dist/server/ssr.d.ts.map +1 -1
- package/dist/server/ssr.js +175 -184
- package/dist/server/ssr.js.map +1 -1
- package/dist/server/totp.js +78 -18
- package/dist/server/totp.js.map +1 -1
- package/dist/server/types.d.ts +13 -21
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/dist/server/users.js +6 -3
- package/dist/server/users.js.map +1 -1
- package/dist/server/utils.js +10 -4
- package/dist/server/utils.js.map +1 -1
- package/package.json +2 -6
- package/src/authorization/index.ts +1 -1
- package/src/cli/index.ts +1 -1
- package/src/client/core/types.ts +14 -14
- package/src/client/factors/device.ts +10 -12
- package/src/client/factors/passkey.ts +23 -26
- package/src/client/index.ts +54 -64
- package/src/client/runtime/invite.ts +5 -7
- package/src/component/index.ts +1 -0
- package/src/component/public/enterprise/audit.ts +6 -1
- package/src/component/public/enterprise/core.ts +1 -0
- package/src/component/public/enterprise/domains.ts +5 -1
- package/src/component/public/enterprise/scim.ts +1 -0
- package/src/component/public/enterprise/secrets.ts +1 -0
- package/src/component/public/enterprise/webhooks.ts +1 -0
- package/src/component/public/factors/devices.ts +1 -0
- package/src/component/public/factors/passkeys.ts +1 -0
- package/src/component/public/factors/totp.ts +1 -0
- package/src/component/public/groups/core.ts +1 -1
- package/src/component/public/groups/invites.ts +7 -1
- package/src/component/public/groups/members.ts +1 -0
- package/src/component/public/identity/accounts.ts +1 -0
- package/src/component/public/identity/codes.ts +1 -0
- package/src/component/public/identity/sessions.ts +1 -0
- package/src/component/public/identity/tokens.ts +1 -0
- package/src/component/public/identity/users.ts +1 -0
- package/src/component/public/identity/verifiers.ts +1 -0
- package/src/component/public/security/keys.ts +1 -0
- package/src/component/public/security/limits.ts +1 -0
- package/src/providers/password.ts +89 -110
- package/src/server/auth.ts +177 -111
- package/src/server/core.ts +197 -233
- package/src/server/crypto.ts +31 -29
- package/src/server/device.ts +65 -32
- package/src/server/enterprise/domain.ts +158 -170
- package/src/server/enterprise/http.ts +46 -39
- package/src/server/http.ts +36 -30
- package/src/server/identity.ts +5 -5
- package/src/server/index.ts +2 -0
- package/src/server/limits.ts +53 -80
- package/src/server/mounts.ts +47 -74
- package/src/server/mutations/account.ts +22 -36
- package/src/server/mutations/code.ts +6 -6
- package/src/server/mutations/invalidate.ts +1 -1
- package/src/server/mutations/oauth.ts +14 -8
- package/src/server/mutations/refresh.ts +5 -4
- package/src/server/mutations/register.ts +87 -132
- package/src/server/mutations/retrieve.ts +44 -44
- package/src/server/mutations/signature.ts +13 -6
- package/src/server/mutations/signout.ts +1 -1
- package/src/server/mutations/store.ts +16 -31
- package/src/server/mutations/verifier.ts +1 -1
- package/src/server/mutations/verify.ts +3 -5
- package/src/server/oauth.ts +60 -69
- package/src/server/passkey.ts +567 -517
- package/src/server/redirects.ts +10 -6
- package/src/server/refresh.ts +14 -18
- package/src/server/runtime.ts +70 -55
- package/src/server/signin.ts +44 -37
- package/src/server/ssr.ts +390 -407
- package/src/server/totp.ts +85 -35
- package/src/server/types.ts +19 -22
- package/src/server/users.ts +7 -6
- package/src/server/utils.ts +10 -12
- package/dist/component/server/authError.js +0 -34
- package/dist/component/server/authError.js.map +0 -1
- package/dist/component/server/errors.d.ts +0 -1
- package/dist/component/server/errors.js +0 -137
- package/dist/component/server/errors.js.map +0 -1
- package/dist/server/authError.d.ts +0 -46
- package/dist/server/authError.d.ts.map +0 -1
- package/dist/server/authError.js +0 -34
- package/dist/server/authError.js.map +0 -1
- package/dist/server/errors.d.ts +0 -177
- package/dist/server/errors.d.ts.map +0 -1
- package/dist/server/errors.js +0 -212
- package/dist/server/errors.js.map +0 -1
- package/src/server/authError.ts +0 -44
- package/src/server/errors.ts +0 -290
package/dist/factors/device.js
CHANGED
|
@@ -64,13 +64,13 @@ function createDeviceClient(deps) {
|
|
|
64
64
|
flow: "poll"
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
|
-
return
|
|
67
|
+
return;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
70
|
+
throw new ConvexError({
|
|
71
|
+
code: "DEVICE_CODE_EXPIRED",
|
|
72
|
+
message: "Device code expired before authorization was completed."
|
|
73
|
+
});
|
|
74
74
|
},
|
|
75
75
|
verify: async (opts) => {
|
|
76
76
|
const params = {
|
|
@@ -89,12 +89,11 @@ function createDeviceClient(deps) {
|
|
|
89
89
|
provider: "device",
|
|
90
90
|
params
|
|
91
91
|
});
|
|
92
|
-
return { ok: true };
|
|
93
92
|
} catch (e) {
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
throw new ConvexError({
|
|
94
|
+
code: "DEVICE_AUTHORIZATION_FAILED",
|
|
96
95
|
message: e instanceof Error ? e.message : "Invalid or expired code."
|
|
97
|
-
};
|
|
96
|
+
});
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
99
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device.js","names":[],"sources":["../../src/client/factors/device.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\nimport { ConvexError } from \"convex/values\";\n\nimport type {\n AuthSession,\n ConvexTransport,\n DeviceClient,\n DeviceCodeResult,\n} from \"../core/types\";\n\ntype DeviceDeps = {\n proxy: string | undefined;\n convex: ConvexTransport;\n requireApiRefs: () => { signIn: any };\n proxyFetch: (body: Record<string, unknown>) => Promise<any>;\n setTokenAndMaybeWait: (\n args:\n | {\n shouldStore: true;\n tokens: AuthSession | null;\n waitForHandshake: boolean;\n context: { provider?: string; flow: string };\n }\n | {\n shouldStore: false;\n tokens: { token: string } | null;\n waitForHandshake: boolean;\n context: { provider?: string; flow: string };\n },\n ) => Promise<boolean>;\n};\n\n/** @internal */\nexport function createDeviceClient(deps: DeviceDeps): DeviceClient {\n const { proxy, convex, requireApiRefs, proxyFetch, setTokenAndMaybeWait } =\n deps;\n\n return {\n poll: async (opts: {
|
|
1
|
+
{"version":3,"file":"device.js","names":[],"sources":["../../src/client/factors/device.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\nimport { ConvexError } from \"convex/values\";\n\nimport type {\n AuthSession,\n ConvexTransport,\n DeviceClient,\n DeviceCodeResult,\n} from \"../core/types\";\n\ntype DeviceDeps = {\n proxy: string | undefined;\n convex: ConvexTransport;\n requireApiRefs: () => { signIn: any };\n proxyFetch: (body: Record<string, unknown>) => Promise<any>;\n setTokenAndMaybeWait: (\n args:\n | {\n shouldStore: true;\n tokens: AuthSession | null;\n waitForHandshake: boolean;\n context: { provider?: string; flow: string };\n }\n | {\n shouldStore: false;\n tokens: { token: string } | null;\n waitForHandshake: boolean;\n context: { provider?: string; flow: string };\n },\n ) => Promise<boolean>;\n};\n\n/** @internal */\nexport function createDeviceClient(deps: DeviceDeps): DeviceClient {\n const { proxy, convex, requireApiRefs, proxyFetch, setTokenAndMaybeWait } =\n deps;\n\n return {\n poll: async (opts: { code: DeviceCodeResult }): Promise<void> => {\n const { code } = opts;\n const intervalMs = code.interval * 1000;\n const expiresAt = Date.now() + code.expiresIn * 1000;\n\n while (Date.now() < expiresAt) {\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n\n const pollResult = await Fx.run(\n Fx.from({\n ok: async () => {\n let result: any;\n const params: Record<string, any> = {\n flow: \"poll\",\n deviceCode: code.deviceCode,\n };\n\n if (proxy) {\n result = await proxyFetch({\n action: \"auth:signIn\",\n args: { provider: \"device\", params },\n });\n } else {\n result = await convex.action(requireApiRefs().signIn, {\n provider: \"device\",\n params,\n });\n }\n\n return result;\n },\n err: (e) => e,\n }).pipe(\n Fx.recover((e: unknown) => {\n const dispatch =\n e instanceof ConvexError\n ? {\n tag:\n (e.data as Record<string, unknown> | undefined)\n ?.code === \"DEVICE_AUTHORIZATION_PENDING\"\n ? \"continue\"\n : (e.data as Record<string, unknown> | undefined)\n ?.code === \"DEVICE_SLOW_DOWN\"\n ? \"slowDown\"\n : \"fatal\",\n }\n : ({ tag: \"fatal\" } as const);\n\n return Fx.match(dispatch, dispatch.tag, {\n continue: () => Fx.succeed({ _poll: \"continue\" as const }),\n slowDown: () => Fx.succeed({ _poll: \"slow_down\" as const }),\n fatal: () => Fx.fatal(e),\n });\n }),\n ),\n );\n\n if (\"_poll\" in pollResult) {\n if (pollResult._poll === \"slow_down\") {\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n continue;\n }\n\n if (pollResult.tokens) {\n if (proxy) {\n await setTokenAndMaybeWait({\n shouldStore: false,\n tokens:\n pollResult.tokens === null\n ? null\n : { token: pollResult.tokens.token },\n waitForHandshake: true,\n context: { provider: \"device\", flow: \"poll\" },\n });\n } else {\n await setTokenAndMaybeWait({\n shouldStore: true,\n tokens: (pollResult.tokens as AuthSession | null) ?? null,\n waitForHandshake: true,\n context: { provider: \"device\", flow: \"poll\" },\n });\n }\n return;\n }\n }\n\n throw new ConvexError({\n code: \"DEVICE_CODE_EXPIRED\",\n message: \"Device code expired before authorization was completed.\",\n });\n },\n\n verify: async (opts: { code: string }): Promise<void> => {\n const params: Record<string, any> = {\n flow: \"verify\",\n userCode: opts.code,\n };\n\n try {\n if (proxy) {\n await proxyFetch({\n action: \"auth:signIn\",\n args: { provider: \"device\", params },\n });\n } else {\n await convex.action(requireApiRefs().signIn, {\n provider: \"device\",\n params,\n });\n }\n } catch (e: unknown) {\n throw new ConvexError({\n code: \"DEVICE_AUTHORIZATION_FAILED\",\n message: e instanceof Error ? e.message : \"Invalid or expired code.\",\n });\n }\n },\n };\n}\n"],"mappings":";;;;;AAiCA,SAAgB,mBAAmB,MAAgC;CACjE,MAAM,EAAE,OAAO,QAAQ,gBAAgB,YAAY,yBACjD;AAEF,QAAO;EACL,MAAM,OAAO,SAAoD;GAC/D,MAAM,EAAE,SAAS;GACjB,MAAM,aAAa,KAAK,WAAW;GACnC,MAAM,YAAY,KAAK,KAAK,GAAG,KAAK,YAAY;AAEhD,UAAO,KAAK,KAAK,GAAG,WAAW;AAC7B,UAAM,IAAI,SAAS,YAAY,WAAW,SAAS,WAAW,CAAC;IAE/D,MAAM,aAAa,MAAM,GAAG,IAC1B,GAAG,KAAK;KACN,IAAI,YAAY;MACd,IAAI;MACJ,MAAM,SAA8B;OAClC,MAAM;OACN,YAAY,KAAK;OAClB;AAED,UAAI,MACF,UAAS,MAAM,WAAW;OACxB,QAAQ;OACR,MAAM;QAAE,UAAU;QAAU;QAAQ;OACrC,CAAC;UAEF,UAAS,MAAM,OAAO,OAAO,gBAAgB,CAAC,QAAQ;OACpD,UAAU;OACV;OACD,CAAC;AAGJ,aAAO;;KAET,MAAM,MAAM;KACb,CAAC,CAAC,KACD,GAAG,SAAS,MAAe;KACzB,MAAM,WACJ,aAAa,cACT,EACE,KACG,EAAE,MACC,SAAS,iCACT,aACC,EAAE,MACG,SAAS,qBACb,aACA,SACT,GACA,EAAE,KAAK,SAAS;AAEvB,YAAO,GAAG,MAAM,UAAU,SAAS,KAAK;MACtC,gBAAgB,GAAG,QAAQ,EAAE,OAAO,YAAqB,CAAC;MAC1D,gBAAgB,GAAG,QAAQ,EAAE,OAAO,aAAsB,CAAC;MAC3D,aAAa,GAAG,MAAM,EAAE;MACzB,CAAC;MACF,CACH,CACF;AAED,QAAI,WAAW,YAAY;AACzB,SAAI,WAAW,UAAU,YACvB,OAAM,IAAI,SAAS,YAAY,WAAW,SAAS,WAAW,CAAC;AAEjE;;AAGF,QAAI,WAAW,QAAQ;AACrB,SAAI,MACF,OAAM,qBAAqB;MACzB,aAAa;MACb,QACE,WAAW,WAAW,OAClB,OACA,EAAE,OAAO,WAAW,OAAO,OAAO;MACxC,kBAAkB;MAClB,SAAS;OAAE,UAAU;OAAU,MAAM;OAAQ;MAC9C,CAAC;SAEF,OAAM,qBAAqB;MACzB,aAAa;MACb,QAAS,WAAW,UAAiC;MACrD,kBAAkB;MAClB,SAAS;OAAE,UAAU;OAAU,MAAM;OAAQ;MAC9C,CAAC;AAEJ;;;AAIJ,SAAM,IAAI,YAAY;IACpB,MAAM;IACN,SAAS;IACV,CAAC;;EAGJ,QAAQ,OAAO,SAA0C;GACvD,MAAM,SAA8B;IAClC,MAAM;IACN,UAAU,KAAK;IAChB;AAED,OAAI;AACF,QAAI,MACF,OAAM,WAAW;KACf,QAAQ;KACR,MAAM;MAAE,UAAU;MAAU;MAAQ;KACrC,CAAC;QAEF,OAAM,OAAO,OAAO,gBAAgB,CAAC,QAAQ;KAC3C,UAAU;KACV;KACD,CAAC;YAEG,GAAY;AACnB,UAAM,IAAI,YAAY;KACpB,MAAM;KACN,SAAS,aAAa,QAAQ,EAAE,UAAU;KAC3C,CAAC;;;EAGP"}
|
package/dist/factors/passkey.js
CHANGED
|
@@ -7,27 +7,24 @@ function createPasskeyClient(deps) {
|
|
|
7
7
|
const { proxy, convex, requireApiRefs, proxyFetch, setTokenAndMaybeWait } = deps;
|
|
8
8
|
const handleSignedInResult = async (result, flow) => {
|
|
9
9
|
return Fx.run(Fx.match(result, result.kind, {
|
|
10
|
-
signedIn: (signedInResult) => Fx.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}) ? { kind: "signedIn" } : { kind: "started" };
|
|
29
|
-
},
|
|
30
|
-
err: (e) => e
|
|
10
|
+
signedIn: (signedInResult) => Fx.promise(async () => {
|
|
11
|
+
return await setTokenAndMaybeWait(proxy ? {
|
|
12
|
+
shouldStore: false,
|
|
13
|
+
tokens: signedInResult.tokens === null ? null : { token: signedInResult.tokens.token },
|
|
14
|
+
waitForHandshake: true,
|
|
15
|
+
context: {
|
|
16
|
+
provider: "passkey",
|
|
17
|
+
flow
|
|
18
|
+
}
|
|
19
|
+
} : {
|
|
20
|
+
shouldStore: true,
|
|
21
|
+
tokens: signedInResult.tokens,
|
|
22
|
+
waitForHandshake: true,
|
|
23
|
+
context: {
|
|
24
|
+
provider: "passkey",
|
|
25
|
+
flow
|
|
26
|
+
}
|
|
27
|
+
}) ? { kind: "signedIn" } : { kind: "started" };
|
|
31
28
|
}),
|
|
32
29
|
redirect: () => Fx.succeed({ kind: "started" }),
|
|
33
30
|
started: () => Fx.succeed({ kind: "started" }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey.js","names":[],"sources":["../../src/client/factors/passkey.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\n\nimport { base64urlDecode, base64urlEncode } from \"../runtime/browser\";\nimport type {\n AuthSession,\n ConvexTransport,\n PasskeyClient,\n SignInActionResult,\n SignInResult,\n} from \"../core/types\";\n\ntype PasskeyDeps = {\n proxy: string | undefined;\n convex: ConvexTransport;\n requireApiRefs: () => { signIn: any };\n proxyFetch: (body: Record<string, unknown>) => Promise<any>;\n setTokenAndMaybeWait: (\n args:\n | {\n shouldStore: true;\n tokens: AuthSession | null;\n waitForHandshake: boolean;\n context: { provider?: string; flow: string };\n }\n | {\n shouldStore: false;\n tokens: { token: string } | null;\n waitForHandshake: boolean;\n context: { provider?: string; flow: string };\n },\n ) => Promise<boolean>;\n};\n\n/** @internal */\nexport function createPasskeyClient(deps: PasskeyDeps): PasskeyClient {\n const { proxy, convex, requireApiRefs, proxyFetch, setTokenAndMaybeWait } =\n deps;\n\n const handleSignedInResult = async (\n result: SignInActionResult,\n flow: string,\n ): Promise<SignInResult> => {\n return Fx.run(\n Fx.match(result, result.kind, {\n signedIn: (signedInResult) =>\n Fx.from({\n ok: async () => {\n const signingIn = await setTokenAndMaybeWait(\n proxy\n ? {\n shouldStore: false as const,\n tokens:\n signedInResult.tokens === null\n ? null\n : { token: signedInResult.tokens.token },\n waitForHandshake: true,\n context: { provider: \"passkey\", flow },\n }\n : {\n shouldStore: true as const,\n tokens: signedInResult.tokens,\n waitForHandshake: true,\n context: { provider: \"passkey\", flow },\n },\n );\n return signingIn\n ? ({ kind: \"signedIn\" as const } as SignInResult)\n : ({ kind: \"started\" as const } as SignInResult);\n },\n err: (e) => e as never,\n }),\n redirect: () => Fx.succeed({ kind: \"started\" as const }),\n started: () => Fx.succeed({ kind: \"started\" as const }),\n passkeyOptions: () => Fx.succeed({ kind: \"started\" as const }),\n totpRequired: () => Fx.succeed({ kind: \"started\" as const }),\n totpSetup: () => Fx.succeed({ kind: \"started\" as const }),\n deviceCode: () => Fx.succeed({ kind: \"started\" as const }),\n }),\n );\n };\n\n return {\n isSupported: (): boolean => {\n return (\n typeof window !== \"undefined\" &&\n typeof window.PublicKeyCredential !== \"undefined\"\n );\n },\n\n isAutofillSupported: async (): Promise<boolean> => {\n if (typeof window === \"undefined\") return false;\n if (typeof window.PublicKeyCredential === \"undefined\") return false;\n if (\n typeof (window.PublicKeyCredential as any)\n .isConditionalMediationAvailable !== \"function\"\n ) {\n return false;\n }\n return (\n window.PublicKeyCredential as any\n ).isConditionalMediationAvailable();\n },\n\n register: async (opts?: {\n name?: string;\n email?: string;\n userName?: string;\n userDisplayName?: string;\n }): Promise<SignInResult> => {\n const phase1Params = {\n flow: \"registerOptions\",\n email: opts?.email,\n userName: opts?.userName,\n userDisplayName: opts?.userDisplayName,\n };\n\n let phase1Result: SignInActionResult;\n if (proxy) {\n phase1Result = (await proxyFetch({\n action: \"auth:signIn\",\n args: { provider: \"passkey\", params: phase1Params },\n })) as SignInActionResult;\n } else {\n phase1Result = (await convex.action(requireApiRefs().signIn, {\n provider: \"passkey\",\n params: phase1Params,\n })) as SignInActionResult;\n }\n\n if (phase1Result.kind !== \"passkeyOptions\") {\n throw new Error(\"Server did not return passkey registration options\");\n }\n\n const options = phase1Result.options;\n const createOptions: CredentialCreationOptions = {\n publicKey: {\n rp: options.rp,\n user: {\n id: base64urlDecode(options.user.id).buffer as ArrayBuffer,\n name: options.user.name,\n displayName: options.user.displayName,\n },\n challenge: base64urlDecode(options.challenge).buffer as ArrayBuffer,\n pubKeyCredParams: options.pubKeyCredParams,\n timeout: options.timeout,\n attestation: options.attestation,\n authenticatorSelection: options.authenticatorSelection,\n excludeCredentials: (options.excludeCredentials ?? []).map(\n (cred: any) => ({\n type: cred.type ?? \"public-key\",\n id: base64urlDecode(cred.id).buffer as ArrayBuffer,\n transports: cred.transports,\n }),\n ),\n },\n };\n\n const credential = (await navigator.credentials.create(\n createOptions,\n )) as PublicKeyCredential | null;\n if (!credential) {\n throw new Error(\"Passkey registration was cancelled\");\n }\n\n const response = credential.response as AuthenticatorAttestationResponse;\n const transports =\n typeof response.getTransports === \"function\"\n ? response.getTransports()\n : undefined;\n\n const phase2Params = {\n flow: \"registerVerify\",\n clientDataJSON: base64urlEncode(response.clientDataJSON),\n attestationObject: base64urlEncode(response.attestationObject),\n transports,\n passkeyName: opts?.name,\n email: opts?.email,\n };\n\n let phase2Result: SignInActionResult;\n if (proxy) {\n phase2Result = (await proxyFetch({\n action: \"auth:signIn\",\n args: {\n provider: \"passkey\",\n params: phase2Params,\n verifier: phase1Result.verifier,\n },\n })) as SignInActionResult;\n } else {\n phase2Result = (await convex.action(requireApiRefs().signIn, {\n provider: \"passkey\",\n params: phase2Params,\n verifier: phase1Result.verifier,\n })) as SignInActionResult;\n }\n\n return handleSignedInResult(phase2Result, \"registerVerify\");\n },\n\n authenticate: async (opts?: {\n email?: string;\n autofill?: boolean;\n }): Promise<SignInResult> => {\n const phase1Params = {\n flow: \"authOptions\",\n email: opts?.email,\n };\n\n let phase1Result: SignInActionResult;\n if (proxy) {\n phase1Result = (await proxyFetch({\n action: \"auth:signIn\",\n args: { provider: \"passkey\", params: phase1Params },\n })) as SignInActionResult;\n } else {\n phase1Result = (await convex.action(requireApiRefs().signIn, {\n provider: \"passkey\",\n params: phase1Params,\n })) as SignInActionResult;\n }\n\n if (phase1Result.kind !== \"passkeyOptions\") {\n throw new Error(\"Server did not return passkey authentication options\");\n }\n\n const options = phase1Result.options;\n const getOptions: CredentialRequestOptions = {\n publicKey: {\n challenge: base64urlDecode(options.challenge).buffer as ArrayBuffer,\n timeout: options.timeout,\n rpId: options.rpId,\n userVerification: options.userVerification,\n allowCredentials: (options.allowCredentials ?? []).map(\n (cred: any) => ({\n type: cred.type ?? \"public-key\",\n id: base64urlDecode(cred.id).buffer as ArrayBuffer,\n transports: cred.transports,\n }),\n ),\n },\n ...(opts?.autofill ? { mediation: \"conditional\" as any } : {}),\n };\n\n const credential = (await navigator.credentials.get(\n getOptions,\n )) as PublicKeyCredential | null;\n if (!credential) {\n throw new Error(\"Passkey authentication was cancelled\");\n }\n\n const response = credential.response as AuthenticatorAssertionResponse;\n const phase2Params = {\n flow: \"authVerify\",\n credentialId: base64urlEncode(credential.rawId),\n clientDataJSON: base64urlEncode(response.clientDataJSON),\n authenticatorData: base64urlEncode(response.authenticatorData),\n signature: base64urlEncode(response.signature),\n };\n\n let phase2Result: SignInActionResult;\n if (proxy) {\n phase2Result = (await proxyFetch({\n action: \"auth:signIn\",\n args: {\n provider: \"passkey\",\n params: phase2Params,\n verifier: phase1Result.verifier,\n },\n })) as SignInActionResult;\n } else {\n phase2Result = (await convex.action(requireApiRefs().signIn, {\n provider: \"passkey\",\n params: phase2Params,\n verifier: phase1Result.verifier,\n })) as SignInActionResult;\n }\n\n return handleSignedInResult(phase2Result, \"authVerify\");\n },\n };\n}\n"],"mappings":";;;;;AAkCA,SAAgB,oBAAoB,MAAkC;CACpE,MAAM,EAAE,OAAO,QAAQ,gBAAgB,YAAY,yBACjD;CAEF,MAAM,uBAAuB,OAC3B,QACA,SAC0B;AAC1B,SAAO,GAAG,IACR,GAAG,MAAM,QAAQ,OAAO,MAAM;GAC5B,WAAW,mBACT,GAAG,KAAK;IACN,IAAI,YAAY;AAmBd,YAlBkB,MAAM,qBACtB,QACI;MACE,aAAa;MACb,QACE,eAAe,WAAW,OACtB,OACA,EAAE,OAAO,eAAe,OAAO,OAAO;MAC5C,kBAAkB;MAClB,SAAS;OAAE,UAAU;OAAW;OAAM;MACvC,GACD;MACE,aAAa;MACb,QAAQ,eAAe;MACvB,kBAAkB;MAClB,SAAS;OAAE,UAAU;OAAW;OAAM;MACvC,CACN,GAEI,EAAE,MAAM,YAAqB,GAC7B,EAAE,MAAM,WAAoB;;IAEnC,MAAM,MAAM;IACb,CAAC;GACJ,gBAAgB,GAAG,QAAQ,EAAE,MAAM,WAAoB,CAAC;GACxD,eAAe,GAAG,QAAQ,EAAE,MAAM,WAAoB,CAAC;GACvD,sBAAsB,GAAG,QAAQ,EAAE,MAAM,WAAoB,CAAC;GAC9D,oBAAoB,GAAG,QAAQ,EAAE,MAAM,WAAoB,CAAC;GAC5D,iBAAiB,GAAG,QAAQ,EAAE,MAAM,WAAoB,CAAC;GACzD,kBAAkB,GAAG,QAAQ,EAAE,MAAM,WAAoB,CAAC;GAC3D,CAAC,CACH;;AAGH,QAAO;EACL,mBAA4B;AAC1B,UACE,OAAO,WAAW,eAClB,OAAO,OAAO,wBAAwB;;EAI1C,qBAAqB,YAA8B;AACjD,OAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,OAAI,OAAO,OAAO,wBAAwB,YAAa,QAAO;AAC9D,OACE,OAAQ,OAAO,oBACZ,oCAAoC,WAEvC,QAAO;AAET,UACE,OAAO,oBACP,iCAAiC;;EAGrC,UAAU,OAAO,SAKY;GAC3B,MAAM,eAAe;IACnB,MAAM;IACN,OAAO,MAAM;IACb,UAAU,MAAM;IAChB,iBAAiB,MAAM;IACxB;GAED,IAAI;AACJ,OAAI,MACF,gBAAgB,MAAM,WAAW;IAC/B,QAAQ;IACR,MAAM;KAAE,UAAU;KAAW,QAAQ;KAAc;IACpD,CAAC;OAEF,gBAAgB,MAAM,OAAO,OAAO,gBAAgB,CAAC,QAAQ;IAC3D,UAAU;IACV,QAAQ;IACT,CAAC;AAGJ,OAAI,aAAa,SAAS,iBACxB,OAAM,IAAI,MAAM,qDAAqD;GAGvE,MAAM,UAAU,aAAa;GAC7B,MAAM,gBAA2C,EAC/C,WAAW;IACT,IAAI,QAAQ;IACZ,MAAM;KACJ,IAAI,gBAAgB,QAAQ,KAAK,GAAG,CAAC;KACrC,MAAM,QAAQ,KAAK;KACnB,aAAa,QAAQ,KAAK;KAC3B;IACD,WAAW,gBAAgB,QAAQ,UAAU,CAAC;IAC9C,kBAAkB,QAAQ;IAC1B,SAAS,QAAQ;IACjB,aAAa,QAAQ;IACrB,wBAAwB,QAAQ;IAChC,qBAAqB,QAAQ,sBAAsB,EAAE,EAAE,KACpD,UAAe;KACd,MAAM,KAAK,QAAQ;KACnB,IAAI,gBAAgB,KAAK,GAAG,CAAC;KAC7B,YAAY,KAAK;KAClB,EACF;IACF,EACF;GAED,MAAM,aAAc,MAAM,UAAU,YAAY,OAC9C,cACD;AACD,OAAI,CAAC,WACH,OAAM,IAAI,MAAM,qCAAqC;GAGvD,MAAM,WAAW,WAAW;GAC5B,MAAM,aACJ,OAAO,SAAS,kBAAkB,aAC9B,SAAS,eAAe,GACxB;GAEN,MAAM,eAAe;IACnB,MAAM;IACN,gBAAgB,gBAAgB,SAAS,eAAe;IACxD,mBAAmB,gBAAgB,SAAS,kBAAkB;IAC9D;IACA,aAAa,MAAM;IACnB,OAAO,MAAM;IACd;GAED,IAAI;AACJ,OAAI,MACF,gBAAgB,MAAM,WAAW;IAC/B,QAAQ;IACR,MAAM;KACJ,UAAU;KACV,QAAQ;KACR,UAAU,aAAa;KACxB;IACF,CAAC;OAEF,gBAAgB,MAAM,OAAO,OAAO,gBAAgB,CAAC,QAAQ;IAC3D,UAAU;IACV,QAAQ;IACR,UAAU,aAAa;IACxB,CAAC;AAGJ,UAAO,qBAAqB,cAAc,iBAAiB;;EAG7D,cAAc,OAAO,SAGQ;GAC3B,MAAM,eAAe;IACnB,MAAM;IACN,OAAO,MAAM;IACd;GAED,IAAI;AACJ,OAAI,MACF,gBAAgB,MAAM,WAAW;IAC/B,QAAQ;IACR,MAAM;KAAE,UAAU;KAAW,QAAQ;KAAc;IACpD,CAAC;OAEF,gBAAgB,MAAM,OAAO,OAAO,gBAAgB,CAAC,QAAQ;IAC3D,UAAU;IACV,QAAQ;IACT,CAAC;AAGJ,OAAI,aAAa,SAAS,iBACxB,OAAM,IAAI,MAAM,uDAAuD;GAGzE,MAAM,UAAU,aAAa;GAC7B,MAAM,aAAuC;IAC3C,WAAW;KACT,WAAW,gBAAgB,QAAQ,UAAU,CAAC;KAC9C,SAAS,QAAQ;KACjB,MAAM,QAAQ;KACd,kBAAkB,QAAQ;KAC1B,mBAAmB,QAAQ,oBAAoB,EAAE,EAAE,KAChD,UAAe;MACd,MAAM,KAAK,QAAQ;MACnB,IAAI,gBAAgB,KAAK,GAAG,CAAC;MAC7B,YAAY,KAAK;MAClB,EACF;KACF;IACD,GAAI,MAAM,WAAW,EAAE,WAAW,eAAsB,GAAG,EAAE;IAC9D;GAED,MAAM,aAAc,MAAM,UAAU,YAAY,IAC9C,WACD;AACD,OAAI,CAAC,WACH,OAAM,IAAI,MAAM,uCAAuC;GAGzD,MAAM,WAAW,WAAW;GAC5B,MAAM,eAAe;IACnB,MAAM;IACN,cAAc,gBAAgB,WAAW,MAAM;IAC/C,gBAAgB,gBAAgB,SAAS,eAAe;IACxD,mBAAmB,gBAAgB,SAAS,kBAAkB;IAC9D,WAAW,gBAAgB,SAAS,UAAU;IAC/C;GAED,IAAI;AACJ,OAAI,MACF,gBAAgB,MAAM,WAAW;IAC/B,QAAQ;IACR,MAAM;KACJ,UAAU;KACV,QAAQ;KACR,UAAU,aAAa;KACxB;IACF,CAAC;OAEF,gBAAgB,MAAM,OAAO,OAAO,gBAAgB,CAAC,QAAQ;IAC3D,UAAU;IACV,QAAQ;IACR,UAAU,aAAa;IACxB,CAAC;AAGJ,UAAO,qBAAqB,cAAc,aAAa;;EAE1D"}
|
|
1
|
+
{"version":3,"file":"passkey.js","names":[],"sources":["../../src/client/factors/passkey.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\n\nimport type {\n AuthSession,\n ConvexTransport,\n PasskeyClient,\n SignInActionResult,\n SignInResult,\n} from \"../core/types\";\nimport { base64urlDecode, base64urlEncode } from \"../runtime/browser\";\n\ntype PasskeyDeps = {\n proxy: string | undefined;\n convex: ConvexTransport;\n requireApiRefs: () => { signIn: any };\n proxyFetch: (body: Record<string, unknown>) => Promise<any>;\n setTokenAndMaybeWait: (\n args:\n | {\n shouldStore: true;\n tokens: AuthSession | null;\n waitForHandshake: boolean;\n context: { provider?: string; flow: string };\n }\n | {\n shouldStore: false;\n tokens: { token: string } | null;\n waitForHandshake: boolean;\n context: { provider?: string; flow: string };\n },\n ) => Promise<boolean>;\n};\n\n/** @internal */\nexport function createPasskeyClient(deps: PasskeyDeps): PasskeyClient {\n const { proxy, convex, requireApiRefs, proxyFetch, setTokenAndMaybeWait } =\n deps;\n\n const handleSignedInResult = async (\n result: SignInActionResult,\n flow: string,\n ): Promise<SignInResult> => {\n return Fx.run(\n Fx.match(result, result.kind, {\n signedIn: (signedInResult) =>\n Fx.promise(async () => {\n const signingIn = await setTokenAndMaybeWait(\n proxy\n ? {\n shouldStore: false as const,\n tokens:\n signedInResult.tokens === null\n ? null\n : { token: signedInResult.tokens.token },\n waitForHandshake: true,\n context: { provider: \"passkey\", flow },\n }\n : {\n shouldStore: true as const,\n tokens: signedInResult.tokens,\n waitForHandshake: true,\n context: { provider: \"passkey\", flow },\n },\n );\n return signingIn\n ? ({ kind: \"signedIn\" as const } as SignInResult)\n : ({ kind: \"started\" as const } as SignInResult);\n }),\n redirect: () => Fx.succeed({ kind: \"started\" as const }),\n started: () => Fx.succeed({ kind: \"started\" as const }),\n passkeyOptions: () => Fx.succeed({ kind: \"started\" as const }),\n totpRequired: () => Fx.succeed({ kind: \"started\" as const }),\n totpSetup: () => Fx.succeed({ kind: \"started\" as const }),\n deviceCode: () => Fx.succeed({ kind: \"started\" as const }),\n }),\n );\n };\n\n return {\n isSupported: (): boolean => {\n return (\n typeof window !== \"undefined\" &&\n typeof window.PublicKeyCredential !== \"undefined\"\n );\n },\n\n isAutofillSupported: async (): Promise<boolean> => {\n if (typeof window === \"undefined\") return false;\n if (typeof window.PublicKeyCredential === \"undefined\") return false;\n if (\n typeof (window.PublicKeyCredential as any)\n .isConditionalMediationAvailable !== \"function\"\n ) {\n return false;\n }\n return (\n window.PublicKeyCredential as any\n ).isConditionalMediationAvailable();\n },\n\n register: async (opts?: {\n name?: string;\n email?: string;\n userName?: string;\n userDisplayName?: string;\n }): Promise<SignInResult> => {\n const phase1Params = {\n flow: \"registerOptions\",\n email: opts?.email,\n userName: opts?.userName,\n userDisplayName: opts?.userDisplayName,\n };\n\n let phase1Result: SignInActionResult;\n if (proxy) {\n phase1Result = (await proxyFetch({\n action: \"auth:signIn\",\n args: { provider: \"passkey\", params: phase1Params },\n })) as SignInActionResult;\n } else {\n phase1Result = (await convex.action(requireApiRefs().signIn, {\n provider: \"passkey\",\n params: phase1Params,\n })) as SignInActionResult;\n }\n\n if (phase1Result.kind !== \"passkeyOptions\") {\n throw new Error(\"Server did not return passkey registration options\");\n }\n\n const options = phase1Result.options;\n const createOptions: CredentialCreationOptions = {\n publicKey: {\n rp: options.rp,\n user: {\n id: base64urlDecode(options.user.id).buffer as ArrayBuffer,\n name: options.user.name,\n displayName: options.user.displayName,\n },\n challenge: base64urlDecode(options.challenge).buffer as ArrayBuffer,\n pubKeyCredParams: options.pubKeyCredParams,\n timeout: options.timeout,\n attestation: options.attestation,\n authenticatorSelection: options.authenticatorSelection,\n excludeCredentials: (options.excludeCredentials ?? []).map(\n (cred: any) => ({\n type: cred.type ?? \"public-key\",\n id: base64urlDecode(cred.id).buffer as ArrayBuffer,\n transports: cred.transports,\n }),\n ),\n },\n };\n\n const credential = (await navigator.credentials.create(\n createOptions,\n )) as PublicKeyCredential | null;\n if (!credential) {\n throw new Error(\"Passkey registration was cancelled\");\n }\n\n const response = credential.response as AuthenticatorAttestationResponse;\n const transports =\n typeof response.getTransports === \"function\"\n ? response.getTransports()\n : undefined;\n\n const phase2Params = {\n flow: \"registerVerify\",\n clientDataJSON: base64urlEncode(response.clientDataJSON),\n attestationObject: base64urlEncode(response.attestationObject),\n transports,\n passkeyName: opts?.name,\n email: opts?.email,\n };\n\n let phase2Result: SignInActionResult;\n if (proxy) {\n phase2Result = (await proxyFetch({\n action: \"auth:signIn\",\n args: {\n provider: \"passkey\",\n params: phase2Params,\n verifier: phase1Result.verifier,\n },\n })) as SignInActionResult;\n } else {\n phase2Result = (await convex.action(requireApiRefs().signIn, {\n provider: \"passkey\",\n params: phase2Params,\n verifier: phase1Result.verifier,\n })) as SignInActionResult;\n }\n\n return handleSignedInResult(phase2Result, \"registerVerify\");\n },\n\n authenticate: async (opts?: {\n email?: string;\n autofill?: boolean;\n }): Promise<SignInResult> => {\n const phase1Params = {\n flow: \"authOptions\",\n email: opts?.email,\n };\n\n let phase1Result: SignInActionResult;\n if (proxy) {\n phase1Result = (await proxyFetch({\n action: \"auth:signIn\",\n args: { provider: \"passkey\", params: phase1Params },\n })) as SignInActionResult;\n } else {\n phase1Result = (await convex.action(requireApiRefs().signIn, {\n provider: \"passkey\",\n params: phase1Params,\n })) as SignInActionResult;\n }\n\n if (phase1Result.kind !== \"passkeyOptions\") {\n throw new Error(\"Server did not return passkey authentication options\");\n }\n\n const options = phase1Result.options;\n const getOptions: CredentialRequestOptions = {\n publicKey: {\n challenge: base64urlDecode(options.challenge).buffer as ArrayBuffer,\n timeout: options.timeout,\n rpId: options.rpId,\n userVerification: options.userVerification,\n allowCredentials: (options.allowCredentials ?? []).map(\n (cred: any) => ({\n type: cred.type ?? \"public-key\",\n id: base64urlDecode(cred.id).buffer as ArrayBuffer,\n transports: cred.transports,\n }),\n ),\n },\n ...(opts?.autofill ? { mediation: \"conditional\" as any } : {}),\n };\n\n const credential = (await navigator.credentials.get(\n getOptions,\n )) as PublicKeyCredential | null;\n if (!credential) {\n throw new Error(\"Passkey authentication was cancelled\");\n }\n\n const response = credential.response as AuthenticatorAssertionResponse;\n const phase2Params = {\n flow: \"authVerify\",\n credentialId: base64urlEncode(credential.rawId),\n clientDataJSON: base64urlEncode(response.clientDataJSON),\n authenticatorData: base64urlEncode(response.authenticatorData),\n signature: base64urlEncode(response.signature),\n };\n\n let phase2Result: SignInActionResult;\n if (proxy) {\n phase2Result = (await proxyFetch({\n action: \"auth:signIn\",\n args: {\n provider: \"passkey\",\n params: phase2Params,\n verifier: phase1Result.verifier,\n },\n })) as SignInActionResult;\n } else {\n phase2Result = (await convex.action(requireApiRefs().signIn, {\n provider: \"passkey\",\n params: phase2Params,\n verifier: phase1Result.verifier,\n })) as SignInActionResult;\n }\n\n return handleSignedInResult(phase2Result, \"authVerify\");\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -109,91 +109,76 @@ var Password = class {
|
|
|
109
109
|
return { userId: user._id };
|
|
110
110
|
};
|
|
111
111
|
return await Fx.run(Fx.match(flowDispatch, flowDispatch.tag, {
|
|
112
|
-
signUp: () => Fx.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return await finalizeCredentialsResult(created.account, created.user);
|
|
126
|
-
},
|
|
127
|
-
err: (e) => e
|
|
112
|
+
signUp: () => Fx.promise(async () => {
|
|
113
|
+
const secret = requirePasswordParam(params.password, "signUp");
|
|
114
|
+
const created = await ctx.auth.account.create(ctx, {
|
|
115
|
+
provider,
|
|
116
|
+
account: {
|
|
117
|
+
id: email,
|
|
118
|
+
secret
|
|
119
|
+
},
|
|
120
|
+
profile,
|
|
121
|
+
shouldLinkViaEmail: config.verify !== void 0,
|
|
122
|
+
shouldLinkViaPhone: false
|
|
123
|
+
});
|
|
124
|
+
return await finalizeCredentialsResult(created.account, created.user);
|
|
128
125
|
}),
|
|
129
|
-
signIn: () => Fx.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return await finalizeCredentialsResult(retrieved.account, retrieved.user);
|
|
141
|
-
},
|
|
142
|
-
err: (e) => e
|
|
126
|
+
signIn: () => Fx.promise(async () => {
|
|
127
|
+
const secret = requirePasswordParam(params.password, "signIn");
|
|
128
|
+
const retrieved = await ctx.auth.account.get(ctx, {
|
|
129
|
+
provider,
|
|
130
|
+
account: {
|
|
131
|
+
id: email,
|
|
132
|
+
secret
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (retrieved === null) throw new Error("Invalid credentials");
|
|
136
|
+
return await finalizeCredentialsResult(retrieved.account, retrieved.user);
|
|
143
137
|
}),
|
|
144
|
-
reset: () => Fx.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
-
},
|
|
156
|
-
err: (e) => e
|
|
138
|
+
reset: () => Fx.promise(async () => {
|
|
139
|
+
if (!config.reset) throw new Error(`Password reset is not enabled for ${provider}`);
|
|
140
|
+
const { account } = await ctx.auth.account.get(ctx, {
|
|
141
|
+
provider,
|
|
142
|
+
account: { id: email }
|
|
143
|
+
});
|
|
144
|
+
return await ctx.auth.provider.signIn(ctx, config.reset, {
|
|
145
|
+
accountId: account._id,
|
|
146
|
+
params
|
|
147
|
+
});
|
|
157
148
|
}),
|
|
158
|
-
resetVerification: () => Fx.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
};
|
|
181
|
-
},
|
|
182
|
-
err: (e) => e
|
|
149
|
+
resetVerification: () => Fx.promise(async () => {
|
|
150
|
+
if (!config.reset) throw new Error(`Password reset is not enabled for ${provider}`);
|
|
151
|
+
if (params.newPassword === void 0) throw new Error("Missing `newPassword` param for `reset-verification` flow");
|
|
152
|
+
const result = await ctx.auth.provider.signIn(ctx, config.reset, { params });
|
|
153
|
+
if (result === null) throw new Error("Invalid code");
|
|
154
|
+
const { userId, sessionId } = result;
|
|
155
|
+
const secret = params.newPassword;
|
|
156
|
+
await ctx.auth.account.update(ctx, {
|
|
157
|
+
provider,
|
|
158
|
+
account: {
|
|
159
|
+
id: email,
|
|
160
|
+
secret
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
await ctx.auth.session.invalidate(ctx, {
|
|
164
|
+
userId,
|
|
165
|
+
except: [sessionId]
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
userId,
|
|
169
|
+
sessionId
|
|
170
|
+
};
|
|
183
171
|
}),
|
|
184
|
-
emailVerification: () => Fx.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
});
|
|
195
|
-
},
|
|
196
|
-
err: (e) => e
|
|
172
|
+
emailVerification: () => Fx.promise(async () => {
|
|
173
|
+
if (!config.verify) throw new Error(`Email verification is not enabled for ${provider}`);
|
|
174
|
+
const { account } = await ctx.auth.account.get(ctx, {
|
|
175
|
+
provider,
|
|
176
|
+
account: { id: email }
|
|
177
|
+
});
|
|
178
|
+
return await ctx.auth.provider.signIn(ctx, config.verify, {
|
|
179
|
+
accountId: account._id,
|
|
180
|
+
params
|
|
181
|
+
});
|
|
197
182
|
}),
|
|
198
183
|
invalid: () => Fx.fatal(/* @__PURE__ */ new Error("Missing `flow` param, it must be one of \"signUp\", \"signIn\", \"reset\", \"reset-verification\" or \"email-verification\"!"))
|
|
199
184
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password.js","names":[],"sources":["../../src/providers/password.ts"],"sourcesContent":["/**\n * Configure {@link Password} provider for email/password authentication.\n *\n * The `Password` provider supports the following flows, determined\n * by the `flow` parameter:\n *\n * - `\"signUp\"`: Create a new account with a password.\n * - `\"signIn\"`: Sign in with an existing account and password.\n * - `\"reset\"`: Request a password reset.\n * - `\"reset-verification\"`: Verify a password reset code and change password.\n * - `\"email-verification\"`: If email verification is enabled and `code` is\n * included in params, verify an OTP.\n *\n * ```ts\n * import { Password } from \"@robelest/convex-auth/providers\";\n *\n * new Password()\n * ```\n *\n * @module\n */\n\nimport { scryptAsync } from \"@noble/hashes/scrypt.js\";\nimport { bytesToHex } from \"@noble/hashes/utils.js\";\nimport { Fx } from \"@robelest/fx\";\nimport {\n DocumentByName,\n GenericDataModel,\n WithoutSystemFields,\n} from \"convex/server\";\nimport { Value } from \"convex/values\";\n\nimport type {\n EmailConfig,\n GenericActionCtxWithAuthConfig,\n GenericDoc,\n AuthProviderConfig,\n ConvexCredentialsConfig,\n} from \"../server/types\";\nimport { Credentials, type CredentialsConfig } from \"./credentials\";\n\n/**\n * The available options to a {@link Password} provider for Convex Auth.\n */\nexport interface PasswordConfig<DataModel extends GenericDataModel> {\n /**\n * Uniquely identifies the provider, allowing to use\n * multiple different {@link Password} providers.\n */\n id?: string;\n /**\n * Perform checks on provided params and customize the user\n * information stored after sign up, including email normalization.\n *\n * Called for every flow (\"signUp\", \"signIn\", \"reset\",\n * \"reset-verification\" and \"email-verification\").\n */\n profile?: (\n /**\n * The values passed to the `signIn` function.\n */\n params: Record<string, Value | undefined>,\n /**\n * Convex ActionCtx in case you want to read from or write to\n * the database.\n */\n ctx: GenericActionCtxWithAuthConfig<DataModel>,\n ) => WithoutSystemFields<DocumentByName<DataModel, \"User\">> & {\n email: string;\n };\n /**\n * Performs custom validation on password provided during sign up or reset.\n *\n * Otherwise the default validation is used (password is not empty and\n * at least 8 characters in length).\n *\n * If the provided password is invalid, implementations must throw an Error.\n *\n * @param password the password supplied during \"signUp\" or\n * \"reset-verification\" flows.\n */\n validatePasswordRequirements?: (password: string) => void;\n /**\n * Provide hashing and verification functions if you want to control\n * how passwords are hashed.\n */\n crypto?: CredentialsConfig[\"crypto\"];\n /**\n * An email provider used to require verification\n * before password reset.\n */\n reset?: EmailConfig | ((...args: any) => EmailConfig);\n /**\n * An email provider used to require verification\n * before sign up / sign in.\n */\n verify?: EmailConfig | ((...args: any) => EmailConfig);\n}\n\ntype PasswordFlowDispatch =\n | { tag: \"signUp\" }\n | { tag: \"signIn\" }\n | { tag: \"reset\" }\n | { tag: \"resetVerification\" }\n | { tag: \"emailVerification\" }\n | { tag: \"invalid\"; flow: unknown };\n\nconst PASSWORD_FLOW_TAG = {\n signUp: \"signUp\",\n signIn: \"signIn\",\n reset: \"reset\",\n \"reset-verification\": \"resetVerification\",\n \"email-verification\": \"emailVerification\",\n} as const;\n\ntype PasswordFlowInput = keyof typeof PASSWORD_FLOW_TAG;\n\nfunction decodePasswordFlow(flow: unknown): PasswordFlowDispatch {\n if (typeof flow !== \"string\") {\n return { tag: \"invalid\", flow };\n }\n\n const tag = PASSWORD_FLOW_TAG[flow as PasswordFlowInput];\n return tag === undefined ? { tag: \"invalid\", flow } : { tag };\n}\n\n/**\n * Email and password authentication provider.\n *\n * Passwords are by default hashed using scrypt.\n * You can customize the hashing via the `crypto` option.\n *\n * Email verification is not required unless you pass\n * an email provider to the `verify` option.\n *\n * @example\n * ```ts\n * import { Password } from \"@robelest/convex-auth/providers\";\n *\n * new Password()\n * new Password({ verify: myEmailProvider })\n * ```\n */\nexport class Password<DataModel extends GenericDataModel = GenericDataModel> {\n readonly id: string;\n readonly type = \"credentials\" as const;\n readonly config: PasswordConfig<DataModel>;\n\n constructor(\n config: PasswordConfig<DataModel> = {} as PasswordConfig<DataModel>,\n ) {\n this.id = config.id ?? \"password\";\n this.config = config;\n }\n\n /** @internal Convert to the internal materialized config shape. */\n _toMaterialized(): ConvexCredentialsConfig {\n const config = this.config;\n const provider = this.id;\n\n return new Credentials<DataModel>({\n id: \"password\",\n authorize: async (params, ctx) => {\n const flowDispatch = decodePasswordFlow(params.flow);\n\n const validatePasswordRequirements = (password: string) => {\n if (config.validatePasswordRequirements !== undefined) {\n config.validatePasswordRequirements(password);\n return;\n }\n validateDefaultPasswordRequirements(password);\n };\n\n await Fx.run(\n Fx.match(flowDispatch, flowDispatch.tag, {\n signUp: () =>\n Fx.sync(() => {\n validatePasswordRequirements(params.password as string);\n }),\n resetVerification: () =>\n Fx.sync(() => {\n validatePasswordRequirements(params.newPassword as string);\n }),\n signIn: () => Fx.succeed(undefined),\n reset: () => Fx.succeed(undefined),\n emailVerification: () => Fx.succeed(undefined),\n invalid: () => Fx.succeed(undefined),\n }),\n );\n\n const profile = config.profile?.(params, ctx) ?? defaultProfile(params);\n const { email } = profile;\n const requirePasswordParam = (\n value: unknown,\n flow: \"signUp\" | \"signIn\",\n ) => {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(`Missing \\`password\\` param for \\`${flow}\\` flow`);\n }\n return value;\n };\n\n const finalizeCredentialsResult = async (\n account: GenericDoc<DataModel, \"Account\">,\n user: GenericDoc<DataModel, \"User\">,\n ) => {\n if (config.verify && !account.emailVerified) {\n return await ctx.auth.provider.signIn(\n ctx,\n config.verify as AuthProviderConfig,\n {\n accountId: account._id,\n params,\n },\n );\n }\n return { userId: user._id };\n };\n\n return await Fx.run(\n Fx.match(flowDispatch, flowDispatch.tag, {\n signUp: () =>\n Fx.from({\n ok: async () => {\n const secret = requirePasswordParam(\n params.password,\n \"signUp\",\n );\n const created = await ctx.auth.account.create(ctx, {\n provider,\n account: { id: email, secret },\n profile: profile as any,\n shouldLinkViaEmail: config.verify !== undefined,\n shouldLinkViaPhone: false,\n });\n return await finalizeCredentialsResult(\n created.account,\n created.user,\n );\n },\n err: (e) => e as never,\n }),\n signIn: () =>\n Fx.from({\n ok: async () => {\n const secret = requirePasswordParam(\n params.password,\n \"signIn\",\n );\n const retrieved = await ctx.auth.account.get(ctx, {\n provider,\n account: { id: email, secret },\n });\n if (retrieved === null) {\n throw new Error(\"Invalid credentials\");\n }\n return await finalizeCredentialsResult(\n retrieved.account,\n retrieved.user,\n );\n },\n err: (e) => e as never,\n }),\n reset: () =>\n Fx.from({\n ok: async () => {\n if (!config.reset) {\n throw new Error(\n `Password reset is not enabled for ${provider}`,\n );\n }\n const { account } = await ctx.auth.account.get(ctx, {\n provider,\n account: { id: email },\n });\n return await ctx.auth.provider.signIn(\n ctx,\n config.reset as AuthProviderConfig,\n {\n accountId: account._id,\n params,\n },\n );\n },\n err: (e) => e as never,\n }),\n resetVerification: () =>\n Fx.from({\n ok: async () => {\n if (!config.reset) {\n throw new Error(\n `Password reset is not enabled for ${provider}`,\n );\n }\n if (params.newPassword === undefined) {\n throw new Error(\n \"Missing `newPassword` param for `reset-verification` flow\",\n );\n }\n const result = await ctx.auth.provider.signIn(\n ctx,\n config.reset as AuthProviderConfig,\n { params },\n );\n if (result === null) {\n throw new Error(\"Invalid code\");\n }\n const { userId, sessionId } = result;\n const secret = params.newPassword as string;\n await ctx.auth.account.update(ctx, {\n provider,\n account: { id: email, secret },\n });\n await ctx.auth.session.invalidate(ctx, {\n userId,\n except: [sessionId],\n });\n return { userId, sessionId };\n },\n err: (e) => e as never,\n }),\n emailVerification: () =>\n Fx.from({\n ok: async () => {\n if (!config.verify) {\n throw new Error(\n `Email verification is not enabled for ${provider}`,\n );\n }\n const { account } = await ctx.auth.account.get(ctx, {\n provider,\n account: { id: email },\n });\n return await ctx.auth.provider.signIn(\n ctx,\n config.verify as AuthProviderConfig,\n {\n accountId: account._id,\n params,\n },\n );\n },\n err: (e) => e as never,\n }),\n invalid: () =>\n Fx.fatal(\n new Error(\n \"Missing `flow` param, it must be one of \" +\n '\"signUp\", \"signIn\", \"reset\", \"reset-verification\" or ' +\n '\"email-verification\"!',\n ),\n ),\n }),\n );\n },\n crypto: config.crypto ?? {\n async hashSecret(password: string) {\n return await hashPassword(password);\n },\n async verifySecret(password: string, hash: string) {\n return await verifyPassword(password, hash);\n },\n },\n extraProviders: [\n config.reset as AuthProviderConfig | undefined,\n config.verify as AuthProviderConfig | undefined,\n ],\n ...config,\n })._toMaterialized();\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction validateDefaultPasswordRequirements(password: string) {\n if (!password || password.length < 8) {\n throw new Error(\"Invalid password\");\n }\n}\n\nfunction defaultProfile(params: Record<string, unknown>) {\n const email = params.email;\n if (typeof email !== \"string\" || email.trim().length === 0) {\n throw new Error(\"Missing `email` param\");\n }\n return {\n email,\n };\n}\n\nconst PASSWORD_HASH_PARAMS = {\n N: 16384,\n r: 16,\n p: 1,\n dkLen: 64,\n} as const;\n\nconst PASSWORD_HASH_PREFIX = `scrypt:N=${PASSWORD_HASH_PARAMS.N},r=${PASSWORD_HASH_PARAMS.r},p=${PASSWORD_HASH_PARAMS.p},dkLen=${PASSWORD_HASH_PARAMS.dkLen}`;\n\nasync function hashPassword(password: string) {\n const salt = crypto.getRandomValues(new Uint8Array(32));\n const hash = await scryptAsync(password, salt, PASSWORD_HASH_PARAMS);\n return `${PASSWORD_HASH_PREFIX}$${bytesToHex(salt)}$${bytesToHex(hash)}`;\n}\n\nasync function verifyPassword(password: string, storedHash: string) {\n const [prefix, saltHex, hashHex] = storedHash.split(\"$\");\n if (\n prefix !== PASSWORD_HASH_PREFIX ||\n saltHex === undefined ||\n hashHex === undefined\n ) {\n return false;\n }\n\n let salt: Uint8Array;\n let expectedHash: Uint8Array;\n try {\n salt = hexToBytes(saltHex);\n expectedHash = hexToBytes(hashHex);\n } catch {\n return false;\n }\n if (\n salt.length !== 32 ||\n expectedHash.length !== PASSWORD_HASH_PARAMS.dkLen\n ) {\n return false;\n }\n\n const actualHash = await scryptAsync(password, salt, PASSWORD_HASH_PARAMS);\n return constantTimeEqual(actualHash, expectedHash);\n}\n\nfunction hexToBytes(hex: string) {\n if (hex.length % 2 !== 0) {\n throw new Error(\"Invalid password hash\");\n }\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n const start = i * 2;\n const value = Number.parseInt(hex.slice(start, start + 2), 16);\n if (Number.isNaN(value)) {\n throw new Error(\"Invalid password hash\");\n }\n bytes[i] = value;\n }\n return bytes;\n}\n\nfunction constantTimeEqual(left: Uint8Array, right: Uint8Array) {\n if (left.length !== right.length) {\n return false;\n }\n let diff = 0;\n for (let i = 0; i < left.length; i++) {\n diff |= left[i] ^ right[i];\n }\n return diff === 0;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"password.js","names":[],"sources":["../../src/providers/password.ts"],"sourcesContent":["/**\n * Configure {@link Password} provider for email/password authentication.\n *\n * The `Password` provider supports the following flows, determined\n * by the `flow` parameter:\n *\n * - `\"signUp\"`: Create a new account with a password.\n * - `\"signIn\"`: Sign in with an existing account and password.\n * - `\"reset\"`: Request a password reset.\n * - `\"reset-verification\"`: Verify a password reset code and change password.\n * - `\"email-verification\"`: If email verification is enabled and `code` is\n * included in params, verify an OTP.\n *\n * ```ts\n * import { Password } from \"@robelest/convex-auth/providers\";\n *\n * new Password()\n * ```\n *\n * @module\n */\n\nimport { scryptAsync } from \"@noble/hashes/scrypt.js\";\nimport { bytesToHex } from \"@noble/hashes/utils.js\";\nimport { Fx } from \"@robelest/fx\";\nimport {\n DocumentByName,\n GenericDataModel,\n WithoutSystemFields,\n} from \"convex/server\";\nimport { Value } from \"convex/values\";\n\nimport type {\n EmailConfig,\n GenericActionCtxWithAuthConfig,\n GenericDoc,\n AuthProviderConfig,\n ConvexCredentialsConfig,\n} from \"../server/types\";\nimport { Credentials, type CredentialsConfig } from \"./credentials\";\n\n/**\n * The available options to a {@link Password} provider for Convex Auth.\n */\nexport interface PasswordConfig<DataModel extends GenericDataModel> {\n /**\n * Uniquely identifies the provider, allowing to use\n * multiple different {@link Password} providers.\n */\n id?: string;\n /**\n * Perform checks on provided params and customize the user\n * information stored after sign up, including email normalization.\n *\n * Called for every flow (\"signUp\", \"signIn\", \"reset\",\n * \"reset-verification\" and \"email-verification\").\n */\n profile?: (\n /**\n * The values passed to the `signIn` function.\n */\n params: Record<string, Value | undefined>,\n /**\n * Convex ActionCtx in case you want to read from or write to\n * the database.\n */\n ctx: GenericActionCtxWithAuthConfig<DataModel>,\n ) => WithoutSystemFields<DocumentByName<DataModel, \"User\">> & {\n email: string;\n };\n /**\n * Performs custom validation on password provided during sign up or reset.\n *\n * Otherwise the default validation is used (password is not empty and\n * at least 8 characters in length).\n *\n * If the provided password is invalid, implementations must throw an Error.\n *\n * @param password the password supplied during \"signUp\" or\n * \"reset-verification\" flows.\n */\n validatePasswordRequirements?: (password: string) => void;\n /**\n * Provide hashing and verification functions if you want to control\n * how passwords are hashed.\n */\n crypto?: CredentialsConfig[\"crypto\"];\n /**\n * An email provider used to require verification\n * before password reset.\n */\n reset?: EmailConfig | ((...args: any) => EmailConfig);\n /**\n * An email provider used to require verification\n * before sign up / sign in.\n */\n verify?: EmailConfig | ((...args: any) => EmailConfig);\n}\n\ntype PasswordFlowDispatch =\n | { tag: \"signUp\" }\n | { tag: \"signIn\" }\n | { tag: \"reset\" }\n | { tag: \"resetVerification\" }\n | { tag: \"emailVerification\" }\n | { tag: \"invalid\"; flow: unknown };\n\nconst PASSWORD_FLOW_TAG = {\n signUp: \"signUp\",\n signIn: \"signIn\",\n reset: \"reset\",\n \"reset-verification\": \"resetVerification\",\n \"email-verification\": \"emailVerification\",\n} as const;\n\ntype PasswordFlowInput = keyof typeof PASSWORD_FLOW_TAG;\n\nfunction decodePasswordFlow(flow: unknown): PasswordFlowDispatch {\n if (typeof flow !== \"string\") {\n return { tag: \"invalid\", flow };\n }\n\n const tag = PASSWORD_FLOW_TAG[flow as PasswordFlowInput];\n return tag === undefined ? { tag: \"invalid\", flow } : { tag };\n}\n\n/**\n * Email and password authentication provider.\n *\n * Passwords are by default hashed using scrypt.\n * You can customize the hashing via the `crypto` option.\n *\n * Email verification is not required unless you pass\n * an email provider to the `verify` option.\n *\n * @example\n * ```ts\n * import { Password } from \"@robelest/convex-auth/providers\";\n *\n * new Password()\n * new Password({ verify: myEmailProvider })\n * ```\n */\nexport class Password<DataModel extends GenericDataModel = GenericDataModel> {\n readonly id: string;\n readonly type = \"credentials\" as const;\n readonly config: PasswordConfig<DataModel>;\n\n constructor(\n config: PasswordConfig<DataModel> = {} as PasswordConfig<DataModel>,\n ) {\n this.id = config.id ?? \"password\";\n this.config = config;\n }\n\n /** @internal Convert to the internal materialized config shape. */\n _toMaterialized(): ConvexCredentialsConfig {\n const config = this.config;\n const provider = this.id;\n\n return new Credentials<DataModel>({\n id: \"password\",\n authorize: async (params, ctx) => {\n const flowDispatch = decodePasswordFlow(params.flow);\n\n const validatePasswordRequirements = (password: string) => {\n if (config.validatePasswordRequirements !== undefined) {\n config.validatePasswordRequirements(password);\n return;\n }\n validateDefaultPasswordRequirements(password);\n };\n\n await Fx.run(\n Fx.match(flowDispatch, flowDispatch.tag, {\n signUp: () =>\n Fx.sync(() => {\n validatePasswordRequirements(params.password as string);\n }),\n resetVerification: () =>\n Fx.sync(() => {\n validatePasswordRequirements(params.newPassword as string);\n }),\n signIn: () => Fx.succeed(undefined),\n reset: () => Fx.succeed(undefined),\n emailVerification: () => Fx.succeed(undefined),\n invalid: () => Fx.succeed(undefined),\n }),\n );\n\n const profile = config.profile?.(params, ctx) ?? defaultProfile(params);\n const { email } = profile;\n const requirePasswordParam = (\n value: unknown,\n flow: \"signUp\" | \"signIn\",\n ) => {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(`Missing \\`password\\` param for \\`${flow}\\` flow`);\n }\n return value;\n };\n\n const finalizeCredentialsResult = async (\n account: GenericDoc<DataModel, \"Account\">,\n user: GenericDoc<DataModel, \"User\">,\n ) => {\n if (config.verify && !account.emailVerified) {\n return await ctx.auth.provider.signIn(\n ctx,\n config.verify as AuthProviderConfig,\n {\n accountId: account._id,\n params,\n },\n );\n }\n return { userId: user._id };\n };\n\n return await Fx.run(\n Fx.match(flowDispatch, flowDispatch.tag, {\n signUp: () =>\n Fx.promise(async () => {\n const secret = requirePasswordParam(params.password, \"signUp\");\n const created = await ctx.auth.account.create(ctx, {\n provider,\n account: { id: email, secret },\n profile: profile as any,\n shouldLinkViaEmail: config.verify !== undefined,\n shouldLinkViaPhone: false,\n });\n return await finalizeCredentialsResult(\n created.account,\n created.user,\n );\n }),\n signIn: () =>\n Fx.promise(async () => {\n const secret = requirePasswordParam(params.password, \"signIn\");\n const retrieved = await ctx.auth.account.get(ctx, {\n provider,\n account: { id: email, secret },\n });\n if (retrieved === null) {\n throw new Error(\"Invalid credentials\");\n }\n return await finalizeCredentialsResult(\n retrieved.account,\n retrieved.user,\n );\n }),\n reset: () =>\n Fx.promise(async () => {\n if (!config.reset) {\n throw new Error(\n `Password reset is not enabled for ${provider}`,\n );\n }\n const { account } = await ctx.auth.account.get(ctx, {\n provider,\n account: { id: email },\n });\n return await ctx.auth.provider.signIn(\n ctx,\n config.reset as AuthProviderConfig,\n {\n accountId: account._id,\n params,\n },\n );\n }),\n resetVerification: () =>\n Fx.promise(async () => {\n if (!config.reset) {\n throw new Error(\n `Password reset is not enabled for ${provider}`,\n );\n }\n if (params.newPassword === undefined) {\n throw new Error(\n \"Missing `newPassword` param for `reset-verification` flow\",\n );\n }\n const result = await ctx.auth.provider.signIn(\n ctx,\n config.reset as AuthProviderConfig,\n { params },\n );\n if (result === null) {\n throw new Error(\"Invalid code\");\n }\n const { userId, sessionId } = result;\n const secret = params.newPassword as string;\n await ctx.auth.account.update(ctx, {\n provider,\n account: { id: email, secret },\n });\n await ctx.auth.session.invalidate(ctx, {\n userId,\n except: [sessionId],\n });\n return { userId, sessionId };\n }),\n emailVerification: () =>\n Fx.promise(async () => {\n if (!config.verify) {\n throw new Error(\n `Email verification is not enabled for ${provider}`,\n );\n }\n const { account } = await ctx.auth.account.get(ctx, {\n provider,\n account: { id: email },\n });\n return await ctx.auth.provider.signIn(\n ctx,\n config.verify as AuthProviderConfig,\n {\n accountId: account._id,\n params,\n },\n );\n }),\n invalid: () =>\n Fx.fatal(\n new Error(\n \"Missing `flow` param, it must be one of \" +\n '\"signUp\", \"signIn\", \"reset\", \"reset-verification\" or ' +\n '\"email-verification\"!',\n ),\n ),\n }),\n );\n },\n crypto: config.crypto ?? {\n async hashSecret(password: string) {\n return await hashPassword(password);\n },\n async verifySecret(password: string, hash: string) {\n return await verifyPassword(password, hash);\n },\n },\n extraProviders: [\n config.reset as AuthProviderConfig | undefined,\n config.verify as AuthProviderConfig | undefined,\n ],\n ...config,\n })._toMaterialized();\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction validateDefaultPasswordRequirements(password: string) {\n if (!password || password.length < 8) {\n throw new Error(\"Invalid password\");\n }\n}\n\nfunction defaultProfile(params: Record<string, unknown>) {\n const email = params.email;\n if (typeof email !== \"string\" || email.trim().length === 0) {\n throw new Error(\"Missing `email` param\");\n }\n return {\n email,\n };\n}\n\nconst PASSWORD_HASH_PARAMS = {\n N: 16384,\n r: 16,\n p: 1,\n dkLen: 64,\n} as const;\n\nconst PASSWORD_HASH_PREFIX = `scrypt:N=${PASSWORD_HASH_PARAMS.N},r=${PASSWORD_HASH_PARAMS.r},p=${PASSWORD_HASH_PARAMS.p},dkLen=${PASSWORD_HASH_PARAMS.dkLen}`;\n\nasync function hashPassword(password: string) {\n const salt = crypto.getRandomValues(new Uint8Array(32));\n const hash = await scryptAsync(password, salt, PASSWORD_HASH_PARAMS);\n return `${PASSWORD_HASH_PREFIX}$${bytesToHex(salt)}$${bytesToHex(hash)}`;\n}\n\nasync function verifyPassword(password: string, storedHash: string) {\n const [prefix, saltHex, hashHex] = storedHash.split(\"$\");\n if (\n prefix !== PASSWORD_HASH_PREFIX ||\n saltHex === undefined ||\n hashHex === undefined\n ) {\n return false;\n }\n\n let salt: Uint8Array;\n let expectedHash: Uint8Array;\n try {\n salt = hexToBytes(saltHex);\n expectedHash = hexToBytes(hashHex);\n } catch {\n return false;\n }\n if (\n salt.length !== 32 ||\n expectedHash.length !== PASSWORD_HASH_PARAMS.dkLen\n ) {\n return false;\n }\n\n const actualHash = await scryptAsync(password, salt, PASSWORD_HASH_PARAMS);\n return constantTimeEqual(actualHash, expectedHash);\n}\n\nfunction hexToBytes(hex: string) {\n if (hex.length % 2 !== 0) {\n throw new Error(\"Invalid password hash\");\n }\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n const start = i * 2;\n const value = Number.parseInt(hex.slice(start, start + 2), 16);\n if (Number.isNaN(value)) {\n throw new Error(\"Invalid password hash\");\n }\n bytes[i] = value;\n }\n return bytes;\n}\n\nfunction constantTimeEqual(left: Uint8Array, right: Uint8Array) {\n if (left.length !== right.length) {\n return false;\n }\n let diff = 0;\n for (let i = 0; i < left.length; i++) {\n diff |= left[i] ^ right[i];\n }\n return diff === 0;\n}\n"],"mappings":"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"}
|
package/dist/runtime/invite.js
CHANGED
|
@@ -29,19 +29,13 @@ function createInviteManager(args) {
|
|
|
29
29
|
if (pendingInvite.email) await storageSet(emailKey, pendingInvite.email);
|
|
30
30
|
},
|
|
31
31
|
async acceptInvite() {
|
|
32
|
-
if (!pendingInvite)
|
|
33
|
-
ok: false,
|
|
34
|
-
message: "No pending invite"
|
|
35
|
-
};
|
|
32
|
+
if (!pendingInvite) throw new Error("No pending invite to accept.");
|
|
36
33
|
const { token } = pendingInvite;
|
|
37
34
|
pendingInvite = null;
|
|
38
35
|
storageRemove(tokenKey);
|
|
39
36
|
storageRemove(emailKey);
|
|
40
37
|
cleanUrlParams(["invite", "email"]);
|
|
41
|
-
return {
|
|
42
|
-
ok: true,
|
|
43
|
-
token
|
|
44
|
-
};
|
|
38
|
+
return { token };
|
|
45
39
|
}
|
|
46
40
|
};
|
|
47
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite.js","names":[],"sources":["../../src/client/runtime/invite.ts"],"sourcesContent":["/** @internal */\nexport function createInviteManager(args: {\n param: (name: string) => string | null;\n storageGet: (name: string) => Promise<string | null>;\n storageSet: (name: string, value: string) => Promise<void>;\n storageRemove: (name: string) => Promise<void>;\n cleanUrlParams: (params: string[]) => void;\n tokenKey: string;\n emailKey: string;\n}) {\n const {\n param,\n storageGet,\n storageSet,\n storageRemove,\n cleanUrlParams,\n tokenKey,\n emailKey,\n } = args;\n\n let pendingInvite: { token: string; email: string | null } | null = null;\n\n const urlInviteToken = param(\"invite\");\n if (urlInviteToken) {\n pendingInvite = { token: urlInviteToken, email: param(\"email\") };\n } else {\n void (async () => {\n const storedToken = await storageGet(tokenKey);\n if (storedToken && !pendingInvite) {\n pendingInvite = {\n token: storedToken,\n email: (await storageGet(emailKey)) ?? null,\n };\n void storageRemove(tokenKey);\n void storageRemove(emailKey);\n }\n })();\n }\n\n return {\n getPendingInvite() {\n return pendingInvite;\n },\n async persistInvite() {\n if (!pendingInvite) return;\n await storageSet(tokenKey, pendingInvite.token);\n if (pendingInvite.email) {\n await storageSet(emailKey, pendingInvite.email);\n }\n },\n async acceptInvite(): Promise<{
|
|
1
|
+
{"version":3,"file":"invite.js","names":[],"sources":["../../src/client/runtime/invite.ts"],"sourcesContent":["/** @internal */\nexport function createInviteManager(args: {\n param: (name: string) => string | null;\n storageGet: (name: string) => Promise<string | null>;\n storageSet: (name: string, value: string) => Promise<void>;\n storageRemove: (name: string) => Promise<void>;\n cleanUrlParams: (params: string[]) => void;\n tokenKey: string;\n emailKey: string;\n}) {\n const {\n param,\n storageGet,\n storageSet,\n storageRemove,\n cleanUrlParams,\n tokenKey,\n emailKey,\n } = args;\n\n let pendingInvite: { token: string; email: string | null } | null = null;\n\n const urlInviteToken = param(\"invite\");\n if (urlInviteToken) {\n pendingInvite = { token: urlInviteToken, email: param(\"email\") };\n } else {\n void (async () => {\n const storedToken = await storageGet(tokenKey);\n if (storedToken && !pendingInvite) {\n pendingInvite = {\n token: storedToken,\n email: (await storageGet(emailKey)) ?? null,\n };\n void storageRemove(tokenKey);\n void storageRemove(emailKey);\n }\n })();\n }\n\n return {\n getPendingInvite() {\n return pendingInvite;\n },\n async persistInvite() {\n if (!pendingInvite) return;\n await storageSet(tokenKey, pendingInvite.token);\n if (pendingInvite.email) {\n await storageSet(emailKey, pendingInvite.email);\n }\n },\n async acceptInvite(): Promise<{ token: string }> {\n if (!pendingInvite) {\n throw new Error(\"No pending invite to accept.\");\n }\n const { token } = pendingInvite;\n pendingInvite = null;\n void storageRemove(tokenKey);\n void storageRemove(emailKey);\n cleanUrlParams([\"invite\", \"email\"]);\n return { token };\n },\n };\n}\n"],"mappings":";;AACA,SAAgB,oBAAoB,MAQjC;CACD,MAAM,EACJ,OACA,YACA,YACA,eACA,gBACA,UACA,aACE;CAEJ,IAAI,gBAAgE;CAEpE,MAAM,iBAAiB,MAAM,SAAS;AACtC,KAAI,eACF,iBAAgB;EAAE,OAAO;EAAgB,OAAO,MAAM,QAAQ;EAAE;KAEhE,EAAM,YAAY;EAChB,MAAM,cAAc,MAAM,WAAW,SAAS;AAC9C,MAAI,eAAe,CAAC,eAAe;AACjC,mBAAgB;IACd,OAAO;IACP,OAAQ,MAAM,WAAW,SAAS,IAAK;IACxC;AACD,GAAK,cAAc,SAAS;AAC5B,GAAK,cAAc,SAAS;;KAE5B;AAGN,QAAO;EACL,mBAAmB;AACjB,UAAO;;EAET,MAAM,gBAAgB;AACpB,OAAI,CAAC,cAAe;AACpB,SAAM,WAAW,UAAU,cAAc,MAAM;AAC/C,OAAI,cAAc,MAChB,OAAM,WAAW,UAAU,cAAc,MAAM;;EAGnD,MAAM,eAA2C;AAC/C,OAAI,CAAC,cACH,OAAM,IAAI,MAAM,+BAA+B;GAEjD,MAAM,EAAE,UAAU;AAClB,mBAAgB;AAChB,GAAK,cAAc,SAAS;AAC5B,GAAK,cAAc,SAAS;AAC5B,kBAAe,CAAC,UAAU,QAAQ,CAAC;AACnC,UAAO,EAAE,OAAO;;EAEnB"}
|