@absolutejs/auth 0.75.7 → 0.76.1
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/agents/context.d.ts +2 -6
- package/dist/agents/index.js +5 -6
- package/dist/agents/index.js.map +3 -3
- package/dist/agents/routes.d.ts +4 -10
- package/dist/authContext.d.ts +66 -178
- package/dist/authorization/protectPermission.d.ts +3 -8
- package/dist/cli/migrate.js +19 -15
- package/dist/cli/migrate.js.map +5 -5
- package/dist/htmx/configuredRoutes.d.ts +37 -126
- package/dist/htmx/routes.d.ts +37 -126
- package/dist/index.d.ts +237 -431
- package/dist/index.js +332 -113
- package/dist/index.js.map +17 -17
- package/dist/mfa/challenge.d.ts +53 -7
- package/dist/mfa/management.d.ts +49 -6
- package/dist/mfa/postgresMfaStore.d.ts +31 -1
- package/dist/mfa/rotation.d.ts +1 -1
- package/dist/mfa/routes.d.ts +78 -2
- package/dist/mfa/sms.d.ts +7 -2
- package/dist/mfa/totp.d.ts +8 -2
- package/dist/mfa/types.d.ts +47 -0
- package/dist/routes/protectRoute.d.ts +4 -21
- package/dist/routes/stepUp.d.ts +3 -8
- package/dist/server.js +330 -113
- package/dist/server.js.map +17 -17
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -4368,8 +4368,8 @@ var failureResponse = (config, failure, requiredScopes) => new Response(JSON.str
|
|
|
4368
4368
|
var agentAuthContextPlugin = (config) => new Elysia2({
|
|
4369
4369
|
name: "@absolutejs/auth/agent-context",
|
|
4370
4370
|
seed: pluginDependencySeed(config)
|
|
4371
|
-
}).derive(({ request }) => {
|
|
4372
|
-
|
|
4371
|
+
}).derive(({ request }) => ({
|
|
4372
|
+
protectAgent: async (requiredScopes, handleAuth, handleAuthFail) => {
|
|
4373
4373
|
if (config === undefined) {
|
|
4374
4374
|
const failure = {
|
|
4375
4375
|
code: "Unauthorized",
|
|
@@ -4395,9 +4395,8 @@ var agentAuthContextPlugin = (config) => new Elysia2({
|
|
|
4395
4395
|
return await handleAuthFail?.(failure) ?? failureResponse(config, failure, requiredScopes);
|
|
4396
4396
|
}
|
|
4397
4397
|
return handleAuth(principal);
|
|
4398
|
-
}
|
|
4399
|
-
|
|
4400
|
-
}).as("global");
|
|
4398
|
+
}
|
|
4399
|
+
})).as("global");
|
|
4401
4400
|
|
|
4402
4401
|
// src/agents/oauthGuide.ts
|
|
4403
4402
|
var DEFAULT_GUIDE_ROUTE = "/auth.md";
|
|
@@ -5608,17 +5607,17 @@ var protectPermissionPlugin = ({
|
|
|
5608
5607
|
}).use(sessionStore()).guard({
|
|
5609
5608
|
cookie: t3.Cookie({ user_session_id: userSessionIdTypebox }),
|
|
5610
5609
|
schema: "merge"
|
|
5611
|
-
}).derive(({ store: { session }, cookie: { user_session_id }, status }) => {
|
|
5612
|
-
|
|
5610
|
+
}).derive(({ store: { session }, cookie: { user_session_id }, status }) => ({
|
|
5611
|
+
protectPermission: (check, handleAuth, handleAuthFail) => getStatusFromSource({
|
|
5613
5612
|
authSessionStore,
|
|
5614
5613
|
session,
|
|
5615
5614
|
user_session_id
|
|
5616
5615
|
}).then(async ({ user, error }) => {
|
|
5617
5616
|
if (error) {
|
|
5618
|
-
return
|
|
5617
|
+
return handleAuthFail?.(error) ?? status(error.code, error.message);
|
|
5619
5618
|
}
|
|
5620
5619
|
if (!user) {
|
|
5621
|
-
return
|
|
5620
|
+
return handleAuthFail?.({
|
|
5622
5621
|
code: "Unauthorized",
|
|
5623
5622
|
message: "User is not authenticated"
|
|
5624
5623
|
}) ?? status("Unauthorized", "User is not authenticated");
|
|
@@ -5635,15 +5634,14 @@ var protectPermissionPlugin = ({
|
|
|
5635
5634
|
organizationId: check.organizationId,
|
|
5636
5635
|
type: "authorization_denied"
|
|
5637
5636
|
});
|
|
5638
|
-
return
|
|
5637
|
+
return handleAuthFail?.({
|
|
5639
5638
|
code: "Forbidden",
|
|
5640
5639
|
message: "Insufficient permissions"
|
|
5641
5640
|
}) ?? status("Forbidden", "Insufficient permissions");
|
|
5642
5641
|
}
|
|
5643
5642
|
return handleAuth(user);
|
|
5644
|
-
})
|
|
5645
|
-
|
|
5646
|
-
}).as("global");
|
|
5643
|
+
})
|
|
5644
|
+
})).as("global");
|
|
5647
5645
|
|
|
5648
5646
|
// src/routes/protectRoute.ts
|
|
5649
5647
|
import { Elysia as Elysia6, t as t4 } from "elysia";
|
|
@@ -5707,7 +5705,8 @@ var protectRoutePlugin = ({
|
|
|
5707
5705
|
}).derive(async ({
|
|
5708
5706
|
store: { session },
|
|
5709
5707
|
cookie: { user_session_id },
|
|
5710
|
-
headers
|
|
5708
|
+
headers,
|
|
5709
|
+
status
|
|
5711
5710
|
}) => {
|
|
5712
5711
|
const sessionStatus = await getStatusFromSource({
|
|
5713
5712
|
authSessionStore,
|
|
@@ -5726,30 +5725,25 @@ var protectRoutePlugin = ({
|
|
|
5726
5725
|
authorization: headers.authorization,
|
|
5727
5726
|
config: accessTokens
|
|
5728
5727
|
});
|
|
5729
|
-
return {
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
return
|
|
5746
|
-
code: "Unauthorized",
|
|
5747
|
-
message: "User is not authenticated"
|
|
5748
|
-
}) ?? status("Unauthorized", "User is not authenticated");
|
|
5728
|
+
return {
|
|
5729
|
+
authPrincipal,
|
|
5730
|
+
protectRoute: (handleAuth, handleAuthFail) => {
|
|
5731
|
+
const user = authPrincipal?.user;
|
|
5732
|
+
const { error } = sessionStatus;
|
|
5733
|
+
if (error) {
|
|
5734
|
+
if (user)
|
|
5735
|
+
return handleAuth(user);
|
|
5736
|
+
return handleAuthFail?.(error) ?? status(error.code, error.message);
|
|
5737
|
+
}
|
|
5738
|
+
if (!user) {
|
|
5739
|
+
return handleAuthFail?.({
|
|
5740
|
+
code: "Unauthorized",
|
|
5741
|
+
message: "User is not authenticated"
|
|
5742
|
+
}) ?? status("Unauthorized", "User is not authenticated");
|
|
5743
|
+
}
|
|
5744
|
+
return handleAuth(user);
|
|
5749
5745
|
}
|
|
5750
|
-
return handleAuth(user);
|
|
5751
5746
|
};
|
|
5752
|
-
return { protectRoute };
|
|
5753
5747
|
}).as("global");
|
|
5754
5748
|
|
|
5755
5749
|
// src/routes/stepUp.ts
|
|
@@ -5762,24 +5756,23 @@ var stepUpPlugin = ({
|
|
|
5762
5756
|
}).use(sessionStore()).guard({
|
|
5763
5757
|
cookie: t5.Cookie({ user_session_id: userSessionIdTypebox }),
|
|
5764
5758
|
schema: "merge"
|
|
5765
|
-
}).derive(({ store: { session }, cookie: { user_session_id }, status }) => {
|
|
5766
|
-
|
|
5759
|
+
}).derive(({ store: { session }, cookie: { user_session_id }, status }) => ({
|
|
5760
|
+
requireRecentAuth: (maxAgeMs, handleAuth, handleAuthFail) => loadSessionFromSource({
|
|
5767
5761
|
authSessionStore,
|
|
5768
5762
|
session,
|
|
5769
5763
|
userSessionId: user_session_id.value
|
|
5770
|
-
}).then(
|
|
5764
|
+
}).then((userSession) => {
|
|
5771
5765
|
const authenticatedAt = userSession?.authenticatedAt;
|
|
5772
5766
|
const isRecent = authenticatedAt !== undefined && Date.now() - authenticatedAt <= maxAgeMs;
|
|
5773
5767
|
if (!userSession || !isRecent) {
|
|
5774
|
-
return
|
|
5768
|
+
return handleAuthFail?.({
|
|
5775
5769
|
code: "Unauthorized",
|
|
5776
5770
|
message: "Recent authentication required"
|
|
5777
5771
|
}) ?? status("Unauthorized", "Recent authentication required");
|
|
5778
5772
|
}
|
|
5779
5773
|
return handleAuth(userSession.user);
|
|
5780
|
-
})
|
|
5781
|
-
|
|
5782
|
-
}).as("global");
|
|
5774
|
+
})
|
|
5775
|
+
})).as("global");
|
|
5783
5776
|
|
|
5784
5777
|
// src/oidc/socketTickets.ts
|
|
5785
5778
|
init_crypto();
|
|
@@ -7190,7 +7183,45 @@ var createLockoutGuard = ({
|
|
|
7190
7183
|
});
|
|
7191
7184
|
|
|
7192
7185
|
// src/mfa/types.ts
|
|
7193
|
-
var
|
|
7186
|
+
var LEGACY_SMS_FACTOR_ID = "legacy-sms";
|
|
7187
|
+
var LEGACY_TOTP_FACTOR_ID = "legacy-totp";
|
|
7188
|
+
var getMfaFactors = (enrollment) => {
|
|
7189
|
+
if (enrollment.factors !== undefined)
|
|
7190
|
+
return enrollment.factors;
|
|
7191
|
+
const factors = [];
|
|
7192
|
+
if (enrollment.totpSecretCiphertext) {
|
|
7193
|
+
factors.push({
|
|
7194
|
+
id: LEGACY_TOTP_FACTOR_ID,
|
|
7195
|
+
label: "Authenticator app",
|
|
7196
|
+
secretCiphertext: enrollment.totpSecretCiphertext,
|
|
7197
|
+
type: "totp",
|
|
7198
|
+
verified: enrollment.totpVerified
|
|
7199
|
+
});
|
|
7200
|
+
}
|
|
7201
|
+
if (enrollment.smsPhone) {
|
|
7202
|
+
factors.push({
|
|
7203
|
+
id: LEGACY_SMS_FACTOR_ID,
|
|
7204
|
+
label: "Text message",
|
|
7205
|
+
phone: enrollment.smsPhone,
|
|
7206
|
+
type: "sms",
|
|
7207
|
+
verified: enrollment.smsVerified
|
|
7208
|
+
});
|
|
7209
|
+
}
|
|
7210
|
+
return factors;
|
|
7211
|
+
};
|
|
7212
|
+
var isMfaEnrolled = (enrollment) => enrollment !== undefined && (getMfaFactors(enrollment).some((factor) => factor.verified) || enrollment.backupCodeHashes.length > 0);
|
|
7213
|
+
var withMfaFactors = (enrollment, factors) => {
|
|
7214
|
+
const firstSms = factors.find((factor) => factor.type === "sms");
|
|
7215
|
+
const firstTotp = factors.find((factor) => factor.type === "totp");
|
|
7216
|
+
return {
|
|
7217
|
+
...enrollment,
|
|
7218
|
+
factors,
|
|
7219
|
+
smsPhone: firstSms?.phone,
|
|
7220
|
+
smsVerified: firstSms?.verified ?? false,
|
|
7221
|
+
totpSecretCiphertext: firstTotp?.secretCiphertext,
|
|
7222
|
+
totpVerified: firstTotp?.verified ?? false
|
|
7223
|
+
};
|
|
7224
|
+
};
|
|
7194
7225
|
|
|
7195
7226
|
// src/mfa/gate.ts
|
|
7196
7227
|
var createMfaGate = ({ getUserId, mfaStore }) => async (user) => isMfaEnrolled(await mfaStore.getEnrollment(getUserId(user)));
|
|
@@ -7406,6 +7437,8 @@ var hasRecentAuthentication = (session, maxAgeMs, now = Date.now()) => session.a
|
|
|
7406
7437
|
// src/mfa/sms.ts
|
|
7407
7438
|
var DECIMAL_RADIX2 = 10;
|
|
7408
7439
|
var MASK_VISIBLE_DIGITS = 4;
|
|
7440
|
+
var FACTOR_LABEL_MAX_LENGTH = 80;
|
|
7441
|
+
var DEFAULT_SMS_LABEL = "Text message";
|
|
7409
7442
|
var E164_PATTERN = /^\+[1-9]\d{7,14}$/u;
|
|
7410
7443
|
var generateNumericCode = (length) => {
|
|
7411
7444
|
const bytes = new Uint8Array(length);
|
|
@@ -7579,10 +7612,13 @@ var mfaSmsRoutes = ({
|
|
|
7579
7612
|
smsSetupRoute = "/auth/mfa/sms/setup",
|
|
7580
7613
|
smsVerifyRoute = "/auth/mfa/sms/verify"
|
|
7581
7614
|
}) => new Elysia18().use(sessionStore()).post(smsSetupRoute, {
|
|
7582
|
-
body: t12.Object({
|
|
7615
|
+
body: t12.Object({
|
|
7616
|
+
label: t12.Optional(t12.String({ maxLength: FACTOR_LABEL_MAX_LENGTH })),
|
|
7617
|
+
phone: t12.String()
|
|
7618
|
+
}),
|
|
7583
7619
|
cookie: t12.Cookie({ user_session_id: userSessionIdTypebox })
|
|
7584
7620
|
}, async ({
|
|
7585
|
-
body: { phone },
|
|
7621
|
+
body: { label, phone },
|
|
7586
7622
|
cookie: { user_session_id },
|
|
7587
7623
|
status,
|
|
7588
7624
|
store: { session }
|
|
@@ -7610,21 +7646,35 @@ var mfaSmsRoutes = ({
|
|
|
7610
7646
|
if (existing?.smsCodeSentAt !== undefined && now - existing.smsCodeSentAt < smsResendCooldownMs) {
|
|
7611
7647
|
return status("Too Many Requests", "SMS resend cooldown active");
|
|
7612
7648
|
}
|
|
7649
|
+
const base = existing ?? {
|
|
7650
|
+
backupCodeHashes: [],
|
|
7651
|
+
createdAt: now,
|
|
7652
|
+
smsVerified: false,
|
|
7653
|
+
totpVerified: false,
|
|
7654
|
+
updatedAt: now,
|
|
7655
|
+
userId
|
|
7656
|
+
};
|
|
7657
|
+
const factor = {
|
|
7658
|
+
id: crypto.randomUUID(),
|
|
7659
|
+
label: label?.trim() || DEFAULT_SMS_LABEL,
|
|
7660
|
+
phone,
|
|
7661
|
+
type: "sms",
|
|
7662
|
+
verified: false
|
|
7663
|
+
};
|
|
7664
|
+
const factors = [
|
|
7665
|
+
...getMfaFactors(base).filter((existingFactor) => existingFactor.type !== "sms" || existingFactor.verified),
|
|
7666
|
+
factor
|
|
7667
|
+
];
|
|
7613
7668
|
try {
|
|
7614
7669
|
await issueAndStoreSmsCode({
|
|
7615
7670
|
codeLength: smsCodeLength,
|
|
7616
7671
|
enrollment: {
|
|
7617
|
-
|
|
7618
|
-
createdAt: existing?.createdAt ?? now,
|
|
7619
|
-
lastUsedAt: existing?.lastUsedAt,
|
|
7672
|
+
...withMfaFactors(base, factors),
|
|
7620
7673
|
smsFailedAttempts: 0,
|
|
7674
|
+
smsPendingFactorId: factor.id,
|
|
7621
7675
|
smsPhone: phone,
|
|
7622
|
-
smsVerified: false,
|
|
7623
|
-
totpFailedAttempts: existing?.totpFailedAttempts ?? 0,
|
|
7624
|
-
totpSecretCiphertext: existing?.totpSecretCiphertext,
|
|
7625
|
-
totpVerified: existing?.totpVerified ?? false,
|
|
7626
7676
|
updatedAt: now,
|
|
7627
|
-
userId
|
|
7677
|
+
userId: base.userId
|
|
7628
7678
|
},
|
|
7629
7679
|
mfaStore,
|
|
7630
7680
|
onSendSmsCode,
|
|
@@ -7641,12 +7691,18 @@ var mfaSmsRoutes = ({
|
|
|
7641
7691
|
throw error;
|
|
7642
7692
|
return status(mapped.status, mapped.message);
|
|
7643
7693
|
}
|
|
7644
|
-
return status("OK", {
|
|
7694
|
+
return status("OK", {
|
|
7695
|
+
factorId: factor.id,
|
|
7696
|
+
phone: maskPhone(phone)
|
|
7697
|
+
});
|
|
7645
7698
|
}).post(smsVerifyRoute, {
|
|
7646
|
-
body: t12.Object({
|
|
7699
|
+
body: t12.Object({
|
|
7700
|
+
code: t12.String(),
|
|
7701
|
+
factorId: t12.Optional(t12.String())
|
|
7702
|
+
}),
|
|
7647
7703
|
cookie: t12.Cookie({ user_session_id: userSessionIdTypebox })
|
|
7648
7704
|
}, async ({
|
|
7649
|
-
body: { code },
|
|
7705
|
+
body: { code, factorId },
|
|
7650
7706
|
cookie: { user_session_id },
|
|
7651
7707
|
status,
|
|
7652
7708
|
store: { session }
|
|
@@ -7667,6 +7723,12 @@ var mfaSmsRoutes = ({
|
|
|
7667
7723
|
if (!enrollment?.smsPhone) {
|
|
7668
7724
|
return status("Bad Request", "No SMS enrollment in progress");
|
|
7669
7725
|
}
|
|
7726
|
+
const factors = getMfaFactors(enrollment);
|
|
7727
|
+
const pendingFactorId = factorId ?? enrollment.smsPendingFactorId;
|
|
7728
|
+
const pendingFactor = factors.find((candidate) => candidate.type === "sms" && (candidate.id === pendingFactorId || pendingFactorId === undefined && candidate.phone === enrollment.smsPhone));
|
|
7729
|
+
if (!pendingFactor || pendingFactor.type !== "sms") {
|
|
7730
|
+
return status("Bad Request", "No SMS enrollment in progress");
|
|
7731
|
+
}
|
|
7670
7732
|
const localCodeHash = enrollment.smsPendingCodeHash;
|
|
7671
7733
|
const challengeId = enrollment.smsChallengeId;
|
|
7672
7734
|
const localCodeExpiresAt = enrollment.smsPendingCodeExpiresAt;
|
|
@@ -7694,7 +7756,7 @@ var mfaSmsRoutes = ({
|
|
|
7694
7756
|
purpose: "mfa_enrollment",
|
|
7695
7757
|
reference: providerReference,
|
|
7696
7758
|
subject: userId,
|
|
7697
|
-
to:
|
|
7759
|
+
to: pendingFactor.phone
|
|
7698
7760
|
});
|
|
7699
7761
|
if (checked.error !== undefined) {
|
|
7700
7762
|
return status(checked.error.status, checked.error.message);
|
|
@@ -7710,8 +7772,10 @@ var mfaSmsRoutes = ({
|
|
|
7710
7772
|
});
|
|
7711
7773
|
return status(providerResult?.status === "max_attempts_reached" || attempts === undefined ? "Too Many Requests" : "Bad Request", providerResult?.status === "expired" ? "SMS code expired" : "Invalid SMS code");
|
|
7712
7774
|
}
|
|
7775
|
+
const verifiedFactors = factors.map((candidate) => candidate.id === pendingFactor.id ? { ...candidate, verified: true } : candidate);
|
|
7713
7776
|
const completed = await mfaStore.completeSmsChallenge({
|
|
7714
7777
|
challengeId,
|
|
7778
|
+
factors: verifiedFactors,
|
|
7715
7779
|
smsVerified: true,
|
|
7716
7780
|
userId
|
|
7717
7781
|
});
|
|
@@ -7741,15 +7805,50 @@ var mfaChallenge = ({
|
|
|
7741
7805
|
smsMaxAttempts = DEFAULT_SMS_MAX_ATTEMPTS,
|
|
7742
7806
|
smsResendCooldownMs = DEFAULT_SMS_RESEND_COOLDOWN_MS,
|
|
7743
7807
|
totpMaxAttempts = DEFAULT_TOTP_MAX_ATTEMPTS
|
|
7744
|
-
}) => new Elysia19().use(sessionStore()).
|
|
7808
|
+
}) => new Elysia19().use(sessionStore()).get(challengeRoute, { cookie: t13.Cookie({ user_session_id: userSessionIdTypebox }) }, async ({
|
|
7809
|
+
cookie: { user_session_id },
|
|
7810
|
+
status,
|
|
7811
|
+
store: { unregisteredSession }
|
|
7812
|
+
}) => {
|
|
7813
|
+
const compatibilityLayer = await createSessionCompatibilityLayer({
|
|
7814
|
+
authSessionStore,
|
|
7815
|
+
userSessionId: user_session_id.value
|
|
7816
|
+
});
|
|
7817
|
+
const challengeUnregistered = authSessionStore ? compatibilityLayer.unregisteredSession : unregisteredSession;
|
|
7818
|
+
const pendingId = user_session_id.value;
|
|
7819
|
+
const pending = pendingId ? challengeUnregistered[pendingId] : undefined;
|
|
7820
|
+
if (!pending) {
|
|
7821
|
+
return status("Unauthorized", "No MFA challenge in progress");
|
|
7822
|
+
}
|
|
7823
|
+
const user = await getChallengeUser(pending.userIdentity ?? {});
|
|
7824
|
+
const enrollment = user ? await mfaStore.getEnrollment(getUserId(user)) : undefined;
|
|
7825
|
+
if (!enrollment) {
|
|
7826
|
+
return status("Unauthorized", "No MFA challenge in progress");
|
|
7827
|
+
}
|
|
7828
|
+
const response = {
|
|
7829
|
+
backupCodesAvailable: enrollment.backupCodeHashes.length > 0,
|
|
7830
|
+
factors: getMfaFactors(enrollment).filter((factor) => factor.verified).map((factor) => ({
|
|
7831
|
+
id: factor.id,
|
|
7832
|
+
label: factor.label,
|
|
7833
|
+
phone: factor.type === "sms" ? maskPhone(factor.phone) : null,
|
|
7834
|
+
type: factor.type
|
|
7835
|
+
}))
|
|
7836
|
+
};
|
|
7837
|
+
return status("OK", response);
|
|
7838
|
+
}).post(challengeRoute, {
|
|
7745
7839
|
body: t13.Object({
|
|
7746
7840
|
action: t13.Optional(t13.Union([t13.Literal("send"), t13.Literal("verify")])),
|
|
7747
7841
|
code: t13.Optional(t13.String()),
|
|
7748
|
-
factor: t13.Optional(t13.
|
|
7842
|
+
factor: t13.Optional(t13.Union([
|
|
7843
|
+
t13.Literal("backup_codes"),
|
|
7844
|
+
t13.Literal("sms"),
|
|
7845
|
+
t13.Literal("totp")
|
|
7846
|
+
])),
|
|
7847
|
+
factorId: t13.Optional(t13.String())
|
|
7749
7848
|
}),
|
|
7750
7849
|
cookie: t13.Cookie({ user_session_id: userSessionIdTypebox })
|
|
7751
7850
|
}, async ({
|
|
7752
|
-
body: { action, code, factor },
|
|
7851
|
+
body: { action, code, factor, factorId },
|
|
7753
7852
|
cookie: { user_session_id },
|
|
7754
7853
|
status,
|
|
7755
7854
|
store: { session, unregisteredSession }
|
|
@@ -7770,6 +7869,7 @@ var mfaChallenge = ({
|
|
|
7770
7869
|
if (!user || !enrollment) {
|
|
7771
7870
|
return status("Unauthorized", "No MFA challenge in progress");
|
|
7772
7871
|
}
|
|
7872
|
+
const factors = getMfaFactors(enrollment);
|
|
7773
7873
|
const promote = async () => {
|
|
7774
7874
|
delete challengeUnregistered[pendingId];
|
|
7775
7875
|
const userSessionId = crypto.randomUUID();
|
|
@@ -7792,10 +7892,18 @@ var mfaChallenge = ({
|
|
|
7792
7892
|
if (enrollment.smsCodeSentAt !== undefined && Date.now() - enrollment.smsCodeSentAt < smsResendCooldownMs) {
|
|
7793
7893
|
return status("Too Many Requests", "SMS resend cooldown active");
|
|
7794
7894
|
}
|
|
7895
|
+
const smsFactor = factors.find((candidate) => candidate.type === "sms" && candidate.verified && (candidate.id === factorId || factorId === undefined));
|
|
7896
|
+
if (!smsFactor || smsFactor.type !== "sms") {
|
|
7897
|
+
return status("Bad Request", "SMS factor not found");
|
|
7898
|
+
}
|
|
7795
7899
|
try {
|
|
7796
7900
|
await issueAndStoreSmsCode({
|
|
7797
7901
|
codeLength: smsCodeLength,
|
|
7798
|
-
enrollment
|
|
7902
|
+
enrollment: {
|
|
7903
|
+
...withMfaFactors(enrollment, factors),
|
|
7904
|
+
smsPendingFactorId: smsFactor.id,
|
|
7905
|
+
smsPhone: smsFactor.phone
|
|
7906
|
+
},
|
|
7799
7907
|
mfaStore,
|
|
7800
7908
|
onSendSmsCode,
|
|
7801
7909
|
previousEnrollment: enrollment,
|
|
@@ -7811,10 +7919,19 @@ var mfaChallenge = ({
|
|
|
7811
7919
|
throw error;
|
|
7812
7920
|
return status(mapped.status, mapped.message);
|
|
7813
7921
|
}
|
|
7814
|
-
return status("OK", {
|
|
7922
|
+
return status("OK", {
|
|
7923
|
+
factorId: smsFactor.id,
|
|
7924
|
+
phone: maskPhone(smsFactor.phone),
|
|
7925
|
+
status: "sent"
|
|
7926
|
+
});
|
|
7815
7927
|
};
|
|
7816
7928
|
const runSmsChallenge = async () => {
|
|
7817
|
-
if (
|
|
7929
|
+
if (action !== "send" && factorId !== undefined && enrollment.smsPendingFactorId !== undefined && factorId !== enrollment.smsPendingFactorId) {
|
|
7930
|
+
return status("Bad Request", "No SMS code in progress");
|
|
7931
|
+
}
|
|
7932
|
+
const selectedFactorId = enrollment.smsPendingFactorId ?? factorId;
|
|
7933
|
+
const smsFactor = factors.find((candidate) => candidate.type === "sms" && candidate.verified && (candidate.id === selectedFactorId || selectedFactorId === undefined));
|
|
7934
|
+
if (!smsFactor || smsFactor.type !== "sms") {
|
|
7818
7935
|
return status("Unauthorized", "No MFA challenge in progress");
|
|
7819
7936
|
}
|
|
7820
7937
|
if (action === "send")
|
|
@@ -7853,7 +7970,7 @@ var mfaChallenge = ({
|
|
|
7853
7970
|
purpose: "mfa_challenge",
|
|
7854
7971
|
reference: providerReference,
|
|
7855
7972
|
subject: getUserId(user),
|
|
7856
|
-
to:
|
|
7973
|
+
to: smsFactor.phone
|
|
7857
7974
|
});
|
|
7858
7975
|
if (checked.error !== undefined) {
|
|
7859
7976
|
return status(checked.error.status, checked.error.message);
|
|
@@ -7875,6 +7992,7 @@ var mfaChallenge = ({
|
|
|
7875
7992
|
}
|
|
7876
7993
|
const completed = await mfaStore.completeSmsChallenge({
|
|
7877
7994
|
challengeId,
|
|
7995
|
+
factors,
|
|
7878
7996
|
lastUsedAt: Date.now(),
|
|
7879
7997
|
smsVerified: true,
|
|
7880
7998
|
userId: getUserId(user)
|
|
@@ -7896,11 +8014,14 @@ var mfaChallenge = ({
|
|
|
7896
8014
|
});
|
|
7897
8015
|
return status("Unauthorized", "Too many attempts");
|
|
7898
8016
|
}
|
|
7899
|
-
const
|
|
7900
|
-
|
|
8017
|
+
const totpFactors = factors.filter((candidate) => candidate.type === "totp" && candidate.verified && (candidate.id === factorId || factorId === undefined));
|
|
8018
|
+
const totpChecks = factor === "backup_codes" ? [] : await Promise.all(totpFactors.map(async (candidate) => verifyTotp({
|
|
8019
|
+
secret: await decryptTotpSecret(candidate.secretCiphertext, encryptionKey),
|
|
7901
8020
|
token: code
|
|
7902
|
-
})
|
|
7903
|
-
const
|
|
8021
|
+
})));
|
|
8022
|
+
const totpValid = totpChecks.some(Boolean);
|
|
8023
|
+
const mayUseBackupCode = factor === "backup_codes" || factor === undefined && factorId === undefined;
|
|
8024
|
+
const remainingBackupHashes = totpValid || !mayUseBackupCode ? undefined : await consumeBackupCode(code, enrollment.backupCodeHashes);
|
|
7904
8025
|
if (!totpValid && remainingBackupHashes === undefined) {
|
|
7905
8026
|
await mfaStore.saveEnrollment({
|
|
7906
8027
|
...enrollment,
|
|
@@ -7932,6 +8053,12 @@ var maskPhone2 = (phone) => {
|
|
|
7932
8053
|
const suffix = phone.slice(-visibleDigits);
|
|
7933
8054
|
return `${"*".repeat(Math.max(0, phone.length - visibleDigits))}${suffix}`;
|
|
7934
8055
|
};
|
|
8056
|
+
var publicFactors = (enrollment) => enrollment ? getMfaFactors(enrollment).filter((factor) => factor.verified).map((factor) => ({
|
|
8057
|
+
id: factor.id,
|
|
8058
|
+
label: factor.label,
|
|
8059
|
+
phone: factor.type === "sms" ? maskPhone2(factor.phone) : null,
|
|
8060
|
+
type: factor.type
|
|
8061
|
+
})) : [];
|
|
7935
8062
|
var mfaManagementRoutes = ({
|
|
7936
8063
|
authSessionStore,
|
|
7937
8064
|
getUserId,
|
|
@@ -7955,6 +8082,7 @@ var mfaManagementRoutes = ({
|
|
|
7955
8082
|
const response = {
|
|
7956
8083
|
backupCodesRemaining: enrollment?.backupCodeHashes.length ?? 0,
|
|
7957
8084
|
enabled: isMfaEnrolled(enrollment),
|
|
8085
|
+
factors: publicFactors(enrollment),
|
|
7958
8086
|
smsBackup: {
|
|
7959
8087
|
enabled: enrollment?.smsVerified ?? false,
|
|
7960
8088
|
phone: maskPhone2(enrollment?.smsPhone)
|
|
@@ -7980,11 +8108,54 @@ var mfaManagementRoutes = ({
|
|
|
7980
8108
|
}
|
|
7981
8109
|
await mfaStore.removeEnrollment(getUserId(userSession.user));
|
|
7982
8110
|
return status("OK", { status: "disabled" });
|
|
8111
|
+
}).delete(`${managementRoute}/factors/:factorId`, {
|
|
8112
|
+
cookie: t14.Cookie({ user_session_id: userSessionIdTypebox }),
|
|
8113
|
+
params: t14.Object({ factorId: t14.String() })
|
|
8114
|
+
}, async ({
|
|
8115
|
+
cookie: { user_session_id },
|
|
8116
|
+
params: { factorId },
|
|
8117
|
+
status,
|
|
8118
|
+
store: { session }
|
|
8119
|
+
}) => {
|
|
8120
|
+
const userSession = await loadSessionFromSource({
|
|
8121
|
+
authSessionStore,
|
|
8122
|
+
session,
|
|
8123
|
+
userSessionId: user_session_id.value
|
|
8124
|
+
});
|
|
8125
|
+
if (!userSession) {
|
|
8126
|
+
return status("Unauthorized", "Authentication required");
|
|
8127
|
+
}
|
|
8128
|
+
if (!hasRecentAuthentication(userSession, managementAuthMaxAgeMs)) {
|
|
8129
|
+
return status("Unauthorized", "Recent authentication required");
|
|
8130
|
+
}
|
|
8131
|
+
const userId = getUserId(userSession.user);
|
|
8132
|
+
const enrollment = await mfaStore.getEnrollment(userId);
|
|
8133
|
+
if (!enrollment) {
|
|
8134
|
+
return status("Not Found", "MFA factor not found");
|
|
8135
|
+
}
|
|
8136
|
+
const factors = getMfaFactors(enrollment);
|
|
8137
|
+
if (!factors.some((factor) => factor.id === factorId)) {
|
|
8138
|
+
return status("Not Found", "MFA factor not found");
|
|
8139
|
+
}
|
|
8140
|
+
const remaining = factors.filter((factor) => factor.id !== factorId);
|
|
8141
|
+
if (remaining.length === 0) {
|
|
8142
|
+
await mfaStore.removeEnrollment(userId);
|
|
8143
|
+
} else {
|
|
8144
|
+
const hasVerifiedFactor = remaining.some((factor) => factor.verified);
|
|
8145
|
+
await mfaStore.saveEnrollment(withMfaFactors({
|
|
8146
|
+
...enrollment,
|
|
8147
|
+
backupCodeHashes: hasVerifiedFactor ? enrollment.backupCodeHashes : [],
|
|
8148
|
+
updatedAt: Date.now()
|
|
8149
|
+
}, remaining));
|
|
8150
|
+
}
|
|
8151
|
+
return status("OK", { status: "removed" });
|
|
7983
8152
|
});
|
|
7984
8153
|
|
|
7985
8154
|
// src/mfa/totp.ts
|
|
7986
8155
|
init_crypto();
|
|
7987
8156
|
import { Elysia as Elysia21, t as t15 } from "elysia";
|
|
8157
|
+
var FACTOR_LABEL_MAX_LENGTH2 = 80;
|
|
8158
|
+
var DEFAULT_TOTP_LABEL = "Authenticator app";
|
|
7988
8159
|
var mfaTotpRoutes = ({
|
|
7989
8160
|
authSessionStore,
|
|
7990
8161
|
backupCodeCount = DEFAULT_BACKUP_CODE_COUNT,
|
|
@@ -7996,7 +8167,13 @@ var mfaTotpRoutes = ({
|
|
|
7996
8167
|
onMfaEnrolled,
|
|
7997
8168
|
totpSetupRoute = "/auth/mfa/totp/setup",
|
|
7998
8169
|
totpVerifyRoute = "/auth/mfa/totp/verify"
|
|
7999
|
-
}) => new Elysia21().use(sessionStore()).post(totpSetupRoute, {
|
|
8170
|
+
}) => new Elysia21().use(sessionStore()).post(totpSetupRoute, {
|
|
8171
|
+
body: t15.Object({
|
|
8172
|
+
label: t15.Optional(t15.String({ maxLength: FACTOR_LABEL_MAX_LENGTH2 }))
|
|
8173
|
+
}),
|
|
8174
|
+
cookie: t15.Cookie({ user_session_id: userSessionIdTypebox })
|
|
8175
|
+
}, async ({
|
|
8176
|
+
body: { label },
|
|
8000
8177
|
cookie: { user_session_id },
|
|
8001
8178
|
status,
|
|
8002
8179
|
store: { session }
|
|
@@ -8016,20 +8193,25 @@ var mfaTotpRoutes = ({
|
|
|
8016
8193
|
const secret = generateTotpSecret();
|
|
8017
8194
|
const existing = await mfaStore.getEnrollment(userId);
|
|
8018
8195
|
const now = Date.now();
|
|
8019
|
-
|
|
8020
|
-
backupCodeHashes:
|
|
8021
|
-
createdAt:
|
|
8022
|
-
|
|
8023
|
-
smsPendingCodeExpiresAt: existing?.smsPendingCodeExpiresAt,
|
|
8024
|
-
smsPendingCodeHash: existing?.smsPendingCodeHash,
|
|
8025
|
-
smsPhone: existing?.smsPhone,
|
|
8026
|
-
smsVerified: existing?.smsVerified ?? false,
|
|
8027
|
-
totpSecretCiphertext: await encryptTotpSecret(secret, encryptionKey),
|
|
8196
|
+
const base = existing ?? {
|
|
8197
|
+
backupCodeHashes: [],
|
|
8198
|
+
createdAt: now,
|
|
8199
|
+
smsVerified: false,
|
|
8028
8200
|
totpVerified: false,
|
|
8029
8201
|
updatedAt: now,
|
|
8030
8202
|
userId
|
|
8031
|
-
}
|
|
8203
|
+
};
|
|
8204
|
+
const factor = {
|
|
8205
|
+
id: crypto.randomUUID(),
|
|
8206
|
+
label: label?.trim() || DEFAULT_TOTP_LABEL,
|
|
8207
|
+
secretCiphertext: await encryptTotpSecret(secret, encryptionKey),
|
|
8208
|
+
type: "totp",
|
|
8209
|
+
verified: false
|
|
8210
|
+
};
|
|
8211
|
+
const factors = getMfaFactors(base).filter((existingFactor) => existingFactor.type !== "totp" || existingFactor.verified);
|
|
8212
|
+
await mfaStore.saveEnrollment(withMfaFactors({ ...base, updatedAt: now }, [...factors, factor]));
|
|
8032
8213
|
return status("OK", {
|
|
8214
|
+
factorId: factor.id,
|
|
8033
8215
|
secret,
|
|
8034
8216
|
uri: createTotpKeyUri({
|
|
8035
8217
|
accountName: userId,
|
|
@@ -8038,10 +8220,13 @@ var mfaTotpRoutes = ({
|
|
|
8038
8220
|
})
|
|
8039
8221
|
});
|
|
8040
8222
|
}).post(totpVerifyRoute, {
|
|
8041
|
-
body: t15.Object({
|
|
8223
|
+
body: t15.Object({
|
|
8224
|
+
code: t15.String(),
|
|
8225
|
+
factorId: t15.Optional(t15.String())
|
|
8226
|
+
}),
|
|
8042
8227
|
cookie: t15.Cookie({ user_session_id: userSessionIdTypebox })
|
|
8043
8228
|
}, async ({
|
|
8044
|
-
body: { code },
|
|
8229
|
+
body: { code, factorId },
|
|
8045
8230
|
cookie: { user_session_id },
|
|
8046
8231
|
status,
|
|
8047
8232
|
store: { session }
|
|
@@ -8059,21 +8244,26 @@ var mfaTotpRoutes = ({
|
|
|
8059
8244
|
}
|
|
8060
8245
|
const userId = getUserId(userSession.user);
|
|
8061
8246
|
const enrollment = await mfaStore.getEnrollment(userId);
|
|
8062
|
-
if (!enrollment
|
|
8247
|
+
if (!enrollment) {
|
|
8248
|
+
return status("Bad Request", "No TOTP enrollment in progress");
|
|
8249
|
+
}
|
|
8250
|
+
const factors = getMfaFactors(enrollment);
|
|
8251
|
+
const factor = factors.find((candidate) => candidate.type === "totp" && (candidate.id === factorId || factorId === undefined && !candidate.verified));
|
|
8252
|
+
if (!factor || factor.type !== "totp") {
|
|
8063
8253
|
return status("Bad Request", "No TOTP enrollment in progress");
|
|
8064
8254
|
}
|
|
8065
|
-
const secret = await decryptTotpSecret(
|
|
8255
|
+
const secret = await decryptTotpSecret(factor.secretCiphertext, encryptionKey);
|
|
8066
8256
|
const valid = await verifyTotp({ secret, token: code });
|
|
8067
8257
|
if (!valid) {
|
|
8068
8258
|
return status("Bad Request", "Invalid TOTP code");
|
|
8069
8259
|
}
|
|
8070
8260
|
const { codes, hashes } = await generateBackupCodes(backupCodeCount);
|
|
8071
|
-
|
|
8261
|
+
const verifiedFactors = factors.map((candidate) => candidate.id === factor.id ? { ...candidate, verified: true } : candidate);
|
|
8262
|
+
await mfaStore.saveEnrollment(withMfaFactors({
|
|
8072
8263
|
...enrollment,
|
|
8073
8264
|
backupCodeHashes: hashes,
|
|
8074
|
-
totpVerified: true,
|
|
8075
8265
|
updatedAt: Date.now()
|
|
8076
|
-
});
|
|
8266
|
+
}, verifiedFactors));
|
|
8077
8267
|
await onMfaEnrolled?.({ userId });
|
|
8078
8268
|
return status("OK", { backupCodes: codes });
|
|
8079
8269
|
});
|
|
@@ -35948,23 +36138,33 @@ var ensureReadable = async (ciphertext, key, userId) => {
|
|
|
35948
36138
|
}
|
|
35949
36139
|
};
|
|
35950
36140
|
var planEnrollment = async (enrollment, oldKey, newKey) => {
|
|
35951
|
-
const
|
|
35952
|
-
|
|
36141
|
+
const factors = getMfaFactors(enrollment);
|
|
36142
|
+
const totpFactors = factors.filter((factor) => factor.type === "totp");
|
|
36143
|
+
if (totpFactors.length === 0) {
|
|
35953
36144
|
const plan2 = { kind: "skip" };
|
|
35954
36145
|
return plan2;
|
|
35955
36146
|
}
|
|
35956
|
-
|
|
35957
|
-
|
|
35958
|
-
|
|
36147
|
+
let rotated = false;
|
|
36148
|
+
const nextFactors = await Promise.all(factors.map(async (factor) => {
|
|
36149
|
+
if (factor.type !== "totp")
|
|
36150
|
+
return factor;
|
|
36151
|
+
const secret = await tryDecrypt(factor.secretCiphertext, oldKey);
|
|
36152
|
+
if (secret === undefined) {
|
|
36153
|
+
await ensureReadable(factor.secretCiphertext, newKey, enrollment.userId);
|
|
36154
|
+
return factor;
|
|
36155
|
+
}
|
|
36156
|
+
rotated = true;
|
|
36157
|
+
return {
|
|
36158
|
+
...factor,
|
|
36159
|
+
secretCiphertext: await encryptTotpSecret(secret, newKey)
|
|
36160
|
+
};
|
|
36161
|
+
}));
|
|
36162
|
+
if (!rotated) {
|
|
35959
36163
|
const plan2 = { kind: "already" };
|
|
35960
36164
|
return plan2;
|
|
35961
36165
|
}
|
|
35962
36166
|
const plan = {
|
|
35963
|
-
enrollment: {
|
|
35964
|
-
...enrollment,
|
|
35965
|
-
totpSecretCiphertext: await encryptTotpSecret(secret, newKey),
|
|
35966
|
-
updatedAt: Date.now()
|
|
35967
|
-
},
|
|
36167
|
+
enrollment: withMfaFactors({ ...enrollment, updatedAt: Date.now() }, nextFactors),
|
|
35968
36168
|
kind: "rotate"
|
|
35969
36169
|
};
|
|
35970
36170
|
return plan;
|
|
@@ -35987,7 +36187,8 @@ var rotateMfaEncryptionKey = async ({
|
|
|
35987
36187
|
// src/mfa/inMemoryMfaStore.ts
|
|
35988
36188
|
var cloneEnrollment = (value) => ({
|
|
35989
36189
|
...value,
|
|
35990
|
-
backupCodeHashes: [...value.backupCodeHashes]
|
|
36190
|
+
backupCodeHashes: [...value.backupCodeHashes],
|
|
36191
|
+
factors: value.factors?.map((factor) => ({ ...factor }))
|
|
35991
36192
|
});
|
|
35992
36193
|
var createInMemoryMfaStore = () => {
|
|
35993
36194
|
const enrollments = new Map;
|
|
@@ -36009,6 +36210,7 @@ var createInMemoryMfaStore = () => {
|
|
|
36009
36210
|
},
|
|
36010
36211
|
completeSmsChallenge: async ({
|
|
36011
36212
|
challengeId,
|
|
36213
|
+
factors,
|
|
36012
36214
|
lastUsedAt,
|
|
36013
36215
|
smsVerified,
|
|
36014
36216
|
userId
|
|
@@ -36018,11 +36220,13 @@ var createInMemoryMfaStore = () => {
|
|
|
36018
36220
|
return false;
|
|
36019
36221
|
enrollments.set(userId, cloneEnrollment({
|
|
36020
36222
|
...current,
|
|
36223
|
+
factors: factors ?? current.factors,
|
|
36021
36224
|
lastUsedAt: lastUsedAt ?? current.lastUsedAt,
|
|
36022
36225
|
smsChallengeId: undefined,
|
|
36023
36226
|
smsFailedAttempts: 0,
|
|
36024
36227
|
smsPendingCodeExpiresAt: undefined,
|
|
36025
36228
|
smsPendingCodeHash: undefined,
|
|
36229
|
+
smsPendingFactorId: undefined,
|
|
36026
36230
|
smsPendingPurpose: undefined,
|
|
36027
36231
|
smsProviderReference: undefined,
|
|
36028
36232
|
smsVerified,
|
|
@@ -36083,6 +36287,7 @@ var mfaEnrollmentsTable = pgTable("auth_mfa_enrollments", {
|
|
|
36083
36287
|
backup_code_hashes: jsonb("backup_code_hashes").$type().notNull().default([]),
|
|
36084
36288
|
created_at_ms: bigint("created_at_ms", { mode: "number" }).notNull(),
|
|
36085
36289
|
last_used_at_ms: bigint("last_used_at_ms", { mode: "number" }),
|
|
36290
|
+
mfa_factors: jsonb("mfa_factors").$type(),
|
|
36086
36291
|
sms_challenge_id: text("sms_challenge_id"),
|
|
36087
36292
|
sms_code_sent_at_ms: bigint("sms_code_sent_at_ms", { mode: "number" }),
|
|
36088
36293
|
sms_failed_attempts: smallint("sms_failed_attempts").notNull().default(0),
|
|
@@ -36090,6 +36295,7 @@ var mfaEnrollmentsTable = pgTable("auth_mfa_enrollments", {
|
|
|
36090
36295
|
mode: "number"
|
|
36091
36296
|
}),
|
|
36092
36297
|
sms_pending_code_hash: text("sms_pending_code_hash"),
|
|
36298
|
+
sms_pending_factor_id: text("sms_pending_factor_id"),
|
|
36093
36299
|
sms_pending_purpose: text("sms_pending_purpose").$type(),
|
|
36094
36300
|
sms_phone: varchar("sms_phone", { length: PHONE_LENGTH }),
|
|
36095
36301
|
sms_provider_reference: text("sms_provider_reference"),
|
|
@@ -36103,12 +36309,14 @@ var mfaEnrollmentsTable = pgTable("auth_mfa_enrollments", {
|
|
|
36103
36309
|
var toEnrollment = (row) => ({
|
|
36104
36310
|
backupCodeHashes: row.backup_code_hashes,
|
|
36105
36311
|
createdAt: row.created_at_ms,
|
|
36312
|
+
factors: row.mfa_factors ?? undefined,
|
|
36106
36313
|
lastUsedAt: row.last_used_at_ms ?? undefined,
|
|
36107
36314
|
smsChallengeId: row.sms_challenge_id ?? undefined,
|
|
36108
36315
|
smsCodeSentAt: row.sms_code_sent_at_ms ?? undefined,
|
|
36109
36316
|
smsFailedAttempts: row.sms_failed_attempts,
|
|
36110
36317
|
smsPendingCodeExpiresAt: row.sms_pending_code_expires_at_ms ?? undefined,
|
|
36111
36318
|
smsPendingCodeHash: row.sms_pending_code_hash ?? undefined,
|
|
36319
|
+
smsPendingFactorId: row.sms_pending_factor_id ?? undefined,
|
|
36112
36320
|
smsPendingPurpose: row.sms_pending_purpose ?? undefined,
|
|
36113
36321
|
smsPhone: row.sms_phone ?? undefined,
|
|
36114
36322
|
smsProviderReference: row.sms_provider_reference ?? undefined,
|
|
@@ -36125,11 +36333,13 @@ var createPostgresMfaStore = (db) => {
|
|
|
36125
36333
|
backup_code_hashes: enrollment.backupCodeHashes,
|
|
36126
36334
|
created_at_ms: enrollment.createdAt,
|
|
36127
36335
|
last_used_at_ms: enrollment.lastUsedAt ?? null,
|
|
36336
|
+
mfa_factors: enrollment.factors ?? null,
|
|
36128
36337
|
sms_challenge_id: enrollment.smsChallengeId ?? null,
|
|
36129
36338
|
sms_code_sent_at_ms: enrollment.smsCodeSentAt ?? null,
|
|
36130
36339
|
sms_failed_attempts: enrollment.smsFailedAttempts ?? 0,
|
|
36131
36340
|
sms_pending_code_expires_at_ms: enrollment.smsPendingCodeExpiresAt ?? null,
|
|
36132
36341
|
sms_pending_code_hash: enrollment.smsPendingCodeHash ?? null,
|
|
36342
|
+
sms_pending_factor_id: enrollment.smsPendingFactorId ?? null,
|
|
36133
36343
|
sms_pending_purpose: enrollment.smsPendingPurpose ?? null,
|
|
36134
36344
|
sms_phone: enrollment.smsPhone ?? null,
|
|
36135
36345
|
sms_provider_reference: enrollment.smsProviderReference ?? null,
|
|
@@ -36152,11 +36362,13 @@ var createPostgresMfaStore = (db) => {
|
|
|
36152
36362
|
});
|
|
36153
36363
|
const rows = await db.insert(mfaEnrollmentsTable).values(values).onConflictDoUpdate({
|
|
36154
36364
|
set: {
|
|
36365
|
+
mfa_factors: enrollment.factors ?? null,
|
|
36155
36366
|
sms_challenge_id: challengeId,
|
|
36156
36367
|
sms_code_sent_at_ms: enrollment.smsCodeSentAt ?? null,
|
|
36157
36368
|
sms_failed_attempts: 0,
|
|
36158
36369
|
sms_pending_code_expires_at_ms: enrollment.smsPendingCodeExpiresAt ?? null,
|
|
36159
36370
|
sms_pending_code_hash: null,
|
|
36371
|
+
sms_pending_factor_id: enrollment.smsPendingFactorId ?? null,
|
|
36160
36372
|
sms_pending_purpose: enrollment.smsPendingPurpose ?? null,
|
|
36161
36373
|
sms_phone: enrollment.smsPhone ?? null,
|
|
36162
36374
|
sms_provider_reference: null,
|
|
@@ -36170,16 +36382,19 @@ var createPostgresMfaStore = (db) => {
|
|
|
36170
36382
|
},
|
|
36171
36383
|
completeSmsChallenge: async ({
|
|
36172
36384
|
challengeId,
|
|
36385
|
+
factors,
|
|
36173
36386
|
lastUsedAt,
|
|
36174
36387
|
smsVerified,
|
|
36175
36388
|
userId
|
|
36176
36389
|
}) => {
|
|
36177
36390
|
const rows = await db.update(mfaEnrollmentsTable).set({
|
|
36178
36391
|
last_used_at_ms: lastUsedAt,
|
|
36392
|
+
mfa_factors: factors,
|
|
36179
36393
|
sms_challenge_id: null,
|
|
36180
36394
|
sms_failed_attempts: 0,
|
|
36181
36395
|
sms_pending_code_expires_at_ms: null,
|
|
36182
36396
|
sms_pending_code_hash: null,
|
|
36397
|
+
sms_pending_factor_id: null,
|
|
36183
36398
|
sms_pending_purpose: null,
|
|
36184
36399
|
sms_provider_reference: null,
|
|
36185
36400
|
sms_verified: smsVerified,
|
|
@@ -40573,12 +40788,7 @@ var renderChunk = (chunk) => {
|
|
|
40573
40788
|
return "";
|
|
40574
40789
|
};
|
|
40575
40790
|
var formatSqlTemplate = (value) => value.queryChunks.map(renderChunk).join("");
|
|
40576
|
-
var
|
|
40577
|
-
const dimensions = Reflect.get(column, "dimensions");
|
|
40578
|
-
const elementType = column.getSQLType();
|
|
40579
|
-
return typeof dimensions === "number" && dimensions > 0 ? `${elementType}${"[]".repeat(dimensions)}` : elementType;
|
|
40580
|
-
};
|
|
40581
|
-
var formatScalarDefault = (value) => {
|
|
40791
|
+
var formatDefault = (value) => {
|
|
40582
40792
|
if (value === null || value === undefined)
|
|
40583
40793
|
return "NULL";
|
|
40584
40794
|
if (is(value, SQL))
|
|
@@ -40589,17 +40799,15 @@ var formatScalarDefault = (value) => {
|
|
|
40589
40799
|
return value ? "true" : "false";
|
|
40590
40800
|
if (typeof value === "number")
|
|
40591
40801
|
return String(value);
|
|
40592
|
-
if (typeof value === "object") {
|
|
40802
|
+
if (Array.isArray(value) || typeof value === "object") {
|
|
40593
40803
|
return `'${JSON.stringify(value)}'::jsonb`;
|
|
40594
40804
|
}
|
|
40595
40805
|
return String(value);
|
|
40596
40806
|
};
|
|
40597
|
-
var
|
|
40598
|
-
var formatDefault = (column, value) => {
|
|
40807
|
+
var columnSqlType = (column) => {
|
|
40599
40808
|
const dimensions = Reflect.get(column, "dimensions");
|
|
40600
|
-
|
|
40601
|
-
|
|
40602
|
-
return formatScalarDefault(value);
|
|
40809
|
+
const elementType = column.getSQLType();
|
|
40810
|
+
return typeof dimensions === "number" && dimensions > 0 ? `${elementType}${"[]".repeat(dimensions)}` : elementType;
|
|
40603
40811
|
};
|
|
40604
40812
|
var columnSql = (column) => {
|
|
40605
40813
|
const parts = [`"${column.name}"`, columnSqlType(column)];
|
|
@@ -40608,7 +40816,7 @@ var columnSql = (column) => {
|
|
|
40608
40816
|
if (column.notNull && !column.primary)
|
|
40609
40817
|
parts.push("NOT NULL");
|
|
40610
40818
|
if (column.hasDefault && column.default !== undefined) {
|
|
40611
|
-
parts.push(`DEFAULT ${formatDefault(column
|
|
40819
|
+
parts.push(`DEFAULT ${formatDefault(column.default)}`);
|
|
40612
40820
|
}
|
|
40613
40821
|
if (column.isUnique)
|
|
40614
40822
|
parts.push("UNIQUE");
|
|
@@ -40726,6 +40934,14 @@ var mfaSmsAtomicChallengeMigration = {
|
|
|
40726
40934
|
id: "0005_sms_atomic_challenge",
|
|
40727
40935
|
sql: 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_challenge_id" text;'
|
|
40728
40936
|
};
|
|
40937
|
+
var mfaMultipleFactorsMigration = {
|
|
40938
|
+
id: "0006_multiple_factors",
|
|
40939
|
+
sql: [
|
|
40940
|
+
'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "mfa_factors" jsonb;',
|
|
40941
|
+
'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_pending_factor_id" text;'
|
|
40942
|
+
].join(`
|
|
40943
|
+
`)
|
|
40944
|
+
};
|
|
40729
40945
|
var oidcResourceAudienceMigration = {
|
|
40730
40946
|
id: "0002_resource_audience",
|
|
40731
40947
|
sql: [
|
|
@@ -40807,7 +41023,8 @@ var blockMigrations = {
|
|
|
40807
41023
|
mfaSmsColumnsMigration,
|
|
40808
41024
|
mfaTotpLockoutMigration,
|
|
40809
41025
|
mfaSmsDeliveryPolicyMigration,
|
|
40810
|
-
mfaSmsAtomicChallengeMigration
|
|
41026
|
+
mfaSmsAtomicChallengeMigration,
|
|
41027
|
+
mfaMultipleFactorsMigration
|
|
40811
41028
|
]
|
|
40812
41029
|
},
|
|
40813
41030
|
oidc: {
|
|
@@ -41849,5 +42066,5 @@ export {
|
|
|
41849
42066
|
userSessionIdTypebox
|
|
41850
42067
|
};
|
|
41851
42068
|
|
|
41852
|
-
//# debugId=
|
|
42069
|
+
//# debugId=EBB1CE453260D04564756E2164756E21
|
|
41853
42070
|
//# sourceMappingURL=server.js.map
|