@voltro/plugin-auth 0.84.0 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/strategy.js CHANGED
@@ -1,3 +1,65 @@
1
- import "./session.js";
2
- import { t as e } from "./strategy-XaGjSicX.js";
3
- export { e as voltroPasswordStrategy };
1
+ import { sessionSecretsOf as e } from "./session.js";
2
+ import { makeSessionRevocationChecker as t } from "./revocation.js";
3
+ import { sessionIdOfSubject as n } from "./userStore.js";
4
+ import { Effect as r } from "effect";
5
+ import { readSessionValue as i, resolveSessionSecret as a, verifySessionKeyed as o } from "@voltro/protocol/session";
6
+ //#region src/strategy.ts
7
+ var s = (e) => typeof e.isLive == "function", c = (e) => e.revocation && s(e.revocation) ? e.revocation : e.store ? t(e.store, e.revocation ?? {}) : null, l = (l = {}) => {
8
+ let d = l.cookieName ?? "voltro:session", f = c(l), p = l.revocation && !s(l.revocation) ? l.revocation : {}, m = /* @__PURE__ */ new WeakMap(), h = (e) => {
9
+ if (f) return f;
10
+ let n = l.sessionRegistry;
11
+ if (n === void 0) return null;
12
+ if (typeof n != "function") return t(n, p);
13
+ if (e === void 0) return null;
14
+ let r = m.get(e);
15
+ if (r) return r;
16
+ let i = t(n(e), p);
17
+ return m.set(e, i), i;
18
+ };
19
+ return {
20
+ id: "voltro-password",
21
+ resolve: (t) => {
22
+ let s = i(t.headers, d);
23
+ if (!s) return { kind: "skip" };
24
+ let c = e(l.secret ?? a()), f = o(s, c);
25
+ if (!f) return {
26
+ kind: "failed",
27
+ reason: "voltro session cookie failed verification"
28
+ };
29
+ let p = u(f.subject, "voltro-password"), m = n(p);
30
+ if (!m) return l.requireSessionId === !0 ? {
31
+ kind: "failed",
32
+ reason: "voltro session cookie carries no session id"
33
+ } : {
34
+ kind: "matched",
35
+ subject: p
36
+ };
37
+ let g = h(t.store);
38
+ return g ? r.runPromise(g.isLive(m)).then((e) => e ? {
39
+ kind: "matched",
40
+ subject: p
41
+ } : {
42
+ kind: "failed",
43
+ reason: "voltro session revoked"
44
+ }) : l.requireSessionId === !0 && l.sessionRegistry !== void 0 ? {
45
+ kind: "failed",
46
+ reason: "session registry not available yet"
47
+ } : {
48
+ kind: "matched",
49
+ subject: p
50
+ };
51
+ }
52
+ };
53
+ }, u = (e, t) => {
54
+ if (e.type === "anonymous") return e;
55
+ let n = e.metadata ?? {};
56
+ return {
57
+ ...e,
58
+ metadata: {
59
+ ...n,
60
+ provider: t
61
+ }
62
+ };
63
+ };
64
+ //#endregion
65
+ export { l as voltroPasswordStrategy };
@@ -0,0 +1,11 @@
1
+ import { Effect as e } from "effect";
2
+ //#region src/subjectGuard.ts
3
+ var t = { ok: !0 }, n = (n, r) => e.gen(function* () {
4
+ for (let e of n) {
5
+ let t = yield* e(r);
6
+ if (!t.ok) return t;
7
+ }
8
+ return t;
9
+ });
10
+ //#endregion
11
+ export { n as runSubjectGuards };
package/dist/totp.js ADDED
@@ -0,0 +1,55 @@
1
+ import { createHmac as e, randomBytes as t, timingSafeEqual as n } from "node:crypto";
2
+ import { Data as r, Effect as i } from "effect";
3
+ //#region src/totp.ts
4
+ var a = 30, o = 6, s = 20, c = "sha1", l = 1, u = class extends r.TaggedError("TotpVerifyError") {}, d = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", f = (e) => {
5
+ let t = 0, n = 0, r = "";
6
+ for (let i of e) for (n = n << 8 | i, t += 8; t >= 5;) r += d[n >>> t - 5 & 31], t -= 5;
7
+ return t > 0 && (r += d[n << 5 - t & 31]), r;
8
+ }, p = (e) => {
9
+ let t = e.toUpperCase().replace(/[\s=]+/g, "");
10
+ if (!/^[A-Z2-7]+$/.test(t)) return null;
11
+ let n = new Uint8Array(Math.floor(t.length * 5 / 8)), r = 0, i = 0, a = 0;
12
+ for (let e of t) {
13
+ let t = d.indexOf(e);
14
+ if (t === -1) return null;
15
+ i = i << 5 | t, r += 5, r >= 8 && (n[a++] = i >>> r - 8 & 255, r -= 8);
16
+ }
17
+ return n.subarray(0, a);
18
+ }, m = (e) => {
19
+ let t = Buffer.alloc(8);
20
+ return t.writeBigUInt64BE(BigInt(e), 0), t;
21
+ }, h = (t, n) => {
22
+ let r = e(c, Buffer.from(t));
23
+ r.update(m(n));
24
+ let i = r.digest(), a = i[i.length - 1] & 15, s = (i[a] & 127) << 24 | (i[a + 1] & 255) << 16 | (i[a + 2] & 255) << 8 | i[a + 3] & 255;
25
+ return String(s % 10 ** o).padStart(o, "0");
26
+ }, g = () => f(t(s)), _ = (e) => `otpauth://totp/${encodeURIComponent(`${e.issuer}:${e.accountName}`)}?${new URLSearchParams({
27
+ secret: e.secret,
28
+ issuer: e.issuer,
29
+ algorithm: "SHA1",
30
+ digits: String(o),
31
+ period: String(a)
32
+ }).toString()}`, v = (e, t = Date.now()) => {
33
+ let n = p(e);
34
+ return n ? h(n, Math.floor(t / 1e3 / a)) : "000000";
35
+ }, y = (e, t, r = Date.now()) => i.gen(function* () {
36
+ let o = p(e);
37
+ if (!o) return yield* i.fail(new u({ reason: "malformedSecret" }));
38
+ let s = t.replace(/\s+/g, "");
39
+ if (!/^[0-9]{6}$/.test(s)) return yield* i.fail(new u({ reason: "invalidCode" }));
40
+ let c = Math.floor(r / 1e3 / a), d = Buffer.from(s);
41
+ for (let e = -1; e <= l; e++) {
42
+ let t = h(o, c + e), r = Buffer.from(t);
43
+ if (r.length === d.length && n(r, d)) return !0;
44
+ }
45
+ return yield* i.fail(new u({ reason: "invalidCode" }));
46
+ }), b = (e = 10) => {
47
+ let n = [];
48
+ for (let r = 0; r < e; r++) {
49
+ let e = f(t(16)).slice(0, 25).match(/.{1,5}/g) ?? [];
50
+ n.push(e.join("-"));
51
+ }
52
+ return n;
53
+ };
54
+ //#endregion
55
+ export { u as TotpVerifyError, b as generateRecoveryCodes, v as generateTotpCode, g as generateTotpSecret, _ as otpauthUrl, y as verifyTotpCode };
@@ -0,0 +1,285 @@
1
+ import { Data as e, Effect as t } from "effect";
2
+ import { generateId as n } from "@voltro/database";
3
+ //#region src/userStore.ts
4
+ var r = class extends e.TaggedError("UserAlreadyExistsError") {}, i = class extends e.TaggedError("UserNotFoundError") {}, a = (e) => n({
5
+ kind: "typeid",
6
+ prefix: e
7
+ }, e), o = (e = []) => {
8
+ let n = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), s = [], c = [], l = [], u = [], d = [], f = [], p = [], m = /* @__PURE__ */ new Map();
9
+ for (let t of e) n.set(t.id, t), o.set(t.email.toLowerCase(), t);
10
+ let h = (e, r) => t.gen(function* () {
11
+ let a = n.get(e);
12
+ if (!a) return yield* t.fail(new i({ userId: e }));
13
+ let s = r(a);
14
+ return n.set(e, s), o.set(s.email.toLowerCase(), s), s;
15
+ });
16
+ return {
17
+ findByEmail: (e) => t.sync(() => o.get(e.toLowerCase()) ?? null),
18
+ findById: (e) => t.sync(() => n.get(e) ?? null),
19
+ insert: (e) => t.gen(function* () {
20
+ let i = e.email.toLowerCase();
21
+ if (o.has(i)) return yield* t.fail(new r({ email: i }));
22
+ let a = {
23
+ ...e,
24
+ email: i,
25
+ createdAt: /* @__PURE__ */ new Date()
26
+ };
27
+ return n.set(a.id, a), o.set(i, a), a;
28
+ }),
29
+ updatePassword: (e, t) => h(e, (e) => ({
30
+ ...e,
31
+ passwordHash: t
32
+ })),
33
+ setMfaSecret: (e, t) => h(e, (e) => ({
34
+ ...e,
35
+ mfaSecret: t,
36
+ mfaEnrolledAt: null
37
+ })),
38
+ markMfaEnrolled: (e) => h(e, (e) => ({
39
+ ...e,
40
+ mfaEnrolledAt: /* @__PURE__ */ new Date()
41
+ })),
42
+ clearMfa: (e) => h(e, (e) => ({
43
+ ...e,
44
+ mfaSecret: null,
45
+ mfaEnrolledAt: null
46
+ })),
47
+ markEmailVerified: (e) => h(e, (e) => e.emailVerifiedAt ? e : {
48
+ ...e,
49
+ emailVerifiedAt: /* @__PURE__ */ new Date()
50
+ }),
51
+ listMemberships: (e) => t.sync(() => s.filter((t) => t.userId === e)),
52
+ addMembership: (e) => t.sync(() => {
53
+ let t = s.findIndex((t) => t.userId === e.userId && t.tenantId === e.tenantId), n = {
54
+ ...e,
55
+ joinedAt: /* @__PURE__ */ new Date()
56
+ };
57
+ if (t >= 0) {
58
+ let e = s[t], r = {
59
+ ...n,
60
+ joinedAt: e.joinedAt
61
+ };
62
+ return s[t] = r, r;
63
+ }
64
+ return s.push(n), n;
65
+ }),
66
+ membershipRole: (e, n) => t.sync(() => s.find((t) => t.userId === e && t.tenantId === n)?.role ?? null),
67
+ listSessions: (e) => t.sync(() => c.filter((t) => t.userId === e && t.rotatedAt == null).sort((e, t) => t.createdAt.getTime() - e.createdAt.getTime())),
68
+ insertSession: (e) => t.sync(() => {
69
+ let t = /* @__PURE__ */ new Date(), n = {
70
+ ...e,
71
+ familyId: e.familyId ?? e.id,
72
+ rotatedAt: e.rotatedAt ?? null,
73
+ createdAt: t,
74
+ lastSeenAt: t
75
+ };
76
+ return c.push(n), n;
77
+ }),
78
+ findSession: (e) => t.sync(() => c.find((t) => t.id === e && t.rotatedAt == null) ?? null),
79
+ touchSession: (e, n) => t.sync(() => {
80
+ let t = c.findIndex((t) => t.id === e);
81
+ if (t >= 0 && c[t].rotatedAt == null) {
82
+ c[t] = {
83
+ ...c[t],
84
+ expiresAt: n,
85
+ lastSeenAt: /* @__PURE__ */ new Date()
86
+ };
87
+ let r = c[t].familyId ?? e, i = c.findIndex((e) => e.id === r);
88
+ i >= 0 && c[i].expiresAt < n && (c[i] = {
89
+ ...c[i],
90
+ expiresAt: n
91
+ });
92
+ }
93
+ }),
94
+ revokeSession: (e, n) => t.sync(() => {
95
+ let t = c.find((t) => t.id === n && t.userId === e);
96
+ if (!t) return !1;
97
+ let r = t.familyId ?? t.id, i = !1;
98
+ for (let t = c.length - 1; t >= 0; t--) {
99
+ let n = c[t];
100
+ n.userId === e && (n.familyId ?? n.id) === r && (c.splice(t, 1), i = !0);
101
+ }
102
+ return i;
103
+ }),
104
+ revokeAllOtherSessions: (e, n) => t.sync(() => {
105
+ let t = c.find((t) => t.id === n && t.userId === e), r = t ? t.familyId ?? t.id : void 0, i = 0;
106
+ for (let t = c.length - 1; t >= 0; t--) {
107
+ let n = c[t];
108
+ n.userId === e && (n.familyId ?? n.id) !== r && (c.splice(t, 1), n.rotatedAt ?? (i += 1));
109
+ }
110
+ return i;
111
+ }),
112
+ revokeAllSessions: (e) => t.sync(() => {
113
+ let t = 0;
114
+ for (let n = c.length - 1; n >= 0; n--) if (c[n].userId === e) {
115
+ let e = c[n];
116
+ c.splice(n, 1), e.rotatedAt ?? t++;
117
+ }
118
+ return t;
119
+ }),
120
+ insertToken: (e) => t.sync(() => {
121
+ let t = {
122
+ ...e,
123
+ consumedAt: null,
124
+ createdAt: /* @__PURE__ */ new Date()
125
+ };
126
+ return l.push(t), t;
127
+ }),
128
+ consumeToken: (e, n) => t.sync(() => {
129
+ let t = Date.now(), r = l.find((r) => r.tokenHash === e && r.purpose === n && r.consumedAt === null && r.expiresAt.getTime() > t);
130
+ if (!r) return null;
131
+ let i = {
132
+ ...r,
133
+ consumedAt: /* @__PURE__ */ new Date()
134
+ }, a = l.indexOf(r);
135
+ return l[a] = i, i;
136
+ }),
137
+ latestToken: (e, n) => t.sync(() => {
138
+ let t = null;
139
+ for (let r of l) r.userId === e && r.purpose === n && (t === null || r.createdAt.getTime() > t.createdAt.getTime()) && (t = r);
140
+ return t;
141
+ }),
142
+ insertInvitation: (e) => t.sync(() => {
143
+ let t = {
144
+ ...e,
145
+ email: e.email.toLowerCase(),
146
+ acceptedAt: null,
147
+ acceptedByUserId: null,
148
+ revokedAt: null,
149
+ revokedByUserId: null,
150
+ createdAt: /* @__PURE__ */ new Date()
151
+ };
152
+ return f.push(t), t;
153
+ }),
154
+ findInvitationByHash: (e) => t.sync(() => f.find((t) => t.tokenHash === e) ?? null),
155
+ acceptInvitation: (e, n, r) => t.sync(() => {
156
+ let t = f.findIndex((t) => t.tokenHash === e && t.acceptedAt === null && t.revokedAt === null && t.expiresAt.getTime() > r.getTime());
157
+ if (t < 0) return null;
158
+ let i = {
159
+ ...f[t],
160
+ acceptedAt: r,
161
+ acceptedByUserId: n
162
+ };
163
+ return f[t] = i, i;
164
+ }),
165
+ revokeInvitation: (e, n, r, i) => t.sync(() => {
166
+ let t = f.findIndex((t) => t.id === e && t.tenantId === n && t.acceptedAt === null && t.revokedAt === null);
167
+ return t < 0 ? !1 : (f[t] = {
168
+ ...f[t],
169
+ revokedAt: i,
170
+ revokedByUserId: r
171
+ }, !0);
172
+ }),
173
+ listInvitations: (e) => t.sync(() => f.filter((t) => t.tenantId === e).sort((e, t) => t.createdAt.getTime() - e.createdAt.getTime())),
174
+ countPendingInvitations: (e, n) => t.sync(() => f.filter((t) => t.tenantId === e && t.acceptedAt === null && t.revokedAt === null && t.expiresAt.getTime() > n.getTime()).length),
175
+ supersedePendingInvitations: (e, n, r, i) => t.sync(() => {
176
+ let t = n.toLowerCase(), a = 0;
177
+ for (let n = 0; n < f.length; n++) {
178
+ let o = f[n];
179
+ o.tenantId === e && o.email === t && o.acceptedAt === null && o.revokedAt === null && (f[n] = {
180
+ ...o,
181
+ revokedAt: i,
182
+ revokedByUserId: r
183
+ }, a++);
184
+ }
185
+ return a;
186
+ }),
187
+ insertImpersonationGrant: (e) => t.sync(() => {
188
+ let t = {
189
+ ...e,
190
+ startedAt: /* @__PURE__ */ new Date(),
191
+ endedAt: null,
192
+ endedReason: null
193
+ };
194
+ return p.push(t), t;
195
+ }),
196
+ findImpersonationGrantBySession: (e) => t.sync(() => p.find((t) => t.sessionId === e) ?? null),
197
+ endImpersonationGrant: (e, n, r) => t.sync(() => {
198
+ let t = p.findIndex((t) => t.sessionId === e && t.endedAt === null);
199
+ return t < 0 ? !1 : (p[t] = {
200
+ ...p[t],
201
+ endedAt: r,
202
+ endedReason: n
203
+ }, !0);
204
+ }),
205
+ replaceRecoveryCodes: (e, n) => t.sync(() => {
206
+ for (let t = d.length - 1; t >= 0; t--) d[t].userId === e && d.splice(t, 1);
207
+ let t = /* @__PURE__ */ new Date();
208
+ for (let r of n) d.push({
209
+ id: a("rc"),
210
+ userId: e,
211
+ codeHash: r,
212
+ consumedAt: null,
213
+ createdAt: t
214
+ });
215
+ }),
216
+ consumeRecoveryCode: (e, n) => t.sync(() => {
217
+ let t = d.findIndex((t) => t.userId === e && t.codeHash === n && t.consumedAt === null);
218
+ return t < 0 ? !1 : (d[t] = {
219
+ ...d[t],
220
+ consumedAt: /* @__PURE__ */ new Date()
221
+ }, !0);
222
+ }),
223
+ countRecoveryCodes: (e) => t.sync(() => d.filter((t) => t.userId === e && t.consumedAt === null).length),
224
+ listPasskeys: (e) => t.sync(() => u.filter((t) => t.userId === e)),
225
+ findPasskey: (e) => t.sync(() => u.find((t) => t.credentialId === e) ?? null),
226
+ insertPasskey: (e) => t.sync(() => {
227
+ let t = /* @__PURE__ */ new Date(), n = {
228
+ ...e,
229
+ createdAt: t,
230
+ lastUsedAt: null
231
+ };
232
+ return u.push(n), n;
233
+ }),
234
+ advancePasskeyCounter: (e, n) => t.sync(() => {
235
+ let t = u.findIndex((t) => t.credentialId === e);
236
+ return t < 0 || n <= u[t].counter ? !1 : (u[t] = {
237
+ ...u[t],
238
+ counter: n,
239
+ lastUsedAt: /* @__PURE__ */ new Date()
240
+ }, !0);
241
+ }),
242
+ isLockedOut: (e, n) => t.sync(() => {
243
+ let t = m.get(e.toLowerCase());
244
+ return t?.lockedUntil != null && n < t.lockedUntil ? {
245
+ locked: !0,
246
+ retryAfterSeconds: Math.ceil((t.lockedUntil - n) / 1e3)
247
+ } : {
248
+ locked: !1,
249
+ retryAfterSeconds: 0
250
+ };
251
+ }),
252
+ recordLoginFailure: (e, n, r) => t.sync(() => {
253
+ let t = e.toLowerCase(), i = m.get(t), a = i?.windowStartedAt == null || n - i.windowStartedAt >= r.windowMs, o = i?.lockedUntil != null && n >= i.lockedUntil, s = !i || a || o, c = s ? 1 : i.failedCount + 1, l = s ? n : i.windowStartedAt, u = c >= r.maxAttempts ? n + r.lockMs : null;
254
+ m.set(t, {
255
+ failedCount: c,
256
+ windowStartedAt: l,
257
+ lockedUntil: u
258
+ });
259
+ }),
260
+ clearLoginFailures: (e) => t.sync(() => {
261
+ m.delete(e.toLowerCase());
262
+ })
263
+ };
264
+ }, s = (e, t = {}) => {
265
+ let n = { ...t.metadata };
266
+ return t.memberships && (n.memberships = t.memberships.map((e) => ({
267
+ tenantId: e.tenantId,
268
+ role: e.role
269
+ }))), {
270
+ type: "user",
271
+ id: e.id,
272
+ tenantId: t.tenantId ?? e.tenantId,
273
+ ...Object.keys(n).length > 0 ? { metadata: n } : {}
274
+ };
275
+ }, c = (e) => {
276
+ if (e.type !== "user") return null;
277
+ let t = e.metadata?.sessionId;
278
+ return typeof t == "string" && t.length > 0 ? t : null;
279
+ }, l = (e) => {
280
+ if (e.type !== "user") return [];
281
+ let t = e.metadata?.memberships;
282
+ return Array.isArray(t) ? t.filter((e) => typeof e == "object" && !!e && typeof e.tenantId == "string" && typeof e.role == "string") : [];
283
+ };
284
+ //#endregion
285
+ export { r as UserAlreadyExistsError, i as UserNotFoundError, o as memoryUserStore, a as newAuthId, c as sessionIdOfSubject, s as subjectFromUser, l as subjectMemberships };
@@ -0,0 +1,78 @@
1
+ //#region src/web/passkeyClient.ts
2
+ var e = (e) => {
3
+ let t = new Uint8Array(e), n = "";
4
+ for (let e of t) n += String.fromCharCode(e);
5
+ return btoa(n).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6
+ }, t = (e) => {
7
+ let t = e.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - e.length % 4) % 4), n = atob(t), r = new Uint8Array(n.length);
8
+ for (let e = 0; e < n.length; e++) r[e] = n.charCodeAt(e);
9
+ return r.buffer;
10
+ }, n = () => typeof window < "u" && window.PublicKeyCredential !== void 0 && typeof navigator < "u" && !!navigator.credentials, r = async (e, t, n) => await (await (n.fetchImpl ?? fetch)(e, {
11
+ method: "POST",
12
+ credentials: "include",
13
+ headers: {
14
+ "content-type": "application/json",
15
+ ...n.csrfToken ? { "x-csrf-token": n.csrfToken } : {}
16
+ },
17
+ body: JSON.stringify(t)
18
+ })).json(), i = async (i, a = {}) => {
19
+ if (!n()) throw Error("passkeys not supported in this browser");
20
+ let o = a.basePath ?? "/auth", s = await r(`${o}/passkey/register/options`, i, a), c = s.challenge, l = s.rp, u = s.user, d = s.excludeCredentials ?? [], f = await navigator.credentials.create({ publicKey: {
21
+ challenge: t(c),
22
+ rp: l,
23
+ user: {
24
+ id: new TextEncoder().encode(u.id),
25
+ name: u.name,
26
+ displayName: u.displayName
27
+ },
28
+ pubKeyCredParams: [{
29
+ type: "public-key",
30
+ alg: -7
31
+ }, {
32
+ type: "public-key",
33
+ alg: -257
34
+ }],
35
+ excludeCredentials: d.map((e) => ({
36
+ type: "public-key",
37
+ id: t(e.id)
38
+ })),
39
+ authenticatorSelection: {
40
+ userVerification: "preferred",
41
+ residentKey: "preferred"
42
+ },
43
+ timeout: 3e5
44
+ } });
45
+ if (!f) throw Error("credential creation was cancelled");
46
+ let p = f.response, m = typeof p.getAuthenticatorData == "function" ? p.getAuthenticatorData() : /* @__PURE__ */ new ArrayBuffer(0), h = await r(`${o}/passkey/register/verify`, {
47
+ userId: i.userId,
48
+ credentialId: e(f.rawId),
49
+ clientDataJSON: e(p.clientDataJSON),
50
+ authenticatorData: e(m)
51
+ }, a);
52
+ if (!h.ok) throw Error(`passkey registration failed: ${String(h.error)}`);
53
+ return { credentialId: h.credentialId };
54
+ }, a = async (i = {}, a = {}) => {
55
+ if (!n()) throw Error("passkeys not supported in this browser");
56
+ let o = a.basePath ?? "/auth", s = await r(`${o}/passkey/assert/options`, i.userId ? { userId: i.userId } : {}, a), c = s.challenge, l = s.rpId, u = s.allowCredentials ?? [], d = await navigator.credentials.get({ publicKey: {
57
+ challenge: t(c),
58
+ rpId: l,
59
+ allowCredentials: u.map((e) => ({
60
+ type: "public-key",
61
+ id: t(e.id)
62
+ })),
63
+ userVerification: "preferred",
64
+ timeout: 3e5
65
+ } });
66
+ if (!d) throw Error("assertion was cancelled");
67
+ let f = d.response, p = await r(`${o}/passkey/assert/verify`, {
68
+ credentialId: e(d.rawId),
69
+ clientDataJSON: e(f.clientDataJSON),
70
+ authenticatorData: e(f.authenticatorData),
71
+ signature: e(f.signature),
72
+ ...i.userId ? { userId: i.userId } : {}
73
+ }, a);
74
+ if (!p.ok) throw Error(`passkey sign-in failed: ${String(p.error)}`);
75
+ return { subject: p.subject };
76
+ };
77
+ //#endregion
78
+ export { n as isPasskeySupported, i as registerPasskey, a as signInWithPasskey };
@@ -0,0 +1,9 @@
1
+ import { useOptionalSubject as e } from "./subjectContext.js";
2
+ import { Fragment as t, jsx as n } from "react/jsx-runtime";
3
+ //#region src/web/requireAuth.tsx
4
+ var r = ({ children: r, fallback: i }) => {
5
+ let a = e();
6
+ return a ? typeof r == "function" ? /* @__PURE__ */ n(t, { children: r(a) }) : /* @__PURE__ */ n(t, { children: r }) : /* @__PURE__ */ n(t, { children: i });
7
+ };
8
+ //#endregion
9
+ export { r as RequireAuth };
@@ -0,0 +1,13 @@
1
+ import { createContext as e, useContext as t } from "react";
2
+ import { jsx as n } from "react/jsx-runtime";
3
+ //#region src/web/subjectContext.tsx
4
+ var r = e(null), i = ({ subject: e, children: t }) => /* @__PURE__ */ n(r.Provider, {
5
+ value: e,
6
+ children: t
7
+ }), a = () => {
8
+ let e = t(r);
9
+ if (!e) throw Error("useSubject() called without a signed-in user. Use useOptionalSubject() or wrap the consumer in <RequireAuth>.");
10
+ return e;
11
+ }, o = () => t(r);
12
+ //#endregion
13
+ export { i as SubjectProvider, o as useOptionalSubject, a as useSubject };
package/dist/web.js CHANGED
@@ -1,90 +1,4 @@
1
- import { createContext as e, useContext as t } from "react";
2
- import { Fragment as n, jsx as r } from "react/jsx-runtime";
3
- //#region src/web/subjectContext.tsx
4
- var i = e(null), a = ({ subject: e, children: t }) => /* @__PURE__ */ r(i.Provider, {
5
- value: e,
6
- children: t
7
- }), o = () => {
8
- let e = t(i);
9
- if (!e) throw Error("useSubject() called without a signed-in user. Use useOptionalSubject() or wrap the consumer in <RequireAuth>.");
10
- return e;
11
- }, s = () => t(i), c = ({ children: e, fallback: t }) => {
12
- let i = s();
13
- return i ? typeof e == "function" ? /* @__PURE__ */ r(n, { children: e(i) }) : /* @__PURE__ */ r(n, { children: e }) : /* @__PURE__ */ r(n, { children: t });
14
- }, l = (e) => {
15
- let t = new Uint8Array(e), n = "";
16
- for (let e of t) n += String.fromCharCode(e);
17
- return btoa(n).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
18
- }, u = (e) => {
19
- let t = e.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - e.length % 4) % 4), n = atob(t), r = new Uint8Array(n.length);
20
- for (let e = 0; e < n.length; e++) r[e] = n.charCodeAt(e);
21
- return r.buffer;
22
- }, d = () => typeof window < "u" && window.PublicKeyCredential !== void 0 && typeof navigator < "u" && !!navigator.credentials, f = async (e, t, n) => await (await (n.fetchImpl ?? fetch)(e, {
23
- method: "POST",
24
- credentials: "include",
25
- headers: {
26
- "content-type": "application/json",
27
- ...n.csrfToken ? { "x-csrf-token": n.csrfToken } : {}
28
- },
29
- body: JSON.stringify(t)
30
- })).json(), p = async (e, t = {}) => {
31
- if (!d()) throw Error("passkeys not supported in this browser");
32
- let n = t.basePath ?? "/auth", r = await f(`${n}/passkey/register/options`, e, t), i = r.challenge, a = r.rp, o = r.user, s = r.excludeCredentials ?? [], c = await navigator.credentials.create({ publicKey: {
33
- challenge: u(i),
34
- rp: a,
35
- user: {
36
- id: new TextEncoder().encode(o.id),
37
- name: o.name,
38
- displayName: o.displayName
39
- },
40
- pubKeyCredParams: [{
41
- type: "public-key",
42
- alg: -7
43
- }, {
44
- type: "public-key",
45
- alg: -257
46
- }],
47
- excludeCredentials: s.map((e) => ({
48
- type: "public-key",
49
- id: u(e.id)
50
- })),
51
- authenticatorSelection: {
52
- userVerification: "preferred",
53
- residentKey: "preferred"
54
- },
55
- timeout: 3e5
56
- } });
57
- if (!c) throw Error("credential creation was cancelled");
58
- let p = c.response, m = typeof p.getAuthenticatorData == "function" ? p.getAuthenticatorData() : /* @__PURE__ */ new ArrayBuffer(0), h = await f(`${n}/passkey/register/verify`, {
59
- userId: e.userId,
60
- credentialId: l(c.rawId),
61
- clientDataJSON: l(p.clientDataJSON),
62
- authenticatorData: l(m)
63
- }, t);
64
- if (!h.ok) throw Error(`passkey registration failed: ${String(h.error)}`);
65
- return { credentialId: h.credentialId };
66
- }, m = async (e = {}, t = {}) => {
67
- if (!d()) throw Error("passkeys not supported in this browser");
68
- let n = t.basePath ?? "/auth", r = await f(`${n}/passkey/assert/options`, e.userId ? { userId: e.userId } : {}, t), i = r.challenge, a = r.rpId, o = r.allowCredentials ?? [], s = await navigator.credentials.get({ publicKey: {
69
- challenge: u(i),
70
- rpId: a,
71
- allowCredentials: o.map((e) => ({
72
- type: "public-key",
73
- id: u(e.id)
74
- })),
75
- userVerification: "preferred",
76
- timeout: 3e5
77
- } });
78
- if (!s) throw Error("assertion was cancelled");
79
- let c = s.response, p = await f(`${n}/passkey/assert/verify`, {
80
- credentialId: l(s.rawId),
81
- clientDataJSON: l(c.clientDataJSON),
82
- authenticatorData: l(c.authenticatorData),
83
- signature: l(c.signature),
84
- ...e.userId ? { userId: e.userId } : {}
85
- }, t);
86
- if (!p.ok) throw Error(`passkey sign-in failed: ${String(p.error)}`);
87
- return { subject: p.subject };
88
- };
89
- //#endregion
90
- export { c as RequireAuth, a as SubjectProvider, d as isPasskeySupported, p as registerPasskey, m as signInWithPasskey, s as useOptionalSubject, o as useSubject };
1
+ import { SubjectProvider as e, useOptionalSubject as t, useSubject as n } from "./web/subjectContext.js";
2
+ import { RequireAuth as r } from "./web/requireAuth.js";
3
+ import { isPasskeySupported as i, registerPasskey as a, signInWithPasskey as o } from "./web/passkeyClient.js";
4
+ export { r as RequireAuth, e as SubjectProvider, i as isPasskeySupported, a as registerPasskey, o as signInWithPasskey, t as useOptionalSubject, n as useSubject };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-auth",
3
- "version": "0.84.0",
3
+ "version": "0.86.0",
4
4
  "description": "Authentication primitives: password hashing, session creation, schema mixin + tables. Pairs with the `auth` app template for the UI; both independently usable. Server-side only (uses node:crypto).",
5
5
  "keywords": [
6
6
  "voltro",
@@ -83,12 +83,11 @@
83
83
  "node": ">=24.0.0"
84
84
  },
85
85
  "dependencies": {
86
- "@effect/sql": "^0.52.1",
87
- "@voltro/database": "0.84.0",
88
- "@voltro/protocol": "0.84.0"
86
+ "@voltro/database": "0.86.0",
87
+ "@voltro/protocol": "0.86.0"
89
88
  },
90
89
  "peerDependencies": {
91
- "effect": "^3.22.2",
90
+ "effect": "4.0.1",
92
91
  "react": "^19.0.0"
93
92
  },
94
93
  "peerDependenciesMeta": {