feat: 1.5.0 prep — API v1 cutover, LICENSE, CHANGELOG, CI
- Remove legacy /api/ mount; all routes now under /api/v1/ only - Update path matchers (CSRF excludes, public routes, audit log, rate limits) - Move standalone routes (/api/network/ips, /api/docs, /api/docs/spec) to v1 - Update openapi.yaml (110 paths), CA pages, and 4 lingering frontend files - Add LICENSE (proprietary EULA), CHANGELOG.md (Keep a Changelog format) - Add .gitea/workflows/ci.yml (test+lint and security audit jobs) - Fix 9 pre-existing no-empty lint errors so CI starts green - Drop ad-hoc scratch reports and *.bak files from repo root All 739 jest tests pass. Lint is clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-41
@@ -92,7 +92,7 @@ module.exports = function configureMiddleware(app, {
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
metrics.recordRequest(req.method, req.path, res.statusCode, duration);
|
||||
if (req.path !== '/health' && req.path !== '/api/health') {
|
||||
if (req.path !== '/health' && req.path !== '/api/v1/health') {
|
||||
const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'debug';
|
||||
log[level]('http', `${req.method} ${req.path} ${res.statusCode}`, {
|
||||
ms: duration, ip: req.ip, id: req.id
|
||||
@@ -108,11 +108,11 @@ module.exports = function configureMiddleware(app, {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.path === '/health' || req.path === '/api/health' || req.path.startsWith('/probe/')) {
|
||||
if (req.path === '/health' || req.path === '/api/v1/health' || req.path.startsWith('/probe/')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.path.startsWith('/api/tailscale/')) {
|
||||
if (req.path.startsWith('/api/v1/tailscale/')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -273,41 +273,39 @@ module.exports = function configureMiddleware(app, {
|
||||
|
||||
// ── Public routes (bypass TOTP and JWT auth) ──
|
||||
const PUBLIC_ROUTES = [
|
||||
{ path: '/health', exact: true },
|
||||
{ path: '/api/health', exact: true },
|
||||
{ path: '/probe/', prefix: true },
|
||||
{ path: '/api/tailscale/', prefix: true },
|
||||
{ path: '/api/totp/config', exact: true, method: 'GET' },
|
||||
{ path: '/api/totp/verify', exact: true },
|
||||
{ path: '/api/totp/setup', exact: true, method: 'POST' },
|
||||
{ path: '/api/totp/verify-setup', exact: true, method: 'POST' },
|
||||
{ path: '/api/totp/check-session', exact: true },
|
||||
{ path: '/api/auth/gate/', prefix: true },
|
||||
{ path: '/api/auth/app-token/', prefix: true },
|
||||
{ path: '/api/services', exact: true, method: 'GET' },
|
||||
{ path: '/api/ca/info', exact: true, method: 'GET' },
|
||||
{ path: '/api/ca/root.crt', exact: true, method: 'GET' },
|
||||
{ path: '/api/ca/install-script', exact: true, method: 'GET' },
|
||||
{ path: '/api/health/ca', exact: true, method: 'GET' },
|
||||
{ path: '/api/ca/cert/', prefix: true, method: 'GET' },
|
||||
{ path: '/api/ca/certs', exact: true, method: 'GET' },
|
||||
{ path: '/api/csrf-token', exact: true, method: 'GET' },
|
||||
{ path: '/api/logo', exact: true, method: 'GET' },
|
||||
{ path: '/api/favicon', exact: true, method: 'GET' },
|
||||
{ path: '/api/themes', exact: true, method: 'GET' },
|
||||
{ path: '/api/license/status', exact: true, method: 'GET' },
|
||||
{ path: '/api/license/feature/', prefix: true, method: 'GET' },
|
||||
{ path: '/api/config', exact: true, method: 'GET' },
|
||||
{ path: '/api/services/status', exact: true, method: 'GET' },
|
||||
{ path: '/api/system/update-notify', exact: true, method: 'POST' },
|
||||
{ path: '/health', exact: true },
|
||||
{ path: '/api/v1/health', exact: true },
|
||||
{ path: '/probe/', prefix: true },
|
||||
{ path: '/api/v1/tailscale/', prefix: true },
|
||||
{ path: '/api/v1/totp/config', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/totp/verify', exact: true },
|
||||
{ path: '/api/v1/totp/setup', exact: true, method: 'POST' },
|
||||
{ path: '/api/v1/totp/verify-setup', exact: true, method: 'POST' },
|
||||
{ path: '/api/v1/totp/check-session', exact: true },
|
||||
{ path: '/api/v1/auth/gate/', prefix: true },
|
||||
{ path: '/api/v1/auth/app-token/', prefix: true },
|
||||
{ path: '/api/v1/services', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/ca/info', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/ca/root.crt', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/ca/install-script', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/health/ca', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/ca/cert/', prefix: true, method: 'GET' },
|
||||
{ path: '/api/v1/ca/certs', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/csrf-token', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/logo', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/favicon', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/themes', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/license/status', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/license/feature/', prefix: true, method: 'GET' },
|
||||
{ path: '/api/v1/config', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/services/status', exact: true, method: 'GET' },
|
||||
{ path: '/api/v1/system/update-notify', exact: true, method: 'POST' },
|
||||
];
|
||||
|
||||
function isPublicRoute(req) {
|
||||
// Normalize /api/v1/... to /api/... so public routes work with both
|
||||
const p = req.path.replace(/^\/api\/v1\//, '/api/');
|
||||
return PUBLIC_ROUTES.some(r => {
|
||||
if (r.method && req.method !== r.method) return false;
|
||||
return r.prefix ? p.startsWith(r.path) : p === r.path;
|
||||
return r.prefix ? req.path.startsWith(r.path) : req.path === r.path;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -389,7 +387,7 @@ module.exports = function configureMiddleware(app, {
|
||||
...RATE_LIMITS.GENERAL,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
skip: (req) => isTest || req.path === '/health' || req.path === '/api/health' || req.path.startsWith('/probe/') || req.path.startsWith('/api/auth/gate/') || req.path === '/api/totp/check-session' || req.path.endsWith('/health-checks/status') || req.path.endsWith('/csrf-token') || req.path === '/api/v1/dns/logs' || req.path === '/api/license/status' || req.path.startsWith('/api/license/feature/') || req.path === '/api/services' || req.path === '/api/config',
|
||||
skip: (req) => isTest || req.path === '/health' || req.path === '/api/v1/health' || req.path.startsWith('/probe/') || req.path.startsWith('/api/v1/auth/gate/') || req.path === '/api/v1/totp/check-session' || req.path.endsWith('/health-checks/status') || req.path.endsWith('/csrf-token') || req.path === '/api/v1/dns/logs' || req.path === '/api/v1/license/status' || req.path.startsWith('/api/v1/license/feature/') || req.path === '/api/v1/services' || req.path === '/api/v1/config',
|
||||
message: { success: false, error: 'Too many requests, please try again later' }
|
||||
});
|
||||
|
||||
@@ -402,11 +400,11 @@ module.exports = function configureMiddleware(app, {
|
||||
});
|
||||
|
||||
app.use(generalLimiter);
|
||||
app.use('/api/dns/credentials', strictLimiter);
|
||||
app.use('/api/apps/deploy', strictLimiter);
|
||||
app.use('/api/backup/restore', strictLimiter);
|
||||
app.use('/api/site', strictLimiter);
|
||||
app.use('/api/credentials/rotate-key', strictLimiter);
|
||||
app.use('/api/v1/dns/credentials', strictLimiter);
|
||||
app.use('/api/v1/apps/deploy', strictLimiter);
|
||||
app.use('/api/v1/backup/restore', strictLimiter);
|
||||
app.use('/api/v1/site', strictLimiter);
|
||||
app.use('/api/v1/credentials/rotate-key', strictLimiter);
|
||||
|
||||
const totpLimiter = rateLimit({
|
||||
...RATE_LIMITS.TOTP,
|
||||
@@ -414,8 +412,8 @@ module.exports = function configureMiddleware(app, {
|
||||
legacyHeaders: false,
|
||||
message: { success: false, error: 'Too many TOTP attempts, please try again later' }
|
||||
});
|
||||
app.use('/api/totp/verify', totpLimiter);
|
||||
app.use('/api/totp/verify-setup', totpLimiter);
|
||||
app.use('/api/v1/totp/verify', totpLimiter);
|
||||
app.use('/api/v1/totp/verify-setup', totpLimiter);
|
||||
|
||||
// ── Audit logging middleware (logs non-GET API requests) ──
|
||||
app.use(auditLogger.middleware());
|
||||
|
||||
Reference in New Issue
Block a user