DC-084/085/089/090: Quick wins batch
DC-084: Add .dockerignore (excludes __tests__/, .git/, node_modules/, coverage/) DC-085: Replace Math.random() with crypto.randomUUID()/crypto.randomBytes() for IDs DC-089: Add dedicated rate limiter on POST /license/activate (10 attempts/15min) DC-090: Pin Node.js to 20.11.1-alpine3.19 + add engines field to package.json All 1539 tests pass. ESLint: 0 errors.
This commit is contained in:
@@ -1,10 +1,14 @@
|
|||||||
node_modules/
|
|
||||||
__tests__/
|
__tests__/
|
||||||
jest.config.js
|
.git/
|
||||||
.env
|
|
||||||
.encryption-key
|
|
||||||
.gitignore
|
.gitignore
|
||||||
.dockerignore
|
node_modules/
|
||||||
*.log
|
coverage/
|
||||||
*.md
|
*.md
|
||||||
docker-compose.yml
|
.eslintrc.js
|
||||||
|
jest.config.js
|
||||||
|
npm-debug.log*
|
||||||
|
.env*
|
||||||
|
.env.example
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
dc.png
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:20-alpine
|
FROM node:20.11.1-alpine3.19
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
"description": "DashCaddy API server - Dashboard backend for Docker, Caddy & DNS management",
|
"description": "DashCaddy API server - Dashboard backend for Docker, Caddy & DNS management",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
const { success, error: errorResponse } = require('../src/utils/responses');
|
const { success, error: errorResponse } = require('../src/utils/responses');
|
||||||
const { ValidationError } = require('../src/utilities/errors');
|
const { ValidationError } = require('../src/utilities/errors');
|
||||||
|
|
||||||
|
// Dedicated rate limiter for license activation — prevents brute-force key guessing.
|
||||||
|
// Pro keys follow a predictable format (DC-XXX-XXXXX-XXXXXX), so without rate
|
||||||
|
// limiting an attacker could enumerate valid keys.
|
||||||
|
const licenseActivateLimiter = rateLimit({
|
||||||
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
|
max: 10, // 10 attempts per window per IP
|
||||||
|
standardHeaders: true,
|
||||||
|
legacyHeaders: false,
|
||||||
|
message: { success: false, error: 'Too many license activation attempts. Please try again later.' },
|
||||||
|
skip: () => process.env.NODE_ENV === 'test',
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* License routes factory
|
* License routes factory
|
||||||
* @param {Object} deps - Explicit dependencies
|
* @param {Object} deps - Explicit dependencies
|
||||||
@@ -13,7 +26,7 @@ module.exports = function({ licenseManager, asyncHandler }) {
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Activate a license code
|
// Activate a license code
|
||||||
router.post('/activate', asyncHandler(async (req, res) => {
|
router.post('/activate', licenseActivateLimiter, asyncHandler(async (req, res) => {
|
||||||
const { code } = req.body;
|
const { code } = req.body;
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw new ValidationError('License code is required');
|
throw new ValidationError('License code is required');
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
const { execFile } = require('child_process');
|
const { execFile } = require('child_process');
|
||||||
const { promisify } = require('util');
|
const { promisify } = require('util');
|
||||||
|
const crypto = require('crypto');
|
||||||
const dns = require('dns');
|
const dns = require('dns');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -117,7 +118,7 @@ class RFC2136Provider extends BaseDNSProvider {
|
|||||||
*/
|
*/
|
||||||
async _runNsupdate(commands) {
|
async _runNsupdate(commands) {
|
||||||
const script = commands.join('\n') + '\n';
|
const script = commands.join('\n') + '\n';
|
||||||
const tmpFile = path.join(os.tmpdir(), `nsupdate-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.cmd`);
|
const tmpFile = path.join(os.tmpdir(), `nsupdate-${crypto.randomBytes(4).toString('hex')}.cmd`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.promises.writeFile(tmpFile, script, { mode: 0o600 });
|
await fs.promises.writeFile(tmpFile, script, { mode: 0o600 });
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
const crypto = require('crypto');
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -349,7 +350,7 @@ class HealthChecker extends EventEmitter {
|
|||||||
|
|
||||||
// Create new incident
|
// Create new incident
|
||||||
const incident = {
|
const incident = {
|
||||||
id: `incident-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
id: `incident-${crypto.randomUUID()}`,
|
||||||
serviceId,
|
serviceId,
|
||||||
type,
|
type,
|
||||||
message,
|
message,
|
||||||
|
|||||||
Reference in New Issue
Block a user