@absolutejs/auth 0.57.9 → 0.58.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/README.md +33 -0
- package/dist/cli/migrate.js +15 -2
- package/dist/cli/migrate.js.map +4 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +221 -24
- package/dist/index.js.map +13 -12
- package/dist/manifest.js +8 -1
- package/dist/manifest.js.map +3 -3
- package/dist/manifest.json +9 -0
- package/dist/mfa/challenge.d.ts +4 -2
- package/dist/mfa/config.d.ts +6 -0
- package/dist/mfa/postgresMfaStore.d.ts +45 -0
- package/dist/mfa/routes.d.ts +9 -4
- package/dist/mfa/sms.d.ts +38 -5
- package/dist/mfa/types.d.ts +4 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +221 -25
- package/dist/server.js.map +14 -13
- package/dist/types.d.ts +5 -1
- package/dist/verification/types.d.ts +47 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1015,7 +1015,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
|
|
|
1015
1015
|
query: unknown;
|
|
1016
1016
|
headers: unknown;
|
|
1017
1017
|
response: {
|
|
1018
|
-
400:
|
|
1018
|
+
400: string;
|
|
1019
1019
|
401: "Authentication required";
|
|
1020
1020
|
501: "SMS MFA is not configured";
|
|
1021
1021
|
200: {
|
|
@@ -1030,6 +1030,8 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
|
|
|
1030
1030
|
property?: string;
|
|
1031
1031
|
expected?: string;
|
|
1032
1032
|
};
|
|
1033
|
+
429: string;
|
|
1034
|
+
503: string;
|
|
1033
1035
|
};
|
|
1034
1036
|
};
|
|
1035
1037
|
};
|
|
@@ -1043,7 +1045,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
|
|
|
1043
1045
|
query: unknown;
|
|
1044
1046
|
headers: unknown;
|
|
1045
1047
|
response: {
|
|
1046
|
-
400:
|
|
1048
|
+
400: string;
|
|
1047
1049
|
401: "Authentication required";
|
|
1048
1050
|
200: {
|
|
1049
1051
|
readonly status: "enrolled";
|
|
@@ -1057,7 +1059,8 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
|
|
|
1057
1059
|
property?: string;
|
|
1058
1060
|
expected?: string;
|
|
1059
1061
|
};
|
|
1060
|
-
429:
|
|
1062
|
+
429: string;
|
|
1063
|
+
503: string;
|
|
1061
1064
|
};
|
|
1062
1065
|
};
|
|
1063
1066
|
};
|
|
@@ -1073,7 +1076,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
|
|
|
1073
1076
|
query: unknown;
|
|
1074
1077
|
headers: unknown;
|
|
1075
1078
|
response: {
|
|
1076
|
-
400:
|
|
1079
|
+
400: string;
|
|
1077
1080
|
401: "SMS code expired" | "Too many attempts" | "No MFA challenge in progress" | "Invalid MFA code";
|
|
1078
1081
|
200: {
|
|
1079
1082
|
readonly status: "sent";
|
|
@@ -1089,6 +1092,8 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
|
|
|
1089
1092
|
property?: string;
|
|
1090
1093
|
expected?: string;
|
|
1091
1094
|
};
|
|
1095
|
+
429: string;
|
|
1096
|
+
503: string;
|
|
1092
1097
|
};
|
|
1093
1098
|
};
|
|
1094
1099
|
};
|
|
@@ -3486,6 +3491,7 @@ export { createNeonDatabase } from './stores/postgres';
|
|
|
3486
3491
|
export type { AnyPgDatabase } from './stores/postgres';
|
|
3487
3492
|
export * from './mfa/config';
|
|
3488
3493
|
export * from './mfa/types';
|
|
3494
|
+
export * from './verification/types';
|
|
3489
3495
|
export { consumeBackupCode, generateBackupCodes } from './mfa/backupCodes';
|
|
3490
3496
|
export { createMfaGate } from './mfa/gate';
|
|
3491
3497
|
export { mfaChallenge } from './mfa/challenge';
|
package/dist/index.js
CHANGED
|
@@ -5853,6 +5853,7 @@ var DEFAULT_SMS_CODE_LENGTH = 6;
|
|
|
5853
5853
|
var SMS_CODE_TTL_MINUTES = 5;
|
|
5854
5854
|
var DEFAULT_SMS_CODE_TTL_MS = SMS_CODE_TTL_MINUTES * SECONDS_IN_A_MINUTE * MILLISECONDS_IN_A_SECOND;
|
|
5855
5855
|
var DEFAULT_SMS_MAX_ATTEMPTS = 3;
|
|
5856
|
+
var DEFAULT_SMS_RESEND_COOLDOWN_MS = 30 * MILLISECONDS_IN_A_SECOND;
|
|
5856
5857
|
var DEFAULT_TOTP_MAX_ATTEMPTS = 5;
|
|
5857
5858
|
|
|
5858
5859
|
// src/mfa/secret.ts
|
|
@@ -5863,6 +5864,20 @@ var encryptTotpSecret = (secret, encryptionKey) => encryptionKey ? encryptSecret
|
|
|
5863
5864
|
// src/mfa/sms.ts
|
|
5864
5865
|
init_crypto();
|
|
5865
5866
|
import { Elysia as Elysia17, t as t11 } from "elysia";
|
|
5867
|
+
|
|
5868
|
+
// src/verification/types.ts
|
|
5869
|
+
class VerificationProviderError extends Error {
|
|
5870
|
+
kind;
|
|
5871
|
+
provider;
|
|
5872
|
+
constructor(input) {
|
|
5873
|
+
super(input.message, { cause: input.cause });
|
|
5874
|
+
this.name = "VerificationProviderError";
|
|
5875
|
+
this.kind = input.kind;
|
|
5876
|
+
this.provider = input.provider;
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
|
|
5880
|
+
// src/mfa/sms.ts
|
|
5866
5881
|
var DECIMAL_RADIX2 = 10;
|
|
5867
5882
|
var MASK_VISIBLE_DIGITS = 4;
|
|
5868
5883
|
var E164_PATTERN = /^\+[1-9]\d{7,14}$/u;
|
|
@@ -5877,28 +5892,83 @@ var issueSmsCode = async (codeLength, ttlMs) => {
|
|
|
5877
5892
|
const expiresAt = Date.now() + ttlMs;
|
|
5878
5893
|
return { code, expiresAt, hash };
|
|
5879
5894
|
};
|
|
5895
|
+
var checkWithVerificationProvider = async (provider, input) => {
|
|
5896
|
+
try {
|
|
5897
|
+
return { result: await provider.check(input) };
|
|
5898
|
+
} catch (error) {
|
|
5899
|
+
const mapped = mapVerificationProviderError(error);
|
|
5900
|
+
if (mapped === undefined)
|
|
5901
|
+
throw error;
|
|
5902
|
+
return { error: mapped };
|
|
5903
|
+
}
|
|
5904
|
+
};
|
|
5880
5905
|
var isE164Phone = (phone) => E164_PATTERN.test(phone);
|
|
5881
5906
|
var issueAndStoreSmsCode = async ({
|
|
5882
5907
|
codeLength,
|
|
5883
5908
|
enrollment,
|
|
5884
5909
|
mfaStore,
|
|
5885
5910
|
onSendSmsCode,
|
|
5886
|
-
|
|
5911
|
+
purpose,
|
|
5912
|
+
verificationProvider,
|
|
5913
|
+
ttlMs,
|
|
5914
|
+
userId
|
|
5887
5915
|
}) => {
|
|
5888
5916
|
const phone = enrollment.smsPhone;
|
|
5889
5917
|
if (phone === undefined)
|
|
5890
5918
|
return;
|
|
5919
|
+
if (verificationProvider !== undefined) {
|
|
5920
|
+
const started = await verificationProvider.start({
|
|
5921
|
+
channel: "sms",
|
|
5922
|
+
purpose,
|
|
5923
|
+
subject: userId,
|
|
5924
|
+
to: phone
|
|
5925
|
+
});
|
|
5926
|
+
await mfaStore.saveEnrollment({
|
|
5927
|
+
...enrollment,
|
|
5928
|
+
smsCodeSentAt: Date.now(),
|
|
5929
|
+
smsFailedAttempts: 0,
|
|
5930
|
+
smsPendingCodeExpiresAt: started.expiresAt,
|
|
5931
|
+
smsPendingCodeHash: undefined,
|
|
5932
|
+
smsPendingPurpose: purpose,
|
|
5933
|
+
smsProviderReference: started.reference,
|
|
5934
|
+
updatedAt: Date.now()
|
|
5935
|
+
});
|
|
5936
|
+
return started.expiresAt;
|
|
5937
|
+
}
|
|
5891
5938
|
const { code, expiresAt, hash } = await issueSmsCode(codeLength, ttlMs);
|
|
5892
5939
|
await mfaStore.saveEnrollment({
|
|
5893
5940
|
...enrollment,
|
|
5941
|
+
smsCodeSentAt: Date.now(),
|
|
5894
5942
|
smsFailedAttempts: 0,
|
|
5895
5943
|
smsPendingCodeExpiresAt: expiresAt,
|
|
5896
5944
|
smsPendingCodeHash: hash,
|
|
5945
|
+
smsPendingPurpose: purpose,
|
|
5946
|
+
smsProviderReference: undefined,
|
|
5897
5947
|
updatedAt: Date.now()
|
|
5898
5948
|
});
|
|
5899
|
-
await onSendSmsCode?.({ code, expiresAt, phone });
|
|
5949
|
+
await onSendSmsCode?.({ code, expiresAt, phone, purpose, userId });
|
|
5900
5950
|
return expiresAt;
|
|
5901
5951
|
};
|
|
5952
|
+
var mapVerificationProviderError = (error) => {
|
|
5953
|
+
if (!(error instanceof VerificationProviderError))
|
|
5954
|
+
return;
|
|
5955
|
+
if (error.kind === "rate_limited") {
|
|
5956
|
+
return {
|
|
5957
|
+
message: "Verification provider rate limit reached",
|
|
5958
|
+
status: "Too Many Requests"
|
|
5959
|
+
};
|
|
5960
|
+
}
|
|
5961
|
+
if (error.kind === "invalid_destination") {
|
|
5962
|
+
return {
|
|
5963
|
+
message: "Phone number cannot receive verification codes",
|
|
5964
|
+
status: "Bad Request"
|
|
5965
|
+
};
|
|
5966
|
+
}
|
|
5967
|
+
return {
|
|
5968
|
+
message: "Verification provider unavailable",
|
|
5969
|
+
status: "Service Unavailable"
|
|
5970
|
+
};
|
|
5971
|
+
};
|
|
5902
5972
|
var maskPhone = (phone) => {
|
|
5903
5973
|
const visible = phone.slice(-MASK_VISIBLE_DIGITS);
|
|
5904
5974
|
const maskedLength = Math.max(phone.length - MASK_VISIBLE_DIGITS, 0);
|
|
@@ -5910,9 +5980,11 @@ var mfaSmsRoutes = ({
|
|
|
5910
5980
|
mfaStore,
|
|
5911
5981
|
onMfaEnrolled,
|
|
5912
5982
|
onSendSmsCode,
|
|
5983
|
+
verificationProvider,
|
|
5913
5984
|
smsCodeLength = DEFAULT_SMS_CODE_LENGTH,
|
|
5914
5985
|
smsCodeTtlMs = DEFAULT_SMS_CODE_TTL_MS,
|
|
5915
5986
|
smsMaxAttempts = DEFAULT_SMS_MAX_ATTEMPTS,
|
|
5987
|
+
smsResendCooldownMs = DEFAULT_SMS_RESEND_COOLDOWN_MS,
|
|
5916
5988
|
smsSetupRoute = "/auth/mfa/sms/setup",
|
|
5917
5989
|
smsVerifyRoute = "/auth/mfa/sms/verify"
|
|
5918
5990
|
}) => new Elysia17().use(sessionStore()).post(smsSetupRoute, async ({
|
|
@@ -5921,7 +5993,7 @@ var mfaSmsRoutes = ({
|
|
|
5921
5993
|
status,
|
|
5922
5994
|
store: { session }
|
|
5923
5995
|
}) => {
|
|
5924
|
-
if (onSendSmsCode === undefined) {
|
|
5996
|
+
if (onSendSmsCode === undefined && verificationProvider === undefined) {
|
|
5925
5997
|
return status("Not Implemented", "SMS MFA is not configured");
|
|
5926
5998
|
}
|
|
5927
5999
|
const userSession = await loadSessionFromSource({
|
|
@@ -5938,21 +6010,51 @@ var mfaSmsRoutes = ({
|
|
|
5938
6010
|
const userId = getUserId(userSession.user);
|
|
5939
6011
|
const existing = await mfaStore.getEnrollment(userId);
|
|
5940
6012
|
const now = Date.now();
|
|
5941
|
-
|
|
6013
|
+
if (existing?.smsCodeSentAt !== undefined && now - existing.smsCodeSentAt < smsResendCooldownMs) {
|
|
6014
|
+
return status("Too Many Requests", "SMS resend cooldown active");
|
|
6015
|
+
}
|
|
6016
|
+
let started;
|
|
6017
|
+
try {
|
|
6018
|
+
started = verificationProvider ? await verificationProvider.start({
|
|
6019
|
+
channel: "sms",
|
|
6020
|
+
purpose: "mfa_enrollment",
|
|
6021
|
+
subject: userId,
|
|
6022
|
+
to: phone
|
|
6023
|
+
}) : undefined;
|
|
6024
|
+
} catch (error) {
|
|
6025
|
+
const mapped = mapVerificationProviderError(error);
|
|
6026
|
+
if (mapped === undefined)
|
|
6027
|
+
throw error;
|
|
6028
|
+
return status(mapped.status, mapped.message);
|
|
6029
|
+
}
|
|
6030
|
+
const local = started ? undefined : await issueSmsCode(smsCodeLength, smsCodeTtlMs);
|
|
5942
6031
|
await mfaStore.saveEnrollment({
|
|
5943
6032
|
backupCodeHashes: existing?.backupCodeHashes ?? [],
|
|
5944
6033
|
createdAt: existing?.createdAt ?? now,
|
|
6034
|
+
lastUsedAt: existing?.lastUsedAt,
|
|
6035
|
+
smsCodeSentAt: now,
|
|
5945
6036
|
smsFailedAttempts: 0,
|
|
5946
|
-
smsPendingCodeExpiresAt: expiresAt,
|
|
5947
|
-
smsPendingCodeHash: hash,
|
|
6037
|
+
smsPendingCodeExpiresAt: started?.expiresAt ?? local?.expiresAt,
|
|
6038
|
+
smsPendingCodeHash: local?.hash,
|
|
6039
|
+
smsPendingPurpose: "mfa_enrollment",
|
|
5948
6040
|
smsPhone: phone,
|
|
6041
|
+
smsProviderReference: started?.reference,
|
|
5949
6042
|
smsVerified: false,
|
|
6043
|
+
totpFailedAttempts: existing?.totpFailedAttempts ?? 0,
|
|
5950
6044
|
totpSecretCiphertext: existing?.totpSecretCiphertext,
|
|
5951
6045
|
totpVerified: existing?.totpVerified ?? false,
|
|
5952
6046
|
updatedAt: now,
|
|
5953
6047
|
userId
|
|
5954
6048
|
});
|
|
5955
|
-
|
|
6049
|
+
if (local !== undefined && onSendSmsCode !== undefined) {
|
|
6050
|
+
await onSendSmsCode({
|
|
6051
|
+
code: local.code,
|
|
6052
|
+
expiresAt: local.expiresAt,
|
|
6053
|
+
phone,
|
|
6054
|
+
purpose: "mfa_enrollment",
|
|
6055
|
+
userId
|
|
6056
|
+
});
|
|
6057
|
+
}
|
|
5956
6058
|
return status("OK", { phone: maskPhone(phone) });
|
|
5957
6059
|
}, {
|
|
5958
6060
|
body: t11.Object({ phone: t11.String() }),
|
|
@@ -5973,29 +6075,58 @@ var mfaSmsRoutes = ({
|
|
|
5973
6075
|
}
|
|
5974
6076
|
const userId = getUserId(userSession.user);
|
|
5975
6077
|
const enrollment = await mfaStore.getEnrollment(userId);
|
|
5976
|
-
if (!enrollment?.
|
|
6078
|
+
if (!enrollment?.smsPhone) {
|
|
6079
|
+
return status("Bad Request", "No SMS enrollment in progress");
|
|
6080
|
+
}
|
|
6081
|
+
const localCodeHash = enrollment.smsPendingCodeHash;
|
|
6082
|
+
const localCodeExpiresAt = enrollment.smsPendingCodeExpiresAt;
|
|
6083
|
+
const providerReference = enrollment.smsProviderReference;
|
|
6084
|
+
if (enrollment.smsPendingPurpose !== "mfa_enrollment" || localCodeExpiresAt === undefined) {
|
|
5977
6085
|
return status("Bad Request", "No SMS enrollment in progress");
|
|
5978
6086
|
}
|
|
5979
|
-
if (
|
|
6087
|
+
if (verificationProvider === undefined && localCodeHash === undefined) {
|
|
6088
|
+
return status("Bad Request", "No SMS enrollment in progress");
|
|
6089
|
+
}
|
|
6090
|
+
if (Date.now() > localCodeExpiresAt) {
|
|
5980
6091
|
return status("Bad Request", "SMS code expired");
|
|
5981
6092
|
}
|
|
5982
6093
|
if ((enrollment.smsFailedAttempts ?? 0) >= smsMaxAttempts) {
|
|
5983
6094
|
return status("Too Many Requests", "Too many attempts");
|
|
5984
6095
|
}
|
|
5985
|
-
|
|
6096
|
+
let providerResult;
|
|
6097
|
+
if (verificationProvider !== undefined) {
|
|
6098
|
+
if (providerReference === undefined) {
|
|
6099
|
+
return status("Bad Request", "No SMS enrollment in progress");
|
|
6100
|
+
}
|
|
6101
|
+
const checked = await checkWithVerificationProvider(verificationProvider, {
|
|
6102
|
+
channel: "sms",
|
|
6103
|
+
code,
|
|
6104
|
+
purpose: "mfa_enrollment",
|
|
6105
|
+
reference: providerReference,
|
|
6106
|
+
subject: userId,
|
|
6107
|
+
to: enrollment.smsPhone
|
|
6108
|
+
});
|
|
6109
|
+
if (checked.error !== undefined) {
|
|
6110
|
+
return status(checked.error.status, checked.error.message);
|
|
6111
|
+
}
|
|
6112
|
+
providerResult = checked.result;
|
|
6113
|
+
}
|
|
6114
|
+
const codeValid = providerResult ? providerResult.status === "approved" : localCodeHash !== undefined && await constantTimeEqual(await hashToken(code), localCodeHash);
|
|
5986
6115
|
if (!codeValid) {
|
|
5987
6116
|
await mfaStore.saveEnrollment({
|
|
5988
6117
|
...enrollment,
|
|
5989
6118
|
smsFailedAttempts: (enrollment.smsFailedAttempts ?? 0) + 1,
|
|
5990
6119
|
updatedAt: Date.now()
|
|
5991
6120
|
});
|
|
5992
|
-
return status("Bad Request", "Invalid SMS code");
|
|
6121
|
+
return status(providerResult?.status === "max_attempts_reached" ? "Too Many Requests" : "Bad Request", providerResult?.status === "expired" ? "SMS code expired" : "Invalid SMS code");
|
|
5993
6122
|
}
|
|
5994
6123
|
await mfaStore.saveEnrollment({
|
|
5995
6124
|
...enrollment,
|
|
5996
6125
|
smsFailedAttempts: 0,
|
|
5997
6126
|
smsPendingCodeExpiresAt: undefined,
|
|
5998
6127
|
smsPendingCodeHash: undefined,
|
|
6128
|
+
smsPendingPurpose: undefined,
|
|
6129
|
+
smsProviderReference: undefined,
|
|
5999
6130
|
smsVerified: true,
|
|
6000
6131
|
updatedAt: Date.now()
|
|
6001
6132
|
});
|
|
@@ -6018,10 +6149,12 @@ var mfaChallenge = ({
|
|
|
6018
6149
|
onMfaChallengeError,
|
|
6019
6150
|
onMfaChallengeSuccess,
|
|
6020
6151
|
onSendSmsCode,
|
|
6152
|
+
verificationProvider,
|
|
6021
6153
|
sessionDurationMs = DEFAULT_MFA_SESSION_TTL_MS,
|
|
6022
6154
|
smsCodeLength = DEFAULT_SMS_CODE_LENGTH,
|
|
6023
6155
|
smsCodeTtlMs = DEFAULT_SMS_CODE_TTL_MS,
|
|
6024
6156
|
smsMaxAttempts = DEFAULT_SMS_MAX_ATTEMPTS,
|
|
6157
|
+
smsResendCooldownMs = DEFAULT_SMS_RESEND_COOLDOWN_MS,
|
|
6025
6158
|
totpMaxAttempts = DEFAULT_TOTP_MAX_ATTEMPTS
|
|
6026
6159
|
}) => new Elysia18().use(sessionStore()).post(challengeRoute, async ({
|
|
6027
6160
|
body: { action, code, factor },
|
|
@@ -6063,27 +6196,48 @@ var mfaChallenge = ({
|
|
|
6063
6196
|
await onMfaChallengeSuccess?.({ user, userSessionId });
|
|
6064
6197
|
return status("OK", { status: "authenticated" });
|
|
6065
6198
|
};
|
|
6066
|
-
const
|
|
6067
|
-
if (
|
|
6068
|
-
return status("
|
|
6199
|
+
const sendSmsChallenge = async () => {
|
|
6200
|
+
if (enrollment.smsCodeSentAt !== undefined && Date.now() - enrollment.smsCodeSentAt < smsResendCooldownMs) {
|
|
6201
|
+
return status("Too Many Requests", "SMS resend cooldown active");
|
|
6069
6202
|
}
|
|
6070
|
-
|
|
6203
|
+
try {
|
|
6071
6204
|
await issueAndStoreSmsCode({
|
|
6072
6205
|
codeLength: smsCodeLength,
|
|
6073
6206
|
enrollment,
|
|
6074
6207
|
mfaStore,
|
|
6075
6208
|
onSendSmsCode,
|
|
6076
|
-
|
|
6209
|
+
purpose: "mfa_challenge",
|
|
6210
|
+
ttlMs: smsCodeTtlMs,
|
|
6211
|
+
userId: getUserId(user),
|
|
6212
|
+
verificationProvider
|
|
6077
6213
|
});
|
|
6078
|
-
|
|
6214
|
+
} catch (error) {
|
|
6215
|
+
const mapped = mapVerificationProviderError(error);
|
|
6216
|
+
if (mapped === undefined)
|
|
6217
|
+
throw error;
|
|
6218
|
+
return status(mapped.status, mapped.message);
|
|
6079
6219
|
}
|
|
6220
|
+
return status("OK", { status: "sent" });
|
|
6221
|
+
};
|
|
6222
|
+
const runSmsChallenge = async () => {
|
|
6223
|
+
if (!enrollment.smsVerified || !enrollment.smsPhone) {
|
|
6224
|
+
return status("Unauthorized", "No MFA challenge in progress");
|
|
6225
|
+
}
|
|
6226
|
+
if (action === "send")
|
|
6227
|
+
return sendSmsChallenge();
|
|
6080
6228
|
if (code === undefined) {
|
|
6081
6229
|
return status("Bad Request", "SMS code required");
|
|
6082
6230
|
}
|
|
6083
|
-
|
|
6231
|
+
const localCodeHash = enrollment.smsPendingCodeHash;
|
|
6232
|
+
const localCodeExpiresAt = enrollment.smsPendingCodeExpiresAt;
|
|
6233
|
+
const providerReference = enrollment.smsProviderReference;
|
|
6234
|
+
if (enrollment.smsPendingPurpose !== "mfa_challenge" || localCodeExpiresAt === undefined) {
|
|
6084
6235
|
return status("Bad Request", "No SMS code in progress");
|
|
6085
6236
|
}
|
|
6086
|
-
if (
|
|
6237
|
+
if (verificationProvider === undefined && localCodeHash === undefined) {
|
|
6238
|
+
return status("Bad Request", "No SMS code in progress");
|
|
6239
|
+
}
|
|
6240
|
+
if (Date.now() > localCodeExpiresAt) {
|
|
6087
6241
|
return status("Unauthorized", "SMS code expired");
|
|
6088
6242
|
}
|
|
6089
6243
|
if ((enrollment.smsFailedAttempts ?? 0) >= smsMaxAttempts) {
|
|
@@ -6093,7 +6247,25 @@ var mfaChallenge = ({
|
|
|
6093
6247
|
});
|
|
6094
6248
|
return status("Unauthorized", "Too many attempts");
|
|
6095
6249
|
}
|
|
6096
|
-
|
|
6250
|
+
let providerResult;
|
|
6251
|
+
if (verificationProvider !== undefined) {
|
|
6252
|
+
if (providerReference === undefined) {
|
|
6253
|
+
return status("Bad Request", "No SMS code in progress");
|
|
6254
|
+
}
|
|
6255
|
+
const checked = await checkWithVerificationProvider(verificationProvider, {
|
|
6256
|
+
channel: "sms",
|
|
6257
|
+
code,
|
|
6258
|
+
purpose: "mfa_challenge",
|
|
6259
|
+
reference: providerReference,
|
|
6260
|
+
subject: getUserId(user),
|
|
6261
|
+
to: enrollment.smsPhone
|
|
6262
|
+
});
|
|
6263
|
+
if (checked.error !== undefined) {
|
|
6264
|
+
return status(checked.error.status, checked.error.message);
|
|
6265
|
+
}
|
|
6266
|
+
providerResult = checked.result;
|
|
6267
|
+
}
|
|
6268
|
+
const smsValid = providerResult ? providerResult.status === "approved" : localCodeHash !== undefined && await constantTimeEqual(await hashToken(code), localCodeHash);
|
|
6097
6269
|
if (!smsValid) {
|
|
6098
6270
|
await mfaStore.saveEnrollment({
|
|
6099
6271
|
...enrollment,
|
|
@@ -6104,7 +6276,7 @@ var mfaChallenge = ({
|
|
|
6104
6276
|
error: new Error("invalid_mfa_code"),
|
|
6105
6277
|
userId: getUserId(user)
|
|
6106
6278
|
});
|
|
6107
|
-
return status("Unauthorized", "Invalid MFA code");
|
|
6279
|
+
return status(providerResult?.status === "max_attempts_reached" ? "Too Many Requests" : "Unauthorized", providerResult?.status === "expired" ? "SMS code expired" : "Invalid MFA code");
|
|
6108
6280
|
}
|
|
6109
6281
|
await mfaStore.saveEnrollment({
|
|
6110
6282
|
...enrollment,
|
|
@@ -6112,6 +6284,8 @@ var mfaChallenge = ({
|
|
|
6112
6284
|
smsFailedAttempts: 0,
|
|
6113
6285
|
smsPendingCodeExpiresAt: undefined,
|
|
6114
6286
|
smsPendingCodeHash: undefined,
|
|
6287
|
+
smsPendingPurpose: undefined,
|
|
6288
|
+
smsProviderReference: undefined,
|
|
6115
6289
|
updatedAt: Date.now()
|
|
6116
6290
|
});
|
|
6117
6291
|
return promote();
|
|
@@ -31840,12 +32014,15 @@ var mfaEnrollmentsTable = pgTable("auth_mfa_enrollments", {
|
|
|
31840
32014
|
backup_code_hashes: jsonb("backup_code_hashes").$type().notNull().default([]),
|
|
31841
32015
|
created_at_ms: bigint("created_at_ms", { mode: "number" }).notNull(),
|
|
31842
32016
|
last_used_at_ms: bigint("last_used_at_ms", { mode: "number" }),
|
|
32017
|
+
sms_code_sent_at_ms: bigint("sms_code_sent_at_ms", { mode: "number" }),
|
|
31843
32018
|
sms_failed_attempts: smallint("sms_failed_attempts").notNull().default(0),
|
|
31844
32019
|
sms_pending_code_expires_at_ms: bigint("sms_pending_code_expires_at_ms", {
|
|
31845
32020
|
mode: "number"
|
|
31846
32021
|
}),
|
|
31847
32022
|
sms_pending_code_hash: text("sms_pending_code_hash"),
|
|
32023
|
+
sms_pending_purpose: text("sms_pending_purpose").$type(),
|
|
31848
32024
|
sms_phone: varchar("sms_phone", { length: PHONE_LENGTH }),
|
|
32025
|
+
sms_provider_reference: text("sms_provider_reference"),
|
|
31849
32026
|
sms_verified: boolean("sms_verified").notNull().default(false),
|
|
31850
32027
|
totp_failed_attempts: smallint("totp_failed_attempts").notNull().default(0),
|
|
31851
32028
|
totp_secret_ciphertext: text("totp_secret_ciphertext"),
|
|
@@ -31857,10 +32034,13 @@ var toEnrollment = (row) => ({
|
|
|
31857
32034
|
backupCodeHashes: row.backup_code_hashes,
|
|
31858
32035
|
createdAt: row.created_at_ms,
|
|
31859
32036
|
lastUsedAt: row.last_used_at_ms ?? undefined,
|
|
32037
|
+
smsCodeSentAt: row.sms_code_sent_at_ms ?? undefined,
|
|
31860
32038
|
smsFailedAttempts: row.sms_failed_attempts,
|
|
31861
32039
|
smsPendingCodeExpiresAt: row.sms_pending_code_expires_at_ms ?? undefined,
|
|
31862
32040
|
smsPendingCodeHash: row.sms_pending_code_hash ?? undefined,
|
|
32041
|
+
smsPendingPurpose: row.sms_pending_purpose ?? undefined,
|
|
31863
32042
|
smsPhone: row.sms_phone ?? undefined,
|
|
32043
|
+
smsProviderReference: row.sms_provider_reference ?? undefined,
|
|
31864
32044
|
smsVerified: row.sms_verified,
|
|
31865
32045
|
totpFailedAttempts: row.totp_failed_attempts,
|
|
31866
32046
|
totpSecretCiphertext: row.totp_secret_ciphertext ?? undefined,
|
|
@@ -31886,10 +32066,13 @@ var createPostgresMfaStore = (db) => ({
|
|
|
31886
32066
|
backup_code_hashes: enrollment.backupCodeHashes,
|
|
31887
32067
|
created_at_ms: enrollment.createdAt,
|
|
31888
32068
|
last_used_at_ms: enrollment.lastUsedAt ?? null,
|
|
32069
|
+
sms_code_sent_at_ms: enrollment.smsCodeSentAt ?? null,
|
|
31889
32070
|
sms_failed_attempts: enrollment.smsFailedAttempts ?? 0,
|
|
31890
32071
|
sms_pending_code_expires_at_ms: enrollment.smsPendingCodeExpiresAt ?? null,
|
|
31891
32072
|
sms_pending_code_hash: enrollment.smsPendingCodeHash ?? null,
|
|
32073
|
+
sms_pending_purpose: enrollment.smsPendingPurpose ?? null,
|
|
31892
32074
|
sms_phone: enrollment.smsPhone ?? null,
|
|
32075
|
+
sms_provider_reference: enrollment.smsProviderReference ?? null,
|
|
31893
32076
|
sms_verified: enrollment.smsVerified,
|
|
31894
32077
|
totp_failed_attempts: enrollment.totpFailedAttempts ?? 0,
|
|
31895
32078
|
totp_secret_ciphertext: enrollment.totpSecretCiphertext ?? null,
|
|
@@ -36238,6 +36421,15 @@ var mfaTotpLockoutMigration = {
|
|
|
36238
36421
|
id: "0003_totp_lockout",
|
|
36239
36422
|
sql: 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "totp_failed_attempts" smallint NOT NULL DEFAULT 0;'
|
|
36240
36423
|
};
|
|
36424
|
+
var mfaSmsDeliveryPolicyMigration = {
|
|
36425
|
+
id: "0004_sms_delivery_policy",
|
|
36426
|
+
sql: [
|
|
36427
|
+
'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_code_sent_at_ms" bigint;',
|
|
36428
|
+
'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_pending_purpose" text;',
|
|
36429
|
+
'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_provider_reference" text;'
|
|
36430
|
+
].join(`
|
|
36431
|
+
`)
|
|
36432
|
+
};
|
|
36241
36433
|
var oidcResourceAudienceMigration = {
|
|
36242
36434
|
id: "0002_resource_audience",
|
|
36243
36435
|
sql: [
|
|
@@ -36292,7 +36484,8 @@ var blockMigrations = {
|
|
|
36292
36484
|
migrations: [
|
|
36293
36485
|
...initMigration("mfa", [mfaEnrollmentsTable]).migrations,
|
|
36294
36486
|
mfaSmsColumnsMigration,
|
|
36295
|
-
mfaTotpLockoutMigration
|
|
36487
|
+
mfaTotpLockoutMigration,
|
|
36488
|
+
mfaSmsDeliveryPolicyMigration
|
|
36296
36489
|
]
|
|
36297
36490
|
},
|
|
36298
36491
|
oidc: {
|
|
@@ -36805,6 +36998,7 @@ var buildAuthApplications = async (configuration) => {
|
|
|
36805
36998
|
credentials,
|
|
36806
36999
|
customProviders,
|
|
36807
37000
|
mfa,
|
|
37001
|
+
verificationProvider,
|
|
36808
37002
|
passwordless,
|
|
36809
37003
|
lockout,
|
|
36810
37004
|
sessions,
|
|
@@ -37037,7 +37231,8 @@ var buildAuthApplications = async (configuration) => {
|
|
|
37037
37231
|
auditedMfa ? mfaRoutes({
|
|
37038
37232
|
...auditedMfa,
|
|
37039
37233
|
authSessionStore,
|
|
37040
|
-
cookieSecure: resolvedCookieSecure
|
|
37234
|
+
cookieSecure: resolvedCookieSecure,
|
|
37235
|
+
verificationProvider
|
|
37041
37236
|
}) : new Elysia45,
|
|
37042
37237
|
passwordless ? passwordlessRoutes({
|
|
37043
37238
|
...passwordless,
|
|
@@ -37564,6 +37759,7 @@ export {
|
|
|
37564
37759
|
accessTokensTable,
|
|
37565
37760
|
acceptInvitation,
|
|
37566
37761
|
WEBAUTHN_CHALLENGE_COOKIE,
|
|
37762
|
+
VerificationProviderError,
|
|
37567
37763
|
STATUS_LIST_TYP,
|
|
37568
37764
|
STATUS_LIST_SUB_TYP,
|
|
37569
37765
|
REQUEST_URI_PREFIX,
|
|
@@ -37581,6 +37777,7 @@ export {
|
|
|
37581
37777
|
DEFAULT_STATUS_ROUTE,
|
|
37582
37778
|
DEFAULT_SSO_SESSION_TTL_MS,
|
|
37583
37779
|
DEFAULT_SSO_ROUTE,
|
|
37780
|
+
DEFAULT_SMS_RESEND_COOLDOWN_MS,
|
|
37584
37781
|
DEFAULT_SMS_MAX_ATTEMPTS,
|
|
37585
37782
|
DEFAULT_SMS_CODE_TTL_MS,
|
|
37586
37783
|
DEFAULT_SMS_CODE_LENGTH,
|
|
@@ -37613,5 +37810,5 @@ export {
|
|
|
37613
37810
|
AGENT_CLAIM_GRANT_TYPE
|
|
37614
37811
|
};
|
|
37615
37812
|
|
|
37616
|
-
//# debugId=
|
|
37813
|
+
//# debugId=588FAC011547AB7964756E2164756E21
|
|
37617
37814
|
//# sourceMappingURL=index.js.map
|