76 lines
3.4 KiB
TypeScript
76 lines
3.4 KiB
TypeScript
import { constants, createHash, publicEncrypt } from 'node:crypto';
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import { createInMemoryUserRepository } from '../src/auth/inMemoryUserRepository.js';
|
|
import { createPasswordEnvelopeService } from '../src/auth/passwordEnvelope.js';
|
|
import { createPasswordHasher } from '../src/auth/passwordHasher.js';
|
|
|
|
describe('password credential compatibility', () => {
|
|
it('seals browser passwords with RSA-OAEP before opening them at the gateway', () => {
|
|
const service = createPasswordEnvelopeService();
|
|
const publicKey = service.getPublicKey();
|
|
const ciphertext = publicEncrypt(
|
|
{
|
|
key: publicKey.publicKeyPem,
|
|
padding: constants.RSA_PKCS1_OAEP_PADDING,
|
|
oaepHash: 'sha256',
|
|
},
|
|
Buffer.from('터널에-보이지-않는-비밀번호', 'utf8')
|
|
).toString('base64');
|
|
|
|
expect(ciphertext).not.toContain('비밀번호');
|
|
expect(service.open({ keyId: publicKey.keyId, ciphertext })).toBe('터널에-보이지-않는-비밀번호');
|
|
expect(() => service.open({ keyId: 'stale-key', ciphertext })).toThrow(/key has changed/i);
|
|
expect(() => service.open({ keyId: publicKey.keyId, ciphertext: '*not-base64*' })).toThrow(/base64/i);
|
|
});
|
|
|
|
it('upgrades the former core SHA-256 credential after a successful login', async () => {
|
|
const hasher = createPasswordHasher();
|
|
const users = createInMemoryUserRepository(hasher);
|
|
const user = await users.createUser({
|
|
username: 'core-legacy',
|
|
password: 'current-password',
|
|
});
|
|
user.passwordSalt = 'core-salt';
|
|
user.passwordHash = createHash('sha256').update('core-salt:current-password').digest('hex');
|
|
|
|
expect(await users.verifyPassword(user, 'current-password')).toBe(true);
|
|
expect(user.passwordHash.startsWith('$argon2id$')).toBe(true);
|
|
expect(user.passwordSalt).toBe('');
|
|
});
|
|
|
|
it('upgrades an imported ref double-SHA-512 credential after a successful login', async () => {
|
|
const globalSalt = 'ref-global-salt';
|
|
const hasher = createPasswordHasher({ legacyGlobalSalt: globalSalt });
|
|
const users = createInMemoryUserRepository(hasher);
|
|
const user = await users.createUser({
|
|
username: 'ref-legacy',
|
|
password: 'current-password',
|
|
});
|
|
const userSalt = 'ref-user-salt';
|
|
const browserHash = createHash('sha512')
|
|
.update(`${globalSalt}current-password${globalSalt}`)
|
|
.digest('hex');
|
|
user.passwordSalt = userSalt;
|
|
user.passwordHash = createHash('sha512').update(`${userSalt}${browserHash}${userSalt}`).digest('hex');
|
|
|
|
expect(await users.verifyPassword(user, 'current-password')).toBe(true);
|
|
expect(user.passwordHash.startsWith('$argon2id$')).toBe(true);
|
|
expect(user.passwordSalt).toBe('');
|
|
});
|
|
|
|
it('does not accept an imported ref credential without the matching global salt', async () => {
|
|
const users = createInMemoryUserRepository(createPasswordHasher());
|
|
const user = await users.createUser({
|
|
username: 'ref-no-salt',
|
|
password: 'current-password',
|
|
});
|
|
user.passwordSalt = 'ref-user-salt';
|
|
user.passwordHash = 'a'.repeat(128);
|
|
|
|
expect(await users.verifyPassword(user, 'current-password')).toBe(false);
|
|
expect(user.passwordHash).toBe('a'.repeat(128));
|
|
});
|
|
});
|