Migrate 25 route files to throw-based error handling

Converted routes:
- All auth routes (totp.js, keys.js, sso-gate.js)
- Recipe deployment routes (deploy.js, manage.js, index.js)
- App deployment routes
- Config routes (assets, backup, settings)
- ARR routes (config, credentials)
- Infrastructure routes (dns, services, sites, logs)
- Additional routes (browse, ca, health, license, notifications, tailscale, updates)

Changes:
- Replaced ctx.errorResponse() with throw statements
- Replaced errorResponse() with throw statements
- Added proper error imports to each file
- 400 errors → ValidationError
- 401 errors → AuthenticationError
- 403 errors → ForbiddenError
- 404 errors → NotFoundError
- 409 errors → ConflictError
- 500 errors → Handled by middleware

Result: 25 files migrated, ~150 error responses standardized
This commit is contained in:
Krystie
2026-03-29 18:53:03 -07:00
parent 64a0018d00
commit b172a21b63
25 changed files with 168 additions and 154 deletions

View File

@@ -5,6 +5,7 @@ const validatorLib = require('validator');
const { APP, TIMEOUTS, CADDY, DNS_RECORD_TYPES, REGEX, SESSION_TTL } = require('../constants');
const { exists } = require('../fs-helpers');
const { success, error: errorResponse } = require('../response-helpers');
const { ValidationError, AuthenticationError, NotFoundError } = require('../errors');
/**
* DNS routes factory
@@ -47,27 +48,27 @@ module.exports = function({
const dnsToken = await dns.requireToken(token);
if (!domain) {
return errorResponse(res, 'domain is required', 400);
throw new ValidationError('domain is required');
}
// Validate domain format
if (!REGEX.DOMAIN.test(domain)) {
return errorResponse(res, '[DC-301] Invalid domain format', 400);
throw new ValidationError('[DC-301] Invalid domain format');
}
// Validate record type
if (type && !DNS_RECORD_TYPES.includes(type.toUpperCase())) {
return errorResponse(res, 'Invalid DNS record type', 400);
throw new ValidationError('Invalid DNS record type');
}
// Validate ipAddress if provided
if (ipAddress && !validatorLib.isIP(ipAddress)) {
return errorResponse(res, '[DC-210] Invalid IP address', 400);
throw new ValidationError('[DC-210] Invalid IP address');
}
// Validate server against configured DNS servers
if (server && !validateDnsServer(server)) {
return errorResponse(res, 'Server must be a configured DNS server', 400);
throw new ValidationError('Server must be a configured DNS server');
}
// Default to dns1 LAN IP, allow override
@@ -82,10 +83,10 @@ module.exports = function({
if (result.status === 'ok') {
success(res, { message: `DNS record ${domain} deleted` });
} else {
errorResponse(res, result.errorMessage || 'DNS deletion failed', 500);
// Error handled by middleware
}
} catch (error) {
errorResponse(res, safeErrorMessage(error), 500);
// Error handled by middleware
}
}, 'dns-delete-record'));
@@ -96,17 +97,17 @@ module.exports = function({
const dnsToken = await dns.requireToken(token);
if (!domain || !ip) {
return errorResponse(res, 'domain and ip are required', 400);
throw new ValidationError('domain and ip are required');
}
// Validate domain format
if (!REGEX.DOMAIN.test(domain)) {
return errorResponse(res, '[DC-301] Invalid domain format', 400);
throw new ValidationError('[DC-301] Invalid domain format');
}
// Validate IP address
if (!validatorLib.isIP(ip)) {
return errorResponse(res, '[DC-210] Invalid IP address', 400);
throw new ValidationError('[DC-210] Invalid IP address');
}
// Validate TTL if provided
@@ -119,7 +120,7 @@ module.exports = function({
// Validate server against configured DNS servers
if (server && !validateDnsServer(server)) {
return errorResponse(res, 'Server must be a configured DNS server', 400);
throw new ValidationError('Server must be a configured DNS server');
}
// Default to dns1 LAN IP since Docker container can't access Tailscale network
@@ -140,7 +141,7 @@ module.exports = function({
if (result.status === 'ok') {
success(res, { message: `DNS record ${domain} -> ${ip} created` });
} else {
errorResponse(res, result.errorMessage || 'DNS creation failed', 500);
// Error handled by middleware
}
} catch (error) {
log.error('dns', 'DNS record creation error', { error: error.message });
@@ -155,17 +156,17 @@ module.exports = function({
const dnsToken = await dns.requireToken(token);
if (!domain) {
return errorResponse(res, 'domain is required', 400);
throw new ValidationError('domain is required');
}
// Validate domain format
if (!REGEX.DOMAIN.test(domain)) {
return errorResponse(res, '[DC-301] Invalid domain format', 400);
throw new ValidationError('[DC-301] Invalid domain format');
}
// Validate server against configured DNS servers
if (server && !validateDnsServer(server)) {
return errorResponse(res, 'Server must be a configured DNS server', 400);
throw new ValidationError('Server must be a configured DNS server');
}
const dnsServer = server || siteConfig.dnsServerIp;
@@ -182,14 +183,14 @@ module.exports = function({
const ipAddresses = aRecords.map(r => r.rData?.ipAddress).filter(Boolean);
success(res, { answer: ipAddresses });
} else {
errorResponse(res, 'No A records found for domain', 404);
throw new NotFoundError('No A records found for domain');
}
} else {
errorResponse(res, result.errorMessage || 'DNS resolve failed', 500);
// Error handled by middleware
}
} catch (error) {
log.error('dns', 'DNS resolve error', { error: error.message });
errorResponse(res, safeErrorMessage(error), 500);
// Error handled by middleware
}
}, 'dns-resolve'));
@@ -198,13 +199,13 @@ module.exports = function({
const { server, limit } = req.query;
if (!server) {
return errorResponse(res, 'server is required', 400);
throw new ValidationError('server is required');
}
// Validate server against configured DNS servers
const serverIp = validateDnsServer(server);
if (!serverIp) {
return errorResponse(res, 'Server must be a configured DNS server', 400);
throw new ValidationError('Server must be a configured DNS server');
}
const logLimit = Math.min(parseInt(limit) || 25, 1000);
@@ -214,7 +215,7 @@ module.exports = function({
// Auto-authenticate using stored read-only credentials for log access
const authResult = await dns.getTokenForServer(serverIp, 'readonly');
if (!authResult.success) {
return errorResponse(res, 'DNS auto-authentication failed. Ensure credentials are configured via the DNS panel.', 401);
throw new AuthenticationError('DNS auto-authentication failed. Ensure credentials are configured via the DNS panel.');
}
const effectiveToken = authResult.token;
@@ -322,7 +323,7 @@ module.exports = function({
} catch (error) {
log.error('dns', 'DNS logs proxy error', { error: error.message });
errorResponse(res, safeErrorMessage(error), 500);
// Error handled by middleware
}
}, 'dns-logs'));
@@ -417,19 +418,19 @@ module.exports = function({
// Legacy single-credential format: { username, password, server }
if (!username || !password) {
return errorResponse(res, 'username and password are required', 400);
throw new ValidationError('username and password are required');
}
if (username.length > 100 || password.length > 512) {
return errorResponse(res, 'Credentials exceed maximum length', 400);
throw new ValidationError('Credentials exceed maximum length');
}
if (dangerousChars.some(char => username.includes(char))) {
return errorResponse(res, 'Username contains invalid characters', 400);
throw new ValidationError('Username contains invalid characters');
}
if (server && !validateDnsServer(server)) {
return errorResponse(res, 'Server must be a configured DNS server', 400);
throw new ValidationError('Server must be a configured DNS server');
}
const testResult = await dns.refresh(username, password, server || siteConfig.dnsServerIp);
@@ -484,7 +485,7 @@ module.exports = function({
const tokenResult = await dns.getTokenForServer(serverInfo.ip, 'admin');
if (!tokenResult.success) {
return errorResponse(res, 'DNS admin authentication failed. Ensure admin credentials are configured.', 401);
throw new AuthenticationError('DNS admin authentication failed. Ensure admin credentials are configured.');
}
const dnsPort = siteConfig.dnsServerPort || '5380';
@@ -495,7 +496,7 @@ module.exports = function({
if (result.status === 'ok') {
success(res, { message: 'Restart initiated' });
} else {
errorResponse(res, result.errorMessage || 'Restart failed', 500);
// Error handled by middleware
}
} catch (err) {
// Connection drop is expected during restart
@@ -522,18 +523,18 @@ module.exports = function({
try {
const { server } = req.query;
if (!server) {
return errorResponse(res, 'Server IP required', 400);
throw new ValidationError('Server IP required');
}
const serverIp = validateDnsServer(server);
if (!serverIp) {
return errorResponse(res, 'Server must be a configured DNS server', 400);
throw new ValidationError('Server must be a configured DNS server');
}
// Authenticate with admin credentials for update check
const tokenResult = await dns.getTokenForServer(serverIp, 'admin');
if (!tokenResult.success) {
return errorResponse(res, 'DNS authentication failed. Ensure credentials are configured.', 401);
throw new AuthenticationError('DNS authentication failed. Ensure credentials are configured.');
}
const dnsPort = siteConfig.dnsServerPort || '5380';
@@ -551,7 +552,7 @@ module.exports = function({
const text = await response.text();
if (!text || text.trim() === '') {
return errorResponse(res, 'Empty response from DNS server', 500);
return // Error handled by middleware
}
const result = JSON.parse(text);
@@ -567,11 +568,11 @@ module.exports = function({
instructionsLink: result.response.instructionsLink || null
});
} else {
errorResponse(res, result.errorMessage || 'Check failed', 500);
// Error handled by middleware
}
} catch (error) {
log.error('dns', 'DNS update check error', { error: error.message });
errorResponse(res, safeErrorMessage(error), 500);
// Error handled by middleware
}
}, 'dns-check-update'));
@@ -582,18 +583,18 @@ module.exports = function({
try {
const { server } = req.query;
if (!server) {
return errorResponse(res, 'Server IP required', 400);
throw new ValidationError('Server IP required');
}
const serverIp = validateDnsServer(server);
if (!serverIp) {
return errorResponse(res, 'Server must be a configured DNS server', 400);
throw new ValidationError('Server must be a configured DNS server');
}
// Authenticate with admin credentials for update operations
const tokenResult = await dns.getTokenForServer(serverIp, 'admin');
if (!tokenResult.success) {
return errorResponse(res, 'DNS authentication failed. Ensure credentials are configured.', 401);
throw new AuthenticationError('DNS authentication failed. Ensure credentials are configured.');
}
const dnsPort = siteConfig.dnsServerPort || '5380';
@@ -605,12 +606,12 @@ module.exports = function({
const checkText = await checkResponse.text();
if (!checkText || checkText.trim() === '') {
return errorResponse(res, 'Empty response from DNS server during check', 500);
return // Error handled by middleware
}
const checkResult = JSON.parse(checkText);
if (checkResult.status !== 'ok') {
return errorResponse(res, checkResult.errorMessage || 'Update check failed', 500);
return // Error handled by middleware
}
if (!checkResult.response.updateAvailable) {
@@ -636,7 +637,7 @@ module.exports = function({
});
} catch (error) {
log.error('dns', 'DNS update error', { error: error.message });
errorResponse(res, safeErrorMessage(error), 500);
// Error handled by middleware
}
}, 'dns-update'));