Skip to content

Commit 262da49

Browse files
committed
security fix #10
1 parent 6089278 commit 262da49

1 file changed

Lines changed: 40 additions & 6 deletions

File tree

src/lib/auth.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
import { authApi, utilityApi } from './api-client';
22

3+
// Secure randomness utilities
4+
const getCrypto = (): Crypto => {
5+
if (typeof globalThis !== 'undefined' && globalThis.crypto && typeof globalThis.crypto.getRandomValues === 'function') {
6+
return globalThis.crypto as Crypto;
7+
}
8+
throw new Error('Secure crypto.getRandomValues is not available in this environment');
9+
};
10+
11+
const getSecureRandomInt = (maxExclusive: number): number => {
12+
if (maxExclusive <= 0) throw new Error('maxExclusive must be > 0');
13+
const cryptoObj = getCrypto();
14+
// Rejection sampling to avoid modulo bias
15+
const maxUint32 = 0xFFFFFFFF;
16+
const limit = Math.floor((maxUint32 + 1) / maxExclusive) * maxExclusive;
17+
const buffer = new Uint32Array(1);
18+
let value = 0;
19+
do {
20+
cryptoObj.getRandomValues(buffer);
21+
value = buffer[0];
22+
} while (value >= limit);
23+
return value % maxExclusive;
24+
};
25+
26+
const secureShuffle = (input: string[]): string[] => {
27+
for (let i = input.length - 1; i > 0; i--) {
28+
const j = getSecureRandomInt(i + 1);
29+
const tmp = input[i];
30+
input[i] = input[j];
31+
input[j] = tmp;
32+
}
33+
return input;
34+
};
35+
336
// Check if this is the first time setup (no admin exists)
437
export const isFirstTimeSetup = async (): Promise<boolean> => {
538
try {
@@ -81,19 +114,20 @@ export const generateSecurePassword = async (): Promise<string> => {
81114

82115
// Ensure at least one character from each category
83116
let password = '';
84-
password += uppercase[Math.floor(Math.random() * uppercase.length)];
85-
password += lowercase[Math.floor(Math.random() * lowercase.length)];
86-
password += numbers[Math.floor(Math.random() * numbers.length)];
87-
password += special[Math.floor(Math.random() * special.length)];
117+
password += uppercase[getSecureRandomInt(uppercase.length)];
118+
password += lowercase[getSecureRandomInt(lowercase.length)];
119+
password += numbers[getSecureRandomInt(numbers.length)];
120+
password += special[getSecureRandomInt(special.length)];
88121

89122
// Fill remaining length with random characters
90123
const allChars = uppercase + lowercase + numbers + special;
91124
for (let i = 4; i < 16; i++) {
92-
password += allChars[Math.floor(Math.random() * allChars.length)];
125+
password += allChars[getSecureRandomInt(allChars.length)];
93126
}
94127

95128
// Shuffle the password
96-
return password.split('').sort(() => Math.random() - 0.5).join('');
129+
const shuffled = secureShuffle(password.split(''));
130+
return shuffled.join('');
97131
}
98132
};
99133

0 commit comments

Comments
 (0)