All checks were successful
continuous-integration/drone/push Build is passing
61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { decodeJwtPayload, isJwtExpired } from '../../src/auth/jwt';
|
|
|
|
function createJwt(payload: Record<string, unknown>) {
|
|
const header = { alg: 'HS256', typ: 'JWT' };
|
|
const encode = (value: object) =>
|
|
btoa(JSON.stringify(value)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
|
|
return `${encode(header)}.${encode(payload)}.signature`;
|
|
}
|
|
|
|
describe('decodeJwtPayload', () => {
|
|
it('returns null for malformed tokens or invalid payloads', () => {
|
|
expect(decodeJwtPayload('not-a-jwt')).toBeNull();
|
|
expect(decodeJwtPayload('a.b')).toBeNull();
|
|
expect(decodeJwtPayload('a.b.c.d')).toBeNull();
|
|
|
|
const nonJson = 'header.' + btoa('nope') + '.sig';
|
|
expect(decodeJwtPayload(nonJson)).toBeNull();
|
|
|
|
const nonObjectPayload = createJwt({ value: 1 }).replace(
|
|
/\.[^.]+\./,
|
|
`.${btoa('1').replace(/=+$/g, '')}.`,
|
|
);
|
|
expect(decodeJwtPayload(nonObjectPayload)).toBeNull();
|
|
});
|
|
|
|
it('decodes valid base64url payloads', () => {
|
|
const payload = { sub: 'user-1', role: 'ADMIN', exp: 1735689600 };
|
|
expect(decodeJwtPayload(createJwt(payload))).toEqual(payload);
|
|
});
|
|
});
|
|
|
|
describe('isJwtExpired', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
it('returns false for malformed tokens and invalid exp claim', () => {
|
|
expect(isJwtExpired('not-a-jwt')).toBe(false);
|
|
expect(isJwtExpired(createJwt({}))).toBe(false);
|
|
expect(isJwtExpired(createJwt({ exp: 'nope' }))).toBe(false);
|
|
expect(isJwtExpired(createJwt({ exp: Number.POSITIVE_INFINITY }))).toBe(false);
|
|
});
|
|
|
|
it('returns true when token is expired', () => {
|
|
vi.setSystemTime(new Date('2026-01-01T00:00:00Z'));
|
|
const exp = Math.floor(new Date('2025-12-31T23:59:00Z').getTime() / 1000);
|
|
|
|
expect(isJwtExpired(createJwt({ exp }))).toBe(true);
|
|
});
|
|
|
|
it('applies skew seconds when evaluating expiration', () => {
|
|
vi.setSystemTime(new Date('2026-01-01T00:00:00Z'));
|
|
const exp = Math.floor(new Date('2026-01-01T00:00:10Z').getTime() / 1000);
|
|
|
|
expect(isJwtExpired(createJwt({ exp }), 15)).toBe(true);
|
|
expect(isJwtExpired(createJwt({ exp }), 5)).toBe(false);
|
|
});
|
|
});
|