Ctrl K
ComunidadGuías…

test/account-password-transition.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
var test = require('node:test');
var assert = require('node:assert/strict');
var EventEmitter = require('node:events');
var fixture = require('./helpers/account-auth-fixture');
var passwords = require('../lib/account-passwords');
var guardSocket = require('../lib/account-auth-socket').guardSocket;

function request(server, method, url, data, cookie) {
  return new Promise(function (resolve) {
    var req = new EventEmitter();
    req.method = method; req.url = url; req.headers = { host: 'localhost', origin: 'http://localhost', 'content-type': 'application/json', cookie: cookie || '' }; req.socket = { remoteAddress: '127.0.0.1' };
    var result = {};
    var res = { writeHead: function (code, headers) { result.code = code; result.headers = headers || {}; }, end: function (body) { result.body = JSON.parse(body); resolve(result); } };
    assert.equal(server.handleRequest(req, res, url), true);
    req.emit('data', JSON.stringify(data || {})); req.emit('end');
  });
}
function cookie(result) { var value = result.headers['Set-Cookie']; return (Array.isArray(value) ? value : [value]).map(function (entry) { return entry.split(';')[0]; }).join('; '); }
function setup(t) {
  var h = fixture.isolated(t);
  fixture.seed(h, h.users.hashPin('012345'));
  var server = h.server();
  return { h: h, server: server, oldCookie: server.createMultiUserSession('owner').cookie };
}
var phrase = 'this is a long passphrase';

test('account sessions migrate on a fresh server with no daemon configuration', function (t) {
  var h = fixture.isolated(t);
  fixture.seed(h, h.users.hashPin('012345'));
  h.config.loadConfig = function () { return null; };
  var token = 'legacy-account-token';
  require('node:fs').writeFileSync(require('node:path').join(h.root, 'auth-tokens.json'), JSON.stringify({ 'legacy-account-token': 'owner' }));
  var server = h.server();
  assert.equal(server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: 'relay_auth_user=' + token } }), true);
});

test('policy requires fresh PIN; pending authority cannot enter workspace; conversion survives restart', async function (t) {
  var f = setup(t); var h = f.h; var server = f.server;
  assert.equal((await request(server, 'PUT', '/api/account-auth-policy', { method: 'password' })).code, 403);
  assert.equal((await request(server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie)).code, 200);
  assert.equal(server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: f.oldCookie } }), false);
  assert.equal((await request(server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, f.oldCookie)).code, 400);
  assert.equal((await request(server, 'POST', '/auth/login', { username: 'Owner', pin: '000000' })).code, 400);
  var proof = await request(server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  assert.equal(proof.body.passwordRequired, true);
  assert.equal(server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: cookie(proof) } }), false);
  assert.equal((await request(server, 'POST', '/auth/password-transition', { password: 'short', confirmPassword: 'short' }, cookie(proof))).code, 400);
  assert.ok(h.users.findUserById('owner').pinHash);
  var done = await request(server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof));
  assert.equal(done.code, 200);
  assert.equal(h.users.findUserById('owner').pinHash, null);
  assert.equal(h.users.getSoloNoPinUser(), null);
  assert.equal(h.server().isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: cookie(done) } }), true);
  assert.equal(h.server().isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: f.oldCookie } }), false);
  assert.equal((await request(server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof))).code, 400);
  assert.equal((await request(server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' })).code, 400);
  assert.equal((await request(h.server(), 'POST', '/auth/login', { username: 'Owner', password: phrase })).code, 200);
  assert.equal((await request(server, 'POST', '/auth/login', { username: 'Owner', password: 'not the right password' })).code, 400);
  // Sessions minted by alternate legacy routes lack password verification.
  assert.equal(server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: server.createMultiUserSession('owner').cookie } }), false);
});

test('failed password write preserves PIN and lets the same verified transition retry', async function (t) {
  var f = setup(t); var h = f.h;
  await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie);
  var proof = await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  h.state.failUsers = true;
  assert.equal((await request(f.server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof))).code, 400);
  assert.ok(h.users.findUserById('owner').pinHash); assert.equal(h.users.findUserById('owner').passwordHash, undefined);
  h.state.failUsers = false;
  assert.equal((await request(f.server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof))).code, 200);
});

test('policy save failure, solo installs, and missing account credentials do not change access', async function (t) {
  var f = setup(t); var h = f.h;
  h.state.failUsers = true;
  assert.equal((await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie)).code, 400);
  assert.equal(f.server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: f.oldCookie } }), true);
  h.state.failUsers = false;
  h.config.saveConfig({ singleUserMigratedUserId: 'owner', singleUserMigratedAt: 1, pinHash: 'legacy-server-credential' });
  assert.equal((await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie)).code, 400);
  h.config.saveConfig({}); fixture.seed(h, null);
  assert.equal((await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie)).code, 400);
  assert.equal(f.server.isRequestAuthed({ headers: {} }), true);
});

