Initial commit - realms platform
This commit is contained in:
parent
c590ab6d18
commit
c717c3751c
234 changed files with 74103 additions and 15231 deletions
|
|
@ -4,6 +4,12 @@ const DB_NAME = 'pgp_storage';
|
|||
const DB_VERSION = 1;
|
||||
const STORE_NAME = 'encrypted_keys';
|
||||
|
||||
// Rate limiting for unlock attempts
|
||||
let unlockAttempts = 0;
|
||||
let lockoutUntil = null;
|
||||
const MAX_ATTEMPTS = 5;
|
||||
const LOCKOUT_DURATION = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
// Initialize IndexedDB
|
||||
async function initDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -48,21 +54,23 @@ async function deriveKey(passphrase, salt) {
|
|||
|
||||
// Validate passphrase strength
|
||||
export function validatePassphrase(passphrase) {
|
||||
if (!passphrase || passphrase.length < 12) {
|
||||
return 'Passphrase must be at least 12 characters';
|
||||
if (!passphrase || passphrase.length < 16) {
|
||||
return 'Passphrase must be at least 16 characters';
|
||||
}
|
||||
|
||||
|
||||
// Check for complexity
|
||||
const hasUpper = /[A-Z]/.test(passphrase);
|
||||
const hasLower = /[a-z]/.test(passphrase);
|
||||
const hasNumber = /[0-9]/.test(passphrase);
|
||||
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(passphrase);
|
||||
|
||||
const hasSpecial = /[!@#$%^&*(),.?":{}|<>\-_=+\[\]\\;'`~]/.test(passphrase);
|
||||
|
||||
const complexity = [hasUpper, hasLower, hasNumber, hasSpecial].filter(Boolean).length;
|
||||
if (complexity < 3) {
|
||||
return 'Passphrase must contain at least 3 of: uppercase, lowercase, numbers, special characters';
|
||||
|
||||
// Require all 4 types, OR 20+ characters with 3 types
|
||||
if (complexity < 4 && !(passphrase.length >= 20 && complexity >= 3)) {
|
||||
return 'Passphrase must contain uppercase, lowercase, numbers, AND special characters (or be 20+ chars with 3 types)';
|
||||
}
|
||||
|
||||
|
||||
return null; // Valid
|
||||
}
|
||||
|
||||
|
|
@ -165,34 +173,40 @@ export async function unlockPrivateKey(passphrase) {
|
|||
if (typeof window === 'undefined') {
|
||||
throw new Error('Storage operations can only be performed in the browser');
|
||||
}
|
||||
|
||||
|
||||
// Check lockout
|
||||
if (lockoutUntil && Date.now() < lockoutUntil) {
|
||||
const remaining = Math.ceil((lockoutUntil - Date.now()) / 60000);
|
||||
throw new Error(`Too many failed attempts. Try again in ${remaining} minute${remaining !== 1 ? 's' : ''}.`);
|
||||
}
|
||||
|
||||
if (!passphrase) {
|
||||
throw new Error('Passphrase required');
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const db = await initDB();
|
||||
const transaction = db.transaction([STORE_NAME], 'readonly');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
|
||||
|
||||
const data = await new Promise((resolve, reject) => {
|
||||
const request = store.get('primary_key');
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
|
||||
|
||||
if (!data) {
|
||||
throw new Error('No encrypted key found');
|
||||
}
|
||||
|
||||
|
||||
// Reconstruct typed arrays
|
||||
const salt = new Uint8Array(data.salt);
|
||||
const iv = new Uint8Array(data.iv);
|
||||
const encrypted = new Uint8Array(data.encrypted);
|
||||
|
||||
|
||||
// Derive decryption key
|
||||
const key = await deriveKey(passphrase, salt);
|
||||
|
||||
|
||||
// Decrypt
|
||||
const decrypted = await crypto.subtle.decrypt(
|
||||
{
|
||||
|
|
@ -202,12 +216,25 @@ export async function unlockPrivateKey(passphrase) {
|
|||
key,
|
||||
encrypted
|
||||
);
|
||||
|
||||
|
||||
// Success - reset attempts
|
||||
unlockAttempts = 0;
|
||||
lockoutUntil = null;
|
||||
|
||||
const dec = new TextDecoder();
|
||||
return dec.decode(decrypted);
|
||||
} catch (error) {
|
||||
// Track failed attempts
|
||||
unlockAttempts++;
|
||||
if (unlockAttempts >= MAX_ATTEMPTS) {
|
||||
lockoutUntil = Date.now() + LOCKOUT_DURATION;
|
||||
unlockAttempts = 0;
|
||||
console.error('Too many failed unlock attempts. Locked out for 30 minutes.');
|
||||
throw new Error('Too many failed attempts. Locked out for 30 minutes.');
|
||||
}
|
||||
|
||||
console.error('Failed to unlock private key:', error);
|
||||
throw new Error('Failed to unlock private key. Check your passphrase.');
|
||||
throw new Error(`Failed to unlock private key. Check your passphrase. (${MAX_ATTEMPTS - unlockAttempts} attempts remaining)`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue