joplock/app/recoveryService.js

62 lines
1.4 KiB
JavaScript

'use strict';
const crypto = require('crypto');
const RECOVERY_COOKIE = 'joplockRecoverySession';
const timingSafeEqual = (a, b) => {
const left = Buffer.from(`${a || ''}`);
const right = Buffer.from(`${b || ''}`);
if (left.length !== right.length) return false;
return crypto.timingSafeEqual(left, right);
};
const createRecoveryService = options => {
const {
enabled = false,
password = '',
sessionTtlMinutes = 30,
now = () => Date.now(),
} = options || {};
const sessions = new Map();
const isEnabled = () => !!enabled && !!password;
const ttlMs = Math.max(1, Number(sessionTtlMinutes || 30)) * 60 * 1000;
const createSession = attempt => {
if (!isEnabled()) throw new Error('Recovery mode is disabled');
if (!timingSafeEqual(attempt, password)) return '';
const token = crypto.randomBytes(24).toString('hex');
sessions.set(token, now() + ttlMs);
return token;
};
const validateSession = token => {
if (!isEnabled() || !token) return false;
const expiresAt = sessions.get(token);
if (!expiresAt) return false;
if (expiresAt <= now()) {
sessions.delete(token);
return false;
}
return true;
};
const endSession = token => {
if (token) sessions.delete(token);
};
return {
RECOVERY_COOKIE,
isEnabled,
createSession,
validateSession,
endSession,
};
};
module.exports = {
RECOVERY_COOKIE,
createRecoveryService,
};