test('PIN recovery invalidates password sessions and requires a new password', async function (t) {
  var f = setup(t); var h = f.h;
  await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie);
  var proof = await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  var done = await request(f.server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof));
  h.users.updateUserPin('owner', '654321');
  assert.equal(f.server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: cookie(done) } }), false);
  assert.equal((await request(f.server, 'POST', '/auth/login', { username: 'Owner', password: phrase })).code, 400);
  assert.equal((await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '654321' })).body.passwordRequired, true);
});

test('concurrent and stale transitions cannot overwrite a replacement credential', async function (t) {
  var f = setup(t);
  await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie);
  var proof = await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  var results = await Promise.all([request(f.server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof)), request(f.server, 'POST', '/auth/password-transition', { password: 'another long passphrase', confirmPassword: 'another long passphrase' }, cookie(proof))]);
  assert.deepEqual(results.map(function (result) { return result.code; }).sort(), [200, 400]);
});

test('connected sockets reject incoming and outgoing workspace traffic after policy changes', function () {
  var ws = new EventEmitter(); var allowed = true; var sent = []; var handled = 0;
  ws.readyState = 1; ws.send = function (text) { sent.push(text); }; ws.close = function () { ws.readyState = 3; ws.emit('close'); };
  guardSocket(ws, {}, function () { return allowed; });
  ws.on('message', function () { handled++; }); ws.emit('message', 'before');
  allowed = false; ws.emit('message', 'after'); ws.send('secret output');
  assert.equal(handled, 1); assert.equal(ws.readyState, 3);
  assert.deepEqual(sent, [JSON.stringify({ type: 'account_auth_required' })]);
});

test('password hashing supports spaces and Unicode and preserves exact input', async function () {
  var value = '  긴 문장으로 사용하는 안전한 비밀번호  ';
  var hash = await passwords.hash(value);
  assert.equal(await passwords.verify(value, hash), true);
  assert.equal(await passwords.verify(value.trim(), hash), false);
  await assert.rejects(passwords.hash('short'));
  await assert.rejects(passwords.hash('x'.repeat(129)));
});

test('password changes verify current password and invalidate other sessions', async function (t) {
  var f = setup(t);
  await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie);
  var proof = await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  var done = await request(f.server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof));
  assert.equal((await request(f.server, 'POST', '/auth/password-change', { currentPassword: 'wrong', password: 'new long password value', confirmPassword: 'new long password value' }, cookie(done))).code, 400);
  var changed = await request(f.server, 'POST', '/auth/password-change', { currentPassword: phrase, password: 'new long password value', confirmPassword: 'new long password value' }, cookie(done));
  assert.equal(changed.code, 200);
  assert.equal(f.server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: cookie(done) } }), false);
  assert.equal(f.server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: cookie(changed) } }), true);
});

test('transition proof expires and rate limits reject repeated guesses', async function (t) {
  var f = setup(t);
  await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie);
  var proof = await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  t.mock.timers.enable({ apis: ['Date'], now: Date.now() });
  t.mock.timers.tick(600001);
  assert.equal((await request(f.server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof))).code, 400);
  for (var i = 0; i < 10; i++) await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '000000' });
  var limited = await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  assert.match(limited.body.error, /Too many attempts/);
});

test('logout cancels pending enrollment and credential resets invalidate pending proof', async function (t) {
  var f = setup(t);
  await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie);
  var proof = await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  await request(f.server, 'POST', '/auth/logout', {}, cookie(proof));
  assert.equal((await request(f.server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof))).code, 400);
  proof = await request(f.server, 'POST', '/auth/login', { username: 'Owner', pin: '012345' });
  f.h.users.updateUserPin('owner', '654321');
  assert.equal((await request(f.server, 'POST', '/auth/password-transition', { password: phrase, confirmPassword: phrase }, cookie(proof))).code, 400);
  assert.equal(f.h.users.authenticateUser('Owner', '654321').id, 'owner');
});

test('non-admin users cannot change policy and email-only accounts block activation', async function (t) {
  var f = setup(t);
  var other = f.h.users.createUserWithoutPin({ username: 'EmailOnly', email: 'fixture@example.invalid' }).user;
  var cookieValue = f.server.createMultiUserSession(other.id).cookie;
  assert.equal((await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, cookieValue)).code, 403);
  var result = await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie);
  assert.equal(result.code, 400); assert.match(result.body.error, /temporary account PIN/);
  assert.equal(f.server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: f.oldCookie } }), true);
  f.h.users.updateUserPin(other.id, '123456');
  assert.equal((await request(f.server, 'PUT', '/api/account-auth-policy', { method: 'password' }, f.oldCookie)).code, 200);
  assert.equal(f.server.isRequestAuthed({ socket: { remoteAddress: '127.0.0.1' }, headers: { host: 'localhost', cookie: cookieValue } }), false);
});