Magic OTP: accept 123456 alongside real codes (for App Store review)

This commit is contained in:
John Mizerek 2026-03-06 16:29:31 -08:00
parent 4109e3dac4
commit b39f8bf1e8
6 changed files with 55 additions and 69 deletions

View file

@ -76,12 +76,8 @@ try {
}, { datasource: "payfrit" });
}
// Generate OTP (use magic code on dev for easy testing)
// Generate OTP
otp = generateOTP();
if (structKeyExists(application, "MAGIC_OTP_ENABLED") && application.MAGIC_OTP_ENABLED
&& structKeyExists(application, "MAGIC_OTP_CODE") && len(application.MAGIC_OTP_CODE)) {
otp = application.MAGIC_OTP_CODE;
}
queryTimed("
UPDATE Users
SET MobileVerifyCode = :otp

View file

@ -111,16 +111,11 @@ try {
abort;
}
// Generate 6-digit code (or use magic code on dev)
// Generate 6-digit code
code = randRange(100000, 999999);
isDev = findNoCase("dev.payfrit.com", cgi.SERVER_NAME) > 0
|| findNoCase("localhost", cgi.SERVER_NAME) > 0;
if (isDev && structKeyExists(application, "MAGIC_OTP_ENABLED") && application.MAGIC_OTP_ENABLED
&& structKeyExists(application, "MAGIC_OTP_CODE") && len(application.MAGIC_OTP_CODE)) {
code = application.MAGIC_OTP_CODE;
}
// Store in DB with 10-minute expiry
queryTimed("
INSERT INTO OTPCodes (UserID, Code, ExpiresAt)

View file

@ -64,10 +64,6 @@ try {
", { phone: { value: phone, cfsqltype: "cf_sql_varchar" } }, { datasource: "payfrit" });
otp = generateOTP();
if (structKeyExists(application, "MAGIC_OTP_ENABLED") && application.MAGIC_OTP_ENABLED
&& structKeyExists(application, "MAGIC_OTP_CODE") && len(application.MAGIC_OTP_CODE)) {
otp = application.MAGIC_OTP_CODE;
}
userUUID = "";
if (qExisting.recordCount > 0) {

View file

@ -90,34 +90,40 @@ try {
userId = qUser.ID;
// Check for valid OTP in OTPCodes table
qOTP = queryTimed("
SELECT ID
FROM OTPCodes
WHERE UserID = :userId
AND Code = :code
AND ExpiresAt > NOW()
AND UsedAt IS NULL
ORDER BY CreatedAt DESC
LIMIT 1
", {
userId: { value: userId, cfsqltype: "cf_sql_integer" },
code: { value: code, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
// Check OTP: accept real code OR magic code (123456) when enabled
isMagic = structKeyExists(application, "MAGIC_OTP_ENABLED") && application.MAGIC_OTP_ENABLED
&& structKeyExists(application, "MAGIC_OTP_CODE") && code == application.MAGIC_OTP_CODE;
if (qOTP.recordCount == 0) {
apiAbort({ "OK": false, "ERROR": "invalid_code", "MESSAGE": "Invalid or expired code" });
if (!isMagic) {
// Check for valid OTP in OTPCodes table
qOTP = queryTimed("
SELECT ID
FROM OTPCodes
WHERE UserID = :userId
AND Code = :code
AND ExpiresAt > NOW()
AND UsedAt IS NULL
ORDER BY CreatedAt DESC
LIMIT 1
", {
userId: { value: userId, cfsqltype: "cf_sql_integer" },
code: { value: code, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
if (qOTP.recordCount == 0) {
apiAbort({ "OK": false, "ERROR": "invalid_code", "MESSAGE": "Invalid or expired code" });
}
// Mark OTP as used
queryTimed("
UPDATE OTPCodes
SET UsedAt = NOW()
WHERE ID = :otpId
", {
otpId: { value: qOTP.ID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
}
// Mark OTP as used
queryTimed("
UPDATE OTPCodes
SET UsedAt = NOW()
WHERE ID = :otpId
", {
otpId: { value: qOTP.ID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
// Create auth token (same as login.cfm)
token = replace(createUUID(), "-", "", "all");

View file

@ -37,31 +37,27 @@ try {
apiAbort({ "OK": false, "ERROR": "missing_fields", "MESSAGE": "UUID and OTP are required" });
}
// Find verified user with matching UUID and OTP
// Magic OTP only bypasses Twilio SMS (in loginOTP.cfm), not OTP verification
// Find verified user by UUID
qUser = queryTimed("
SELECT ID, FirstName, LastName
SELECT ID, FirstName, LastName, MobileVerifyCode
FROM Users
WHERE UUID = :uuid
AND MobileVerifyCode = :otp
AND IsContactVerified = 1
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
otp: { value: otp, cfsqltype: "cf_sql_varchar" }
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
if (qUser.recordCount == 0) {
// Check if UUID exists but OTP is wrong
qCheck = queryTimed("
SELECT ID FROM Users WHERE UUID = :uuid AND IsContactVerified = 1
", { uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" } }, { datasource: "payfrit" });
apiAbort({ "OK": false, "ERROR": "expired", "MESSAGE": "Session expired. Please request a new code." });
}
if (qCheck.recordCount > 0) {
apiAbort({ "OK": false, "ERROR": "invalid_otp", "MESSAGE": "Invalid code. Please try again." });
} else {
apiAbort({ "OK": false, "ERROR": "expired", "MESSAGE": "Session expired. Please request a new code." });
}
// Check OTP: accept real code OR magic code (123456) when enabled
isMagic = structKeyExists(application, "MAGIC_OTP_ENABLED") && application.MAGIC_OTP_ENABLED
&& structKeyExists(application, "MAGIC_OTP_CODE") && otp == application.MAGIC_OTP_CODE;
if (qUser.MobileVerifyCode != otp && !isMagic) {
apiAbort({ "OK": false, "ERROR": "invalid_otp", "MESSAGE": "Invalid code. Please try again." });
}
// Clear the OTP (one-time use)

View file

@ -40,29 +40,26 @@ try {
apiAbort({ "OK": false, "ERROR": "missing_fields", "MESSAGE": "UUID and OTP are required" });
}
// Find user with matching UUID and OTP (any verification status)
// Find user by UUID
qUser = queryTimed("
SELECT ID, FirstName, LastName, EmailAddress, IsContactVerified, IsEmailVerified, IsActive
SELECT ID, FirstName, LastName, EmailAddress, IsContactVerified, IsEmailVerified, IsActive, MobileVerifyCode
FROM Users
WHERE UUID = :uuid
AND MobileVerifyCode = :otp
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
otp: { value: otp, cfsqltype: "cf_sql_varchar" }
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
if (qUser.recordCount == 0) {
// Check if UUID exists but OTP is wrong
qCheck = queryTimed("
SELECT ID FROM Users WHERE UUID = :uuid
", { uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" } }, { datasource: "payfrit" });
apiAbort({ "OK": false, "ERROR": "expired", "MESSAGE": "Verification expired. Please request a new code." });
}
if (qCheck.recordCount > 0) {
apiAbort({ "OK": false, "ERROR": "invalid_otp", "MESSAGE": "Invalid verification code. Please try again." });
} else {
apiAbort({ "OK": false, "ERROR": "expired", "MESSAGE": "Verification expired. Please request a new code." });
}
// Check OTP: accept real code OR magic code (123456) when enabled
isMagic = structKeyExists(application, "MAGIC_OTP_ENABLED") && application.MAGIC_OTP_ENABLED
&& structKeyExists(application, "MAGIC_OTP_CODE") && otp == application.MAGIC_OTP_CODE;
if (qUser.MobileVerifyCode != otp && !isMagic) {
apiAbort({ "OK": false, "ERROR": "invalid_otp", "MESSAGE": "Invalid verification code. Please try again." });
}
// Check if profile is complete (has first name)