UNPKG

@prism-engineer/router

Version:

Type-safe Express.js router with automatic client generation

589 lines 23.4 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 router_1 = require("../../router"); const typebox_1 = require("@sinclair/typebox"); (0, vitest_1.describe)('Authentication - Auth Middleware', () => { let router; (0, vitest_1.beforeEach)(() => { router = (0, router_1.createRouter)(); vitest_1.vi.clearAllMocks(); }); (0, vitest_1.it)('should integrate auth middleware with Express router', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'middleware-test', validate: async (req) => { const token = req.headers.authorization; if (!token) { throw new Error('No token provided'); } return { user: { id: '1', token } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/protected', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { message: `Authenticated as ${req.auth.context.user.id}` } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); (0, vitest_1.expect)(router.app).toBeDefined(); }); (0, vitest_1.it)('should handle middleware execution order', () => { const executionOrder = []; const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'order-test', validate: async (req) => { executionOrder.push('auth-validate'); return { user: { id: '1' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/order-test', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ order: typebox_1.Type.Array(typebox_1.Type.String()) }) } }, handler: async () => { executionOrder.push('route-handler'); return { status: 200, body: { order: executionOrder } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle middleware with multiple auth schemes', () => { 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'); } return { user: { id: '1', type: 'bearer' } }; } }); 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'); } return { client: { id: '1', type: 'api-key' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/multi-auth-middleware', 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)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle auth middleware with request transformation', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'transform-middleware', validate: async (req) => { // Middleware can transform request const userId = req.headers['x-user-id']; const orgId = req.headers['x-org-id']; return { user: { id: userId || 'anonymous', organizationId: orgId || 'default' }, enrichedRequest: { timestamp: Date.now(), ip: req.ip || 'unknown' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/transform-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), hasEnrichment: typebox_1.Type.Boolean() }) } }, handler: async (req) => { return { status: 200, body: { userId: req.auth.context.user.id, hasEnrichment: !!req.auth.context.enrichedRequest } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle async middleware operations', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'async-middleware', validate: async (req) => { // Simulate async database lookup await new Promise(resolve => setTimeout(resolve, 1)); const token = req.headers.authorization; if (!token) { throw new Error('Token required'); } // Simulate async validation await new Promise(resolve => setTimeout(resolve, 1)); return { user: { id: '1', validatedAt: new Date().toISOString() } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/async-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ validatedAt: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { validatedAt: req.auth.context.user.validatedAt } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle middleware error handling', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'error-middleware', validate: async (req) => { const shouldFail = req.headers['x-force-error']; if (shouldFail) { throw new Error('Forced authentication error'); } return { user: { id: '1' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/error-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }) } }, handler: async () => { return { status: 200, body: { message: 'Success' } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle middleware with caching layer', () => { const cache = new Map(); const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'cache-middleware', validate: async (req) => { const token = req.headers.authorization; const cacheKey = `auth:${token}`; // Check cache first if (cache.has(cacheKey)) { const cached = cache.get(cacheKey); return { ...cached, fromCache: true }; } // Simulate expensive validation await new Promise(resolve => setTimeout(resolve, 1)); const result = { user: { id: '1', token }, fromCache: false }; // Cache for future requests cache.set(cacheKey, result); setTimeout(() => cache.delete(cacheKey), 100); // TTL return result; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/cache-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ fromCache: typebox_1.Type.Boolean() }) } }, handler: async (req) => { return { status: 200, body: { fromCache: req.auth.context.fromCache } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle middleware with rate limiting', () => { const rateLimits = new Map(); const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'rate-limit-middleware', validate: async (req) => { const clientId = req.headers['x-client-id'] || req.ip; const now = Date.now(); const windowMs = 60000; // 1 minute const maxRequests = 100; if (!rateLimits.has(clientId)) { rateLimits.set(clientId, { count: 0, resetTime: now + windowMs }); } const limit = rateLimits.get(clientId); if (now > limit.resetTime) { limit.count = 0; limit.resetTime = now + windowMs; } limit.count++; if (limit.count > maxRequests) { throw new Error('Rate limit exceeded'); } return { user: { id: '1' }, rateLimit: { remaining: maxRequests - limit.count, resetTime: limit.resetTime } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/rate-limit-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ remaining: typebox_1.Type.Number() }) } }, handler: async (req) => { return { status: 200, body: { remaining: req.auth.context.rateLimit.remaining } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle middleware with request logging', () => { const logs = []; const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'logging-middleware', validate: async (req) => { const logEntry = { timestamp: new Date().toISOString(), method: req.method, path: req.path, userAgent: req.headers['user-agent'], ip: req.ip }; logs.push(logEntry); return { user: { id: '1' }, requestId: `req-${Date.now()}` }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/logging-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ requestId: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { requestId: req.auth.context.requestId } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); (0, vitest_1.expect)(logs).toBeDefined(); }); (0, vitest_1.it)('should handle middleware with context injection', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'injection-middleware', validate: async (req) => { // Inject additional context based on request const userAgent = req.headers['user-agent']; const acceptLanguage = req.headers['accept-language']; return { user: { id: '1' }, context: { device: { type: userAgent?.includes('Mobile') ? 'mobile' : 'desktop', userAgent }, locale: { language: acceptLanguage?.split(',')[0] || 'en', acceptedLanguages: acceptLanguage?.split(',') || ['en'] }, request: { timestamp: Date.now(), protocol: req.headers['x-forwarded-proto'] || 'http', secure: req.headers['x-forwarded-proto'] === 'https' } } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/injection-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ deviceType: typebox_1.Type.String(), language: typebox_1.Type.String(), isSecure: typebox_1.Type.Boolean() }) } }, handler: async (req) => { const ctx = req.auth.context.context; return { status: 200, body: { deviceType: ctx.device.type, language: ctx.locale.language, isSecure: ctx.request.secure } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle middleware with external service integration', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'external-service-middleware', validate: async (req) => { const token = req.headers.authorization?.replace('Bearer ', ''); // Simulate external service call const validateWithExternalService = async (token) => { // Mock external validation if (token === 'external-valid') { return { userId: 'ext-user-123', permissions: ['read', 'write'], metadata: { provider: 'oauth2', scope: 'profile' } }; } throw new Error('Invalid external token'); }; try { const externalResult = await validateWithExternalService(token); return { user: { id: externalResult.userId, permissions: externalResult.permissions, external: true }, provider: externalResult.metadata.provider }; } catch (error) { throw new Error(`External validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/external-service-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), provider: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { userId: req.auth.context.user.id, provider: req.auth.context.provider } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle middleware with session management', () => { const sessions = new Map(); const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'session-middleware', validate: async (req) => { const sessionId = req.headers.cookie?.match(/sessionId=([^;]+)/)?.[1]; if (!sessionId) { throw new Error('Session required'); } if (!sessions.has(sessionId)) { throw new Error('Invalid session'); } const session = sessions.get(sessionId); // Check expiration if (session.expiresAt < Date.now()) { sessions.delete(sessionId); throw new Error('Session expired'); } // Update last access session.lastAccess = Date.now(); sessions.set(sessionId, session); return { user: session.user, session: { id: sessionId, createdAt: session.createdAt, lastAccess: session.lastAccess, expiresAt: session.expiresAt } }; } }); // Setup a test session sessions.set('test-session', { user: { id: 'user-123', name: 'Test User' }, createdAt: Date.now(), lastAccess: Date.now(), expiresAt: Date.now() + 3600000 // 1 hour }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/session-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), sessionId: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { userId: req.auth.context.user.id, sessionId: req.auth.context.session.id } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should handle middleware with permission checking', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'permission-middleware', validate: async (req) => { const userId = req.headers['x-user-id']; const requiredPermission = req.headers['x-required-permission']; // Mock user permissions const userPermissions = { 'user-1': ['read', 'write'], 'user-2': ['read'], 'admin-1': ['read', 'write', 'delete', 'admin'] }; const permissions = userPermissions[userId] || []; if (requiredPermission && !permissions.includes(requiredPermission)) { throw new Error(`Permission denied: ${requiredPermission} required`); } return { user: { id: userId || 'anonymous', permissions }, hasRequiredPermission: !requiredPermission || permissions.includes(requiredPermission) }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/permission-middleware', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), permissionCount: typebox_1.Type.Number() }) } }, handler: async (req) => { return { status: 200, body: { userId: req.auth.context.user.id, permissionCount: req.auth.context.user.permissions.length } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); }); //# sourceMappingURL=auth-middleware.test.js.map