@absolutejs/auth 0.27.0-beta.6 → 0.27.0-beta.8
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.
|
@@ -2,14 +2,18 @@ import type { AuditEvent, AuditSink } from './types';
|
|
|
2
2
|
export type AuditIntegrity = {
|
|
3
3
|
hash: string;
|
|
4
4
|
previousHash: string;
|
|
5
|
+
writerId?: string;
|
|
5
6
|
};
|
|
6
7
|
export type AuditChainResult = {
|
|
7
8
|
brokenAt?: number;
|
|
8
9
|
ok: boolean;
|
|
9
10
|
};
|
|
10
|
-
export declare const createTamperEvidentSink: ({ secret, sink }: {
|
|
11
|
+
export declare const createTamperEvidentSink: ({ loadWriterHead, secret, seedScanLimit, sink, writerId }: {
|
|
12
|
+
loadWriterHead?: (writerId: string) => Promise<string | undefined> | string | undefined;
|
|
11
13
|
secret?: string;
|
|
14
|
+
seedScanLimit?: number;
|
|
12
15
|
sink: AuditSink;
|
|
16
|
+
writerId?: string;
|
|
13
17
|
}) => AuditSink;
|
|
14
18
|
export declare const hashAuditEvent: (event: AuditEvent, previousHash: string, secret?: string) => Promise<string>;
|
|
15
19
|
export declare const verifyAuditChain: (events: AuditEvent[], secret?: string) => Promise<AuditChainResult>;
|
|
@@ -19,6 +19,7 @@ export type CredentialEmailMessage = {
|
|
|
19
19
|
type: CredentialEmailType;
|
|
20
20
|
};
|
|
21
21
|
export type CredentialsConfig<UserType> = {
|
|
22
|
+
checkBreachesOnLogin?: boolean;
|
|
22
23
|
credentialStore: CredentialStore;
|
|
23
24
|
getUserByEmail: (email: string) => Promise<UserType | null | undefined> | UserType | null | undefined;
|
|
24
25
|
isMfaRequired?: (user: UserType) => boolean | Promise<boolean>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import { type CredentialRouteProps } from './config';
|
|
3
|
-
export declare const credentialsLogin: <UserType>({ authSessionStore, credentialStore, getUserByEmail, isMfaRequired, lockoutGuard, loginRoute, onCredentialsLoginError, onCredentialsLoginSuccess, requireEmailVerification, sessionDurationMs }: CredentialRouteProps<UserType>) => Elysia<"", {
|
|
3
|
+
export declare const credentialsLogin: <UserType>({ authSessionStore, checkBreachesOnLogin, credentialStore, getUserByEmail, isMfaRequired, lockoutGuard, loginRoute, onCredentialsLoginError, onCredentialsLoginSuccess, requireEmailVerification, sessionDurationMs }: CredentialRouteProps<UserType>) => Elysia<"", {
|
|
4
4
|
decorator: {};
|
|
5
5
|
store: {
|
|
6
6
|
session: import("..").SessionRecord<UserType>;
|
|
@@ -32,6 +32,7 @@ export declare const credentialsLogin: <UserType>({ authSessionStore, credential
|
|
|
32
32
|
200: {
|
|
33
33
|
readonly status: "mfa_required";
|
|
34
34
|
} | {
|
|
35
|
+
readonly passwordCompromised: boolean;
|
|
35
36
|
readonly status: "authenticated";
|
|
36
37
|
};
|
|
37
38
|
401: "Invalid email or password";
|
|
@@ -100,6 +100,7 @@ export declare const credentialRoutes: <UserType>(config: CredentialRouteProps<U
|
|
|
100
100
|
200: {
|
|
101
101
|
readonly status: "mfa_required";
|
|
102
102
|
} | {
|
|
103
|
+
readonly passwordCompromised: boolean;
|
|
103
104
|
readonly status: "authenticated";
|
|
104
105
|
};
|
|
105
106
|
401: "Invalid email or password";
|
package/dist/index.d.ts
CHANGED
|
@@ -12725,6 +12725,7 @@ export declare const auth: <UserType>({ providersConfiguration, authorizeRoute,
|
|
|
12725
12725
|
200: {
|
|
12726
12726
|
readonly status: "mfa_required";
|
|
12727
12727
|
} | {
|
|
12728
|
+
readonly passwordCompromised: boolean;
|
|
12728
12729
|
readonly status: "authenticated";
|
|
12729
12730
|
};
|
|
12730
12731
|
401: "Invalid email or password";
|
package/dist/index.js
CHANGED
|
@@ -3391,8 +3391,60 @@ var credentialsEmailVerification = ({
|
|
|
3391
3391
|
|
|
3392
3392
|
// src/credentials/login.ts
|
|
3393
3393
|
import { Elysia as Elysia6, t as t6 } from "elysia";
|
|
3394
|
+
|
|
3395
|
+
// src/credentials/passwordPolicy.ts
|
|
3396
|
+
var DEFAULT_MIN_LENGTH = 12;
|
|
3397
|
+
var HEX_RADIX = 16;
|
|
3398
|
+
var HIBP_PREFIX_LENGTH = 5;
|
|
3399
|
+
var HIBP_RANGE_URL = "https://api.pwnedpasswords.com/range/";
|
|
3400
|
+
var sha1Hex = async (input) => {
|
|
3401
|
+
const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(input));
|
|
3402
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(HEX_RADIX).padStart(2, "0")).join("").toUpperCase();
|
|
3403
|
+
};
|
|
3404
|
+
var isPasswordBreached = async (password) => {
|
|
3405
|
+
try {
|
|
3406
|
+
const hash = await sha1Hex(password);
|
|
3407
|
+
const prefix = hash.slice(0, HIBP_PREFIX_LENGTH);
|
|
3408
|
+
const suffix = hash.slice(HIBP_PREFIX_LENGTH);
|
|
3409
|
+
const response = await fetch(`${HIBP_RANGE_URL}${prefix}`);
|
|
3410
|
+
if (!response.ok)
|
|
3411
|
+
return false;
|
|
3412
|
+
const body = await response.text();
|
|
3413
|
+
return body.split(`
|
|
3414
|
+
`).some((line) => line.split(":")[0]?.trim() === suffix);
|
|
3415
|
+
} catch {
|
|
3416
|
+
return false;
|
|
3417
|
+
}
|
|
3418
|
+
};
|
|
3419
|
+
var evaluatePassword = async (password, policy = {}) => {
|
|
3420
|
+
const minLength = policy.minLength ?? DEFAULT_MIN_LENGTH;
|
|
3421
|
+
const violations = [];
|
|
3422
|
+
if (password.length < minLength) {
|
|
3423
|
+
violations.push("too_short");
|
|
3424
|
+
}
|
|
3425
|
+
if (policy.requireUppercase && !/[A-Z]/u.test(password)) {
|
|
3426
|
+
violations.push("missing_uppercase");
|
|
3427
|
+
}
|
|
3428
|
+
if (policy.requireLowercase && !/[a-z]/u.test(password)) {
|
|
3429
|
+
violations.push("missing_lowercase");
|
|
3430
|
+
}
|
|
3431
|
+
if (policy.requireDigit && !/\d/u.test(password)) {
|
|
3432
|
+
violations.push("missing_digit");
|
|
3433
|
+
}
|
|
3434
|
+
if (policy.requireSymbol && !/[^A-Za-z0-9]/u.test(password)) {
|
|
3435
|
+
violations.push("missing_symbol");
|
|
3436
|
+
}
|
|
3437
|
+
if (policy.checkBreaches && await isPasswordBreached(password)) {
|
|
3438
|
+
violations.push("breached");
|
|
3439
|
+
}
|
|
3440
|
+
return { ok: violations.length === 0, violations };
|
|
3441
|
+
};
|
|
3442
|
+
var isPasswordCompromised = (password) => isPasswordBreached(password);
|
|
3443
|
+
|
|
3444
|
+
// src/credentials/login.ts
|
|
3394
3445
|
var credentialsLogin = ({
|
|
3395
3446
|
authSessionStore,
|
|
3447
|
+
checkBreachesOnLogin,
|
|
3396
3448
|
credentialStore,
|
|
3397
3449
|
getUserByEmail,
|
|
3398
3450
|
isMfaRequired,
|
|
@@ -3451,6 +3503,7 @@ var credentialsLogin = ({
|
|
|
3451
3503
|
await persistWhen(authSessionStore !== undefined, compatibilityLayer.persist);
|
|
3452
3504
|
return status("OK", { status: "mfa_required" });
|
|
3453
3505
|
}
|
|
3506
|
+
const passwordCompromised = checkBreachesOnLogin ? await isPasswordCompromised(password) : false;
|
|
3454
3507
|
const userSessionId = await promoteToSession({
|
|
3455
3508
|
authSessionStore,
|
|
3456
3509
|
cookie: user_session_id,
|
|
@@ -3459,7 +3512,7 @@ var credentialsLogin = ({
|
|
|
3459
3512
|
user
|
|
3460
3513
|
});
|
|
3461
3514
|
await onCredentialsLoginSuccess?.({ user, userSessionId });
|
|
3462
|
-
return status("OK", { status: "authenticated" });
|
|
3515
|
+
return status("OK", { passwordCompromised, status: "authenticated" });
|
|
3463
3516
|
}, {
|
|
3464
3517
|
body: t6.Object({ email: t6.String(), password: t6.String() }),
|
|
3465
3518
|
cookie: t6.Cookie({ user_session_id: userSessionIdTypebox })
|
|
@@ -3467,57 +3520,6 @@ var credentialsLogin = ({
|
|
|
3467
3520
|
|
|
3468
3521
|
// src/credentials/passwordReset.ts
|
|
3469
3522
|
import { Elysia as Elysia7, t as t7 } from "elysia";
|
|
3470
|
-
|
|
3471
|
-
// src/credentials/passwordPolicy.ts
|
|
3472
|
-
var DEFAULT_MIN_LENGTH = 12;
|
|
3473
|
-
var HEX_RADIX = 16;
|
|
3474
|
-
var HIBP_PREFIX_LENGTH = 5;
|
|
3475
|
-
var HIBP_RANGE_URL = "https://api.pwnedpasswords.com/range/";
|
|
3476
|
-
var sha1Hex = async (input) => {
|
|
3477
|
-
const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(input));
|
|
3478
|
-
return [...new Uint8Array(digest)].map((byte) => byte.toString(HEX_RADIX).padStart(2, "0")).join("").toUpperCase();
|
|
3479
|
-
};
|
|
3480
|
-
var isPasswordBreached = async (password) => {
|
|
3481
|
-
try {
|
|
3482
|
-
const hash = await sha1Hex(password);
|
|
3483
|
-
const prefix = hash.slice(0, HIBP_PREFIX_LENGTH);
|
|
3484
|
-
const suffix = hash.slice(HIBP_PREFIX_LENGTH);
|
|
3485
|
-
const response = await fetch(`${HIBP_RANGE_URL}${prefix}`);
|
|
3486
|
-
if (!response.ok)
|
|
3487
|
-
return false;
|
|
3488
|
-
const body = await response.text();
|
|
3489
|
-
return body.split(`
|
|
3490
|
-
`).some((line) => line.split(":")[0]?.trim() === suffix);
|
|
3491
|
-
} catch {
|
|
3492
|
-
return false;
|
|
3493
|
-
}
|
|
3494
|
-
};
|
|
3495
|
-
var evaluatePassword = async (password, policy = {}) => {
|
|
3496
|
-
const minLength = policy.minLength ?? DEFAULT_MIN_LENGTH;
|
|
3497
|
-
const violations = [];
|
|
3498
|
-
if (password.length < minLength) {
|
|
3499
|
-
violations.push("too_short");
|
|
3500
|
-
}
|
|
3501
|
-
if (policy.requireUppercase && !/[A-Z]/u.test(password)) {
|
|
3502
|
-
violations.push("missing_uppercase");
|
|
3503
|
-
}
|
|
3504
|
-
if (policy.requireLowercase && !/[a-z]/u.test(password)) {
|
|
3505
|
-
violations.push("missing_lowercase");
|
|
3506
|
-
}
|
|
3507
|
-
if (policy.requireDigit && !/\d/u.test(password)) {
|
|
3508
|
-
violations.push("missing_digit");
|
|
3509
|
-
}
|
|
3510
|
-
if (policy.requireSymbol && !/[^A-Za-z0-9]/u.test(password)) {
|
|
3511
|
-
violations.push("missing_symbol");
|
|
3512
|
-
}
|
|
3513
|
-
if (policy.checkBreaches && await isPasswordBreached(password)) {
|
|
3514
|
-
violations.push("breached");
|
|
3515
|
-
}
|
|
3516
|
-
return { ok: violations.length === 0, violations };
|
|
3517
|
-
};
|
|
3518
|
-
var isPasswordCompromised = (password) => isPasswordBreached(password);
|
|
3519
|
-
|
|
3520
|
-
// src/credentials/passwordReset.ts
|
|
3521
3523
|
var credentialsPasswordReset = ({
|
|
3522
3524
|
credentialStore,
|
|
3523
3525
|
onPasswordReset,
|
|
@@ -18708,6 +18710,7 @@ var INTEGRITY_KEY = "__integrity";
|
|
|
18708
18710
|
var GENESIS = "";
|
|
18709
18711
|
var HEX_RADIX2 = 16;
|
|
18710
18712
|
var HEX_PAD = 2;
|
|
18713
|
+
var DEFAULT_SEED_SCAN_LIMIT = 1000;
|
|
18711
18714
|
var encoder = new TextEncoder;
|
|
18712
18715
|
var toHex = (buffer) => [...new Uint8Array(buffer)].map((byte) => byte.toString(HEX_RADIX2).padStart(HEX_PAD, "0")).join("");
|
|
18713
18716
|
var sha256Hex = async (message) => toHex(await crypto.subtle.digest("SHA-256", encoder.encode(message)));
|
|
@@ -18738,15 +18741,31 @@ var brokenAt = (index) => {
|
|
|
18738
18741
|
return result;
|
|
18739
18742
|
};
|
|
18740
18743
|
var createTamperEvidentSink = ({
|
|
18744
|
+
loadWriterHead,
|
|
18741
18745
|
secret,
|
|
18742
|
-
|
|
18746
|
+
seedScanLimit = DEFAULT_SEED_SCAN_LIMIT,
|
|
18747
|
+
sink,
|
|
18748
|
+
writerId
|
|
18743
18749
|
}) => {
|
|
18750
|
+
const chainWriterId = writerId ?? crypto.randomUUID();
|
|
18751
|
+
const isResuming = writerId !== undefined;
|
|
18744
18752
|
let lastHash;
|
|
18753
|
+
let seeded = false;
|
|
18745
18754
|
const seed = async () => {
|
|
18746
|
-
if (
|
|
18755
|
+
if (seeded)
|
|
18756
|
+
return;
|
|
18757
|
+
seeded = true;
|
|
18758
|
+
if (!isResuming) {
|
|
18759
|
+
lastHash = GENESIS;
|
|
18760
|
+
return;
|
|
18761
|
+
}
|
|
18762
|
+
if (loadWriterHead) {
|
|
18763
|
+
lastHash = await loadWriterHead(chainWriterId) ?? GENESIS;
|
|
18747
18764
|
return;
|
|
18748
|
-
|
|
18749
|
-
|
|
18765
|
+
}
|
|
18766
|
+
const recent = await sink.list?.({ limit: seedScanLimit }) ?? [];
|
|
18767
|
+
const head = recent.find((event) => readIntegrity(event)?.writerId === chainWriterId);
|
|
18768
|
+
lastHash = head ? readIntegrity(head)?.hash ?? GENESIS : GENESIS;
|
|
18750
18769
|
};
|
|
18751
18770
|
return {
|
|
18752
18771
|
list: sink.list,
|
|
@@ -18755,7 +18774,11 @@ var createTamperEvidentSink = ({
|
|
|
18755
18774
|
const previousHash = lastHash ?? GENESIS;
|
|
18756
18775
|
const hash = await hashAuditEvent(event, previousHash, secret);
|
|
18757
18776
|
lastHash = hash;
|
|
18758
|
-
const integrity = {
|
|
18777
|
+
const integrity = {
|
|
18778
|
+
hash,
|
|
18779
|
+
previousHash,
|
|
18780
|
+
writerId: chainWriterId
|
|
18781
|
+
};
|
|
18759
18782
|
await sink.append({
|
|
18760
18783
|
...event,
|
|
18761
18784
|
metadata: { ...event.metadata, [INTEGRITY_KEY]: integrity }
|
|
@@ -18771,17 +18794,19 @@ var hashAuditEvent = async (event, previousHash, secret) => {
|
|
|
18771
18794
|
return secret === undefined ? sha256Hex(message) : hmacSha256Hex(secret, message);
|
|
18772
18795
|
};
|
|
18773
18796
|
var verifyAuditChain = async (events, secret) => {
|
|
18774
|
-
|
|
18797
|
+
const heads = new Map;
|
|
18775
18798
|
for (let index = 0;index < events.length; index += 1) {
|
|
18776
18799
|
const event = events[index];
|
|
18777
18800
|
if (event === undefined)
|
|
18778
18801
|
return brokenAt(index);
|
|
18779
18802
|
const integrity = readIntegrity(event);
|
|
18803
|
+
const chain = integrity?.writerId ?? GENESIS;
|
|
18804
|
+
const previousHash = heads.get(chain) ?? GENESIS;
|
|
18780
18805
|
const expected = await hashAuditEvent(event, previousHash, secret);
|
|
18781
18806
|
if (integrity === undefined || integrity.previousHash !== previousHash || integrity.hash !== expected) {
|
|
18782
18807
|
return brokenAt(index);
|
|
18783
18808
|
}
|
|
18784
|
-
|
|
18809
|
+
heads.set(chain, integrity.hash);
|
|
18785
18810
|
}
|
|
18786
18811
|
const valid = { ok: true };
|
|
18787
18812
|
return valid;
|
|
@@ -20974,5 +20999,5 @@ export {
|
|
|
20974
20999
|
AuthIdentityConflictError
|
|
20975
21000
|
};
|
|
20976
21001
|
|
|
20977
|
-
//# debugId=
|
|
21002
|
+
//# debugId=B234E4311897F15C64756E2164756E21
|
|
20978
21003
|
//# sourceMappingURL=index.js.map
|