@prism-engineer/router
Version:
Type-safe Express.js router with automatic client generation
547 lines • 22 kB
JavaScript
"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