UNPKG

@prism-engineer/router

Version:

Type-safe Express.js router with automatic client generation

549 lines 23.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const createAuthScheme_1 = require("../../createAuthScheme"); const createApiRoute_1 = require("../../createApiRoute"); const typebox_1 = require("@sinclair/typebox"); (0, vitest_1.describe)('Authentication - Auth Failures', () => { (0, vitest_1.beforeEach)(() => { vitest_1.vi.clearAllMocks(); }); (0, vitest_1.it)('should handle missing authorization header', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'bearer-required', validate: async (req) => { const authHeader = req.headers.authorization; if (!authHeader) { throw new Error('Authorization header required'); } return { user: { id: '1' } }; } }); const mockReq = { headers: {}, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(mockReq)).rejects.toThrow('Authorization header required'); }); (0, vitest_1.it)('should handle invalid authorization format', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'bearer-format', validate: async (req) => { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { throw new Error('Invalid authorization format'); } return { user: { id: '1' } }; } }); const mockReq = { headers: { authorization: 'Basic invalid' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(mockReq)).rejects.toThrow('Invalid authorization format'); }); (0, vitest_1.it)('should handle expired tokens', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'token-expiry', validate: async (req) => { const token = req.headers.authorization?.replace('Bearer ', ''); // Simulate token expiry check if (token === 'expired-token') { throw new Error('Token has expired'); } if (!token) { throw new Error('Token required'); } return { user: { id: '1', token } }; } }); const mockReq = { headers: { authorization: 'Bearer expired-token' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(mockReq)).rejects.toThrow('Token has expired'); }); (0, vitest_1.it)('should handle invalid API keys', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'api-key-validation', validate: async (req) => { const apiKey = req.headers['x-api-key']; if (!apiKey) { throw new Error('API key required'); } if (apiKey !== 'valid-api-key') { throw new Error('Invalid API key'); } return { client: { id: '1', apiKey } }; } }); const mockReq = { headers: { 'x-api-key': 'invalid-key' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(mockReq)).rejects.toThrow('Invalid API key'); }); (0, vitest_1.it)('should handle missing required headers', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'required-headers', validate: async (req) => { const signature = req.headers['x-signature']; const timestamp = req.headers['x-timestamp']; if (!signature) { throw new Error('X-Signature header required'); } if (!timestamp) { throw new Error('X-Timestamp header required'); } return { client: { id: '1', signature, timestamp } }; } }); const mockReq = { headers: { 'x-signature': 'sig123' }, // Missing timestamp body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(mockReq)).rejects.toThrow('X-Timestamp header required'); }); (0, vitest_1.it)('should handle invalid session cookies', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'session-validation', validate: async (req) => { const cookie = req.headers.cookie; if (!cookie) { throw new Error('Session cookie required'); } const sessionId = cookie.match(/sessionId=([^;]+)/)?.[1]; if (!sessionId) { throw new Error('Session ID not found in cookie'); } if (sessionId === 'invalid-session') { throw new Error('Invalid session'); } return { session: { id: sessionId, userId: '1' } }; } }); const mockReq = { headers: { cookie: 'sessionId=invalid-session' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(mockReq)).rejects.toThrow('Invalid session'); }); (0, vitest_1.it)('should handle network timeout during validation', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'network-timeout', validate: async (req) => { const token = req.headers.authorization; // Simulate network timeout if (token === 'Bearer timeout-token') { await new Promise((_, reject) => { setTimeout(() => reject(new Error('Network timeout')), 100); }); } return { user: { id: '1' } }; } }); const mockReq = { headers: { authorization: 'Bearer timeout-token' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(mockReq)).rejects.toThrow('Network timeout'); }); (0, vitest_1.it)('should handle external service failures', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'external-service-failure', validate: async (req) => { const token = req.headers.authorization?.replace('Bearer ', ''); // Simulate external service call const validateWithExternalService = async (token) => { if (token === 'service-down') { throw new Error('External service unavailable'); } if (token === 'rate-limited') { throw new Error('Rate limit exceeded by external service'); } throw new Error('Unknown external service error'); }; try { await validateWithExternalService(token); return { user: { id: '1' } }; } catch (error) { throw new Error(`External validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); const serviceDownReq = { headers: { authorization: 'Bearer service-down' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(serviceDownReq)).rejects.toThrow('External service unavailable'); const rateLimitedReq = { headers: { authorization: 'Bearer rate-limited' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(rateLimitedReq)).rejects.toThrow('Rate limit exceeded by external service'); }); (0, vitest_1.it)('should handle insufficient permissions', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'permission-check', validate: async (req) => { const role = req.headers['x-user-role']; const requiredRole = req.headers['x-required-role']; if (!role) { throw new Error('User role required'); } const roleHierarchy = ['viewer', 'editor', 'admin']; const userLevel = roleHierarchy.indexOf(role); const requiredLevel = roleHierarchy.indexOf(requiredRole); if (requiredLevel !== -1 && userLevel < requiredLevel) { throw new Error(`Insufficient permissions: ${requiredRole} required, got ${role}`); } return { user: { id: '1', role } }; } }); const mockReq = { headers: { 'x-user-role': 'viewer', 'x-required-role': 'admin' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(mockReq)).rejects.toThrow('Insufficient permissions: admin required, got viewer'); }); (0, vitest_1.it)('should handle malformed tokens', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'token-format-validation', validate: async (req) => { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { throw new Error('Token required'); } // Check token format (should be base64-like) if (!/^[A-Za-z0-9+/=]+$/.test(token)) { throw new Error('Malformed token format'); } // Check minimum length if (token.length < 20) { throw new Error('Token too short'); } // Check if token is properly base64 encoded try { atob(token); } catch { throw new Error('Invalid token encoding'); } return { user: { id: '1', token } }; } }); const malformedReq = { headers: { authorization: 'Bearer invalid@token!' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(malformedReq)).rejects.toThrow('Malformed token format'); const shortTokenReq = { headers: { authorization: 'Bearer abc' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(shortTokenReq)).rejects.toThrow('Token too short'); }); (0, vitest_1.it)('should handle database connection failures', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'database-failure', validate: async (req) => { const userId = req.headers['x-user-id']; // Simulate database connection error if (userId === 'db-error') { throw new Error('Database connection failed'); } // Simulate database timeout if (userId === 'db-timeout') { throw new Error('Database query timeout'); } return { user: { id: userId || '1' } }; } }); const dbErrorReq = { headers: { 'x-user-id': 'db-error' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(dbErrorReq)).rejects.toThrow('Database connection failed'); const dbTimeoutReq = { headers: { 'x-user-id': 'db-timeout' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(dbTimeoutReq)).rejects.toThrow('Database query timeout'); }); (0, vitest_1.it)('should handle rate limiting failures', async () => { const rateLimiter = new Map(); const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'rate-limiting', validate: async (req) => { const clientId = req.headers['x-client-id'] || req.ip || 'anonymous'; const now = Date.now(); const windowMs = 60000; // 1 minute const maxAttempts = 5; if (!rateLimiter.has(clientId)) { rateLimiter.set(clientId, { attempts: 0, resetTime: now + windowMs }); } const limit = rateLimiter.get(clientId); if (now > limit.resetTime) { limit.attempts = 0; limit.resetTime = now + windowMs; } limit.attempts++; if (limit.attempts > maxAttempts) { throw new Error(`Rate limit exceeded: ${limit.attempts}/${maxAttempts} attempts in window`); } // Simulate auth failure to trigger rate limiting const token = req.headers.authorization; if (!token || token !== 'Bearer valid-token') { throw new Error('Invalid credentials'); } return { user: { id: '1' } }; } }); const invalidReq = { headers: { 'x-client-id': 'rate-test-client', authorization: 'Bearer invalid' }, body: {}, query: {}, params: {} }; // Make multiple failed attempts for (let i = 0; i < 5; i++) { await (0, vitest_1.expect)(authScheme.validate(invalidReq)).rejects.toThrow('Invalid credentials'); } // 6th attempt should be rate limited await (0, vitest_1.expect)(authScheme.validate(invalidReq)).rejects.toThrow('Rate limit exceeded'); }); (0, vitest_1.it)('should handle account lockout scenarios', async () => { const accountStatus = new Map(); const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'account-lockout', validate: async (req) => { const userId = req.headers['x-user-id']; if (!userId) { throw new Error('User ID required'); } const status = accountStatus.get(userId) || { locked: false, failedAttempts: 0 }; if (status.locked) { const lockExpiry = status.lockedUntil || 0; if (Date.now() < lockExpiry) { throw new Error(`Account locked until ${new Date(lockExpiry).toISOString()}`); } else { // Unlock account status.locked = false; status.failedAttempts = 0; accountStatus.set(userId, status); } } const password = req.headers['x-password']; if (password !== 'correct-password') { status.failedAttempts++; if (status.failedAttempts >= 3) { status.locked = true; status.lockedUntil = Date.now() + 300000; // 5 minutes } accountStatus.set(userId, status); throw new Error('Invalid credentials'); } // Reset failed attempts on successful login status.failedAttempts = 0; accountStatus.set(userId, status); return { user: { id: userId } }; } }); const invalidReq = { headers: { 'x-user-id': 'lockout-test-user', 'x-password': 'wrong-password' }, body: {}, query: {}, params: {} }; // Make 3 failed attempts for (let i = 0; i < 3; i++) { await (0, vitest_1.expect)(authScheme.validate(invalidReq)).rejects.toThrow('Invalid credentials'); } // 4th attempt should show account locked await (0, vitest_1.expect)(authScheme.validate(invalidReq)).rejects.toThrow('Account locked until'); }); (0, vitest_1.it)('should handle token revocation scenarios', async () => { const revokedTokens = new Set(['revoked-token-1', 'revoked-token-2']); const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'token-revocation', validate: async (req) => { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { throw new Error('Token required'); } if (revokedTokens.has(token)) { throw new Error('Token has been revoked'); } return { user: { id: '1', token } }; } }); const revokedReq = { headers: { authorization: 'Bearer revoked-token-1' }, body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(revokedReq)).rejects.toThrow('Token has been revoked'); }); (0, vitest_1.it)('should handle concurrent validation failures', async () => { let concurrentValidations = 0; const maxConcurrent = 3; const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'concurrent-limit', validate: async (req) => { concurrentValidations++; if (concurrentValidations > maxConcurrent) { concurrentValidations--; throw new Error('Too many concurrent authentication attempts'); } try { // Simulate validation work await new Promise(resolve => setTimeout(resolve, 10)); const token = req.headers.authorization; if (!token) { throw new Error('Token required'); } return { user: { id: '1' } }; } finally { concurrentValidations--; } } }); const mockReq = { headers: { authorization: 'Bearer valid-token' }, body: {}, query: {}, params: {} }; // Start multiple concurrent validations const promises = Array(5).fill(null).map(() => authScheme.validate(mockReq)); const results = await Promise.allSettled(promises); const rejected = results.filter(r => r.status === 'rejected'); // Some should be rejected due to concurrent limit (0, vitest_1.expect)(rejected.length).toBeGreaterThan(0); }); (0, vitest_1.it)('should handle validation timeout scenarios', async () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'validation-timeout', validate: async (req) => { const timeout = parseInt(req.headers['x-timeout']) || 0; if (timeout > 0) { await new Promise((resolve, reject) => { setTimeout(() => reject(new Error('Validation timeout')), timeout); }); } return { user: { id: '1' } }; } }); const timeoutReq = { headers: { 'x-timeout': '50' }, // 50ms timeout body: {}, query: {}, params: {} }; await (0, vitest_1.expect)(authScheme.validate(timeoutReq)).rejects.toThrow('Validation timeout'); }); (0, vitest_1.it)('should handle multiple auth schemes with all failures', async () => { const bearerAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'bearer', validate: async (req) => { const auth = req.headers.authorization; if (!auth?.startsWith('Bearer ')) { throw new Error('Bearer token required'); } throw new Error('Invalid bearer token'); } }); const apiKeyAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'apiKey', validate: async (req) => { const apiKey = req.headers['x-api-key']; if (!apiKey) { throw new Error('API key required'); } throw new Error('Invalid API key'); } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/all-auth-fail', method: 'GET', auth: [bearerAuth, apiKeyAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }) } }, handler: async () => { return { status: 200, body: { message: 'Should not reach here' } }; } }); // Route creation should succeed even if auth will fail (0, vitest_1.expect)(route.auth).toHaveLength(2); }); (0, vitest_1.it)('should handle auth scheme creation with invalid parameters', () => { // Test invalid auth scheme creation scenarios (0, vitest_1.expect)(() => { (0, createAuthScheme_1.createAuthScheme)({ name: '', // Empty name validate: async () => ({ user: { id: '1' } }) }); }).toThrow(); (0, vitest_1.expect)(() => { (0, createAuthScheme_1.createAuthScheme)({ name: 'test', validate: null // Null validate function }); }).toThrow(); (0, vitest_1.expect)(() => { (0, createAuthScheme_1.createAuthScheme)({ name: 'test', validate: 'not-a-function' // Invalid validate function }); }).toThrow(); }); }); //# sourceMappingURL=auth-failures.test.js.map