aboutsummaryrefslogtreecommitdiff
path: root/test/token.test.ts
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-12-14 22:43:24 -0800
committerElizabeth Hunt <me@liz.coffee>2025-12-14 22:43:24 -0800
commitcdb1a57614068fcfefa145bc6df45c9def7ccc6a (patch)
tree92cadbecda8658c143b7625d5925e3411976a892 /test/token.test.ts
parent6d318665a08c0d4564d8de23cc39425d2c0bac49 (diff)
downloadposthook-cdb1a57614068fcfefa145bc6df45c9def7ccc6a.tar.gz
posthook-cdb1a57614068fcfefa145bc6df45c9def7ccc6a.zip
Updates
Diffstat (limited to 'test/token.test.ts')
-rw-r--r--test/token.test.ts72
1 files changed, 72 insertions, 0 deletions
diff --git a/test/token.test.ts b/test/token.test.ts
new file mode 100644
index 0000000..060b591
--- /dev/null
+++ b/test/token.test.ts
@@ -0,0 +1,72 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { TokenSigner } from '../src/token/index.js';
+
+describe('TokenSigner', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2020-01-01T00:00:00.000Z'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('generates and validates a token for the expected route', () => {
+ const signer = new TokenSigner('test-secret', 30);
+
+ const now = Date.now();
+ const token = signer.generate('route1');
+
+ const result = signer.validate(token, 'route1');
+ expect(result.left().present()).toBe(false);
+ expect(result.right().get()).toEqual({ routeName: 'route1', timestamp: now });
+ });
+
+ it('rejects token when route does not match', () => {
+ const signer = new TokenSigner('test-secret', 30);
+ const token = signer.generate('route1');
+
+ const result = signer.validate(token, 'route2');
+ expect(result.left().present()).toBe(true);
+ expect(result.left().get().message).toBe('Token route mismatch');
+ });
+
+ it('rejects expired tokens', () => {
+ const signer = new TokenSigner('test-secret', 1);
+ const token = signer.generate('route1');
+
+ vi.advanceTimersByTime(2000);
+
+ const result = signer.validate(token, 'route1');
+ expect(result.left().present()).toBe(true);
+ expect(result.left().get().message).toBe('Token expired');
+ });
+
+ it('rejects tokens with a bad signature', () => {
+ const signer = new TokenSigner('test-secret', 30);
+ const token = signer.generate('route1');
+
+ const decoded = Buffer.from(token, 'base64url').toString('utf-8');
+ const dotIndex = decoded.lastIndexOf('.');
+ const payload = decoded.substring(0, dotIndex);
+ const signature = decoded.substring(dotIndex + 1);
+
+ const parsed = JSON.parse(payload) as { routeName: string; timestamp: number };
+ const tamperedPayload = JSON.stringify({ ...parsed, routeName: 'route2' });
+ const tamperedToken = Buffer.from(`${tamperedPayload}.${signature}`).toString('base64url');
+
+ const result = signer.validate(tamperedToken, 'route2');
+ expect(result.left().present()).toBe(true);
+ expect(result.left().get().message).toBe('Invalid token signature');
+ });
+
+ it('rejects invalid token format', () => {
+ const signer = new TokenSigner('test-secret', 30);
+ const invalidToken = Buffer.from('missing-dot').toString('base64url');
+
+ const result = signer.validate(invalidToken, 'route1');
+ expect(result.left().present()).toBe(true);
+ expect(result.left().get().message).toBe('Invalid token format');
+ });
+});