feat: Refactor random number generation to use a secure method from utils
This commit is contained in:
@@ -2,9 +2,7 @@ import fs from 'fs/promises';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { TarotCard, CardOrientation, CardCategory } from './types.js';
|
import { TarotCard, CardOrientation, CardCategory } from './types.js';
|
||||||
|
import { getSecureRandom } from './utils.js';
|
||||||
// Use global crypto in Node.js >= 18 and browsers
|
|
||||||
declare const crypto: any;
|
|
||||||
|
|
||||||
// Helper to get __dirname in ES modules
|
// Helper to get __dirname in ES modules
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
@@ -206,19 +204,6 @@ export class TarotCardManager {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a cryptographically secure random number between 0 and 1.
|
|
||||||
*/
|
|
||||||
private getSecureRandom(): number {
|
|
||||||
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
|
|
||||||
const array = new Uint32Array(1);
|
|
||||||
crypto.getRandomValues(array);
|
|
||||||
return array[0] / (0xffffffff + 1);
|
|
||||||
}
|
|
||||||
// Fallback for older Node.js or unexpected environments
|
|
||||||
console.warn('crypto.getRandomValues not available, falling back to Math.random().');
|
|
||||||
return Math.random();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fisher-Yates shuffle algorithm for true randomness.
|
* Fisher-Yates shuffle algorithm for true randomness.
|
||||||
@@ -226,7 +211,7 @@ export class TarotCardManager {
|
|||||||
private fisherYatesShuffle<T>(array: readonly T[]): T[] {
|
private fisherYatesShuffle<T>(array: readonly T[]): T[] {
|
||||||
const shuffled = [...array];
|
const shuffled = [...array];
|
||||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(this.getSecureRandom() * (i + 1));
|
const j = Math.floor(getSecureRandom() * (i + 1));
|
||||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||||
}
|
}
|
||||||
return shuffled;
|
return shuffled;
|
||||||
@@ -236,7 +221,7 @@ export class TarotCardManager {
|
|||||||
* Get a random card from the deck.
|
* Get a random card from the deck.
|
||||||
*/
|
*/
|
||||||
public getRandomCard(): TarotCard {
|
public getRandomCard(): TarotCard {
|
||||||
const randomIndex = Math.floor(this.getSecureRandom() * this.allCards.length);
|
const randomIndex = Math.floor(getSecureRandom() * this.allCards.length);
|
||||||
return this.allCards[randomIndex];
|
return this.allCards[randomIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ import { TarotCardManager } from "./card-manager.js";
|
|||||||
import { TarotSessionManager } from "./session-manager.js";
|
import { TarotSessionManager } from "./session-manager.js";
|
||||||
import { TarotReading, DrawnCard, CardOrientation, TarotCard } from "./types.js";
|
import { TarotReading, DrawnCard, CardOrientation, TarotCard } from "./types.js";
|
||||||
import { TAROT_SPREADS, getAllSpreads, getSpread, isValidSpreadType } from "./spreads.js";
|
import { TAROT_SPREADS, getAllSpreads, getSpread, isValidSpreadType } from "./spreads.js";
|
||||||
|
import { getSecureRandom } from "./utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages tarot readings and interpretations
|
* Manages tarot readings and interpretations
|
||||||
@@ -1115,41 +1116,17 @@ export class TarotReadingManager {
|
|||||||
*/
|
*/
|
||||||
private getSecureRandomOrientation(): CardOrientation {
|
private getSecureRandomOrientation(): CardOrientation {
|
||||||
// Use the same secure random method as card manager
|
// Use the same secure random method as card manager
|
||||||
const random = this.getSecureRandom();
|
const random = getSecureRandom();
|
||||||
return random < 0.5 ? "upright" : "reversed"; // 50% chance upright, 50% reversed
|
return random < 0.5 ? "upright" : "reversed"; // 50% chance upright, 50% reversed
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate cryptographically secure random number
|
|
||||||
*/
|
|
||||||
private getSecureRandom(): number {
|
|
||||||
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
||||||
// Browser environment with Web Crypto API
|
|
||||||
const array = new Uint32Array(1);
|
|
||||||
crypto.getRandomValues(array);
|
|
||||||
return array[0] / (0xffffffff + 1);
|
|
||||||
} else if (typeof require !== 'undefined') {
|
|
||||||
// Node.js environment
|
|
||||||
try {
|
|
||||||
const crypto = require('crypto');
|
|
||||||
return crypto.randomBytes(4).readUInt32BE(0) / (0xffffffff + 1);
|
|
||||||
} catch (e) {
|
|
||||||
// Fallback to Math.random if crypto is not available
|
|
||||||
console.warn('Crypto module not available, falling back to Math.random()');
|
|
||||||
return Math.random();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback to Math.random
|
|
||||||
return Math.random();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a unique reading ID with secure randomness
|
* Generate a unique reading ID with secure randomness
|
||||||
*/
|
*/
|
||||||
private generateReadingId(): string {
|
private generateReadingId(): string {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const randomPart = Math.floor(this.getSecureRandom() * 1000000000).toString(36);
|
const randomPart = Math.floor(getSecureRandom() * 1000000000).toString(36);
|
||||||
return `reading_${timestamp}_${randomPart}`;
|
return `reading_${timestamp}_${randomPart}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
src/tarot/utils.ts
Normal file
15
src/tarot/utils.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Use global crypto in Node.js >= 18 and browsers
|
||||||
|
declare const crypto: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a cryptographically secure random number between 0 and 1.
|
||||||
|
* @throws {Error} If crypto.getRandomValues is not available.
|
||||||
|
*/
|
||||||
|
export function getSecureRandom(): number {
|
||||||
|
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
|
||||||
|
const array = new Uint32Array(1);
|
||||||
|
crypto.getRandomValues(array);
|
||||||
|
return array[0] / (0xffffffff + 1);
|
||||||
|
}
|
||||||
|
throw new Error('A secure random number generator (crypto.getRandomValues) is not available in this environment.');
|
||||||
|
}
|
Reference in New Issue
Block a user