@voltro/plugin-auth 0.23.0 → 0.25.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 +537 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +160 -126
- package/dist/{plugin-HgTMFY3m.js → plugin-ZXs8LFn0.js} +94 -77
- package/dist/plugin.d.ts +44 -0
- package/dist/plugin.js +1 -1
- package/dist/schema.d.ts +11 -1
- package/dist/schema.js +12 -3
- package/dist/{strategy-D345j4df.js → strategy-Ce4FlGi4.js} +27 -6
- package/dist/strategy.d.ts +30 -0
- package/dist/strategy.js +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,20 @@ export declare interface AuthConfig {
|
|
|
80
80
|
* but any concern (unverified email, suspended tenant, …) can hook here.
|
|
81
81
|
* Absent / empty ⇒ every authenticated user proceeds unchanged. */
|
|
82
82
|
readonly subjectGuards?: ReadonlyArray<SubjectGuard>;
|
|
83
|
+
/** Brute-force lockout. After `maxAttempts` failed credential attempts
|
|
84
|
+
* (wrong password OR wrong MFA code) within `windowSeconds`, the account —
|
|
85
|
+
* keyed by EMAIL, so an unknown address locks exactly like a real one and
|
|
86
|
+
* the lock can't be used as an existence oracle — is refused with a 429 for
|
|
87
|
+
* `lockSeconds`; a completed login clears the counter. ON by default (a
|
|
88
|
+
* security default, not opt-in); raise `maxAttempts` very high to disable. */
|
|
89
|
+
readonly lockout?: {
|
|
90
|
+
readonly maxAttempts?: number;
|
|
91
|
+
readonly windowSeconds?: number;
|
|
92
|
+
readonly lockSeconds?: number;
|
|
93
|
+
};
|
|
94
|
+
/** Injectable clock (epoch-ms) for the lockout counter. Default `Date.now`;
|
|
95
|
+
* tests pass a controllable one to exercise window/lock expiry. */
|
|
96
|
+
readonly now?: () => number;
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
/**
|
|
@@ -405,6 +419,26 @@ declare interface ListSessionsInput {
|
|
|
405
419
|
readonly userId: string;
|
|
406
420
|
}
|
|
407
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Brute-force lockout policy, resolved from `AuthConfig.lockout` and passed
|
|
424
|
+
* INTO the store on each failure so the store itself stays policy-free.
|
|
425
|
+
* Times in milliseconds; `now` everywhere is epoch-ms so tests can inject a
|
|
426
|
+
* clock. Kept keyed by EMAIL (not user id) on purpose — a locked account must
|
|
427
|
+
* not read differently from an unknown one, or the lock becomes an existence
|
|
428
|
+
* oracle. See `loginAttemptsTable`.
|
|
429
|
+
*/
|
|
430
|
+
declare interface LockoutPolicy {
|
|
431
|
+
readonly maxAttempts: number;
|
|
432
|
+
readonly windowMs: number;
|
|
433
|
+
readonly lockMs: number;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
declare interface LockoutState {
|
|
437
|
+
readonly locked: boolean;
|
|
438
|
+
/** Seconds until the lock lifts; 0 when not locked. */
|
|
439
|
+
readonly retryAfterSeconds: number;
|
|
440
|
+
}
|
|
441
|
+
|
|
408
442
|
/** Default magic-link lifetime — short, since it's an inbox round-trip. */
|
|
409
443
|
export declare const MAGIC_LINK_TTL_S: number;
|
|
410
444
|
|
|
@@ -1092,6 +1126,16 @@ export declare interface UserStore {
|
|
|
1092
1126
|
* the write are ONE atomic statement at the store, so two replicas
|
|
1093
1127
|
* racing the same counter can't both succeed. */
|
|
1094
1128
|
readonly advancePasskeyCounter: (credentialId: string, newCounter: number) => Effect.Effect<boolean>;
|
|
1129
|
+
/** Is this email currently locked out? Reads only — never mutates. Returns
|
|
1130
|
+
* the remaining cooldown so the handler can surface a retry-after. */
|
|
1131
|
+
readonly isLockedOut: (email: string, now: number) => Effect.Effect<LockoutState>;
|
|
1132
|
+
/** Record one failed credential attempt for `email`. Opens a fresh window
|
|
1133
|
+
* when the previous one (or a prior lock) has expired, otherwise increments;
|
|
1134
|
+
* sets the lock once `failedCount` reaches `policy.maxAttempts`. Called only
|
|
1135
|
+
* for a NOT-currently-locked email (the handler checks `isLockedOut` first). */
|
|
1136
|
+
readonly recordLoginFailure: (email: string, now: number, policy: LockoutPolicy) => Effect.Effect<void>;
|
|
1137
|
+
/** Clear the counter for `email` on a completed login. */
|
|
1138
|
+
readonly clearLoginFailures: (email: string) => Effect.Effect<void>;
|
|
1095
1139
|
}
|
|
1096
1140
|
|
|
1097
1141
|
/** Verify a passkey assertion (sign-in). */
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PasswordEmptyError as e, PasswordHashError as t, hashPassword as n, needsRehash as r, parseScryptParams as i, verifyPassword as a, verifyPasswordWithRehash as o } from "./password.js";
|
|
2
|
-
import { SESSION_COOKIE_NAME as s, clearSessionCookie as
|
|
3
|
-
import { a as
|
|
4
|
-
import { A as
|
|
2
|
+
import { SESSION_COOKIE_NAME as s, clearSessionCookie as c, issueSession as l, readSession as u, readSessionKeyed as d, resolveSessionSecret as f, resolveSessionSecrets as p, sessionSecretsOf as m } from "./session.js";
|
|
3
|
+
import { a as h, c as g, i as _, l as v, n as y, o as b, r as x, s as S, t as C } from "./strategy-Ce4FlGi4.js";
|
|
4
|
+
import { A as w, C as T, D as E, E as D, F as O, I as k, L as A, M as ee, N as te, O as j, P as M, S as N, T as P, _ as F, a as I, b as L, c as R, d as z, f as B, g as V, h as H, i as U, j as W, k as G, l as K, m as q, n as J, o as Y, p as X, r as ne, s as Z, t as re, u as ie, v as ae, w as oe, x as se, y as ce } from "./plugin-ZXs8LFn0.js";
|
|
5
5
|
import { MAGIC_LINK_TTL_S as le, PASSWORD_RESET_TTL_S as ue, hashToken as de, mintToken as fe } from "./tokens.js";
|
|
6
6
|
import { CSRF_COOKIE_NAME as pe, CSRF_HEADER_NAME as me, isCsrfTokenWellFormed as Q, issueCsrfToken as he, verifyCsrf as ge } from "./csrf.js";
|
|
7
7
|
import { generateChallenge as _e, verifyAssertion as ve, verifyRegistration as ye } from "./webauthn.js";
|
|
@@ -36,147 +36,128 @@ var be = (e) => {
|
|
|
36
36
|
...t,
|
|
37
37
|
email: n,
|
|
38
38
|
createdAt: r
|
|
39
|
-
})), $.catchAll((e) => be(e) === "23505" ? $.fail(new
|
|
39
|
+
})), $.catchAll((e) => be(e) === "23505" ? $.fail(new y({ email: n })) : $.die(e)));
|
|
40
40
|
}), i = (t) => $.gen(function* () {
|
|
41
41
|
return (yield* e`
|
|
42
42
|
SELECT id, email, "passwordHash", "tenantId", "createdAt", "mfaSecret", "mfaEnrolledAt"
|
|
43
43
|
FROM users WHERE id = ${t} LIMIT 1
|
|
44
|
-
`)[0] || (yield* $.fail(new
|
|
45
|
-
}).pipe($.catchTag("SqlError", (e) => $.die(e)))
|
|
46
|
-
|
|
47
|
-
findByEmail: t,
|
|
48
|
-
findById: n,
|
|
49
|
-
insert: r,
|
|
50
|
-
updatePassword: (t, n) => $.gen(function* () {
|
|
51
|
-
return yield* e`
|
|
44
|
+
`)[0] || (yield* $.fail(new x({ userId: t })));
|
|
45
|
+
}).pipe($.catchTag("SqlError", (e) => $.die(e))), a = (t, n) => $.gen(function* () {
|
|
46
|
+
return yield* e`
|
|
52
47
|
UPDATE users SET "passwordHash" = ${n}, "updatedAt" = ${/* @__PURE__ */ new Date()}
|
|
53
48
|
WHERE id = ${t}
|
|
54
49
|
`.pipe($.catchTag("SqlError", (e) => $.die(e))), yield* i(t);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return yield* e`
|
|
50
|
+
}), o = (t, n) => $.gen(function* () {
|
|
51
|
+
return yield* e`
|
|
58
52
|
UPDATE users
|
|
59
53
|
SET "mfaSecret" = ${n}, "mfaEnrolledAt" = ${null}
|
|
60
54
|
WHERE id = ${t}
|
|
61
55
|
`.pipe($.catchTag("SqlError", (e) => $.die(e))), yield* i(t);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return yield* e`
|
|
56
|
+
}), s = (t) => $.gen(function* () {
|
|
57
|
+
return yield* e`
|
|
65
58
|
UPDATE users
|
|
66
59
|
SET "mfaEnrolledAt" = ${/* @__PURE__ */ new Date()}
|
|
67
60
|
WHERE id = ${t}
|
|
68
61
|
`.pipe($.catchTag("SqlError", (e) => $.die(e))), yield* i(t);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return yield* e`
|
|
62
|
+
}), c = (t) => $.gen(function* () {
|
|
63
|
+
return yield* e`
|
|
72
64
|
UPDATE users
|
|
73
65
|
SET "mfaSecret" = ${null}, "mfaEnrolledAt" = ${null}
|
|
74
66
|
WHERE id = ${t}
|
|
75
67
|
`.pipe($.catchTag("SqlError", (e) => $.die(e))), yield* i(t);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return (yield* e`
|
|
68
|
+
}), l = (t) => $.gen(function* () {
|
|
69
|
+
return (yield* e`
|
|
79
70
|
SELECT "userId", "tenantId", role, "joinedAt"
|
|
80
71
|
FROM memberships WHERE "userId" = ${t}
|
|
81
72
|
ORDER BY "joinedAt" ASC
|
|
82
73
|
`).map((e) => ({ ...e }));
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
yield* e`
|
|
74
|
+
}).pipe($.orElseSucceed(() => [])), u = (t) => $.gen(function* () {
|
|
75
|
+
let n = /* @__PURE__ */ new Date();
|
|
76
|
+
yield* e`
|
|
87
77
|
UPDATE memberships SET role = ${t.role}
|
|
88
78
|
WHERE "userId" = ${t.userId} AND "tenantId" = ${t.tenantId}
|
|
89
79
|
`;
|
|
90
|
-
|
|
80
|
+
let r = yield* e`
|
|
91
81
|
SELECT "userId", "tenantId", role, "joinedAt"
|
|
92
82
|
FROM memberships
|
|
93
83
|
WHERE "userId" = ${t.userId} AND "tenantId" = ${t.tenantId}
|
|
94
84
|
LIMIT 1
|
|
95
85
|
`;
|
|
96
|
-
|
|
86
|
+
return r[0] ? { ...r[0] } : (yield* e`
|
|
97
87
|
INSERT INTO memberships (id, "userId", "tenantId", role, "joinedAt")
|
|
98
|
-
VALUES (${
|
|
88
|
+
VALUES (${h("mem")}, ${t.userId}, ${t.tenantId}, ${t.role}, ${n})
|
|
99
89
|
`, {
|
|
100
|
-
...t,
|
|
101
|
-
joinedAt: n
|
|
102
|
-
});
|
|
103
|
-
}).pipe($.catchAll(() => $.succeed({
|
|
104
90
|
...t,
|
|
105
|
-
joinedAt:
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
joinedAt: n
|
|
92
|
+
});
|
|
93
|
+
}).pipe($.catchAll(() => $.succeed({
|
|
94
|
+
...t,
|
|
95
|
+
joinedAt: /* @__PURE__ */ new Date()
|
|
96
|
+
}))), d = (t, n) => $.gen(function* () {
|
|
97
|
+
return (yield* e`
|
|
109
98
|
SELECT role FROM memberships
|
|
110
99
|
WHERE "userId" = ${t} AND "tenantId" = ${n} LIMIT 1
|
|
111
100
|
`)[0]?.role ?? null;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return (yield* e`
|
|
101
|
+
}).pipe($.orElseSucceed(() => null)), f = (t) => $.gen(function* () {
|
|
102
|
+
return (yield* e`
|
|
115
103
|
SELECT id, "userId", "tenantId", "expiresAt", "ipAddress", "userAgent", "createdAt", "lastSeenAt"
|
|
116
104
|
FROM sessions WHERE "userId" = ${t}
|
|
117
105
|
ORDER BY "createdAt" DESC
|
|
118
106
|
`).map((e) => ({ ...e }));
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return yield* e`
|
|
107
|
+
}).pipe($.orElseSucceed(() => [])), p = (t) => $.gen(function* () {
|
|
108
|
+
let n = /* @__PURE__ */ new Date();
|
|
109
|
+
return yield* e`
|
|
123
110
|
INSERT INTO sessions (id, "userId", "tenantId", "expiresAt", "ipAddress", "userAgent", "createdAt", "lastSeenAt")
|
|
124
111
|
VALUES (
|
|
125
112
|
${t.id}, ${t.userId}, ${t.tenantId}, ${t.expiresAt},
|
|
126
113
|
${t.ipAddress ?? null}, ${t.userAgent ?? null}, ${n}, ${n}
|
|
127
114
|
)
|
|
128
115
|
`, {
|
|
129
|
-
...t,
|
|
130
|
-
createdAt: n,
|
|
131
|
-
lastSeenAt: n
|
|
132
|
-
};
|
|
133
|
-
}).pipe($.catchAll(() => $.succeed({
|
|
134
116
|
...t,
|
|
135
|
-
createdAt:
|
|
136
|
-
lastSeenAt:
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
117
|
+
createdAt: n,
|
|
118
|
+
lastSeenAt: n
|
|
119
|
+
};
|
|
120
|
+
}).pipe($.catchAll(() => $.succeed({
|
|
121
|
+
...t,
|
|
122
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
123
|
+
lastSeenAt: /* @__PURE__ */ new Date()
|
|
124
|
+
}))), m = (t) => $.gen(function* () {
|
|
125
|
+
let n = yield* e`
|
|
140
126
|
SELECT id, "userId", "tenantId", "expiresAt", "ipAddress", "userAgent", "createdAt", "lastSeenAt"
|
|
141
127
|
FROM sessions WHERE id = ${t} LIMIT 1
|
|
142
128
|
`;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
yield* e`
|
|
129
|
+
return n[0] ? { ...n[0] } : null;
|
|
130
|
+
}).pipe($.orElseSucceed(() => null)), g = (t, n) => $.gen(function* () {
|
|
131
|
+
yield* e`
|
|
147
132
|
UPDATE sessions SET "expiresAt" = ${n}, "lastSeenAt" = ${/* @__PURE__ */ new Date()}
|
|
148
133
|
WHERE id = ${t}
|
|
149
134
|
`;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return (yield* e`
|
|
135
|
+
}).pipe($.asVoid, $.orElseSucceed(() => void 0)), _ = (t, n) => $.gen(function* () {
|
|
136
|
+
return (yield* e`
|
|
153
137
|
DELETE FROM sessions WHERE id = ${n} AND "userId" = ${t}
|
|
154
138
|
RETURNING id
|
|
155
139
|
`).length > 0;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return (yield* e`
|
|
140
|
+
}).pipe($.orElseSucceed(() => !1)), v = (t, n) => $.gen(function* () {
|
|
141
|
+
return (yield* e`
|
|
159
142
|
DELETE FROM sessions WHERE "userId" = ${t} AND id <> ${n}
|
|
160
143
|
RETURNING id
|
|
161
144
|
`).length;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return yield* e`
|
|
145
|
+
}).pipe($.orElseSucceed(() => 0)), b = (t) => $.gen(function* () {
|
|
146
|
+
let n = /* @__PURE__ */ new Date();
|
|
147
|
+
return yield* e`
|
|
166
148
|
INSERT INTO "authTokens" (id, "tokenHash", "userId", purpose, "expiresAt", "consumedAt", "createdAt")
|
|
167
149
|
VALUES (${t.id}, ${t.tokenHash}, ${t.userId}, ${t.purpose}, ${t.expiresAt}, ${null}, ${n})
|
|
168
150
|
`, {
|
|
169
|
-
...t,
|
|
170
|
-
consumedAt: null,
|
|
171
|
-
createdAt: n
|
|
172
|
-
};
|
|
173
|
-
}).pipe($.catchAll(() => $.succeed({
|
|
174
151
|
...t,
|
|
175
152
|
consumedAt: null,
|
|
176
|
-
createdAt:
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
153
|
+
createdAt: n
|
|
154
|
+
};
|
|
155
|
+
}).pipe($.catchAll(() => $.succeed({
|
|
156
|
+
...t,
|
|
157
|
+
consumedAt: null,
|
|
158
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
159
|
+
}))), S = (t, n) => $.gen(function* () {
|
|
160
|
+
let r = yield* e`
|
|
180
161
|
UPDATE "authTokens"
|
|
181
162
|
SET "consumedAt" = ${/* @__PURE__ */ new Date()}
|
|
182
163
|
WHERE "tokenHash" = ${t}
|
|
@@ -185,72 +166,125 @@ var be = (e) => {
|
|
|
185
166
|
AND "expiresAt" > ${/* @__PURE__ */ new Date()}
|
|
186
167
|
RETURNING id, "tokenHash", "userId", purpose, "expiresAt", "consumedAt", "createdAt"
|
|
187
168
|
`;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
let r = /* @__PURE__ */ new Date();
|
|
192
|
-
yield* e`DELETE FROM "recoveryCodes" WHERE "userId" = ${t}`;
|
|
193
|
-
for (let i of n) yield* e`
|
|
194
|
-
INSERT INTO "recoveryCodes" (id, "userId", "codeHash", "consumedAt", "createdAt")
|
|
195
|
-
VALUES (${p("rc")}, ${t}, ${i}, ${null}, ${r})
|
|
196
|
-
`;
|
|
197
|
-
}).pipe($.asVoid, $.orElseSucceed(() => void 0)),
|
|
198
|
-
consumeRecoveryCode: (t, n) => $.gen(function* () {
|
|
199
|
-
return (yield* e`
|
|
200
|
-
UPDATE "recoveryCodes"
|
|
201
|
-
SET "consumedAt" = ${/* @__PURE__ */ new Date()}
|
|
202
|
-
WHERE "userId" = ${t}
|
|
203
|
-
AND "codeHash" = ${n}
|
|
204
|
-
AND "consumedAt" IS NULL
|
|
205
|
-
RETURNING id
|
|
206
|
-
`).length > 0;
|
|
207
|
-
}).pipe($.orElseSucceed(() => !1)),
|
|
208
|
-
countRecoveryCodes: (t) => $.gen(function* () {
|
|
209
|
-
return (yield* e`
|
|
210
|
-
SELECT COUNT(*)::int AS n FROM "recoveryCodes"
|
|
211
|
-
WHERE "userId" = ${t} AND "consumedAt" IS NULL
|
|
212
|
-
`)[0]?.n ?? 0;
|
|
213
|
-
}).pipe($.orElseSucceed(() => 0)),
|
|
214
|
-
listPasskeys: (t) => $.gen(function* () {
|
|
215
|
-
return (yield* e`
|
|
169
|
+
return r[0] ? { ...r[0] } : null;
|
|
170
|
+
}).pipe($.orElseSucceed(() => null)), C = (t) => $.gen(function* () {
|
|
171
|
+
return (yield* e`
|
|
216
172
|
SELECT id, "credentialId", "userId", "publicKey", counter, transports, "createdAt", "lastUsedAt"
|
|
217
173
|
FROM passkeys WHERE "userId" = ${t}
|
|
218
174
|
`).map((e) => ({ ...e }));
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
let n = yield* e`
|
|
175
|
+
}).pipe($.orElseSucceed(() => [])), w = (t) => $.gen(function* () {
|
|
176
|
+
let n = yield* e`
|
|
222
177
|
SELECT id, "credentialId", "userId", "publicKey", counter, transports, "createdAt", "lastUsedAt"
|
|
223
178
|
FROM passkeys WHERE "credentialId" = ${t} LIMIT 1
|
|
224
179
|
`;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return yield* e`
|
|
180
|
+
return n[0] ? { ...n[0] } : null;
|
|
181
|
+
}).pipe($.orElseSucceed(() => null)), T = (t) => $.gen(function* () {
|
|
182
|
+
let n = /* @__PURE__ */ new Date();
|
|
183
|
+
return yield* e`
|
|
230
184
|
INSERT INTO passkeys (id, "credentialId", "userId", "publicKey", counter, transports, "createdAt", "lastUsedAt")
|
|
231
185
|
VALUES (
|
|
232
186
|
${t.id}, ${t.credentialId}, ${t.userId}, ${t.publicKey},
|
|
233
187
|
${t.counter}, ${t.transports ?? null}, ${n}, ${null}
|
|
234
188
|
)
|
|
235
189
|
`, {
|
|
236
|
-
...t,
|
|
237
|
-
createdAt: n,
|
|
238
|
-
lastUsedAt: null
|
|
239
|
-
};
|
|
240
|
-
}).pipe($.catchAll(() => $.succeed({
|
|
241
190
|
...t,
|
|
242
|
-
createdAt:
|
|
191
|
+
createdAt: n,
|
|
243
192
|
lastUsedAt: null
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
193
|
+
};
|
|
194
|
+
}).pipe($.catchAll(() => $.succeed({
|
|
195
|
+
...t,
|
|
196
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
197
|
+
lastUsedAt: null
|
|
198
|
+
}))), E = (t, n) => $.gen(function* () {
|
|
199
|
+
return (yield* e`
|
|
247
200
|
UPDATE passkeys
|
|
248
201
|
SET counter = ${n}, "lastUsedAt" = ${/* @__PURE__ */ new Date()}
|
|
249
202
|
WHERE "credentialId" = ${t}
|
|
250
203
|
AND counter < ${n}
|
|
251
204
|
RETURNING id
|
|
252
205
|
`).length > 0;
|
|
253
|
-
|
|
206
|
+
}).pipe($.orElseSucceed(() => !1)), D = (t, n) => $.gen(function* () {
|
|
207
|
+
let r = /* @__PURE__ */ new Date();
|
|
208
|
+
yield* e`DELETE FROM "recoveryCodes" WHERE "userId" = ${t}`;
|
|
209
|
+
for (let i of n) yield* e`
|
|
210
|
+
INSERT INTO "recoveryCodes" (id, "userId", "codeHash", "consumedAt", "createdAt")
|
|
211
|
+
VALUES (${h("rc")}, ${t}, ${i}, ${null}, ${r})
|
|
212
|
+
`;
|
|
213
|
+
}).pipe($.asVoid, $.orElseSucceed(() => void 0)), O = (t, n) => $.gen(function* () {
|
|
214
|
+
return (yield* e`
|
|
215
|
+
UPDATE "recoveryCodes"
|
|
216
|
+
SET "consumedAt" = ${/* @__PURE__ */ new Date()}
|
|
217
|
+
WHERE "userId" = ${t}
|
|
218
|
+
AND "codeHash" = ${n}
|
|
219
|
+
AND "consumedAt" IS NULL
|
|
220
|
+
RETURNING id
|
|
221
|
+
`).length > 0;
|
|
222
|
+
}).pipe($.orElseSucceed(() => !1)), k = (t) => $.gen(function* () {
|
|
223
|
+
return (yield* e`
|
|
224
|
+
SELECT COUNT(*)::int AS n FROM "recoveryCodes"
|
|
225
|
+
WHERE "userId" = ${t} AND "consumedAt" IS NULL
|
|
226
|
+
`)[0]?.n ?? 0;
|
|
227
|
+
}).pipe($.orElseSucceed(() => 0)), A = (e, t) => $.catchAll((n) => $.logWarning(`[auth] lockout ${e} failed — brute-force protection is not counting`).pipe($.annotateLogs("cause", String(n)), $.as(t)));
|
|
228
|
+
return {
|
|
229
|
+
findByEmail: t,
|
|
230
|
+
findById: n,
|
|
231
|
+
insert: r,
|
|
232
|
+
updatePassword: a,
|
|
233
|
+
setMfaSecret: o,
|
|
234
|
+
markMfaEnrolled: s,
|
|
235
|
+
clearMfa: c,
|
|
236
|
+
listMemberships: l,
|
|
237
|
+
addMembership: u,
|
|
238
|
+
membershipRole: d,
|
|
239
|
+
listSessions: f,
|
|
240
|
+
insertSession: p,
|
|
241
|
+
findSession: m,
|
|
242
|
+
touchSession: g,
|
|
243
|
+
revokeSession: _,
|
|
244
|
+
revokeAllOtherSessions: v,
|
|
245
|
+
insertToken: b,
|
|
246
|
+
consumeToken: S,
|
|
247
|
+
replaceRecoveryCodes: D,
|
|
248
|
+
consumeRecoveryCode: O,
|
|
249
|
+
countRecoveryCodes: k,
|
|
250
|
+
listPasskeys: C,
|
|
251
|
+
findPasskey: w,
|
|
252
|
+
insertPasskey: T,
|
|
253
|
+
advancePasskeyCounter: E,
|
|
254
|
+
isLockedOut: (t, n) => $.gen(function* () {
|
|
255
|
+
let r = (yield* e`
|
|
256
|
+
SELECT "lockedUntil" FROM "loginAttempts"
|
|
257
|
+
WHERE email = ${t.toLowerCase()} LIMIT 1
|
|
258
|
+
`)[0]?.lockedUntil ?? null;
|
|
259
|
+
return r != null && n < r.getTime() ? {
|
|
260
|
+
locked: !0,
|
|
261
|
+
retryAfterSeconds: Math.ceil((r.getTime() - n) / 1e3)
|
|
262
|
+
} : {
|
|
263
|
+
locked: !1,
|
|
264
|
+
retryAfterSeconds: 0
|
|
265
|
+
};
|
|
266
|
+
}).pipe(A("read", {
|
|
267
|
+
locked: !1,
|
|
268
|
+
retryAfterSeconds: 0
|
|
269
|
+
})),
|
|
270
|
+
recordLoginFailure: (t, n, r) => $.gen(function* () {
|
|
271
|
+
let i = t.toLowerCase(), a = (yield* e`
|
|
272
|
+
SELECT "failedCount", "windowStartedAt", "lockedUntil"
|
|
273
|
+
FROM "loginAttempts" WHERE email = ${i} LIMIT 1
|
|
274
|
+
`)[0], o = a?.windowStartedAt?.getTime() ?? null, s = a?.lockedUntil?.getTime() ?? null, c = o == null || n - o >= r.windowMs, l = !a || c || s != null && n >= s, u = l ? 1 : a.failedCount + 1, d = l ? new Date(n) : new Date(o), f = u >= r.maxAttempts ? new Date(n + r.lockMs) : null, p = new Date(n);
|
|
275
|
+
yield* e`
|
|
276
|
+
INSERT INTO "loginAttempts" (id, email, "failedCount", "windowStartedAt", "lockedUntil", "createdAt", "updatedAt")
|
|
277
|
+
VALUES (${h("la")}, ${i}, ${u}, ${d}, ${f}, ${p}, ${p})
|
|
278
|
+
ON CONFLICT (email) DO UPDATE SET
|
|
279
|
+
"failedCount" = ${u},
|
|
280
|
+
"windowStartedAt" = ${d},
|
|
281
|
+
"lockedUntil" = ${f},
|
|
282
|
+
"updatedAt" = ${p}
|
|
283
|
+
`;
|
|
284
|
+
}).pipe($.asVoid, A("record", void 0)),
|
|
285
|
+
clearLoginFailures: (t) => $.gen(function* () {
|
|
286
|
+
yield* e`DELETE FROM "loginAttempts" WHERE email = ${t.toLowerCase()}`;
|
|
287
|
+
}).pipe($.asVoid, A("clear", void 0))
|
|
254
288
|
};
|
|
255
289
|
}, Se = (e) => (t) => $.runPromise(e.send({
|
|
256
290
|
to: t.to,
|
|
@@ -259,4 +293,4 @@ var be = (e) => {
|
|
|
259
293
|
text: t.text
|
|
260
294
|
}).pipe($.asVoid, $.orElseSucceed(() => void 0)));
|
|
261
295
|
//#endregion
|
|
262
|
-
export { pe as CSRF_COOKIE_NAME, me as CSRF_HEADER_NAME, le as MAGIC_LINK_TTL_S,
|
|
296
|
+
export { pe as CSRF_COOKIE_NAME, me as CSRF_HEADER_NAME, le as MAGIC_LINK_TTL_S, K as MFA_PENDING_TTL_S, ue as PASSWORD_RESET_TTL_S, e as PasswordEmptyError, t as PasswordHashError, s as SESSION_COOKIE_NAME, W as TotpVerifyError, y as UserAlreadyExistsError, x as UserNotFoundError, re as authRoutesPlugin, c as clearSessionCookie, ie as configSessionSecrets, ne as dataStoreChallengeStore, _e as generateChallenge, ee as generateRecoveryCodes, te as generateTotpCode, M as generateTotpSecret, J as getAuthSessionStrategy, z as guardSubject, B as handleCsrf, X as handleListMemberships, q as handleListSessions, H as handleMagicLinkConsume, V as handleMagicLinkRequest, F as handleMfaEnrollStart, ae as handleMfaEnrollVerify, ce as handleMfaRegenerateRecoveryCodes, L as handleMfaUnenroll, se as handleMfaVerify, U as handlePasskeyAssertOptions, I as handlePasskeyAssertVerify, Y as handlePasskeyRegisterOptions, Z as handlePasskeyRegisterVerify, N as handlePasswordResetConfirm, T as handlePasswordResetRequest, oe as handleRevokeAllOtherSessions, P as handleRevokeSession, D as handleSignIn, E as handleSignOut, j as handleSignUp, G as handleSwitchTenant, n as hashPassword, de as hashToken, Q as isCsrfTokenWellFormed, he as issueCsrfToken, l as issueSession, w as issueUserSession, Se as mailSender, v as makeSessionRevocationChecker, R as memoryChallengeStore, _ as memoryUserStore, fe as mintToken, r as needsRehash, h as newAuthId, O as otpauthUrl, i as parseScryptParams, xe as postgresUserStore, u as readSession, d as readSessionKeyed, f as resolveSessionSecret, p as resolveSessionSecrets, A as runSubjectGuards, b as sessionIdOfSubject, m as sessionSecretsOf, S as subjectFromUser, g as subjectMemberships, ve as verifyAssertion, ge as verifyCsrf, a as verifyPassword, o as verifyPasswordWithRehash, ye as verifyRegistration, k as verifyTotpCode, C as voltroPasswordStrategy };
|