Add dev environment configuration and tools
- api/config/environment.cfm: Central config for dev vs prod settings - Verbose errors, debug logging, magic OTP bypass - Rate limiting toggle, email catch-all, token expiry settings - api/dev/: Development-only endpoints - seedData.cfm: Create/reset test users - timeTravel.cfm: Manipulate timestamps for testing - index.cfm: Dev tools index Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8e3bb681e7
commit
05cf73446f
5 changed files with 542 additions and 0 deletions
|
|
@ -31,6 +31,8 @@
|
|||
datasource="payfrit"
|
||||
showdebugoutput="false"
|
||||
>
|
||||
<!--- Preserve struct key casing in JSON serialization (Flutter expects mixed-case keys) --->
|
||||
<cfset getApplicationSettings().serialization.preserveCaseForStructKey = true>
|
||||
|
||||
<!--- Magic OTP bypass for App Store review (set to true to enable 123456 as universal OTP) --->
|
||||
<cfset application.MAGIC_OTP_ENABLED = false>
|
||||
|
|
@ -49,6 +51,9 @@
|
|||
<!--- Stripe Configuration (loads from config/stripe.cfm) --->
|
||||
<cfinclude template="config/stripe.cfm">
|
||||
|
||||
<!--- Environment Configuration (dev vs prod settings) --->
|
||||
<cfinclude template="config/environment.cfm">
|
||||
|
||||
<cfscript>
|
||||
function apiAbort(payload) {
|
||||
writeOutput(serializeJSON(payload));
|
||||
|
|
@ -145,6 +150,15 @@ if (len(request._api_path)) {
|
|||
if (findNoCase("/api/tasks/callserver", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/createChat.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/expirestalechats.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/listCategories.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/saveCategory.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/deleteCategory.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/seedCategories.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/listAllTypes.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/listTypes.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/saveType.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/deleteType.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/tasks/reorderTypes.cfm", request._api_path)) request._api_isPublic = true;
|
||||
|
||||
// Chat endpoints
|
||||
if (findNoCase("/api/chat/getMessages.cfm", request._api_path)) request._api_isPublic = true;
|
||||
|
|
@ -240,6 +254,10 @@ if (len(request._api_path)) {
|
|||
// Stations endpoints
|
||||
if (findNoCase("/api/stations/list.cfm", request._api_path)) request._api_isPublic = true;
|
||||
|
||||
// Ratings endpoints (setup + token-based submission)
|
||||
if (findNoCase("/api/ratings/setup.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/ratings/submit.cfm", request._api_path)) request._api_isPublic = true;
|
||||
|
||||
// Stripe endpoints
|
||||
if (findNoCase("/api/stripe/onboard.cfm", request._api_path)) request._api_isPublic = true;
|
||||
if (findNoCase("/api/stripe/status.cfm", request._api_path)) request._api_isPublic = true;
|
||||
|
|
|
|||
124
api/config/environment.cfm
Normal file
124
api/config/environment.cfm
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<cfscript>
|
||||
/**
|
||||
* Environment Configuration
|
||||
*
|
||||
* Controls dev vs production behavior.
|
||||
* This file should have DIFFERENT values on dev vs biz servers.
|
||||
*
|
||||
* dev.payfrit.com: isDevEnvironment = true
|
||||
* biz.payfrit.com: isDevEnvironment = false
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// ENVIRONMENT FLAG - CHANGE PER SERVER
|
||||
// ============================================
|
||||
isDevEnvironment = true; // Set to FALSE on biz.payfrit.com
|
||||
|
||||
// ============================================
|
||||
// ERROR HANDLING
|
||||
// ============================================
|
||||
// Dev: Show full stack traces
|
||||
// Prod: Show generic "server error" message
|
||||
application.showDetailedErrors = isDevEnvironment;
|
||||
|
||||
// ============================================
|
||||
// DEBUG LOGGING
|
||||
// ============================================
|
||||
// Dev: Log all API requests/responses
|
||||
// Prod: Minimal logging
|
||||
application.debugLogging = isDevEnvironment;
|
||||
application.logDirectory = expandPath("/api/logs/");
|
||||
|
||||
// ============================================
|
||||
// RATE LIMITING
|
||||
// ============================================
|
||||
// Dev: No rate limits
|
||||
// Prod: Enforce rate limits
|
||||
application.enableRateLimiting = !isDevEnvironment;
|
||||
application.rateLimitPerMinute = 60; // requests per minute per IP
|
||||
|
||||
// ============================================
|
||||
// EMAIL HANDLING
|
||||
// ============================================
|
||||
// Dev: Send all emails to catch-all address
|
||||
// Prod: Send to real recipients
|
||||
application.emailCatchAll = isDevEnvironment ? "dev-emails@payfrit.com" : "";
|
||||
application.emailEnabled = !isDevEnvironment ? true : false; // Disable emails on dev entirely
|
||||
|
||||
// ============================================
|
||||
// MAGIC OTP (for testing)
|
||||
// ============================================
|
||||
// Dev: Allow magic phone number to bypass OTP
|
||||
// Prod: Disabled
|
||||
application.MAGIC_OTP_ENABLED = isDevEnvironment;
|
||||
application.MAGIC_OTP_CODE = "123456";
|
||||
application.MAGIC_PHONE_NUMBERS = ["5555555555", "0000000000"];
|
||||
|
||||
// ============================================
|
||||
// STRIPE MODE
|
||||
// ============================================
|
||||
// Already handled in stripe.cfm, but good to have reference
|
||||
application.stripeTestMode = isDevEnvironment;
|
||||
|
||||
// ============================================
|
||||
// API RESPONSE EXTRAS
|
||||
// ============================================
|
||||
// Dev: Include debug info in API responses (timing, queries, etc)
|
||||
// Prod: Minimal responses
|
||||
application.includeDebugInResponse = isDevEnvironment;
|
||||
|
||||
// ============================================
|
||||
// SESSION/TOKEN SETTINGS
|
||||
// ============================================
|
||||
// Dev: Longer token expiry for easier testing
|
||||
// Prod: Normal expiry
|
||||
application.tokenExpiryHours = isDevEnvironment ? 720 : 24; // 30 days vs 1 day
|
||||
|
||||
// ============================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================
|
||||
function isDev() {
|
||||
return structKeyExists(application, "isDevEnvironment") && application.isDevEnvironment;
|
||||
}
|
||||
|
||||
function logDebug(message, data = {}) {
|
||||
if (!application.debugLogging) return;
|
||||
|
||||
var logFile = application.logDirectory & "debug_" & dateFormat(now(), "yyyy-mm-dd") & ".log";
|
||||
var logLine = "[" & timeFormat(now(), "HH:mm:ss") & "] " & message;
|
||||
|
||||
if (!structIsEmpty(data)) {
|
||||
logLine &= " | " & serializeJSON(data);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!directoryExists(application.logDirectory)) {
|
||||
directoryCreate(application.logDirectory);
|
||||
}
|
||||
fileAppend(logFile, logLine & chr(10));
|
||||
} catch (any e) {
|
||||
// Silent fail - don't break app if logging fails
|
||||
}
|
||||
}
|
||||
|
||||
function apiError(message, detail = "", statusCode = 500) {
|
||||
var response = {
|
||||
"OK": false,
|
||||
"ERROR": "server_error",
|
||||
"MESSAGE": application.showDetailedErrors ? message : "An error occurred"
|
||||
};
|
||||
|
||||
if (application.showDetailedErrors && len(detail)) {
|
||||
response["DETAIL"] = detail;
|
||||
response["STACK"] = "";
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Store in application scope
|
||||
application.isDevEnvironment = isDevEnvironment;
|
||||
application.isDev = isDev;
|
||||
application.logDebug = logDebug;
|
||||
application.apiError = apiError;
|
||||
</cfscript>
|
||||
45
api/dev/index.cfm
Normal file
45
api/dev/index.cfm
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<cfsetting showdebugoutput="false">
|
||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||
|
||||
<cfscript>
|
||||
/**
|
||||
* Dev Tools Index - DEV ONLY
|
||||
* Lists available development endpoints
|
||||
*/
|
||||
|
||||
// SAFETY: Only allow on dev environment
|
||||
if (!structKeyExists(application, "isDevEnvironment") || !application.isDevEnvironment) {
|
||||
writeOutput(serializeJSON({
|
||||
"OK": false,
|
||||
"ERROR": "forbidden",
|
||||
"MESSAGE": "Dev tools are disabled on this server"
|
||||
}));
|
||||
abort;
|
||||
}
|
||||
|
||||
writeOutput(serializeJSON({
|
||||
"OK": true,
|
||||
"environment": "development",
|
||||
"endpoints": {
|
||||
"seedData": {
|
||||
"url": "/api/dev/seedData.cfm",
|
||||
"methods": ["GET", "POST"],
|
||||
"description": "Create and manage test users",
|
||||
"actions": ["seed", "reset"]
|
||||
},
|
||||
"timeTravel": {
|
||||
"url": "/api/dev/timeTravel.cfm",
|
||||
"methods": ["POST"],
|
||||
"description": "Manipulate timestamps for testing",
|
||||
"actions": ["expireTokens", "setUserCreated", "clearOTPs", "resetUser"]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"magicOTP": application.MAGIC_OTP_CODE,
|
||||
"magicPhones": application.MAGIC_PHONE_NUMBERS,
|
||||
"debugLogging": application.debugLogging,
|
||||
"showDetailedErrors": application.showDetailedErrors,
|
||||
"tokenExpiryHours": application.tokenExpiryHours
|
||||
}
|
||||
}));
|
||||
</cfscript>
|
||||
209
api/dev/seedData.cfm
Normal file
209
api/dev/seedData.cfm
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
<cfsetting showdebugoutput="false">
|
||||
<cfsetting enablecfoutputonly="true">
|
||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||
|
||||
<cfscript>
|
||||
/**
|
||||
* Seed Data Script - DEV ONLY
|
||||
*
|
||||
* Creates test users and data for development testing.
|
||||
* This endpoint is disabled on production.
|
||||
*
|
||||
* GET: Returns current seed data info
|
||||
* POST: { "action": "seed" } - Creates seed data
|
||||
* POST: { "action": "reset" } - Clears all test data and re-seeds
|
||||
*/
|
||||
|
||||
function apiAbort(required struct payload) {
|
||||
writeOutput(serializeJSON(payload));
|
||||
abort;
|
||||
}
|
||||
|
||||
// SAFETY: Only allow on dev environment
|
||||
if (!structKeyExists(application, "isDevEnvironment") || !application.isDevEnvironment) {
|
||||
apiAbort({
|
||||
"OK": false,
|
||||
"ERROR": "forbidden",
|
||||
"MESSAGE": "This endpoint is only available in development"
|
||||
});
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
function createTestUser(phone, firstName, lastName, email, isVerified = true) {
|
||||
var userUUID = replace(createUUID(), "-", "", "all");
|
||||
|
||||
queryExecute("
|
||||
INSERT INTO Users (
|
||||
UserContactNumber,
|
||||
UserUUID,
|
||||
UserFirstName,
|
||||
UserLastName,
|
||||
UserEmail,
|
||||
UserIsContactVerified,
|
||||
UserIsEmailVerified,
|
||||
UserIsActive,
|
||||
UserAddedOn,
|
||||
UserPassword,
|
||||
UserPromoCode,
|
||||
UserMobileVerifyCode
|
||||
) VALUES (
|
||||
:phone,
|
||||
:uuid,
|
||||
:firstName,
|
||||
:lastName,
|
||||
:email,
|
||||
:isVerified,
|
||||
0,
|
||||
:isVerified,
|
||||
:addedOn,
|
||||
'',
|
||||
:promoCode,
|
||||
'123456'
|
||||
)
|
||||
", {
|
||||
phone: { value: phone, cfsqltype: "cf_sql_varchar" },
|
||||
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
|
||||
firstName: { value: firstName, cfsqltype: "cf_sql_varchar" },
|
||||
lastName: { value: lastName, cfsqltype: "cf_sql_varchar" },
|
||||
email: { value: email, cfsqltype: "cf_sql_varchar" },
|
||||
isVerified: { value: isVerified ? 1 : 0, cfsqltype: "cf_sql_integer" },
|
||||
addedOn: { value: now(), cfsqltype: "cf_sql_timestamp" },
|
||||
promoCode: { value: randRange(1000000, 9999999), cfsqltype: "cf_sql_varchar" }
|
||||
}, { datasource: "payfrit" });
|
||||
|
||||
return userUUID;
|
||||
}
|
||||
|
||||
function seedTestData() {
|
||||
var created = [];
|
||||
|
||||
// Test User 1: Magic phone (always works with OTP 123456)
|
||||
try {
|
||||
createTestUser("5555555555", "Magic", "User", "magic@test.payfrit.com", true);
|
||||
arrayAppend(created, "Magic User (5555555555)");
|
||||
} catch (any e) {
|
||||
if (e.message contains "Duplicate") {
|
||||
arrayAppend(created, "Magic User already exists");
|
||||
} else {
|
||||
arrayAppend(created, "Magic User failed: " & e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Test User 2: Regular verified user
|
||||
try {
|
||||
createTestUser("5551234567", "Test", "Customer", "test@test.payfrit.com", true);
|
||||
arrayAppend(created, "Test Customer (5551234567)");
|
||||
} catch (any e) {
|
||||
if (e.message contains "Duplicate") {
|
||||
arrayAppend(created, "Test Customer already exists");
|
||||
} else {
|
||||
arrayAppend(created, "Test Customer failed: " & e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Test User 3: Unverified user (for testing signup flow)
|
||||
try {
|
||||
createTestUser("5559876543", "", "", "", false);
|
||||
arrayAppend(created, "Unverified User (5559876543)");
|
||||
} catch (any e) {
|
||||
if (e.message contains "Duplicate") {
|
||||
arrayAppend(created, "Unverified User already exists");
|
||||
} else {
|
||||
arrayAppend(created, "Unverified User failed: " & e.message);
|
||||
}
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
function resetTestData() {
|
||||
// Delete test users (by phone prefix 555)
|
||||
queryExecute("
|
||||
DELETE FROM Users
|
||||
WHERE UserContactNumber LIKE '555%'
|
||||
", {}, { datasource: "payfrit" });
|
||||
|
||||
return seedTestData();
|
||||
}
|
||||
|
||||
function getTestDataInfo() {
|
||||
var qUsers = queryExecute("
|
||||
SELECT UserID, UserContactNumber, UserFirstName, UserLastName,
|
||||
UserIsContactVerified, UserUUID
|
||||
FROM Users
|
||||
WHERE UserContactNumber LIKE '555%'
|
||||
ORDER BY UserContactNumber
|
||||
", {}, { datasource: "payfrit" });
|
||||
|
||||
var users = [];
|
||||
for (var row in qUsers) {
|
||||
arrayAppend(users, {
|
||||
"phone": row.UserContactNumber,
|
||||
"name": trim(row.UserFirstName & " " & row.UserLastName),
|
||||
"verified": row.UserIsContactVerified == 1,
|
||||
"uuid": row.UserUUID
|
||||
});
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
try {
|
||||
requestMethod = cgi.request_method;
|
||||
|
||||
if (requestMethod == "GET") {
|
||||
// Return current test data info
|
||||
writeOutput(serializeJSON({
|
||||
"OK": true,
|
||||
"testUsers": getTestDataInfo(),
|
||||
"magicOTP": application.MAGIC_OTP_CODE,
|
||||
"magicPhones": application.MAGIC_PHONE_NUMBERS
|
||||
}));
|
||||
} else if (requestMethod == "POST") {
|
||||
data = readJsonBody();
|
||||
action = structKeyExists(data, "action") ? lcase(data.action) : "seed";
|
||||
|
||||
if (action == "reset") {
|
||||
created = resetTestData();
|
||||
writeOutput(serializeJSON({
|
||||
"OK": true,
|
||||
"action": "reset",
|
||||
"created": created,
|
||||
"testUsers": getTestDataInfo()
|
||||
}));
|
||||
} else {
|
||||
created = seedTestData();
|
||||
writeOutput(serializeJSON({
|
||||
"OK": true,
|
||||
"action": "seed",
|
||||
"created": created,
|
||||
"testUsers": getTestDataInfo()
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
apiAbort({
|
||||
"OK": false,
|
||||
"ERROR": "method_not_allowed",
|
||||
"MESSAGE": "Use GET to view or POST to seed"
|
||||
});
|
||||
}
|
||||
|
||||
} catch (any e) {
|
||||
apiAbort({
|
||||
"OK": false,
|
||||
"ERROR": "server_error",
|
||||
"MESSAGE": application.showDetailedErrors ? e.message : "An error occurred",
|
||||
"DETAIL": application.showDetailedErrors ? e.detail : ""
|
||||
});
|
||||
}
|
||||
</cfscript>
|
||||
146
api/dev/timeTravel.cfm
Normal file
146
api/dev/timeTravel.cfm
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
<cfsetting showdebugoutput="false">
|
||||
<cfsetting enablecfoutputonly="true">
|
||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||
|
||||
<cfscript>
|
||||
/**
|
||||
* Time Travel Endpoint - DEV ONLY
|
||||
*
|
||||
* Allows manipulation of timestamps for testing time-based features.
|
||||
* Examples: token expiry, trial periods, scheduled events.
|
||||
*
|
||||
* POST: { "action": "expireTokens", "userId": 123 }
|
||||
* POST: { "action": "setUserCreated", "userId": 123, "daysAgo": 30 }
|
||||
*/
|
||||
|
||||
function apiAbort(required struct payload) {
|
||||
writeOutput(serializeJSON(payload));
|
||||
abort;
|
||||
}
|
||||
|
||||
// SAFETY: Only allow on dev environment
|
||||
if (!structKeyExists(application, "isDevEnvironment") || !application.isDevEnvironment) {
|
||||
apiAbort({
|
||||
"OK": false,
|
||||
"ERROR": "forbidden",
|
||||
"MESSAGE": "This endpoint is only available in development"
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
if (cgi.request_method != "POST") {
|
||||
apiAbort({
|
||||
"OK": false,
|
||||
"ERROR": "method_not_allowed",
|
||||
"MESSAGE": "POST required"
|
||||
});
|
||||
}
|
||||
|
||||
data = readJsonBody();
|
||||
action = structKeyExists(data, "action") ? lcase(data.action) : "";
|
||||
|
||||
switch (action) {
|
||||
case "expiretokens":
|
||||
// Expire all tokens for a user (simulate session timeout)
|
||||
if (!structKeyExists(data, "userId")) {
|
||||
apiAbort({ "OK": false, "ERROR": "missing_userId" });
|
||||
}
|
||||
|
||||
queryExecute("
|
||||
UPDATE UserTokens
|
||||
SET CreatedAt = DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
WHERE UserID = :userId
|
||||
", {
|
||||
userId: { value: data.userId, cfsqltype: "cf_sql_integer" }
|
||||
}, { datasource: "payfrit" });
|
||||
|
||||
writeOutput(serializeJSON({
|
||||
"OK": true,
|
||||
"message": "Tokens expired for user " & data.userId
|
||||
}));
|
||||
break;
|
||||
|
||||
case "setusercreated":
|
||||
// Backdate user creation (for testing trial periods, etc)
|
||||
if (!structKeyExists(data, "userId") || !structKeyExists(data, "daysAgo")) {
|
||||
apiAbort({ "OK": false, "ERROR": "missing_userId_or_daysAgo" });
|
||||
}
|
||||
|
||||
queryExecute("
|
||||
UPDATE Users
|
||||
SET UserAddedOn = DATE_SUB(NOW(), INTERVAL :days DAY)
|
||||
WHERE UserID = :userId
|
||||
", {
|
||||
userId: { value: data.userId, cfsqltype: "cf_sql_integer" },
|
||||
days: { value: data.daysAgo, cfsqltype: "cf_sql_integer" }
|
||||
}, { datasource: "payfrit" });
|
||||
|
||||
writeOutput(serializeJSON({
|
||||
"OK": true,
|
||||
"message": "User " & data.userId & " created date set to " & data.daysAgo & " days ago"
|
||||
}));
|
||||
break;
|
||||
|
||||
case "clearotps":
|
||||
// Clear all OTPs (force re-request)
|
||||
queryExecute("
|
||||
UPDATE Users SET UserMobileVerifyCode = ''
|
||||
", {}, { datasource: "payfrit" });
|
||||
|
||||
writeOutput(serializeJSON({
|
||||
"OK": true,
|
||||
"message": "All OTPs cleared"
|
||||
}));
|
||||
break;
|
||||
|
||||
case "resetuser":
|
||||
// Reset a user to unverified state (for retesting signup)
|
||||
if (!structKeyExists(data, "phone")) {
|
||||
apiAbort({ "OK": false, "ERROR": "missing_phone" });
|
||||
}
|
||||
|
||||
queryExecute("
|
||||
UPDATE Users
|
||||
SET UserIsContactVerified = 0,
|
||||
UserIsActive = 0,
|
||||
UserFirstName = NULL,
|
||||
UserLastName = NULL,
|
||||
UserMobileVerifyCode = '123456'
|
||||
WHERE UserContactNumber = :phone
|
||||
", {
|
||||
phone: { value: data.phone, cfsqltype: "cf_sql_varchar" }
|
||||
}, { datasource: "payfrit" });
|
||||
|
||||
writeOutput(serializeJSON({
|
||||
"OK": true,
|
||||
"message": "User with phone " & data.phone & " reset to unverified"
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
apiAbort({
|
||||
"OK": false,
|
||||
"ERROR": "unknown_action",
|
||||
"MESSAGE": "Valid actions: expireTokens, setUserCreated, clearOTPs, resetUser"
|
||||
});
|
||||
}
|
||||
|
||||
} catch (any e) {
|
||||
apiAbort({
|
||||
"OK": false,
|
||||
"ERROR": "server_error",
|
||||
"MESSAGE": application.showDetailedErrors ? e.message : "An error occurred"
|
||||
});
|
||||
}
|
||||
</cfscript>
|
||||
Loading…
Add table
Reference in a new issue