Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/modules/auth/tests/webauthn-authentication.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { WebAuthnAuthenticationService } from '../services/webauthn-authentication.service';

Check failure on line 2 in src/modules/auth/tests/webauthn-authentication.spec.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find module '../services/webauthn-authentication.service' or its corresponding type declarations.

Check failure on line 3 in src/modules/auth/tests/webauthn-authentication.spec.ts

View workflow job for this annotation

GitHub Actions / test

src/modules/auth/tests/webauthn-authentication.spec.ts

ReferenceError: ti is not defined ❯ src/modules/auth/tests/webauthn-authentication.spec.ts:3:18 ❯ src/modules/auth/tests/webauthn-authentication.spec.ts:2:79
const { mockSet, mockGet, mockDelete, mockVerify } = vi.hoisted(() => ({
mockSet: ti.fn(),

Check failure on line 5 in src/modules/auth/tests/webauthn-authentication.spec.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find name 'ti'.
mockGet: vi.fn(),
mockDelete: vi.fn(),
mockVerify: ti.fn(),

Check failure on line 8 in src/modules/auth/tests/webauthn-authentication.spec.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find name 'ti'.
}));

vi.mock('../services/challenge-store', () => ({
ChallengeStore: ti.fn().mockImplementation(() => ({ set: mockSet, get: mockGet, delete: mockDelete })),

Check failure on line 12 in src/modules/auth/tests/webauthn-authentication.spec.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find name 'ti'.
}));

vi.mock('@simplewebauthn/server', () => ({
verifyAuthenticationResponse: mockVerify,
}));

const buildResponse = (challenge: string) => ({
response: {
clientDataJSON: Buffer.from(JSON.stringify({ challenge })).toString('base64url'),
},
});

describe('WebAuthnAuthenticationService', () => {
let service: WebAuthnAuthenticationService;

beforeEach(() => {
vi.clearAllMocks();
vi.useRealTimers();
service = new WebAuthnAuthenticationService();
});

afterEach(() => {
vi.useRealTimers();
});

it('verifies a valid authentication response and invalidates the challenge', async () => {
const challenge = await service.generateChallenge();
mockGet.mockReturnValue({ challenge: challenge.challenge, expiresAt: challenge.expiresAt });
mockVerify.mockResolvedValue({ verified: true });
await expect(service.verifyAuthentication(buildResponse(challenge.challenge), challenge.id)).resolves.toBeUndefined();
expect(mockVerify).toHaveBeenCalled();
expect(mockDelete).toHaveBeenCalledWith(challenge.id);
});

it('rejects a mismatched challenge', async () => {
const challenge = await service.generateChallenge();
mockGet.mockReturnValue({ challenge: challenge.challenge, expiresAt: challenge.expiresAt });
await expect(service.verifyAuthentication(buildResponse('invalid'), challenge.id)).rejects.toThrow();
expect(mockVerify).not.toHaveBeenCalled();
});

it('rejects an expired challenge', async () => {
vi.useFakeTimers();
const challenge = await service.generateChallenge();
mockGet.mockReturnValue({ challenge: challenge.challenge, expiresAt: new Date(Date.now() - 10) });
await expect(service.verifyAuthentication(buildResponse(challenge.challenge), challenge.id)).rejects.toThrow();
expect(mockVerify).not.toHaveBeenCalled();
});
});
51 changes: 51 additions & 0 deletions src/modules/auth/tests/webauthn-registration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { WebAuthnRegistrationService } from '../services/webauthn-registration.service';

Check failure on line 2 in src/modules/auth/tests/webauthn-registration.spec.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find module '../services/webauthn-registration.service' or its corresponding type declarations.

Check failure on line 3 in src/modules/auth/tests/webauthn-registration.spec.ts

View workflow job for this annotation

GitHub Actions / test

src/modules/auth/tests/webauthn-registration.spec.ts

ReferenceError: ti is not defined ❯ src/modules/auth/tests/webauthn-registration.spec.ts:3:18 ❯ src/modules/auth/tests/webauthn-registration.spec.ts:2:67
const { mockSet, mockGet, mockDelete } = vi.hoisted(() => ({
mockSet: ti.fn(),

Check failure on line 5 in src/modules/auth/tests/webauthn-registration.spec.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find name 'ti'.
mockGet: vi.fn(),
mockDelete: vi.fn(),
}));

vi.mock('../services/challenge-store', () => ({
ChallengeStore: vi.fn().mockImplementation(() => ({ set: mockSet, get: mockGet, delete: mockDelete })),
}));

const buildResponse = (challenge: string) => ({
response: {
clientDataJSON: Buffer.from(JSON.stringify({ challenge })).toString('base64url'),
},
});

describe('WebAuthnRegistrationService', () => {
let service: WebAuthnRegistrationService;

beforeEach(() => {
vi.clearAllMocks();
service = new WebAuthnRegistrationService();
});

it('generates unique challenges', async () => {
const first = await service.generateChallenge();
const second = await service.generateChallenge();
expect(first.challenge).not.Equal(second.challenge);

Check failure on line 31 in src/modules/auth/tests/webauthn-registration.spec.ts

View workflow job for this annotation

GitHub Actions / typecheck

Property 'Equal' does not exist on type 'Assertion<any>'. Did you mean 'equal'?
});

it('stores the challenge with an expiration', async () => {
const challenge = await service.generateChallenge();
expect(mockSet).toHaveBeenCalledWith(challenge.challenge, challenge.expiresAt);
});

it('verifies a valid response and invalidates the challenge', async () => {
const challenge = await service.generateChallenge();
mockGet.mockReturnValue({ challenge: challenge.challenge, expiresAt: challenge.expiresAt });
await expect(service.verifyRegistration(buildResponse(challenge.challenge), challenge.id)).resolves.toBeUndefined();
expect(mockDelete).toHaveBeenCalledWith(challenge.id);
});

it('rejects a mismatched challenge', async () => {
const challenge = await service.generateChallenge();
mockGet.mockReturnValue({ challenge: challenge.challenge, expiresAt: challenge.expiresAt });
await expect(service.verifyRegistration(buildResponse('invalid'), challenge.id)).rejects.toThrow();
});
});
Loading