100 lines
2.6 KiB
JavaScript
100 lines
2.6 KiB
JavaScript
import Database from 'better-sqlite3';
|
|
import { createDecipheriv, createHash, hkdfSync } from 'crypto';
|
|
import { join } from 'path';
|
|
|
|
const DB_PATH = process.env.DATABASE_PATH || join(process.cwd(), 'data', 'kaboot.db');
|
|
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY;
|
|
|
|
const ALGORITHM = 'aes-256-gcm';
|
|
const IV_LENGTH = 12;
|
|
const AUTH_TAG_LENGTH = 16;
|
|
const SALT_LENGTH = 16;
|
|
|
|
if (!ENCRYPTION_KEY) {
|
|
console.error('ENCRYPTION_KEY is required to migrate encrypted default_game_config values.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const masterKey = createHash('sha256').update(ENCRYPTION_KEY).digest();
|
|
|
|
const isJsonString = (value) => {
|
|
try {
|
|
JSON.parse(value);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const decryptForUser = (ciphertext, userSub) => {
|
|
if (!ciphertext) return null;
|
|
|
|
try {
|
|
const combined = Buffer.from(ciphertext, 'base64');
|
|
if (combined.length < SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH + 1) {
|
|
return null;
|
|
}
|
|
|
|
const salt = combined.subarray(0, SALT_LENGTH);
|
|
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
const authTag = combined.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
|
|
const key = Buffer.from(
|
|
hkdfSync('sha256', masterKey, salt, `kaboot-user-data:${userSub}`, 32)
|
|
);
|
|
|
|
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
decipher.setAuthTag(authTag);
|
|
|
|
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
return decrypted.toString('utf8');
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const db = new Database(DB_PATH);
|
|
|
|
const rows = db.prepare(`
|
|
SELECT id, default_game_config as defaultGameConfig
|
|
FROM users
|
|
WHERE default_game_config IS NOT NULL
|
|
`).all();
|
|
|
|
const update = db.prepare('UPDATE users SET default_game_config = ? WHERE id = ?');
|
|
|
|
let migrated = 0;
|
|
let alreadyPlaintext = 0;
|
|
let failed = 0;
|
|
|
|
const migrate = db.transaction(() => {
|
|
for (const row of rows) {
|
|
const value = row.defaultGameConfig;
|
|
if (!value) continue;
|
|
|
|
if (isJsonString(value)) {
|
|
alreadyPlaintext += 1;
|
|
continue;
|
|
}
|
|
|
|
const decrypted = decryptForUser(value, row.id);
|
|
if (decrypted && isJsonString(decrypted)) {
|
|
update.run(decrypted, row.id);
|
|
migrated += 1;
|
|
} else {
|
|
failed += 1;
|
|
}
|
|
}
|
|
});
|
|
|
|
migrate();
|
|
|
|
console.log(`Default config migration complete.`);
|
|
console.log(`- migrated: ${migrated}`);
|
|
console.log(`- already plaintext: ${alreadyPlaintext}`);
|
|
console.log(`- failed: ${failed}`);
|
|
|
|
if (failed > 0) {
|
|
console.warn('Some entries could not be decrypted. Check ENCRYPTION_KEY and retry if needed.');
|
|
}
|