feat: establish auth listing and image vertical slices

Includes MFA-capable authentication, session and CSRF security, password recovery, listing lifecycle and ownership rules, inventory image metadata, local ImageStorage, migrations, tests, and implementation status documentation. Verification: npm test (62 passing) and integration suite (14 passing).
This commit is contained in:
2026-09-08 17:32:03 -04:00
commit c728016dec
128 changed files with 9788 additions and 0 deletions
@@ -0,0 +1,79 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createPasswordRecoveryService } from '../../src/modules/auth/services/password-recovery-service.js';
test('does not issue a token for an unknown email while recording a generic request', async () => {
let resetIssued = false;
const auditEvents = [];
const service = createPasswordRecoveryService({
passwordResetRepository: { async issue() { resetIssued = true; } },
securityAuditRepository: { async record(event) { auditEvents.push(event); } },
userRepository: { async findAuthenticationUserByEmail() { return null; } },
});
await service.requestReset({ email: 'unknown@example.test' });
assert.equal(resetIssued, false);
assert.deepEqual(auditEvents, [
{ event: 'PASSWORD_RESET_REQUESTED', ipAddress: null, userAgent: null },
]);
});
test('delivers a reset token without persisting the raw token', async () => {
let issuedReset;
let deliveredReset;
const service = createPasswordRecoveryService({
now: () => new Date('2026-09-07T00:00:00.000Z'),
passwordResetNotifier: { async send(reset) { deliveredReset = reset; } },
passwordResetRepository: { async issue(reset) { issuedReset = reset; } },
userRepository: {
async findAuthenticationUserByEmail() {
return { id: 'user-id', email: 'admin@example.test' };
},
},
});
await service.requestReset({ email: 'admin@example.test' });
assert.equal(issuedReset.userId, 'user-id');
assert.notEqual(issuedReset.tokenHash, deliveredReset.token);
assert.equal(deliveredReset.email, 'admin@example.test');
assert.equal(deliveredReset.expiresAt.toISOString(), '2026-09-07T01:00:00.000Z');
});
test('consumes a reset token once and audits a successful password reset', async () => {
let consumedReset;
const auditEvents = [];
const service = createPasswordRecoveryService({
now: () => new Date('2026-09-07T00:00:00.000Z'),
passwordHasher: async (password) => `hashed:${password}`,
passwordResetRepository: {
async consume(reset) {
consumedReset = reset;
return 'user-id';
},
},
securityAuditRepository: { async record(event) { auditEvents.push(event); } },
});
await service.resetPassword({ token: 'a-valid-reset-token', password: 'new-password-value' });
assert.equal(consumedReset.passwordHash, 'hashed:new-password-value');
assert.notEqual(consumedReset.tokenHash, 'a-valid-reset-token');
assert.deepEqual(auditEvents, [
{ userId: 'user-id', event: 'PASSWORD_RESET', ipAddress: null, userAgent: null },
]);
});
test('rejects expired or previously used reset tokens', async () => {
const service = createPasswordRecoveryService({
passwordHasher: async () => 'new-password-hash',
passwordResetRepository: { async consume() { return null; } },
});
await assert.rejects(
() => service.resetPassword({ token: 'invalid-reset-token', password: 'new-password-value' }),
(error) => error.code === 'PASSWORD_RESET_TOKEN_INVALID',
);
});