Magic OTP: accept 123456 alongside real codes (for App Store review)
This commit is contained in:
parent
4109e3dac4
commit
b39f8bf1e8
6 changed files with 55 additions and 69 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Reference in a new issue