mirror of
https://github.com/DumbWareio/DumbDrop.git
synced 2025-11-17 04:11:28 +00:00
* feat: upgrade dependencies for security and add comprehensive test suite Major security and quality improvements to address GitHub issue #69 BREAKING CHANGES: - ESLint upgraded from 8.x to 9.x with new flat config system - Migrated from eslint-plugin-node to eslint-plugin-n Security Fixes: - Upgraded Multer from 1.4.5-lts.1 to 2.0.0 * Fixes known security vulnerabilities in file upload handling * Addresses path traversal and exploit concerns - Upgraded ESLint from 8.56.0 to 9.0.0 * Ensures continued security patches and support - Replaced deprecated eslint-plugin-node with eslint-plugin-n (v17.0.0) - npm audit: Reduced vulnerabilities from 5 (4 high, 1 low) to 0 Configuration Changes: - Created eslint.config.js using new flat config format - Removed deprecated .eslintrc.json and .eslintignore files - Added ignores configuration for test files and service workers - Disabled cleanup intervals during tests to prevent hanging Code Quality: - Fixed all ESLint errors across codebase - Removed unused variables and imports - Added proper ESLint disable comments where needed - Fixed no-control-regex warnings with proper comments Test Suite (NEW): - Added Node.js built-in test runner (no extra dependencies) - Created 43 tests across 4 test files: * test/upload.test.js - Upload API tests * test/files.test.js - File management tests * test/auth.test.js - Authentication tests * test/security.test.js - Security and validation tests - Test coverage: 81% pass rate (35/43 tests passing) - Added npm test script to package.json Docker Optimization: - Updated .dockerignore to exclude test files from production images - Excluded development configs (eslint.config.js, .prettierrc, nodemon.json) - Reduces production image size and attack surface Fixes #69 Test Results: - 43 tests, 24 suites - 35 passing, 8 failing (minor edge cases) - Execution time: 469ms - All tests complete without hanging * Update multer dependency to v2.0.2 Bumped multer from version 2.0.0 to 2.0.2 in package.json and package-lock.json to include the latest bug fixes and improvements. * Update ESLint ignore patterns and improve config validation Added 'test/**' to ESLint ignore patterns. Enhanced BASE_URL validation error handling to log specific error messages and provide more informative feedback.
218 lines
5.4 KiB
JavaScript
218 lines
5.4 KiB
JavaScript
/**
|
|
* Authentication tests
|
|
* Tests PIN protection and authentication middleware
|
|
*/
|
|
|
|
// Disable batch cleanup for tests
|
|
process.env.DISABLE_BATCH_CLEANUP = 'true';
|
|
|
|
const { describe, it, before, after, beforeEach } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const http = require('node:http');
|
|
|
|
// Import the app
|
|
const { app, initialize } = require('../src/app');
|
|
|
|
let server;
|
|
let baseUrl;
|
|
const originalPin = process.env.PIN;
|
|
|
|
before(async () => {
|
|
// Set PIN for testing
|
|
process.env.PIN = '1234';
|
|
|
|
// Initialize app
|
|
await initialize();
|
|
|
|
// Start server on random port
|
|
server = http.createServer(app);
|
|
await new Promise((resolve) => {
|
|
server.listen(0, () => {
|
|
const { port } = server.address();
|
|
baseUrl = `http://localhost:${port}`;
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
after(async () => {
|
|
// Restore original PIN
|
|
if (originalPin) {
|
|
process.env.PIN = originalPin;
|
|
} else {
|
|
delete process.env.PIN;
|
|
}
|
|
|
|
// Close server
|
|
if (server) {
|
|
await new Promise((resolve) => server.close(resolve));
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Helper function to make HTTP requests
|
|
*/
|
|
async function makeRequest(options, body = null) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = http.request(options, (res) => {
|
|
let data = '';
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
res.on('end', () => {
|
|
try {
|
|
const parsed = data ? JSON.parse(data) : {};
|
|
resolve({ status: res.statusCode, data: parsed, headers: res.headers, cookies: res.headers['set-cookie'] });
|
|
} catch {
|
|
resolve({ status: res.statusCode, data, headers: res.headers, cookies: res.headers['set-cookie'] });
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', reject);
|
|
|
|
if (body) {
|
|
req.write(JSON.stringify(body));
|
|
}
|
|
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
describe('Authentication API Tests', () => {
|
|
describe('GET /api/auth/pin-required', () => {
|
|
it('should indicate if PIN is required', async () => {
|
|
const response = await makeRequest({
|
|
host: 'localhost',
|
|
port: server.address().port,
|
|
path: '/api/auth/pin-required',
|
|
method: 'GET',
|
|
});
|
|
|
|
assert.strictEqual(response.status, 200);
|
|
assert.strictEqual(typeof response.data.required, 'boolean');
|
|
});
|
|
});
|
|
|
|
describe('POST /api/auth/verify-pin', () => {
|
|
it('should accept correct PIN', async () => {
|
|
const response = await makeRequest({
|
|
host: 'localhost',
|
|
port: server.address().port,
|
|
path: '/api/auth/verify-pin',
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
}, {
|
|
pin: '1234',
|
|
});
|
|
|
|
assert.strictEqual(response.status, 200);
|
|
assert.ok(response.cookies);
|
|
});
|
|
|
|
it('should reject incorrect PIN', async () => {
|
|
const response = await makeRequest({
|
|
host: 'localhost',
|
|
port: server.address().port,
|
|
path: '/api/auth/verify-pin',
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
}, {
|
|
pin: 'wrong',
|
|
});
|
|
|
|
assert.strictEqual(response.status, 401);
|
|
});
|
|
|
|
it('should reject empty PIN', async () => {
|
|
const response = await makeRequest({
|
|
host: 'localhost',
|
|
port: server.address().port,
|
|
path: '/api/auth/verify-pin',
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
}, {
|
|
pin: '',
|
|
});
|
|
|
|
assert.strictEqual(response.status, 400);
|
|
});
|
|
});
|
|
|
|
describe('Protected Routes', () => {
|
|
it('should require PIN for upload init', async () => {
|
|
const response = await makeRequest({
|
|
host: 'localhost',
|
|
port: server.address().port,
|
|
path: '/api/upload/init',
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
}, {
|
|
filename: 'test.txt',
|
|
fileSize: 100,
|
|
});
|
|
|
|
// Should be redirected or unauthorized without PIN
|
|
assert.ok(response.status === 401 || response.status === 403);
|
|
});
|
|
|
|
it('should allow upload with valid PIN cookie', async () => {
|
|
// First, get PIN cookie
|
|
const authResponse = await makeRequest({
|
|
host: 'localhost',
|
|
port: server.address().port,
|
|
path: '/api/auth/verify-pin',
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
}, {
|
|
pin: '1234',
|
|
});
|
|
|
|
// Extract cookie
|
|
const cookies = authResponse.cookies;
|
|
const cookie = cookies ? cookies[0].split(';')[0] : '';
|
|
|
|
// Try upload with cookie
|
|
const uploadResponse = await makeRequest({
|
|
host: 'localhost',
|
|
port: server.address().port,
|
|
path: '/api/upload/init',
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Cookie': cookie,
|
|
},
|
|
}, {
|
|
filename: 'test.txt',
|
|
fileSize: 100,
|
|
});
|
|
|
|
assert.strictEqual(uploadResponse.status, 200);
|
|
});
|
|
});
|
|
|
|
describe('POST /api/auth/logout', () => {
|
|
it('should clear authentication cookie', async () => {
|
|
const response = await makeRequest({
|
|
host: 'localhost',
|
|
port: server.address().port,
|
|
path: '/api/auth/logout',
|
|
method: 'POST',
|
|
});
|
|
|
|
assert.strictEqual(response.status, 200);
|
|
});
|
|
});
|
|
});
|
|
|