@absolutejs/auth 0.30.0-beta.0 → 0.30.0-beta.2

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.
@@ -26,6 +26,8 @@ export type CredentialsConfig<UserType> = {
26
26
  headers: Record<string, string | undefined>;
27
27
  ip?: string;
28
28
  }) => boolean | Promise<boolean>;
29
+ passwordVerifier?: (plainPassword: string, storedHash: string) => Promise<boolean>;
30
+ rehashOnLogin?: boolean;
29
31
  loginRoute?: RouteString;
30
32
  onCreateCredentialUser: (identity: CredentialIdentity & Record<string, unknown>) => Promise<Response | StatusReturn | UserType> | Response | StatusReturn | UserType;
31
33
  onCredentialsLoginError?: (context: {
@@ -0,0 +1,34 @@
1
+ import type { CredentialRecord, CredentialStore } from './types';
2
+ export type ImportableUser<UserType> = {
3
+ passwordHash?: string;
4
+ user: UserType;
5
+ };
6
+ export type ImportUserResult<UserType> = {
7
+ error: string;
8
+ ok: false;
9
+ user: UserType;
10
+ } | {
11
+ credential?: CredentialRecord;
12
+ ok: true;
13
+ user: UserType;
14
+ };
15
+ export type ImportUsersOptions<UserType> = {
16
+ onCreateUser: (input: ImportableUser<UserType>) => Promise<{
17
+ email: string;
18
+ emailVerified?: boolean;
19
+ userId?: string;
20
+ } | null>;
21
+ concurrency?: number;
22
+ credentialStore: CredentialStore;
23
+ };
24
+ export declare const importUser: <UserType>(input: ImportableUser<UserType>, options: ImportUsersOptions<UserType>) => Promise<ImportUserResult<UserType>>;
25
+ export declare const importUsers: <UserType>(inputs: readonly ImportableUser<UserType>[], options: ImportUsersOptions<UserType>) => Promise<{
26
+ failed: number;
27
+ results: ImportUserResult<UserType>[];
28
+ succeeded: number;
29
+ }>;
30
+ export declare const rehashCredentialPassword: ({ credentialStore, current, plainPassword }: {
31
+ credentialStore: CredentialStore;
32
+ current: CredentialRecord;
33
+ plainPassword: string;
34
+ }) => Promise<void>;
@@ -0,0 +1,3 @@
1
+ export declare const isLegacyHash: (storedHash: string) => boolean;
2
+ export declare const verifyAuth0Pbkdf2: (plainPassword: string, wrappedHash: string) => Promise<boolean>;
3
+ export declare const verifyCognitoSha256: (plainPassword: string, wrappedHash: string) => 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, checkBreachesOnLogin, 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, passwordVerifier, rehashOnLogin, requireEmailVerification, sessionDurationMs }: CredentialRouteProps<UserType>) => Elysia<"", {
4
4
  decorator: {};
5
5
  store: {
6
6
  session: import("..").SessionRecord<UserType>;
package/dist/index.d.ts CHANGED
@@ -15050,6 +15050,9 @@ export * from './tenancy';
15050
15050
  export * from './credentials/config';
15051
15051
  export * from './credentials/passwordPolicy';
15052
15052
  export * from './credentials/emailValidation';
15053
+ export { importUser, importUsers, rehashCredentialPassword } from './credentials/import';
15054
+ export type { ImportUserResult, ImportUsersOptions, ImportableUser } from './credentials/import';
15055
+ export { isLegacyHash, verifyAuth0Pbkdf2, verifyCognitoSha256 } from './credentials/legacyHashers';
15053
15056
  export * from './credentials/types';
15054
15057
  export { credentialRoutes } from './credentials/routes';
15055
15058
  export { credentialsEmailVerification } from './credentials/emailVerification';
package/dist/index.js CHANGED
@@ -3392,6 +3392,110 @@ var credentialsEmailVerification = ({
3392
3392
  // src/credentials/login.ts
3393
3393
  import { Elysia as Elysia6, t as t6 } from "elysia";
3394
3394
 
3395
+ // src/credentials/import.ts
3396
+ var normalizeEmail = (email) => email.trim().toLowerCase();
3397
+ var buildCredential = (email, emailVerified, passwordHash, userId) => ({
3398
+ createdAt: Date.now(),
3399
+ email,
3400
+ emailVerified,
3401
+ passwordHash,
3402
+ status: "active",
3403
+ updatedAt: Date.now(),
3404
+ userId
3405
+ });
3406
+ var importUser = async (input, options) => {
3407
+ try {
3408
+ const created = await options.onCreateUser(input);
3409
+ if (created === null)
3410
+ return {
3411
+ error: "onCreateUser returned null",
3412
+ ok: false,
3413
+ user: input.user
3414
+ };
3415
+ const email = normalizeEmail(created.email);
3416
+ const emailVerified = created.emailVerified ?? true;
3417
+ if (input.passwordHash === undefined) {
3418
+ return { ok: true, user: input.user };
3419
+ }
3420
+ const credential = buildCredential(email, emailVerified, input.passwordHash, created.userId);
3421
+ await options.credentialStore.saveCredential(credential);
3422
+ return { credential, ok: true, user: input.user };
3423
+ } catch (err) {
3424
+ return {
3425
+ error: err instanceof Error ? err.message : String(err),
3426
+ ok: false,
3427
+ user: input.user
3428
+ };
3429
+ }
3430
+ };
3431
+ var importUsers = async (inputs, options) => {
3432
+ const concurrency = Math.max(1, options.concurrency ?? 1);
3433
+ const results = [];
3434
+ for (let cursor = 0;cursor < inputs.length; cursor += concurrency) {
3435
+ const slice = inputs.slice(cursor, cursor + concurrency);
3436
+ const batch = await Promise.all(slice.map((input) => importUser(input, options)));
3437
+ results.push(...batch);
3438
+ }
3439
+ return {
3440
+ failed: results.filter((result) => !result.ok).length,
3441
+ results,
3442
+ succeeded: results.filter((result) => result.ok).length
3443
+ };
3444
+ };
3445
+ var rehashCredentialPassword = async ({
3446
+ credentialStore,
3447
+ current,
3448
+ plainPassword
3449
+ }) => {
3450
+ const passwordHash = await hashPassword(plainPassword);
3451
+ await credentialStore.saveCredential({
3452
+ ...current,
3453
+ passwordHash,
3454
+ updatedAt: Date.now()
3455
+ });
3456
+ };
3457
+
3458
+ // src/credentials/legacyHashers.ts
3459
+ var PBKDF2_DEFAULT_ITERATIONS = 1e5;
3460
+ var PBKDF2_KEY_LENGTH_BYTES = 32;
3461
+ var constantTimeEqualBytes = (left, right) => {
3462
+ if (left.byteLength !== right.byteLength)
3463
+ return false;
3464
+ let diff = 0;
3465
+ for (let index = 0;index < left.byteLength; index++) {
3466
+ diff |= (left[index] ?? 0) ^ (right[index] ?? 0);
3467
+ }
3468
+ return diff === 0;
3469
+ };
3470
+ var base64Decode = (encoded) => new Uint8Array(Buffer.from(encoded, "base64"));
3471
+ var sha256Bytes = async (input) => new Uint8Array(await crypto.subtle.digest("SHA-256", input));
3472
+ var isLegacyHash = (storedHash) => !storedHash.startsWith("$argon2id$") && !storedHash.startsWith("$2");
3473
+ var verifyAuth0Pbkdf2 = async (plainPassword, wrappedHash) => {
3474
+ const parts = wrappedHash.split(":");
3475
+ if (parts.length !== 4 || parts[0] !== "auth0_pbkdf2")
3476
+ return false;
3477
+ const salt = base64Decode(parts[1] ?? "");
3478
+ const expected = base64Decode(parts[2] ?? "");
3479
+ const iterations = Number(parts[3] ?? PBKDF2_DEFAULT_ITERATIONS);
3480
+ if (Number.isNaN(iterations) || iterations <= 0)
3481
+ return false;
3482
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(plainPassword), { name: "PBKDF2" }, false, ["deriveBits"]);
3483
+ const derivedBits = await crypto.subtle.deriveBits({ hash: "SHA-256", iterations, name: "PBKDF2", salt }, key, PBKDF2_KEY_LENGTH_BYTES * 8);
3484
+ return constantTimeEqualBytes(new Uint8Array(derivedBits), expected);
3485
+ };
3486
+ var verifyCognitoSha256 = async (plainPassword, wrappedHash) => {
3487
+ const parts = wrappedHash.split(":");
3488
+ if (parts.length !== 3 || parts[0] !== "cognito_sha256")
3489
+ return false;
3490
+ const salt = base64Decode(parts[1] ?? "");
3491
+ const expected = base64Decode(parts[2] ?? "");
3492
+ const payload = new Uint8Array(salt.byteLength + plainPassword.length);
3493
+ payload.set(salt, 0);
3494
+ payload.set(new TextEncoder().encode(plainPassword), salt.byteLength);
3495
+ const derived = await sha256Bytes(payload);
3496
+ return constantTimeEqualBytes(derived, expected);
3497
+ };
3498
+
3395
3499
  // src/credentials/passwordPolicy.ts
3396
3500
  var DEFAULT_MIN_LENGTH = 12;
3397
3501
  var HEX_RADIX = 16;
@@ -3452,6 +3556,8 @@ var credentialsLogin = ({
3452
3556
  loginRoute = "/auth/login",
3453
3557
  onCredentialsLoginError,
3454
3558
  onCredentialsLoginSuccess,
3559
+ passwordVerifier,
3560
+ rehashOnLogin = false,
3455
3561
  requireEmailVerification = false,
3456
3562
  sessionDurationMs = DEFAULT_CREDENTIAL_SESSION_TTL_MS
3457
3563
  }) => new Elysia6().use(sessionStore()).post(loginRoute, async ({
@@ -3479,7 +3585,8 @@ var credentialsLogin = ({
3479
3585
  }
3480
3586
  const credential = await credentialStore.getCredentialByEmail(normalizedEmail);
3481
3587
  const user = await getUserByEmail(normalizedEmail);
3482
- const passwordValid = credential ? await verifyPassword(password, credential.passwordHash) : false;
3588
+ const verifyHash = passwordVerifier ?? verifyPassword;
3589
+ const passwordValid = credential === undefined ? false : await verifyHash(password, credential.passwordHash);
3483
3590
  if (!credential || !user || credential.status !== "active" || !passwordValid) {
3484
3591
  await lockoutGuard?.recordFailure(normalizedEmail);
3485
3592
  await onCredentialsLoginError?.({
@@ -3489,6 +3596,13 @@ var credentialsLogin = ({
3489
3596
  return status("Unauthorized", "Invalid email or password");
3490
3597
  }
3491
3598
  await lockoutGuard?.recordSuccess(normalizedEmail);
3599
+ if (rehashOnLogin && isLegacyHash(credential.passwordHash)) {
3600
+ await rehashCredentialPassword({
3601
+ credentialStore,
3602
+ current: credential,
3603
+ plainPassword: password
3604
+ });
3605
+ }
3492
3606
  if (requireEmailVerification && !credential.emailVerified) {
3493
3607
  return status("Forbidden", { status: "email_not_verified" });
3494
3608
  }
@@ -22997,7 +23111,9 @@ export {
22997
23111
  verifyHcaptcha,
22998
23112
  verifyDpopProof,
22999
23113
  verifyDpopNonce,
23114
+ verifyCognitoSha256,
23000
23115
  verifyClientAssertion,
23116
+ verifyAuth0Pbkdf2,
23001
23117
  verifyAuditChain,
23002
23118
  verifyApiKey,
23003
23119
  verifyAccessToken,
@@ -23044,6 +23160,7 @@ export {
23044
23160
  resolveAuthHtmxRenderers,
23045
23161
  resolveApiPrincipal,
23046
23162
  removeFromSessionRing,
23163
+ rehashCredentialPassword,
23047
23164
  registerClient,
23048
23165
  refreshableProviderOptions,
23049
23166
  recordLoginAttempt,
@@ -23104,6 +23221,7 @@ export {
23104
23221
  isPKCEProviderOption,
23105
23222
  isOIDCProviderOption,
23106
23223
  isMfaEnrolled,
23224
+ isLegacyHash,
23107
23225
  isImpersonating,
23108
23226
  isDisposableEmail,
23109
23227
  isAuthIntent,
@@ -23111,6 +23229,8 @@ export {
23111
23229
  inviteToOrganization,
23112
23230
  introspectToken,
23113
23231
  instantiateUserSession,
23232
+ importUsers,
23233
+ importUser,
23114
23234
  hashToken,
23115
23235
  hashPassword,
23116
23236
  hashAuditEvent,
@@ -23330,5 +23450,5 @@ export {
23330
23450
  AuthIdentityConflictError
23331
23451
  };
23332
23452
 
23333
- //# debugId=BAAEA91F75FEB49E64756E2164756E21
23453
+ //# debugId=658AE7172110F24564756E2164756E21
23334
23454
  //# sourceMappingURL=index.js.map