@voltro/plugin-auth 0.86.0 → 0.88.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/CHANGELOG.md +5484 -0
- package/README.md +83 -0
- package/THIRD-PARTY-NOTICES.md +3 -227
- package/dist/admission.js +1 -2
- package/dist/appUserStore.js +1 -2
- package/dist/authExtensions.js +8 -0
- package/dist/csrf.js +1 -2
- package/dist/emailVerification.js +14 -13
- package/dist/guest.d.ts +99 -0
- package/dist/guest.js +3 -0
- package/dist/guestClient.js +236 -0
- package/dist/guestCredential.js +11 -0
- package/dist/guestPasswordUpgrade.js +84 -0
- package/dist/guestPlugin.js +165 -0
- package/dist/guestSchema.js +19 -0
- package/dist/guestSecrets.js +9 -0
- package/dist/guestService.js +223 -0
- package/dist/handlers.js +114 -115
- package/dist/impersonation.js +80 -80
- package/dist/index.d.ts +265 -15
- package/dist/index.js +27 -19
- package/dist/invitations.js +24 -25
- package/dist/mail.js +1 -2
- package/dist/memberships.js +1 -2
- package/dist/native/guestStorage.js +40 -0
- package/dist/native.d.ts +110 -0
- package/dist/native.js +46 -45
- package/dist/nativeRoutes.js +62 -74
- package/dist/overlayPlugin.js +166 -0
- package/dist/passkeyHandlers.js +1 -2
- package/dist/password.js +1 -2
- package/dist/plugin.d.ts +119 -10
- package/dist/plugin.js +100 -80
- package/dist/postgresUserStore.js +20 -9
- package/dist/revocation.js +41 -25
- package/dist/schema.d.ts +41 -1
- package/dist/schema.js +100 -100
- package/dist/session.js +1 -2
- package/dist/sessionFamily.js +14 -0
- package/dist/sessionRegistry.js +94 -80
- package/dist/signedClaims.js +23 -0
- package/dist/strategy.d.ts +13 -9
- package/dist/strategy.js +27 -24
- package/dist/subjectGuard.js +1 -2
- package/dist/tokens.js +1 -2
- package/dist/totp.js +1 -2
- package/dist/userStore.js +146 -140
- package/dist/web/guestStorage.js +46 -0
- package/dist/web/passkeyClient.js +1 -2
- package/dist/web/requireAuth.js +1 -2
- package/dist/web/subjectContext.js +1 -2
- package/dist/web.d.ts +94 -0
- package/dist/web.js +4 -2
- package/dist/webauthn.js +1 -2
- package/package.json +9 -4
package/dist/schema.js
CHANGED
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
o,
|
|
97
|
-
s,
|
|
1
|
+
import { authGuestDevicesTable as e, authGuestsTable as t, guestTables as n } from "./guestSchema.js";
|
|
2
|
+
import { id as r, integer as i, table as a, text as o, timestamp as s } from "@voltro/database";
|
|
3
|
+
// src/schema/index.ts
|
|
4
|
+
var c = a("users", {
|
|
5
|
+
id: r(),
|
|
6
|
+
email: o().nullable(),
|
|
7
|
+
passwordHash: o().nullable(),
|
|
8
|
+
tenantId: o(),
|
|
9
|
+
mfaSecret: o().nullable(),
|
|
10
|
+
mfaEnrolledAt: s().nullable(),
|
|
11
|
+
emailVerifiedAt: s().nullable(),
|
|
12
|
+
createdAt: s().default("now"),
|
|
13
|
+
updatedAt: s().default("now").onUpdate("now")
|
|
14
|
+
}).index(["tenantId"]).uniqueActive("usersEmailUnique", ["email"], { where: "\"email\" IS NOT NULL" }), l = a("sessions", {
|
|
15
|
+
id: r(),
|
|
16
|
+
familyId: o().nullable(),
|
|
17
|
+
rotatedAt: s().nullable(),
|
|
18
|
+
userId: o(),
|
|
19
|
+
tenantId: o(),
|
|
20
|
+
expiresAt: s(),
|
|
21
|
+
ipAddress: o().nullable(),
|
|
22
|
+
userAgent: o().nullable(),
|
|
23
|
+
createdAt: s().default("now"),
|
|
24
|
+
lastSeenAt: s().default("now").onUpdate("now")
|
|
25
|
+
}).index(["userId"]).index(["tenantId"]).index(["familyId"]), u = a("memberships", {
|
|
26
|
+
id: r(),
|
|
27
|
+
userId: o(),
|
|
28
|
+
tenantId: o(),
|
|
29
|
+
role: o(),
|
|
30
|
+
joinedAt: s().default("now")
|
|
31
|
+
}).unique("byUserTenant", ["userId", "tenantId"]).index(["tenantId"]), d = a("authTokens", {
|
|
32
|
+
id: r(),
|
|
33
|
+
tokenHash: o().unique(),
|
|
34
|
+
userId: o(),
|
|
35
|
+
purpose: o(),
|
|
36
|
+
expiresAt: s(),
|
|
37
|
+
consumedAt: s().nullable(),
|
|
38
|
+
createdAt: s().default("now")
|
|
39
|
+
}).index(["userId"]).index(["purpose"]), f = a("passkeys", {
|
|
40
|
+
id: r(),
|
|
41
|
+
credentialId: o().unique(),
|
|
42
|
+
userId: o(),
|
|
43
|
+
publicKey: o(),
|
|
44
|
+
counter: i().default(0),
|
|
45
|
+
transports: o().nullable(),
|
|
46
|
+
createdAt: s().default("now"),
|
|
47
|
+
lastUsedAt: s().nullable()
|
|
48
|
+
}).index(["userId"]), p = a("recoveryCodes", {
|
|
49
|
+
id: r(),
|
|
50
|
+
userId: o(),
|
|
51
|
+
codeHash: o(),
|
|
52
|
+
consumedAt: s().nullable(),
|
|
53
|
+
createdAt: s().default("now")
|
|
54
|
+
}).index(["userId"]), m = a("passkeyChallenges", {
|
|
55
|
+
id: r(),
|
|
56
|
+
key: o().unique(),
|
|
57
|
+
challenge: o(),
|
|
58
|
+
expiresAt: s(),
|
|
59
|
+
createdAt: s().default("now")
|
|
60
|
+
}), h = a("loginAttempts", {
|
|
61
|
+
id: r(),
|
|
62
|
+
email: o().unique(),
|
|
63
|
+
failedCount: i().default(0),
|
|
64
|
+
windowStartedAt: s().nullable(),
|
|
65
|
+
lockedUntil: s().nullable(),
|
|
66
|
+
createdAt: s().default("now"),
|
|
67
|
+
updatedAt: s().default("now").onUpdate("now")
|
|
68
|
+
}), g = a("invitations", {
|
|
69
|
+
id: r({ prefix: "inv" }),
|
|
70
|
+
tokenHash: o().unique(),
|
|
71
|
+
email: o(),
|
|
72
|
+
tenantId: o(),
|
|
73
|
+
role: o(),
|
|
74
|
+
invitedByUserId: o(),
|
|
75
|
+
expiresAt: s(),
|
|
76
|
+
acceptedAt: s().nullable(),
|
|
77
|
+
acceptedByUserId: o().nullable(),
|
|
78
|
+
revokedAt: s().nullable(),
|
|
79
|
+
revokedByUserId: o().nullable(),
|
|
80
|
+
createdAt: s().default("now")
|
|
81
|
+
}).index(["tenantId"]).index(["email"]), _ = a("impersonationGrants", {
|
|
82
|
+
id: r({ prefix: "imp" }),
|
|
83
|
+
sessionId: o().unique(),
|
|
84
|
+
actorUserId: o(),
|
|
85
|
+
actorSessionId: o(),
|
|
86
|
+
targetUserId: o(),
|
|
87
|
+
tenantId: o(),
|
|
88
|
+
reason: o().nullable(),
|
|
89
|
+
startedAt: s().default("now"),
|
|
90
|
+
expiresAt: s(),
|
|
91
|
+
endedAt: s().nullable(),
|
|
92
|
+
endedReason: o().nullable(),
|
|
93
|
+
ipAddress: o().nullable(),
|
|
94
|
+
userAgent: o().nullable()
|
|
95
|
+
}).index(["actorUserId"]).index(["targetUserId"]), v = [
|
|
98
96
|
c,
|
|
99
97
|
l,
|
|
100
98
|
u,
|
|
101
99
|
d,
|
|
102
100
|
f,
|
|
103
101
|
p,
|
|
104
|
-
m
|
|
102
|
+
m,
|
|
103
|
+
h,
|
|
104
|
+
g,
|
|
105
|
+
_
|
|
105
106
|
];
|
|
106
|
-
|
|
107
|
-
export { h as authTables, c as authTokensTable, m as impersonationGrantsTable, p as invitationsTable, f as loginAttemptsTable, s as membershipsTable, d as passkeyChallengesTable, l as passkeysTable, u as recoveryCodesTable, o as sessionsTable, a as usersTable };
|
|
107
|
+
export { e as authGuestDevicesTable, t as authGuestsTable, v as authTables, d as authTokensTable, n as guestTables, _ as impersonationGrantsTable, g as invitationsTable, h as loginAttemptsTable, u as membershipsTable, m as passkeyChallengesTable, f as passkeysTable, p as recoveryCodesTable, l as sessionsTable, c as usersTable };
|
package/dist/session.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_SESSION_KID as e, buildSetCookie as t, readCookie as n, readSessionValue as r, resolveOptionalSessionSecrets as i, resolveSessionSecret as a, resolveSessionSecrets as o, signSession as s, timingSafeStringEqual as c, verifySessionKeyed as l } from "@voltro/protocol/session";
|
|
2
|
-
|
|
2
|
+
// src/session.ts
|
|
3
3
|
var u = "voltro:session", d = 604800, f = a, p = o, m = (e) => typeof e == "string" ? e : e.current, h = (t) => {
|
|
4
4
|
if (typeof t != "string") return t;
|
|
5
5
|
let n = i(), r = {
|
|
@@ -40,5 +40,4 @@ var u = "voltro:session", d = 604800, f = a, p = o, m = (e) => typeof e == "stri
|
|
|
40
40
|
...e.domain ? { domain: e.domain } : {},
|
|
41
41
|
path: "/"
|
|
42
42
|
});
|
|
43
|
-
//#endregion
|
|
44
43
|
export { u as SESSION_COOKIE_NAME, b as clearSessionCookie, g as issueSession, _ as readSession, y as readSessionFromHeaders, v as readSessionKeyed, f as resolveSessionSecret, p as resolveSessionSecrets, h as sessionSecretsOf };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/sessionFamily.ts
|
|
2
|
+
var e = (e) => {
|
|
3
|
+
if (typeof e.id != "string" || e.id.length === 0 || typeof e.userId != "string" || e.userId.length === 0) return null;
|
|
4
|
+
let t = e.familyId ?? e.id;
|
|
5
|
+
return typeof t == "string" && t.length > 0 ? t : null;
|
|
6
|
+
}, t = (t, n) => {
|
|
7
|
+
let r = e(t);
|
|
8
|
+
return r !== null && n != null && n.id === r && n.userId === t.userId && e(n) === n.id;
|
|
9
|
+
}, n = (e) => {
|
|
10
|
+
let t = /* @__PURE__ */ new Map();
|
|
11
|
+
for (let n of e) typeof n.id == "string" && n.id.length !== 0 && t.set(n.id, t.has(n.id) ? null : n);
|
|
12
|
+
return t;
|
|
13
|
+
}, r = (e, t, n, r) => e !== null && typeof t == "string" && t.length > 0 && typeof n == "string" && n.length > 0 && Number.isFinite(r) && e.id === t && e.userId === n && e.rotatedAt == null && e.expiresAt instanceof Date && Number.isFinite(e.expiresAt.getTime()) && e.expiresAt.getTime() > r;
|
|
14
|
+
export { t as hasOwnedSessionFamily, n as indexSessionFamilies, r as isSessionCredentialLiveFor, e as sessionFamilyId };
|
package/dist/sessionRegistry.js
CHANGED
|
@@ -1,48 +1,59 @@
|
|
|
1
1
|
import { issueSession as e } from "./session.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { hasOwnedSessionFamily as t, indexSessionFamilies as n, sessionFamilyId as r } from "./sessionFamily.js";
|
|
3
|
+
import { newAuthId as i } from "./userStore.js";
|
|
4
|
+
import { Effect as a } from "effect";
|
|
5
|
+
import { and as o, columnRef as s, eq as c, isNull as l, or as u } from "@voltro/database";
|
|
6
|
+
// src/sessionRegistry.ts
|
|
7
|
+
var d = "sessions", f = 604800, p = (e) => ({
|
|
7
8
|
...e,
|
|
8
9
|
expiresAt: new Date(e.expiresAt),
|
|
9
10
|
createdAt: new Date(e.createdAt),
|
|
10
11
|
lastSeenAt: new Date(e.lastSeenAt),
|
|
11
12
|
rotatedAt: e.rotatedAt == null ? null : new Date(e.rotatedAt)
|
|
12
|
-
}),
|
|
13
|
-
table:
|
|
13
|
+
}), m = async (e, t, n = !1) => await e.query({
|
|
14
|
+
table: d,
|
|
14
15
|
predicate: t,
|
|
15
|
-
order: [
|
|
16
|
+
order: n ? [{
|
|
17
|
+
column: "id",
|
|
18
|
+
direction: "asc"
|
|
19
|
+
}] : [],
|
|
16
20
|
take: void 0,
|
|
17
21
|
skip: void 0,
|
|
18
22
|
projection: void 0,
|
|
19
23
|
...n ? { lock: "update" } : {}
|
|
20
|
-
}),
|
|
21
|
-
let
|
|
22
|
-
if (!
|
|
23
|
-
let
|
|
24
|
-
if (!
|
|
25
|
-
let
|
|
26
|
-
return
|
|
27
|
-
root:
|
|
28
|
-
current:
|
|
29
|
-
rows: c
|
|
24
|
+
}), h = (e, t, n) => t.transaction === "existing" ? n(e) : e.transactional((e) => n(e)), g = (e) => e.familyId ?? e.id, _ = (e) => e.rotatedAt == null, v = async (e, t, n) => {
|
|
25
|
+
let r = (await m(e, c("id", n)))[0];
|
|
26
|
+
if (!r || r.userId !== t) return null;
|
|
27
|
+
let i = g(r), a = (await m(e, c("id", i), !0))[0];
|
|
28
|
+
if (!a || a.userId !== t || g(a) !== i) return null;
|
|
29
|
+
let o = n === a.id ? a : (await m(e, c("id", n), !0))[0];
|
|
30
|
+
return o && o.userId === t && g(o) === i ? {
|
|
31
|
+
root: a,
|
|
32
|
+
current: o
|
|
30
33
|
} : null;
|
|
31
|
-
},
|
|
32
|
-
let r = await
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let a = 0,
|
|
38
|
-
for (let t of r.rows) await e.delete(i, t.id) && (o = !0, u(t) && (a += 1));
|
|
34
|
+
}, y = async (e, t, n) => {
|
|
35
|
+
let r = await v(e, t, n);
|
|
36
|
+
return r && _(r.current) ? p(r.current) : null;
|
|
37
|
+
}, b = async (e, t, n) => {
|
|
38
|
+
if (n.userId !== t || g(n) !== n.id) throw Error("The locked session family requires reconciliation.");
|
|
39
|
+
let r = await m(e, o(c("userId", t), u(c("id", n.id), c("familyId", n.id))), !0), i = 0, a = !1;
|
|
40
|
+
for (let t of r) await e.delete(d, t.id) && (a = !0, _(t) && (i += 1));
|
|
39
41
|
return {
|
|
40
|
-
removed:
|
|
41
|
-
credentials:
|
|
42
|
+
removed: a,
|
|
43
|
+
credentials: i
|
|
42
44
|
};
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
}, x = async (e, t, n) => {
|
|
46
|
+
let r = await v(e, t, n);
|
|
47
|
+
return r === null ? {
|
|
48
|
+
removed: !1,
|
|
49
|
+
credentials: 0
|
|
50
|
+
} : b(e, t, r.root);
|
|
51
|
+
}, S = (e, t) => m(e, o(c("userId", t), u(l("familyId"), c("familyId", s("id")))), !0), C = (e, i = {}) => ({
|
|
52
|
+
listSessions: (i) => a.promise(async () => {
|
|
53
|
+
let a = await m(e, c("userId", i)), o = n(a);
|
|
54
|
+
return a.filter((e) => e.userId === i && _(e) && o.get(e.id) === e && t(e, o.get(r(e) ?? ""))).map(p).sort((e, t) => t.createdAt.getTime() - e.createdAt.getTime());
|
|
55
|
+
}),
|
|
56
|
+
insertSession: (t) => a.promise(async () => {
|
|
46
57
|
let n = /* @__PURE__ */ new Date(), r = {
|
|
47
58
|
...t,
|
|
48
59
|
familyId: t.familyId ?? t.id,
|
|
@@ -50,69 +61,72 @@ var i = "sessions", a = 604800, o = (e) => ({
|
|
|
50
61
|
createdAt: n,
|
|
51
62
|
lastSeenAt: n
|
|
52
63
|
};
|
|
53
|
-
return await e.insert(
|
|
64
|
+
return await e.insert(d, r), r;
|
|
54
65
|
}),
|
|
55
|
-
findSession: (
|
|
56
|
-
let
|
|
57
|
-
|
|
66
|
+
findSession: (n) => a.promise(async () => {
|
|
67
|
+
let i = await m(e, c("id", n)), a = i.length === 1 ? i[0] : void 0;
|
|
68
|
+
if (!a || a.id !== n || !_(a)) return null;
|
|
69
|
+
let o = r(a);
|
|
70
|
+
if (o === null) return null;
|
|
71
|
+
let s = o === a.id ? [a] : await m(e, c("id", o));
|
|
72
|
+
return s.length === 1 && t(a, s[0]) ? p(a) : null;
|
|
58
73
|
}),
|
|
59
|
-
touchSession: (
|
|
60
|
-
let
|
|
61
|
-
if (!
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
expiresAt:
|
|
74
|
+
touchSession: (t, n) => a.promise(() => h(e, i, async (e) => {
|
|
75
|
+
let r = (await m(e, c("id", t)))[0];
|
|
76
|
+
if (!r) return;
|
|
77
|
+
let i = await v(e, r.userId, t);
|
|
78
|
+
i && _(i.current) && (await e.update(d, t, {
|
|
79
|
+
expiresAt: n,
|
|
65
80
|
lastSeenAt: /* @__PURE__ */ new Date()
|
|
66
|
-
}),
|
|
81
|
+
}), i.root.id !== t && new Date(i.root.expiresAt) < n && await e.update(d, i.root.id, { expiresAt: n }));
|
|
67
82
|
})),
|
|
68
|
-
revokeSession: (
|
|
69
|
-
revokeAllOtherSessions: (
|
|
70
|
-
let t = await
|
|
71
|
-
for (let
|
|
72
|
-
return
|
|
83
|
+
revokeSession: (t, n) => a.promise(() => h(e, i, async (e) => (await x(e, t, n)).removed)),
|
|
84
|
+
revokeAllOtherSessions: (t, n) => a.promise(() => h(e, i, async (e) => {
|
|
85
|
+
let r = await S(e, t), i = (await m(e, o(c("userId", t), c("id", n)), !0))[0], a = i && r.some((e) => e.id === g(i)) ? g(i) : void 0, s = 0;
|
|
86
|
+
for (let n of r) n.id !== a && (s += (await b(e, t, n)).credentials);
|
|
87
|
+
return s;
|
|
73
88
|
})),
|
|
74
|
-
revokeAllSessions: (
|
|
75
|
-
let
|
|
76
|
-
for (let
|
|
77
|
-
return
|
|
89
|
+
revokeAllSessions: (t) => a.promise(() => h(e, i, async (e) => {
|
|
90
|
+
let n = await S(e, t), r = 0;
|
|
91
|
+
for (let i of n) r += (await b(e, t, i)).credentials;
|
|
92
|
+
return r;
|
|
78
93
|
}))
|
|
79
|
-
}),
|
|
94
|
+
}), w = (e, t) => e.type === "user" ? {
|
|
80
95
|
...e,
|
|
81
96
|
metadata: {
|
|
82
97
|
...e.metadata ?? {},
|
|
83
98
|
sessionId: t
|
|
84
99
|
}
|
|
85
|
-
} : e,
|
|
86
|
-
if (
|
|
87
|
-
let
|
|
88
|
-
await
|
|
89
|
-
id:
|
|
90
|
-
familyId:
|
|
100
|
+
} : e, T = async (t, n, r, o, s) => {
|
|
101
|
+
if (n.type !== "user") throw Error("issueRegisteredSession: only a user subject has a session row");
|
|
102
|
+
let c = i("sess"), l = o.ttlSeconds ?? f, u = (o.now ?? Date.now)();
|
|
103
|
+
await a.runPromise(t.insertSession({
|
|
104
|
+
id: c,
|
|
105
|
+
familyId: s ?? c,
|
|
91
106
|
rotatedAt: null,
|
|
92
|
-
userId:
|
|
93
|
-
tenantId:
|
|
94
|
-
expiresAt: new Date(
|
|
95
|
-
ipAddress:
|
|
96
|
-
userAgent:
|
|
107
|
+
userId: n.id,
|
|
108
|
+
tenantId: n.tenantId ?? "",
|
|
109
|
+
expiresAt: new Date(u + l * 1e3),
|
|
110
|
+
ipAddress: o.ipAddress ?? null,
|
|
111
|
+
userAgent: o.userAgent ?? null
|
|
97
112
|
}));
|
|
98
|
-
let { ipAddress:
|
|
113
|
+
let { ipAddress: d, userAgent: p, ...m } = o;
|
|
99
114
|
return {
|
|
100
|
-
...e(
|
|
101
|
-
sessionId:
|
|
115
|
+
...e(w(n, c), r, m),
|
|
116
|
+
sessionId: c
|
|
102
117
|
};
|
|
103
|
-
},
|
|
104
|
-
let
|
|
105
|
-
if (!
|
|
106
|
-
let
|
|
107
|
-
await e.update(
|
|
108
|
-
familyId:
|
|
109
|
-
rotatedAt: new Date(
|
|
110
|
-
}), await e.update(
|
|
111
|
-
familyId:
|
|
112
|
-
expiresAt: new Date(Math.max(new Date(
|
|
118
|
+
}, E = async (e, t, n, r = {}) => T(e, t, n, r), D = async (e, t, n, r, i = {}) => t.type === "user" ? h(e, i, async (e) => {
|
|
119
|
+
let a = await v(e, t.id, n), o = (i.now ?? Date.now)();
|
|
120
|
+
if (!a || !_(a.current) || new Date(a.current.expiresAt).getTime() <= o) return null;
|
|
121
|
+
let s = new Date(o + (i.ttlSeconds ?? f) * 1e3);
|
|
122
|
+
await e.update(d, a.current.id, {
|
|
123
|
+
familyId: a.root.id,
|
|
124
|
+
rotatedAt: new Date(o)
|
|
125
|
+
}), await e.update(d, a.root.id, {
|
|
126
|
+
familyId: a.root.id,
|
|
127
|
+
expiresAt: new Date(Math.max(new Date(a.root.expiresAt).getTime(), s.getTime()))
|
|
113
128
|
});
|
|
114
|
-
let { transaction:
|
|
115
|
-
return
|
|
129
|
+
let { transaction: c, ...l } = i;
|
|
130
|
+
return T(C(e, { transaction: "existing" }), t, r, l, a.root.id);
|
|
116
131
|
}) : null;
|
|
117
|
-
|
|
118
|
-
export { p as dataStoreSessionRegistry, g as issueRegisteredSession, _ as rotateRegisteredSession, m as withSessionId };
|
|
132
|
+
export { C as dataStoreSessionRegistry, E as issueRegisteredSession, y as lockRegisteredSession, D as rotateRegisteredSession, w as withSessionId };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createHmac as e, timingSafeEqual as t } from "node:crypto";
|
|
2
|
+
// src/signedClaims.ts
|
|
3
|
+
var n = (t, n, r) => e("sha256", n).update(`${r}:${t}`).digest(), r = (e, t, r) => {
|
|
4
|
+
if (t.length < 32 || typeof e.exp != "number" || !Number.isSafeInteger(e.exp)) throw Error("Auth handoff requires a project signing key and whole-second expiry");
|
|
5
|
+
let i = Buffer.from(JSON.stringify(e), "utf8").toString("base64url");
|
|
6
|
+
if (i.length > 8192) throw Error("Auth handoff exceeds its payload budget");
|
|
7
|
+
return `${i}.${n(i, t, r).toString("base64url")}`;
|
|
8
|
+
}, i = (e, r, i, a = Date.now) => {
|
|
9
|
+
if (!e || e.length > 8256 || r.length < 32) return null;
|
|
10
|
+
let o = e.split("."), [s, c] = o;
|
|
11
|
+
if (o.length !== 2 || !s || !c || !/^[A-Za-z0-9_-]+$/.test(s) || !/^[A-Za-z0-9_-]{43}$/.test(c)) return null;
|
|
12
|
+
let l = Buffer.from(c, "base64url"), u = n(s, r, i);
|
|
13
|
+
if (l.length !== u.length || !t(l, u)) return null;
|
|
14
|
+
try {
|
|
15
|
+
let e = JSON.parse(Buffer.from(s, "base64url").toString("utf8"));
|
|
16
|
+
if (typeof e != "object" || !e || Array.isArray(e)) return null;
|
|
17
|
+
let t = e, n = a();
|
|
18
|
+
return !Number.isFinite(n) || typeof t.exp != "number" || !Number.isSafeInteger(t.exp) || t.exp * 1e3 <= n ? null : t;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export { r as signAuthClaims, i as verifyAuthClaims };
|
package/dist/strategy.d.ts
CHANGED
|
@@ -243,10 +243,10 @@ declare interface SessionRecord {
|
|
|
243
243
|
declare type SessionRegistry = SessionStore;
|
|
244
244
|
|
|
245
245
|
declare interface SessionRevocationChecker {
|
|
246
|
-
/** Is this session
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
readonly
|
|
246
|
+
/** Is this exact session credential live for the signed subject's user ID?
|
|
247
|
+
* Refuse a missing/rotated/expired row or a different stored owner. Cache
|
|
248
|
+
* verdicts retain native owner identity, never the requested owner. */
|
|
249
|
+
readonly isLiveFor: (sessionId: string, userId: string) => Effect.Effect<boolean>;
|
|
250
250
|
/** Drop the cached verdict for one session — called inline by
|
|
251
251
|
* sign-out / revoke handlers so the kill is immediate on THIS
|
|
252
252
|
* process (other replicas converge within `ttlMs`). */
|
|
@@ -276,13 +276,14 @@ declare type SessionSecretInput = string | SessionSecrets;
|
|
|
276
276
|
|
|
277
277
|
/** Server-side sessions — enumeration + revocation. */
|
|
278
278
|
declare interface SessionStore {
|
|
279
|
-
/**
|
|
279
|
+
/** Unrotated credentials with an existing canonical root owned by the same user, most recent first. */
|
|
280
280
|
readonly listSessions: (userId: string) => Effect.Effect<ReadonlyArray<SessionRecord>>;
|
|
281
281
|
/** Record a session on sign-in. */
|
|
282
282
|
readonly insertSession: (session: Omit<SessionRecord, 'createdAt' | 'lastSeenAt'>) => Effect.Effect<SessionRecord>;
|
|
283
|
-
/** Look up
|
|
284
|
-
*
|
|
285
|
-
*
|
|
283
|
+
/** Look up an unrotated credential only while its canonical family root
|
|
284
|
+
* exists and belongs to the same user. A rotated root remains an anchor;
|
|
285
|
+
* its tenant does not replace the credential tenant. Custom stores must
|
|
286
|
+
* enforce this before returning a row to request-time revocation checks. */
|
|
286
287
|
readonly findSession: (sessionId: string) => Effect.Effect<SessionRecord | null>;
|
|
287
288
|
/** Slide a session row forward: bump `expiresAt` + `lastSeenAt`. Called
|
|
288
289
|
* when the sliding-window renewal re-issues the cookie, so the row
|
|
@@ -356,7 +357,7 @@ declare const UserNotFoundError_base: new <A extends Record<string, any> = {}>(a
|
|
|
356
357
|
|
|
357
358
|
declare interface UserRecord {
|
|
358
359
|
readonly id: string;
|
|
359
|
-
readonly email: string;
|
|
360
|
+
readonly email: string | null;
|
|
360
361
|
/**
|
|
361
362
|
* The stored scrypt hash — OPTIONAL, because not every identity has a
|
|
362
363
|
* password. An SSO-only, magic-link-only, passkey-only, or provider-PAT
|
|
@@ -445,6 +446,9 @@ export declare interface VoltroPasswordStrategyOptions {
|
|
|
445
446
|
* (stateless); with it, nothing but a registered session authenticates,
|
|
446
447
|
* and every session can be revoked. */
|
|
447
448
|
readonly requireSessionId?: boolean;
|
|
449
|
+
/** Verified user identity kinds owned by this carrier. Other verified kinds
|
|
450
|
+
* skip to their own carrier; malformed, forged or expired values still fail. */
|
|
451
|
+
readonly userKinds?: ReadonlyArray<'registered' | 'guest'>;
|
|
448
452
|
}
|
|
449
453
|
|
|
450
454
|
export { }
|
package/dist/strategy.js
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
import { sessionSecretsOf as e } from "./session.js";
|
|
2
|
-
import { makeSessionRevocationChecker as t } from "./revocation.js";
|
|
3
|
-
import { sessionIdOfSubject as
|
|
4
|
-
import { Effect as
|
|
5
|
-
import { readSessionValue as
|
|
6
|
-
|
|
7
|
-
var
|
|
8
|
-
let
|
|
2
|
+
import { makeSessionRevocationChecker as t, validateSessionRevocationOptions as n } from "./revocation.js";
|
|
3
|
+
import { sessionIdOfSubject as r } from "./userStore.js";
|
|
4
|
+
import { Effect as i } from "effect";
|
|
5
|
+
import { readSessionValue as a, resolveSessionSecret as o, verifySessionKeyed as s } from "@voltro/protocol/session";
|
|
6
|
+
// src/strategy.ts
|
|
7
|
+
var c = (e) => typeof e == "object" && !!e && "isLiveFor" in e && typeof e.isLiveFor == "function" && "invalidate" in e && typeof e.invalidate == "function" && typeof e.ttlMs == "number" && Number.isFinite(e.ttlMs) && e.ttlMs >= 0, l = (e) => e.revocation && c(e.revocation) ? e.revocation : (e.revocation !== void 0 && n(e.revocation), e.store ? t(e.store, e.revocation ?? {}) : null), u = (n = {}) => {
|
|
8
|
+
let u = n.cookieName ?? "voltro:session", f = l(n), p = n.revocation && !c(n.revocation) ? n.revocation : {}, m = /* @__PURE__ */ new WeakMap(), h = (e) => {
|
|
9
9
|
if (f) return f;
|
|
10
|
-
let
|
|
11
|
-
if (
|
|
12
|
-
if (typeof
|
|
10
|
+
let r = n.sessionRegistry;
|
|
11
|
+
if (r === void 0) return null;
|
|
12
|
+
if (typeof r != "function") return t(r, p);
|
|
13
13
|
if (e === void 0) return null;
|
|
14
|
-
let
|
|
15
|
-
if (
|
|
16
|
-
let
|
|
17
|
-
return m.set(e,
|
|
14
|
+
let i = m.get(e);
|
|
15
|
+
if (i) return i;
|
|
16
|
+
let a = t(r(e), p);
|
|
17
|
+
return m.set(e, a), a;
|
|
18
18
|
};
|
|
19
19
|
return {
|
|
20
20
|
id: "voltro-password",
|
|
21
21
|
resolve: (t) => {
|
|
22
|
-
let
|
|
23
|
-
if (!
|
|
24
|
-
let
|
|
22
|
+
let c = a(t.headers, u);
|
|
23
|
+
if (!c) return { kind: "skip" };
|
|
24
|
+
let l = e(n.secret ?? o()), f = s(c, l);
|
|
25
25
|
if (!f) return {
|
|
26
26
|
kind: "failed",
|
|
27
27
|
reason: "voltro session cookie failed verification"
|
|
28
28
|
};
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
if (f.subject.type === "user" && n.userKinds && !n.userKinds.includes(f.subject.kind === "guest" ? "guest" : "registered")) return { kind: "skip" };
|
|
30
|
+
let p = d(f.subject, "voltro-password"), m = r(p);
|
|
31
|
+
if (!m) return n.requireSessionId === !0 ? {
|
|
31
32
|
kind: "failed",
|
|
32
33
|
reason: "voltro session cookie carries no session id"
|
|
33
34
|
} : {
|
|
@@ -35,13 +36,16 @@ var s = (e) => typeof e.isLive == "function", c = (e) => e.revocation && s(e.rev
|
|
|
35
36
|
subject: p
|
|
36
37
|
};
|
|
37
38
|
let g = h(t.store);
|
|
38
|
-
return g ?
|
|
39
|
+
return g ? p.type === "user" ? i.runPromise(g.isLiveFor(m, p.id)).then((e) => e ? {
|
|
39
40
|
kind: "matched",
|
|
40
41
|
subject: p
|
|
41
42
|
} : {
|
|
42
43
|
kind: "failed",
|
|
43
44
|
reason: "voltro session revoked"
|
|
44
|
-
}) :
|
|
45
|
+
}) : {
|
|
46
|
+
kind: "failed",
|
|
47
|
+
reason: "registered session requires a user subject"
|
|
48
|
+
} : n.requireSessionId === !0 ? {
|
|
45
49
|
kind: "failed",
|
|
46
50
|
reason: "session registry not available yet"
|
|
47
51
|
} : {
|
|
@@ -50,7 +54,7 @@ var s = (e) => typeof e.isLive == "function", c = (e) => e.revocation && s(e.rev
|
|
|
50
54
|
};
|
|
51
55
|
}
|
|
52
56
|
};
|
|
53
|
-
},
|
|
57
|
+
}, d = (e, t) => {
|
|
54
58
|
if (e.type === "anonymous") return e;
|
|
55
59
|
let n = e.metadata ?? {};
|
|
56
60
|
return {
|
|
@@ -61,5 +65,4 @@ var s = (e) => typeof e.isLive == "function", c = (e) => e.revocation && s(e.rev
|
|
|
61
65
|
}
|
|
62
66
|
};
|
|
63
67
|
};
|
|
64
|
-
|
|
65
|
-
export { l as voltroPasswordStrategy };
|
|
68
|
+
export { u as voltroPasswordStrategy };
|