62 lines
2.7 KiB
TypeScript
62 lines
2.7 KiB
TypeScript
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||
|
|
|
||
|
|
import { LoginSession } from '../ts/reception/classes.loginsession.js';
|
||
|
|
import { User } from '../ts/reception/classes.user.js';
|
||
|
|
import * as plugins from '../ts/plugins.js';
|
||
|
|
|
||
|
|
const createTestLoginSession = () => {
|
||
|
|
const loginSession = new LoginSession();
|
||
|
|
loginSession.id = 'test-session';
|
||
|
|
loginSession.data.userId = 'test-user';
|
||
|
|
(loginSession as LoginSession & { save: () => Promise<void> }).save = async () => undefined;
|
||
|
|
return loginSession;
|
||
|
|
};
|
||
|
|
|
||
|
|
tap.test('hashes passwords with argon2 and verifies them', async () => {
|
||
|
|
const passwordHash = await User.hashPassword('correct horse battery staple');
|
||
|
|
|
||
|
|
expect(passwordHash.startsWith('$argon2')).toBeTrue();
|
||
|
|
expect(await User.verifyPassword('correct horse battery staple', passwordHash)).toBeTrue();
|
||
|
|
expect(await User.verifyPassword('wrong password', passwordHash)).toBeFalse();
|
||
|
|
expect(User.shouldUpgradePasswordHash(passwordHash)).toBeFalse();
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('accepts legacy sha256 hashes and marks them for upgrade', async () => {
|
||
|
|
const legacyHash = await plugins.smarthash.sha256FromString('legacy-password');
|
||
|
|
|
||
|
|
expect(User.isLegacyPasswordHash(legacyHash)).toBeTrue();
|
||
|
|
expect(await User.verifyPassword('legacy-password', legacyHash)).toBeTrue();
|
||
|
|
expect(await User.verifyPassword('different-password', legacyHash)).toBeFalse();
|
||
|
|
expect(User.shouldUpgradePasswordHash(legacyHash)).toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('rotates refresh tokens and detects reuse', async () => {
|
||
|
|
const loginSession = createTestLoginSession();
|
||
|
|
|
||
|
|
const firstRefreshToken = await loginSession.getRefreshToken();
|
||
|
|
const secondRefreshToken = await loginSession.getRefreshToken();
|
||
|
|
|
||
|
|
expect(firstRefreshToken.startsWith('refresh_')).toBeTrue();
|
||
|
|
expect(secondRefreshToken.startsWith('refresh_')).toBeTrue();
|
||
|
|
expect(firstRefreshToken).not.toEqual(secondRefreshToken);
|
||
|
|
expect(loginSession.data.refreshToken).toBeNullOrUndefined();
|
||
|
|
expect(loginSession.data.refreshTokenHash).toBeTruthy();
|
||
|
|
expect(await loginSession.validateRefreshToken(secondRefreshToken)).toEqual('current');
|
||
|
|
expect(await loginSession.validateRefreshToken(firstRefreshToken)).toEqual('reused');
|
||
|
|
|
||
|
|
await loginSession.invalidate();
|
||
|
|
expect(await loginSession.validateRefreshToken(secondRefreshToken)).toEqual('invalidated');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('persists transfer tokens as one-time hashes', async () => {
|
||
|
|
const loginSession = createTestLoginSession();
|
||
|
|
const transferToken = await loginSession.getTransferToken();
|
||
|
|
|
||
|
|
expect(transferToken.startsWith('transfer_')).toBeTrue();
|
||
|
|
expect(loginSession.data.transferTokenHash).toBeTruthy();
|
||
|
|
expect(await loginSession.validateTransferToken(transferToken)).toBeTrue();
|
||
|
|
expect(await loginSession.validateTransferToken(transferToken)).toBeFalse();
|
||
|
|
});
|
||
|
|
|
||
|
|
export default tap.start();
|