This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/auth/verifyOTP.cfm
John Mizerek 6b66d2cef8 Fix normalized DB column names across all API files
Sweep of 26 API files to use prefixed column names matching the
database schema (e.g. BusinessID not ID, BusinessName not Name,
BusinessDeliveryFlatFee not DeliveryFlatFee, ServicePointName not Name).

Files fixed: auth, beacons, businesses, menu, orders, setup, stripe,
tasks, and workers endpoints.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:56:41 -08:00

108 lines
3.5 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfscript>
/**
* Verify OTP and activate user account
*
* POST: { "uuid": "...", "otp": "123456" }
*
* Returns: { OK: true, UserID: 123, Token: "...", NeedsProfile: true/false }
*
* On success, marks phone as verified and returns auth token.
* NeedsProfile indicates if user still needs to provide name/email.
*/
function apiAbort(required struct payload) {
writeOutput(serializeJSON(payload));
abort;
}
function readJsonBody() {
var raw = getHttpRequestData().content;
if (isNull(raw)) raw = "";
if (!len(trim(raw))) return {};
try {
var data = deserializeJSON(raw);
if (isStruct(data)) return data;
} catch (any e) {}
return {};
}
try {
data = readJsonBody();
userUUID = structKeyExists(data, "uuid") ? trim(data.uuid) : "";
otp = structKeyExists(data, "otp") ? trim(data.otp) : "";
if (!len(userUUID) || !len(otp)) {
apiAbort({ "OK": false, "ERROR": "missing_fields", "MESSAGE": "UUID and OTP are required" });
}
// Find unverified user with matching UUID and OTP
// Magic OTP only bypasses Twilio SMS (in sendOTP.cfm), not OTP verification
qUser = queryExecute("
SELECT ID, FirstName, LastName, EmailAddress, IsEmailVerified
FROM Users
WHERE UUID = :uuid
AND MobileVerifyCode = :otp
AND IsContactVerified = 0
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
otp: { value: otp, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
if (qUser.recordCount == 0) {
// Check if UUID exists but OTP is wrong
qCheck = queryExecute("
SELECT ID FROM Users WHERE UUID = :uuid AND IsContactVerified = 0
", { uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" } }, { datasource: "payfrit" });
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." });
}
}
// Clear the OTP code (one-time use) but DON'T mark as verified yet
// Account will be marked verified after profile completion
queryExecute("
UPDATE Users
SET MobileVerifyCode = ''
WHERE ID = :userId
", { userId: { value: qUser.ID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" });
// Create auth token (needed for completeProfile call)
token = replace(createUUID(), "-", "", "all");
queryExecute("
INSERT INTO UserTokens (UserID, Token) VALUES (:userId, :token)
", {
userId: { value: qUser.ID, cfsqltype: "cf_sql_integer" },
token: { value: token, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
// Check if profile is complete (has first name)
// For new signups, this will always be true
needsProfile = !len(trim(qUser.FirstName));
try{logPerf(0);}catch(any e){}
writeOutput(serializeJSON({
"OK": true,
"UserID": qUser.ID,
"Token": token,
"NeedsProfile": needsProfile,
"FirstName": qUser.FirstName ?: "",
"IsEmailVerified": qUser.IsEmailVerified == 1
}));
} catch (any e) {
apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": e.message
});
}
</cfscript>