UNPKG

@prism-engineer/router

Version:

Type-safe Express.js router with automatic client generation

547 lines 22 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 - Multiple Auth Schemes', () => { let bearerAuth; let apiKeyAuth; let sessionAuth; (0, vitest_1.beforeEach)(() => { vitest_1.vi.clearAllMocks(); bearerAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'bearer', validate: async (req) => { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { throw new Error('Invalid bearer token'); } return { user: { id: '1', type: 'bearer', token: authHeader.slice(7) } }; } }); apiKeyAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'apiKey', validate: async (req) => { const apiKey = req.headers['x-api-key']; if (!apiKey || apiKey !== 'valid-api-key') { throw new Error('Invalid API key'); } return { client: { id: '1', type: 'api-key', key: apiKey } }; } }); sessionAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'session', validate: async (req) => { const sessionId = req.headers.cookie?.match(/sessionId=([^;]+)/)?.[1]; if (!sessionId || sessionId !== 'valid-session') { throw new Error('Invalid session'); } return { session: { id: sessionId, userId: '1', type: 'session' } }; } }); }); (0, vitest_1.it)('should create route with multiple auth schemes (OR logic)', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/flexible-auth', method: 'GET', auth: [bearerAuth, apiKeyAuth, sessionAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String(), authType: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { message: 'Authenticated successfully', authType: req.auth.name } }; } }); (0, vitest_1.expect)(route).toBeDefined(); (0, vitest_1.expect)(Array.isArray(route.auth)).toBe(true); (0, vitest_1.expect)(route.auth).toHaveLength(3); }); (0, vitest_1.it)('should handle bearer auth success in multiple auth context', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/multi-auth', method: 'GET', auth: [bearerAuth, apiKeyAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ authType: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { authType: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toContain(bearerAuth); (0, vitest_1.expect)(route.auth).toContain(apiKeyAuth); }); (0, vitest_1.it)('should handle API key auth success in multiple auth context', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/multi-auth', method: 'GET', auth: [bearerAuth, apiKeyAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ authType: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { authType: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toContain(apiKeyAuth); (0, vitest_1.expect)(route.auth).toContain(bearerAuth); }); (0, vitest_1.it)('should test different auth scheme validation scenarios', async () => { // Test bearer auth validation const bearerReq = { headers: { authorization: 'Bearer valid-token' }, body: {}, query: {}, params: {} }; const bearerResult = await bearerAuth.validate(bearerReq); (0, vitest_1.expect)(bearerResult.user.type).toBe('bearer'); // Test API key validation const apiKeyReq = { headers: { 'x-api-key': 'valid-api-key' }, body: {}, query: {}, params: {} }; const apiKeyResult = await apiKeyAuth.validate(apiKeyReq); (0, vitest_1.expect)(apiKeyResult.client.type).toBe('api-key'); // Test session validation const sessionReq = { headers: { cookie: 'sessionId=valid-session' }, body: {}, query: {}, params: {} }; const sessionResult = await sessionAuth.validate(sessionReq); (0, vitest_1.expect)(sessionResult.session.type).toBe('session'); }); (0, vitest_1.it)('should handle priority-based auth schemes', () => { // Higher priority schemes should be tested first const highPriorityAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'high-priority', validate: async () => ({ user: { id: '1', priority: 'high' } }) }); const lowPriorityAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'low-priority', validate: async () => ({ user: { id: '1', priority: 'low' } }) }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/priority-auth', method: 'GET', auth: [highPriorityAuth, lowPriorityAuth], // Order matters response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ priority: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { priority: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth?.[0]).toBe(highPriorityAuth); (0, vitest_1.expect)(route.auth?.[1]).toBe(lowPriorityAuth); }); (0, vitest_1.it)('should handle fallback auth schemes', () => { const primaryAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'primary', validate: async (req) => { const token = req.headers.authorization; if (!token?.startsWith('Primary ')) { throw new Error('Primary auth failed'); } return { user: { id: '1', source: 'primary' } }; } }); const fallbackAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'fallback', validate: async (req) => { const token = req.headers.authorization; if (!token?.startsWith('Fallback ')) { throw new Error('Fallback auth failed'); } return { user: { id: '1', source: 'fallback' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/fallback-auth', method: 'GET', auth: [primaryAuth, fallbackAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ source: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { source: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toHaveLength(2); (0, vitest_1.expect)(route.auth?.[0]?.name).toBe('primary'); (0, vitest_1.expect)(route.auth?.[1]?.name).toBe('fallback'); }); (0, vitest_1.it)('should handle role-based auth schemes', () => { const adminAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'admin', validate: async (req) => { const role = req.headers['x-user-role']; if (role !== 'admin') { throw new Error('Admin role required'); } return { user: { id: '1', role: 'admin', permissions: ['all'] } }; } }); const userAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'user', validate: async (req) => { const role = req.headers['x-user-role']; if (!['user', 'admin'].includes(role)) { throw new Error('User role required'); } return { user: { id: '1', role: role || 'user', permissions: ['read'] } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/role-based', method: 'GET', auth: [adminAuth, userAuth], // Admin first, then user response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ role: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { role: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toHaveLength(2); (0, vitest_1.expect)(route.auth?.[0]?.name).toBe('admin'); (0, vitest_1.expect)(route.auth?.[1]?.name).toBe('user'); }); (0, vitest_1.it)('should handle environment-specific auth schemes', () => { const prodAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'production', validate: async (req) => { if (process.env.NODE_ENV !== 'production') { throw new Error('Production auth only'); } const token = req.headers.authorization; if (!token?.startsWith('Bearer ')) { throw new Error('Bearer token required in production'); } return { user: { id: '1', env: 'production' } }; } }); const devAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'development', validate: async () => { if (process.env.NODE_ENV === 'production') { throw new Error('Development auth not allowed in production'); } return { user: { id: 'dev-user', env: 'development' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/env-auth', method: 'GET', auth: [prodAuth, devAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ env: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { env: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toHaveLength(2); }); (0, vitest_1.it)('should handle service-specific auth schemes', () => { const internalServiceAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'internal-service', validate: async (req) => { const serviceKey = req.headers['x-service-key']; if (!serviceKey || !serviceKey.startsWith('internal-')) { throw new Error('Internal service key required'); } return { service: { id: serviceKey, type: 'internal' } }; } }); const externalServiceAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'external-service', validate: async (req) => { const serviceKey = req.headers['x-service-key']; if (!serviceKey || !serviceKey.startsWith('external-')) { throw new Error('External service key required'); } return { service: { id: serviceKey, type: 'external' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/service-auth', method: 'GET', auth: [internalServiceAuth, externalServiceAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ serviceType: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { serviceType: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toHaveLength(2); }); (0, vitest_1.it)('should handle auth schemes with different context types', () => { const userAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'user-auth', validate: async () => ({ user: { id: '1', name: 'John' } }) }); const clientAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'client-auth', validate: async () => ({ client: { id: 'client-1', name: 'Mobile App' } }) }); const serviceAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'service-auth', validate: async () => ({ service: { id: 'service-1', name: 'Background Service' } }) }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/mixed-context', method: 'GET', auth: [userAuth, clientAuth, serviceAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ contextType: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { contextType: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toHaveLength(3); }); (0, vitest_1.it)('should handle auth schemes with validation timeouts', () => { const fastAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'fast-auth', validate: async () => { // Immediate validation return { user: { id: '1', speed: 'fast' } }; } }); const slowAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'slow-auth', validate: async () => { // Simulate slow validation await new Promise(resolve => setTimeout(resolve, 10)); return { user: { id: '1', speed: 'slow' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/timeout-auth', method: 'GET', auth: [fastAuth, slowAuth], // Fast auth should be tried first response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ speed: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { speed: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth?.[0]?.name).toBe('fast-auth'); (0, vitest_1.expect)(route.auth?.[1]?.name).toBe('slow-auth'); }); (0, vitest_1.it)('should handle auth schemes with conditional logic', () => { const conditionalAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'conditional', validate: async (req) => { const userAgent = req.headers['user-agent']; const acceptsJson = req.headers.accept?.includes('application/json'); if (userAgent?.includes('Bot') && !acceptsJson) { throw new Error('Bots must accept JSON'); } return { user: { id: '1', userAgent: userAgent || 'unknown', acceptsJson: !!acceptsJson } }; } }); const fallbackAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'fallback-conditional', validate: async () => ({ user: { id: 'fallback', type: 'basic' } }) }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/conditional-auth', method: 'GET', auth: [conditionalAuth, fallbackAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ type: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { type: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toHaveLength(2); }); (0, vitest_1.it)('should handle auth schemes with different error types', () => { const strictAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'strict', validate: async (req) => { const token = req.headers.authorization; if (!token) { throw new Error('MISSING_TOKEN'); } if (!token.startsWith('Bearer ')) { throw new Error('INVALID_FORMAT'); } if (token.slice(7).length < 10) { throw new Error('TOKEN_TOO_SHORT'); } return { user: { id: '1', validation: 'strict' } }; } }); const relaxedAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'relaxed', validate: async (req) => { const token = req.headers.authorization || req.query.token; if (!token) { throw new Error('NO_AUTH_PROVIDED'); } return { user: { id: '1', validation: 'relaxed' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/error-types', method: 'GET', auth: [strictAuth, relaxedAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ validation: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { validation: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toHaveLength(2); }); (0, vitest_1.it)('should handle auth schemes with caching strategies', () => { const globalCache = new Map(); const userCache = new Map(); const globalCacheAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'global-cache', validate: async (req) => { const token = req.headers.authorization; if (globalCache.has(token)) { return globalCache.get(token); } const context = { user: { id: '1', cache: 'global' } }; globalCache.set(token, context); return context; } }); const userCacheAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'user-cache', validate: async (req) => { const userId = req.headers['x-user-id']; if (userCache.has(userId)) { return userCache.get(userId); } const context = { user: { id: userId || '1', cache: 'user-specific' } }; userCache.set(userId, context); return context; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/cache-strategies', method: 'GET', auth: [globalCacheAuth, userCacheAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ cache: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { cache: req.auth.name } }; } }); (0, vitest_1.expect)(route.auth).toHaveLength(2); }); }); //# sourceMappingURL=multiple-auth-schemes.test.js.map