Skip to content

Commit bfe9627

Browse files
committed
security fix
1 parent a56b734 commit bfe9627

5 files changed

Lines changed: 277 additions & 43 deletions

File tree

-476 KB
Binary file not shown.

server/auth.js

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import bcrypt from 'bcryptjs';
22
import jwt from 'jsonwebtoken';
33
import { dbGet, dbRun } from './database.js';
4+
import { randomBytes, randomInt } from 'crypto';
45

5-
const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production';
6+
const JWT_SECRET = process.env.JWT_SECRET || randomBytes(32).toString('hex');
7+
if (process.env.NODE_ENV === 'production' && !process.env.JWT_SECRET) {
8+
console.warn('JWT_SECRET is not set. A random key was generated at runtime; tokens will be invalidated on restart. Set JWT_SECRET in production.');
9+
}
610
const SALT_ROUNDS = 12;
711

812
// Check if this is the first time setup (no admin exists)
@@ -47,7 +51,7 @@ export const setupInitialCredentials = async (password) => {
4751
// Authenticate user against database
4852
export const authenticateUser = async (password) => {
4953
try {
50-
console.log('Authenticating admin user');
54+
console.log('Authenticating admin user...');
5155

5256
const user = await dbGet(
5357
'SELECT username, password_hash, salt FROM admin_users WHERE username = ?',
@@ -59,22 +63,26 @@ export const authenticateUser = async (password) => {
5963
return false;
6064
}
6165

62-
console.log('Found admin user, comparing passwords...');
66+
console.log('Found admin user in database');
67+
console.log('Stored hash:', user.password_hash);
6368

64-
// Hash the provided password with the stored salt
65-
const hashedPassword = await bcrypt.hash(password, user.salt);
66-
const isValid = hashedPassword === user.password_hash;
69+
// Log the first few characters of the stored hash and salt for debugging
70+
console.log(`Stored hash (first 10 chars): ${user.password_hash?.substring(0, 10)}...`);
71+
console.log(`Stored salt: ${user.salt}`);
6772

68-
if (!isValid) {
69-
console.log('Password comparison failed');
70-
console.log('Stored hash:', user.password_hash);
71-
console.log('Computed hash:', hashedPassword);
72-
console.log('Using salt:', user.salt);
73+
// Use bcrypt.compare to verify the password
74+
console.log('Verifying password...');
75+
const isMatch = await bcrypt.compare(password, user.password_hash);
76+
77+
if (!isMatch) {
78+
console.log('Password verification failed');
79+
// Log the first few characters of the attempted password for debugging
80+
console.log(`Attempted password (first 5 chars): ${password.substring(0, 5)}...`);
7381
} else {
74-
console.log('Authentication successful');
82+
console.log('Password verification successful');
7583
}
7684

77-
return isValid;
85+
return isMatch;
7886
} catch (error) {
7987
console.error('Error in authenticateUser:', error);
8088
return false;
@@ -149,19 +157,24 @@ export const generateSecurePassword = () => {
149157

150158
// Ensure at least one character from each category
151159
let password = '';
152-
password += uppercase[Math.floor(Math.random() * uppercase.length)];
153-
password += lowercase[Math.floor(Math.random() * lowercase.length)];
154-
password += numbers[Math.floor(Math.random() * numbers.length)];
155-
password += special[Math.floor(Math.random() * special.length)];
160+
password += uppercase[randomInt(uppercase.length)];
161+
password += lowercase[randomInt(lowercase.length)];
162+
password += numbers[randomInt(numbers.length)];
163+
password += special[randomInt(special.length)];
156164

157165
// Fill remaining length with random characters
158166
const allChars = uppercase + lowercase + numbers + special;
159167
for (let i = 4; i < 16; i++) {
160-
password += allChars[Math.floor(Math.random() * allChars.length)];
168+
password += allChars[randomInt(allChars.length)];
161169
}
162170

163-
// Shuffle the password
164-
return password.split('').sort(() => Math.random() - 0.5).join('');
171+
// Secure Fisher-Yates shuffle using crypto randomness
172+
const arr = password.split('');
173+
for (let i = arr.length - 1; i > 0; i--) {
174+
const j = randomInt(i + 1);
175+
[arr[i], arr[j]] = [arr[j], arr[i]];
176+
}
177+
return arr.join('');
165178
};
166179

167180
// Middleware to verify authentication

server/package-lock.json

Lines changed: 60 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"$schema": "https://json.schemastore.org/package.json",
23
"name": "lynx-backend",
34
"version": "1.0.0",
45
"description": "Standalone backend for Lynx links app",
@@ -10,11 +11,15 @@
1011
"serve": "node server.js"
1112
},
1213
"dependencies": {
13-
"express": "^4.18.2",
14-
"sqlite3": "^5.1.6",
1514
"bcryptjs": "^2.4.3",
15+
"cookie-parser": "^1.4.7",
1616
"cors": "^2.8.5",
17+
"express": "^4.18.2",
18+
"express-rate-limit": "^7.2.0",
19+
"helmet": "^7.1.0",
1720
"jsonwebtoken": "^9.0.2",
18-
"multer": "^1.4.5-lts.1"
21+
"multer": "^1.4.5-lts.1",
22+
"sqlite3": "^5.1.6",
23+
"zod": "^3.23.8"
1924
}
2025
}

0 commit comments

Comments
 (0)