1.0.7
This commit is contained in:
1 parent
bd7b832394
commit
7643807717
86 files changed
+3644
-1385
No files matched your search
+46
-10
@@ -9,8 +9,9 @@
|
||||
* - SameSite=Strict (CSRF protection)
|
||||
*/
|
||||
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import os from 'node:os';
|
||||
import { secureRandomBytes, usingFallback } from './crypto-fallback';
|
||||
import { argon2id, argon2Verify } from 'hash-wasm';
|
||||
import type { Cookies } from '@sveltejs/kit';
|
||||
import {
|
||||
getAuthSettings,
|
||||
@@ -94,27 +95,62 @@ export interface LoginResult {
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Password Hashing (Argon2id via Bun.password)
|
||||
// Password Hashing (Argon2id)
|
||||
// ============================================
|
||||
|
||||
// Argon2id parameters (matching Bun.password defaults)
|
||||
const ARGON2_MEMORY_COST = 65536; // 64 MB in kibibytes
|
||||
const ARGON2_TIME_COST = 3; // 3 iterations
|
||||
const ARGON2_PARALLELISM = 1; // Single-threaded
|
||||
const ARGON2_HASH_LENGTH = 32; // 256-bit output
|
||||
const ARGON2_SALT_LENGTH = 16; // 128-bit salt
|
||||
|
||||
/**
|
||||
* Hash a password using Argon2id via Bun's native password API
|
||||
* Hash a password using Argon2id
|
||||
*
|
||||
* On modern kernels (>=3.17): Uses Bun's native password API (faster)
|
||||
* On old kernels (<3.17): Uses hash-wasm (WASM-based, no getrandom dependency)
|
||||
*
|
||||
* Argon2id is the recommended variant - resistant to both side-channel and GPU attacks
|
||||
*/
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
// On old kernels, Bun.password.hash() crashes because it internally uses getrandom()
|
||||
// Use hash-wasm as a fallback which is pure WASM and doesn't depend on the syscall
|
||||
if (usingFallback()) {
|
||||
const salt = secureRandomBytes(ARGON2_SALT_LENGTH);
|
||||
return argon2id({
|
||||
password,
|
||||
salt,
|
||||
iterations: ARGON2_TIME_COST,
|
||||
parallelism: ARGON2_PARALLELISM,
|
||||
memorySize: ARGON2_MEMORY_COST,
|
||||
hashLength: ARGON2_HASH_LENGTH,
|
||||
outputType: 'encoded' // Returns PHC format: $argon2id$v=19$m=65536,t=3,p=1$...
|
||||
});
|
||||
}
|
||||
|
||||
// Modern kernels: use Bun's native implementation (faster)
|
||||
return Bun.password.hash(password, {
|
||||
algorithm: 'argon2id',
|
||||
memoryCost: 65536, // 64 MB
|
||||
timeCost: 3 // 3 iterations
|
||||
memoryCost: ARGON2_MEMORY_COST,
|
||||
timeCost: ARGON2_TIME_COST
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a password against a hash
|
||||
* Uses constant-time comparison internally
|
||||
*
|
||||
* Both Bun.password and hash-wasm use the same PHC format, so hashes are compatible
|
||||
*/
|
||||
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
try {
|
||||
// On old kernels, use hash-wasm for verification
|
||||
if (usingFallback()) {
|
||||
return await argon2Verify({ password, hash });
|
||||
}
|
||||
|
||||
// Modern kernels: use Bun's native implementation
|
||||
return await Bun.password.verify(password, hash);
|
||||
} catch {
|
||||
return false;
|
||||
@@ -130,7 +166,7 @@ export async function verifyPassword(password: string, hash: string): Promise<bo
|
||||
* 32 bytes = 256 bits of entropy
|
||||
*/
|
||||
function generateSessionToken(): string {
|
||||
return randomBytes(32).toString('base64url');
|
||||
return secureRandomBytes(32).toString('base64url');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,7 +447,7 @@ export async function authenticateLocal(
|
||||
|
||||
if (!user) {
|
||||
// Use constant time to prevent timing attacks
|
||||
await Bun.password.hash('dummy', { algorithm: 'argon2id' });
|
||||
await hashPassword('dummy');
|
||||
return { success: false, error: 'Invalid username or password' };
|
||||
}
|
||||
|
||||
@@ -1127,7 +1163,7 @@ async function getOidcDiscovery(issuerUrl: string): Promise<OidcDiscoveryDocumen
|
||||
* Generate PKCE code verifier and challenge
|
||||
*/
|
||||
function generatePkce(): { codeVerifier: string; codeChallenge: string } {
|
||||
const codeVerifier = randomBytes(32).toString('base64url');
|
||||
const codeVerifier = secureRandomBytes(32).toString('base64url');
|
||||
const hasher = new Bun.CryptoHasher('sha256');
|
||||
hasher.update(codeVerifier);
|
||||
const codeChallenge = hasher.digest('base64url') as string;
|
||||
@@ -1150,8 +1186,8 @@ export async function buildOidcAuthorizationUrl(
|
||||
const discovery = await getOidcDiscovery(config.issuerUrl);
|
||||
|
||||
// Generate state, nonce, and PKCE
|
||||
const state = randomBytes(32).toString('base64url');
|
||||
const nonce = randomBytes(16).toString('base64url');
|
||||
const state = secureRandomBytes(32).toString('base64url');
|
||||
const nonce = secureRandomBytes(16).toString('base64url');
|
||||
const { codeVerifier, codeChallenge } = generatePkce();
|
||||
|
||||
// Store state for callback verification (expires in 10 minutes)
|
||||
|
||||
Reference in new issue
Block a user