|
1 | 1 | import { authApi, utilityApi } from './api-client'; |
2 | 2 |
|
| 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 | + |
3 | 36 | // Check if this is the first time setup (no admin exists) |
4 | 37 | export const isFirstTimeSetup = async (): Promise<boolean> => { |
5 | 38 | try { |
@@ -81,19 +114,20 @@ export const generateSecurePassword = async (): Promise<string> => { |
81 | 114 |
|
82 | 115 | // Ensure at least one character from each category |
83 | 116 | 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)]; |
88 | 121 |
|
89 | 122 | // Fill remaining length with random characters |
90 | 123 | const allChars = uppercase + lowercase + numbers + special; |
91 | 124 | for (let i = 4; i < 16; i++) { |
92 | | - password += allChars[Math.floor(Math.random() * allChars.length)]; |
| 125 | + password += allChars[getSecureRandomInt(allChars.length)]; |
93 | 126 | } |
94 | 127 |
|
95 | 128 | // Shuffle the password |
96 | | - return password.split('').sort(() => Math.random() - 0.5).join(''); |
| 129 | + const shuffled = secureShuffle(password.split('')); |
| 130 | + return shuffled.join(''); |
97 | 131 | } |
98 | 132 | }; |
99 | 133 |
|
|
0 commit comments