@shware/http 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/__tests__/index.test.cjs.map +1 -1
- package/dist/__tests__/index.test.mjs +1 -1
- package/dist/__tests__/index.test.mjs.map +1 -1
- package/dist/cache/index.cjs +1 -1
- package/dist/cache/index.cjs.map +1 -1
- package/dist/cache/index.d.cts +5 -5
- package/dist/cache/index.d.ts +5 -5
- package/dist/cache/index.mjs +1 -1
- package/dist/cache/index.mjs.map +1 -1
- package/dist/error/status.cjs +0 -3
- package/dist/error/status.cjs.map +1 -1
- package/dist/error/status.mjs +0 -3
- package/dist/error/status.mjs.map +1 -1
- package/dist/google-one-tap/index.cjs.map +1 -1
- package/dist/google-one-tap/index.mjs.map +1 -1
- package/dist/hono/__tests__/authorizer.test.cjs.map +1 -1
- package/dist/hono/__tests__/authorizer.test.mjs.map +1 -1
- package/dist/hono/__tests__/csrf.test.cjs +166 -0
- package/dist/hono/__tests__/csrf.test.cjs.map +1 -1
- package/dist/hono/__tests__/csrf.test.mjs +166 -0
- package/dist/hono/__tests__/csrf.test.mjs.map +1 -1
- package/dist/hono/csrf.cjs +14 -12
- package/dist/hono/csrf.cjs.map +1 -1
- package/dist/hono/csrf.d.cts +10 -0
- package/dist/hono/csrf.d.ts +10 -0
- package/dist/hono/csrf.mjs +14 -12
- package/dist/hono/csrf.mjs.map +1 -1
- package/dist/hono/handler.cjs.map +1 -1
- package/dist/hono/handler.mjs +1 -1
- package/dist/hono/handler.mjs.map +1 -1
- package/dist/hono/validator.cjs.map +1 -1
- package/dist/hono/validator.mjs +1 -1
- package/dist/hono/validator.mjs.map +1 -1
- package/dist/response.cjs.map +1 -1
- package/dist/response.mjs +1 -1
- package/dist/response.mjs.map +1 -1
- package/dist/utils/invariant.cjs.map +1 -1
- package/dist/utils/invariant.d.cts +1 -1
- package/dist/utils/invariant.d.ts +1 -1
- package/dist/utils/invariant.mjs.map +1 -1
- package/dist/vaild.cjs.map +1 -1
- package/dist/vaild.mjs.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hono/__tests__/authorizer.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { requestId } from 'hono/request-id';\nimport { authorizer, type AuthorizerConfig } from '../authorizer';\nimport { errorHandler, type Env } from '../handler';\n\ndescribe('authorizer', () => {\n let app: Hono<Env>;\n\n beforeEach(() => {\n app = new Hono<Env>();\n app.use(requestId());\n app.onError(errorHandler);\n });\n\n describe('Basic functionality', () => {\n it('should allow all requests when no rules are defined', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(true),\n };\n\n app.use(authorizer({ auth }));\n app.get('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test');\n expect(res.status).toBe(200);\n expect(auth.isAuthenticated).not.toHaveBeenCalled();\n });\n\n it('should allow requests when rules match and authentication passes', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(true),\n };\n\n app.use(authorizer({ auth, rules: [{ path: '/protected', methods: ['GET'] }] }));\n app.get('/protected', (c) => c.text('Protected content'));\n\n const res = await app.request('/protected');\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('Protected content');\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(1);\n });\n\n it('should reject requests when rules match but authentication fails', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(authorizer({ auth, rules: [{ path: '/protected', methods: ['GET'] }] }));\n app.get('/protected', (c) => c.text('Protected content'));\n\n const res = await app.request('/protected');\n expect(res.status).toBe(401);\n // StatusError wraps the message, just verify status\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(1);\n });\n\n it('should support custom error messages', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n const customMessage = 'Please login first';\n\n app.use(\n authorizer({\n auth,\n errorMessage: customMessage,\n rules: [{ path: '/protected', methods: ['GET'] }],\n })\n );\n app.get('/protected', (c) => c.text('Protected content'));\n\n const res = await app.request('/protected');\n expect(res.status).toBe(401);\n // Error message is wrapped in StatusError, just verify status\n });\n });\n\n describe('HTTP methods', () => {\n it('should only protect specified HTTP methods', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/users', methods: ['POST', 'DELETE'] }],\n })\n );\n app.get('/api/users', (c) => c.text('GET allowed'));\n app.post('/api/users', (c) => c.text('POST protected'));\n app.delete('/api/users', (c) => c.text('DELETE protected'));\n app.put('/api/users', (c) => c.text('PUT allowed'));\n\n // GET should pass (not protected)\n const getRes = await app.request('/api/users', { method: 'GET' });\n expect(getRes.status).toBe(200);\n expect(await getRes.text()).toBe('GET allowed');\n\n // POST should be rejected (protected and auth failed)\n const postRes = await app.request('/api/users', { method: 'POST' });\n expect(postRes.status).toBe(401);\n\n // DELETE should be rejected (protected and auth failed)\n const deleteRes = await app.request('/api/users', { method: 'DELETE' });\n expect(deleteRes.status).toBe(401);\n\n // PUT should pass (not protected)\n const putRes = await app.request('/api/users', { method: 'PUT' });\n expect(putRes.status).toBe(200);\n expect(await putRes.text()).toBe('PUT allowed');\n\n // auth.isAuthenticated should only be called for POST and DELETE\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(2);\n });\n\n it('should protect all methods when methods not specified', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/admin' }], // methods not specified\n })\n );\n app.get('/api/admin', (c) => c.text('GET'));\n app.post('/api/admin', (c) => c.text('POST'));\n app.put('/api/admin', (c) => c.text('PUT'));\n app.delete('/api/admin', (c) => c.text('DELETE'));\n\n // All methods should be rejected\n const methods = ['GET', 'POST', 'PUT', 'DELETE'];\n for (const method of methods) {\n const res = await app.request('/api/admin', { method });\n expect(res.status).toBe(401);\n }\n\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(4);\n });\n\n it('should always allow OPTIONS requests', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/*' }], // protect all /api/* paths\n })\n );\n app.options('/api/test', (c) => c.text('OPTIONS OK'));\n\n const res = await app.request('/api/test', { method: 'OPTIONS' });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OPTIONS OK');\n expect(auth.isAuthenticated).not.toHaveBeenCalled();\n });\n });\n\n describe('Path matching', () => {\n it('should support exact path matching', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/users' }],\n })\n );\n app.get('/api/users', (c) => c.text('Exact'));\n app.get('/api/users/123', (c) => c.text('With ID'));\n app.get('/api', (c) => c.text('Parent'));\n\n // Exact match path should be protected\n const exactRes = await app.request('/api/users');\n expect(exactRes.status).toBe(401);\n\n // Other paths should pass\n const idRes = await app.request('/api/users/123');\n expect(idRes.status).toBe(200);\n expect(await idRes.text()).toBe('With ID');\n\n const parentRes = await app.request('/api');\n expect(parentRes.status).toBe(200);\n expect(await parentRes.text()).toBe('Parent');\n });\n\n it('should support wildcard path matching', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/*' }, { path: '/admin/:id' }],\n })\n );\n app.get('/api/users', (c) => c.text('API Users'));\n app.get('/api/posts/123', (c) => c.text('API Post'));\n app.get('/admin/123', (c) => c.text('Admin'));\n app.get('/public', (c) => c.text('Public'));\n\n // All paths under /api/* should be protected\n const apiUsersRes = await app.request('/api/users');\n expect(apiUsersRes.status).toBe(401);\n\n const apiPostRes = await app.request('/api/posts/123');\n expect(apiPostRes.status).toBe(401);\n\n // /admin/:id should be protected\n const adminRes = await app.request('/admin/123');\n expect(adminRes.status).toBe(401);\n\n // /public should pass\n const publicRes = await app.request('/public');\n expect(publicRes.status).toBe(200);\n expect(await publicRes.text()).toBe('Public');\n });\n\n it('should support pattern-like path matching', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [\n { path: '/api/v1/*' },\n { path: '/api/v2/*' },\n { path: '/data.json' },\n { path: '/config.json' },\n ],\n })\n );\n app.get('/api/v1/users', (c) => c.text('V1'));\n app.get('/api/v2/users', (c) => c.text('V2'));\n app.get('/api/v3/users', (c) => c.text('V3'));\n app.get('/data.json', (c) => c.text('JSON'));\n app.get('/config.json', (c) => c.text('Config JSON'));\n app.get('/data.xml', (c) => c.text('XML'));\n\n // v1 and v2 should be protected\n const v1Res = await app.request('/api/v1/users');\n expect(v1Res.status).toBe(401);\n\n const v2Res = await app.request('/api/v2/users');\n expect(v2Res.status).toBe(401);\n\n // v3 should pass\n const v3Res = await app.request('/api/v3/users');\n expect(v3Res.status).toBe(200);\n\n // .json files should be protected\n const jsonRes = await app.request('/data.json');\n expect(jsonRes.status).toBe(401);\n\n const configJsonRes = await app.request('/config.json');\n expect(configJsonRes.status).toBe(401);\n\n // .xml files should pass\n const xmlRes = await app.request('/data.xml');\n expect(xmlRes.status).toBe(200);\n });\n });\n\n describe('Authentication function', () => {\n it('should pass Request object correctly to auth function', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockImplementation(async (request: Request) => {\n const authHeader = request.headers.get('Authorization');\n return authHeader === 'Bearer valid-token';\n }),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/protected' }],\n })\n );\n app.get('/protected', (c) => c.text('Protected'));\n\n // No token should fail\n const noTokenRes = await app.request('/protected');\n expect(noTokenRes.status).toBe(401);\n\n // Invalid token should fail\n const invalidTokenRes = await app.request('/protected', {\n headers: { Authorization: 'Bearer invalid-token' },\n });\n expect(invalidTokenRes.status).toBe(401);\n\n // Valid token should succeed\n const validTokenRes = await app.request('/protected', {\n headers: { Authorization: 'Bearer valid-token' },\n });\n expect(validTokenRes.status).toBe(200);\n expect(await validTokenRes.text()).toBe('Protected');\n\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(3);\n });\n\n it('should handle exceptions thrown by auth function', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockRejectedValue(new Error('Auth service error')),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/protected' }],\n })\n );\n app.get('/protected', (c) => c.text('Protected'));\n\n const res = await app.request('/protected');\n expect(res.status).toBe(500);\n // Error details are wrapped, just verify status\n });\n\n it('should support cookie-based authentication', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockImplementation(async (request: Request) => {\n const cookie = request.headers.get('Cookie');\n return cookie?.includes('session=valid-session');\n }),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/dashboard' }],\n })\n );\n app.get('/dashboard', (c) => c.text('Dashboard'));\n\n // No cookie should fail\n const noCookieRes = await app.request('/dashboard');\n expect(noCookieRes.status).toBe(401);\n\n // Valid cookie should succeed\n const validCookieRes = await app.request('/dashboard', {\n headers: { Cookie: 'session=valid-session; other=value' },\n });\n expect(validCookieRes.status).toBe(200);\n expect(await validCookieRes.text()).toBe('Dashboard');\n });\n\n it('should support query parameter authentication', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockImplementation(async (request: Request) => {\n const url = new URL(request.url);\n return url.searchParams.get('api_key') === 'valid-key';\n }),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/data' }],\n })\n );\n app.get('/api/data', (c) => c.text('Data'));\n\n // No API key should fail\n const noKeyRes = await app.request('/api/data');\n expect(noKeyRes.status).toBe(401);\n\n // Invalid API key should fail\n const invalidKeyRes = await app.request('/api/data?api_key=invalid-key');\n expect(invalidKeyRes.status).toBe(401);\n\n // Valid API key should succeed\n const validKeyRes = await app.request('/api/data?api_key=valid-key');\n expect(validKeyRes.status).toBe(200);\n expect(await validKeyRes.text()).toBe('Data');\n });\n });\n\n describe('Multiple rules', () => {\n it('should support multiple rule combinations', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [\n { path: '/api/users', methods: ['POST', 'PUT', 'DELETE'] },\n { path: '/api/admin/*' },\n { path: '/api/reports', methods: ['GET'] },\n { path: '/webhook/*', methods: ['POST'] },\n ],\n })\n );\n\n // Setup routes\n app.get('/api/users', (c) => c.text('GET users'));\n app.post('/api/users', (c) => c.text('POST users'));\n app.get('/api/admin/settings', (c) => c.text('Admin settings'));\n app.get('/api/reports', (c) => c.text('Reports'));\n app.post('/webhook/github', (c) => c.text('Webhook'));\n app.get('/public', (c) => c.text('Public'));\n\n // GET /api/users should pass (not protected)\n const getUsersRes = await app.request('/api/users');\n expect(getUsersRes.status).toBe(200);\n\n // POST /api/users should be rejected (protected)\n const postUsersRes = await app.request('/api/users', { method: 'POST' });\n expect(postUsersRes.status).toBe(401);\n\n // GET /api/admin/settings should be rejected (all methods protected)\n const adminRes = await app.request('/api/admin/settings');\n expect(adminRes.status).toBe(401);\n\n // GET /api/reports should be rejected (GET method protected)\n const reportsRes = await app.request('/api/reports');\n expect(reportsRes.status).toBe(401);\n\n // POST /webhook/github should be rejected (POST method protected)\n const webhookRes = await app.request('/webhook/github', { method: 'POST' });\n expect(webhookRes.status).toBe(401);\n\n // GET /public should pass (not protected)\n const publicRes = await app.request('/public');\n expect(publicRes.status).toBe(200);\n });\n\n it('should handle overlapping rules correctly', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(true),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [\n { path: '/api/*' }, // protect all /api/* paths\n { path: '/api/public', methods: ['GET'] }, // also protect GET /api/public\n ],\n })\n );\n app.get('/api/public', (c) => c.text('Public API'));\n app.post('/api/public', (c) => c.text('POST Public API'));\n\n // Both rules match, but auth passes, so should succeed\n const getRes = await app.request('/api/public');\n expect(getRes.status).toBe(200);\n\n const postRes = await app.request('/api/public', { method: 'POST' });\n expect(postRes.status).toBe(200);\n\n // Auth function should be called twice\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(2);\n });\n });\n\n describe('Edge cases', () => {\n it('should allow all requests with empty rules array', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(authorizer({ auth, rules: [] }));\n app.get('/any/path', (c) => c.text('OK'));\n\n const res = await app.request('/any/path');\n expect(res.status).toBe(200);\n expect(auth.isAuthenticated).not.toHaveBeenCalled();\n });\n\n it('should handle special characters in paths', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [\n { path: '/api/user@email.com' },\n { path: '/files/*.pdf' },\n { path: '/path-with-dash' },\n { path: '/path_with_underscore' },\n ],\n })\n );\n\n app.get('/api/user@email.com', (c) => c.text('Email'));\n app.get('/files/document.pdf', (c) => c.text('PDF'));\n app.get('/path-with-dash', (c) => c.text('Dash'));\n app.get('/path_with_underscore', (c) => c.text('Underscore'));\n\n // All special character paths should be matched and protected correctly\n const emailRes = await app.request('/api/user@email.com');\n expect(emailRes.status).toBe(401);\n\n const pdfRes = await app.request('/files/document.pdf');\n expect(pdfRes.status).toBe(401);\n\n const dashRes = await app.request('/path-with-dash');\n expect(dashRes.status).toBe(401);\n\n const underscoreRes = await app.request('/path_with_underscore');\n expect(underscoreRes.status).toBe(401);\n });\n\n it('should handle URLs with query parameters and fragments', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/search' }],\n })\n );\n app.get('/api/search', (c) => c.text('Search'));\n\n // Requests with query parameters should be matched correctly\n const res = await app.request('/api/search?q=test&page=1#results');\n expect(res.status).toBe(401);\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(1);\n });\n\n it('should handle root path correctly', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/' }],\n })\n );\n app.get('/', (c) => c.text('Home'));\n app.get('/other', (c) => c.text('Other'));\n\n // Root path should be protected\n const rootRes = await app.request('/');\n expect(rootRes.status).toBe(401);\n\n // Other paths should pass\n const otherRes = await app.request('/other');\n expect(otherRes.status).toBe(200);\n });\n\n it('should handle trailing slashes as different paths', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/users' }, { path: '/api/users/' }],\n })\n );\n app.get('/api/users', (c) => c.text('Users'));\n app.get('/api/users/', (c) => c.text('Users with slash'));\n\n // Both paths should be protected when explicitly defined\n const withoutSlash = await app.request('/api/users');\n expect(withoutSlash.status).toBe(401);\n\n const withSlash = await app.request('/api/users/');\n expect(withSlash.status).toBe(401);\n });\n });\n\n describe('Complex authentication scenarios', () => {\n it('should support role-based authentication', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockImplementation(async (request: Request) => {\n const authHeader = request.headers.get('Authorization');\n const url = new URL(request.url);\n\n // Admin endpoints require admin token\n if (url.pathname.startsWith('/admin')) {\n return authHeader === 'Bearer admin-token';\n }\n\n // Regular endpoints accept any valid token\n return authHeader?.startsWith('Bearer ');\n }),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/admin/*' }, { path: '/api/*' }],\n })\n );\n\n app.get('/admin/users', (c) => c.text('Admin Users'));\n app.get('/api/data', (c) => c.text('API Data'));\n\n // Admin endpoint with regular token should fail\n const adminWithRegularToken = await app.request('/admin/users', {\n headers: { Authorization: 'Bearer user-token' },\n });\n expect(adminWithRegularToken.status).toBe(401);\n\n // Admin endpoint with admin token should succeed\n const adminWithAdminToken = await app.request('/admin/users', {\n headers: { Authorization: 'Bearer admin-token' },\n });\n expect(adminWithAdminToken.status).toBe(200);\n\n // API endpoint with any token should succeed\n const apiWithUserToken = await app.request('/api/data', {\n headers: { Authorization: 'Bearer user-token' },\n });\n expect(apiWithUserToken.status).toBe(200);\n });\n });\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/hono/__tests__/authorizer.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { requestId } from 'hono/request-id';\nimport { type AuthorizerConfig, authorizer } from '../authorizer';\nimport { type Env, errorHandler } from '../handler';\n\ndescribe('authorizer', () => {\n let app: Hono<Env>;\n\n beforeEach(() => {\n app = new Hono<Env>();\n app.use(requestId());\n app.onError(errorHandler);\n });\n\n describe('Basic functionality', () => {\n it('should allow all requests when no rules are defined', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(true),\n };\n\n app.use(authorizer({ auth }));\n app.get('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test');\n expect(res.status).toBe(200);\n expect(auth.isAuthenticated).not.toHaveBeenCalled();\n });\n\n it('should allow requests when rules match and authentication passes', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(true),\n };\n\n app.use(authorizer({ auth, rules: [{ path: '/protected', methods: ['GET'] }] }));\n app.get('/protected', (c) => c.text('Protected content'));\n\n const res = await app.request('/protected');\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('Protected content');\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(1);\n });\n\n it('should reject requests when rules match but authentication fails', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(authorizer({ auth, rules: [{ path: '/protected', methods: ['GET'] }] }));\n app.get('/protected', (c) => c.text('Protected content'));\n\n const res = await app.request('/protected');\n expect(res.status).toBe(401);\n // StatusError wraps the message, just verify status\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(1);\n });\n\n it('should support custom error messages', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n const customMessage = 'Please login first';\n\n app.use(\n authorizer({\n auth,\n errorMessage: customMessage,\n rules: [{ path: '/protected', methods: ['GET'] }],\n })\n );\n app.get('/protected', (c) => c.text('Protected content'));\n\n const res = await app.request('/protected');\n expect(res.status).toBe(401);\n // Error message is wrapped in StatusError, just verify status\n });\n });\n\n describe('HTTP methods', () => {\n it('should only protect specified HTTP methods', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/users', methods: ['POST', 'DELETE'] }],\n })\n );\n app.get('/api/users', (c) => c.text('GET allowed'));\n app.post('/api/users', (c) => c.text('POST protected'));\n app.delete('/api/users', (c) => c.text('DELETE protected'));\n app.put('/api/users', (c) => c.text('PUT allowed'));\n\n // GET should pass (not protected)\n const getRes = await app.request('/api/users', { method: 'GET' });\n expect(getRes.status).toBe(200);\n expect(await getRes.text()).toBe('GET allowed');\n\n // POST should be rejected (protected and auth failed)\n const postRes = await app.request('/api/users', { method: 'POST' });\n expect(postRes.status).toBe(401);\n\n // DELETE should be rejected (protected and auth failed)\n const deleteRes = await app.request('/api/users', { method: 'DELETE' });\n expect(deleteRes.status).toBe(401);\n\n // PUT should pass (not protected)\n const putRes = await app.request('/api/users', { method: 'PUT' });\n expect(putRes.status).toBe(200);\n expect(await putRes.text()).toBe('PUT allowed');\n\n // auth.isAuthenticated should only be called for POST and DELETE\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(2);\n });\n\n it('should protect all methods when methods not specified', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/admin' }], // methods not specified\n })\n );\n app.get('/api/admin', (c) => c.text('GET'));\n app.post('/api/admin', (c) => c.text('POST'));\n app.put('/api/admin', (c) => c.text('PUT'));\n app.delete('/api/admin', (c) => c.text('DELETE'));\n\n // All methods should be rejected\n const methods = ['GET', 'POST', 'PUT', 'DELETE'];\n for (const method of methods) {\n const res = await app.request('/api/admin', { method });\n expect(res.status).toBe(401);\n }\n\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(4);\n });\n\n it('should always allow OPTIONS requests', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/*' }], // protect all /api/* paths\n })\n );\n app.options('/api/test', (c) => c.text('OPTIONS OK'));\n\n const res = await app.request('/api/test', { method: 'OPTIONS' });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OPTIONS OK');\n expect(auth.isAuthenticated).not.toHaveBeenCalled();\n });\n });\n\n describe('Path matching', () => {\n it('should support exact path matching', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/users' }],\n })\n );\n app.get('/api/users', (c) => c.text('Exact'));\n app.get('/api/users/123', (c) => c.text('With ID'));\n app.get('/api', (c) => c.text('Parent'));\n\n // Exact match path should be protected\n const exactRes = await app.request('/api/users');\n expect(exactRes.status).toBe(401);\n\n // Other paths should pass\n const idRes = await app.request('/api/users/123');\n expect(idRes.status).toBe(200);\n expect(await idRes.text()).toBe('With ID');\n\n const parentRes = await app.request('/api');\n expect(parentRes.status).toBe(200);\n expect(await parentRes.text()).toBe('Parent');\n });\n\n it('should support wildcard path matching', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/*' }, { path: '/admin/:id' }],\n })\n );\n app.get('/api/users', (c) => c.text('API Users'));\n app.get('/api/posts/123', (c) => c.text('API Post'));\n app.get('/admin/123', (c) => c.text('Admin'));\n app.get('/public', (c) => c.text('Public'));\n\n // All paths under /api/* should be protected\n const apiUsersRes = await app.request('/api/users');\n expect(apiUsersRes.status).toBe(401);\n\n const apiPostRes = await app.request('/api/posts/123');\n expect(apiPostRes.status).toBe(401);\n\n // /admin/:id should be protected\n const adminRes = await app.request('/admin/123');\n expect(adminRes.status).toBe(401);\n\n // /public should pass\n const publicRes = await app.request('/public');\n expect(publicRes.status).toBe(200);\n expect(await publicRes.text()).toBe('Public');\n });\n\n it('should support pattern-like path matching', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [\n { path: '/api/v1/*' },\n { path: '/api/v2/*' },\n { path: '/data.json' },\n { path: '/config.json' },\n ],\n })\n );\n app.get('/api/v1/users', (c) => c.text('V1'));\n app.get('/api/v2/users', (c) => c.text('V2'));\n app.get('/api/v3/users', (c) => c.text('V3'));\n app.get('/data.json', (c) => c.text('JSON'));\n app.get('/config.json', (c) => c.text('Config JSON'));\n app.get('/data.xml', (c) => c.text('XML'));\n\n // v1 and v2 should be protected\n const v1Res = await app.request('/api/v1/users');\n expect(v1Res.status).toBe(401);\n\n const v2Res = await app.request('/api/v2/users');\n expect(v2Res.status).toBe(401);\n\n // v3 should pass\n const v3Res = await app.request('/api/v3/users');\n expect(v3Res.status).toBe(200);\n\n // .json files should be protected\n const jsonRes = await app.request('/data.json');\n expect(jsonRes.status).toBe(401);\n\n const configJsonRes = await app.request('/config.json');\n expect(configJsonRes.status).toBe(401);\n\n // .xml files should pass\n const xmlRes = await app.request('/data.xml');\n expect(xmlRes.status).toBe(200);\n });\n });\n\n describe('Authentication function', () => {\n it('should pass Request object correctly to auth function', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockImplementation(async (request: Request) => {\n const authHeader = request.headers.get('Authorization');\n return authHeader === 'Bearer valid-token';\n }),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/protected' }],\n })\n );\n app.get('/protected', (c) => c.text('Protected'));\n\n // No token should fail\n const noTokenRes = await app.request('/protected');\n expect(noTokenRes.status).toBe(401);\n\n // Invalid token should fail\n const invalidTokenRes = await app.request('/protected', {\n headers: { Authorization: 'Bearer invalid-token' },\n });\n expect(invalidTokenRes.status).toBe(401);\n\n // Valid token should succeed\n const validTokenRes = await app.request('/protected', {\n headers: { Authorization: 'Bearer valid-token' },\n });\n expect(validTokenRes.status).toBe(200);\n expect(await validTokenRes.text()).toBe('Protected');\n\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(3);\n });\n\n it('should handle exceptions thrown by auth function', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockRejectedValue(new Error('Auth service error')),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/protected' }],\n })\n );\n app.get('/protected', (c) => c.text('Protected'));\n\n const res = await app.request('/protected');\n expect(res.status).toBe(500);\n // Error details are wrapped, just verify status\n });\n\n it('should support cookie-based authentication', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockImplementation(async (request: Request) => {\n const cookie = request.headers.get('Cookie');\n return cookie?.includes('session=valid-session');\n }),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/dashboard' }],\n })\n );\n app.get('/dashboard', (c) => c.text('Dashboard'));\n\n // No cookie should fail\n const noCookieRes = await app.request('/dashboard');\n expect(noCookieRes.status).toBe(401);\n\n // Valid cookie should succeed\n const validCookieRes = await app.request('/dashboard', {\n headers: { Cookie: 'session=valid-session; other=value' },\n });\n expect(validCookieRes.status).toBe(200);\n expect(await validCookieRes.text()).toBe('Dashboard');\n });\n\n it('should support query parameter authentication', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockImplementation(async (request: Request) => {\n const url = new URL(request.url);\n return url.searchParams.get('api_key') === 'valid-key';\n }),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/data' }],\n })\n );\n app.get('/api/data', (c) => c.text('Data'));\n\n // No API key should fail\n const noKeyRes = await app.request('/api/data');\n expect(noKeyRes.status).toBe(401);\n\n // Invalid API key should fail\n const invalidKeyRes = await app.request('/api/data?api_key=invalid-key');\n expect(invalidKeyRes.status).toBe(401);\n\n // Valid API key should succeed\n const validKeyRes = await app.request('/api/data?api_key=valid-key');\n expect(validKeyRes.status).toBe(200);\n expect(await validKeyRes.text()).toBe('Data');\n });\n });\n\n describe('Multiple rules', () => {\n it('should support multiple rule combinations', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [\n { path: '/api/users', methods: ['POST', 'PUT', 'DELETE'] },\n { path: '/api/admin/*' },\n { path: '/api/reports', methods: ['GET'] },\n { path: '/webhook/*', methods: ['POST'] },\n ],\n })\n );\n\n // Setup routes\n app.get('/api/users', (c) => c.text('GET users'));\n app.post('/api/users', (c) => c.text('POST users'));\n app.get('/api/admin/settings', (c) => c.text('Admin settings'));\n app.get('/api/reports', (c) => c.text('Reports'));\n app.post('/webhook/github', (c) => c.text('Webhook'));\n app.get('/public', (c) => c.text('Public'));\n\n // GET /api/users should pass (not protected)\n const getUsersRes = await app.request('/api/users');\n expect(getUsersRes.status).toBe(200);\n\n // POST /api/users should be rejected (protected)\n const postUsersRes = await app.request('/api/users', { method: 'POST' });\n expect(postUsersRes.status).toBe(401);\n\n // GET /api/admin/settings should be rejected (all methods protected)\n const adminRes = await app.request('/api/admin/settings');\n expect(adminRes.status).toBe(401);\n\n // GET /api/reports should be rejected (GET method protected)\n const reportsRes = await app.request('/api/reports');\n expect(reportsRes.status).toBe(401);\n\n // POST /webhook/github should be rejected (POST method protected)\n const webhookRes = await app.request('/webhook/github', { method: 'POST' });\n expect(webhookRes.status).toBe(401);\n\n // GET /public should pass (not protected)\n const publicRes = await app.request('/public');\n expect(publicRes.status).toBe(200);\n });\n\n it('should handle overlapping rules correctly', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(true),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [\n { path: '/api/*' }, // protect all /api/* paths\n { path: '/api/public', methods: ['GET'] }, // also protect GET /api/public\n ],\n })\n );\n app.get('/api/public', (c) => c.text('Public API'));\n app.post('/api/public', (c) => c.text('POST Public API'));\n\n // Both rules match, but auth passes, so should succeed\n const getRes = await app.request('/api/public');\n expect(getRes.status).toBe(200);\n\n const postRes = await app.request('/api/public', { method: 'POST' });\n expect(postRes.status).toBe(200);\n\n // Auth function should be called twice\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(2);\n });\n });\n\n describe('Edge cases', () => {\n it('should allow all requests with empty rules array', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(authorizer({ auth, rules: [] }));\n app.get('/any/path', (c) => c.text('OK'));\n\n const res = await app.request('/any/path');\n expect(res.status).toBe(200);\n expect(auth.isAuthenticated).not.toHaveBeenCalled();\n });\n\n it('should handle special characters in paths', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [\n { path: '/api/user@email.com' },\n { path: '/files/*.pdf' },\n { path: '/path-with-dash' },\n { path: '/path_with_underscore' },\n ],\n })\n );\n\n app.get('/api/user@email.com', (c) => c.text('Email'));\n app.get('/files/document.pdf', (c) => c.text('PDF'));\n app.get('/path-with-dash', (c) => c.text('Dash'));\n app.get('/path_with_underscore', (c) => c.text('Underscore'));\n\n // All special character paths should be matched and protected correctly\n const emailRes = await app.request('/api/user@email.com');\n expect(emailRes.status).toBe(401);\n\n const pdfRes = await app.request('/files/document.pdf');\n expect(pdfRes.status).toBe(401);\n\n const dashRes = await app.request('/path-with-dash');\n expect(dashRes.status).toBe(401);\n\n const underscoreRes = await app.request('/path_with_underscore');\n expect(underscoreRes.status).toBe(401);\n });\n\n it('should handle URLs with query parameters and fragments', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/search' }],\n })\n );\n app.get('/api/search', (c) => c.text('Search'));\n\n // Requests with query parameters should be matched correctly\n const res = await app.request('/api/search?q=test&page=1#results');\n expect(res.status).toBe(401);\n expect(auth.isAuthenticated).toHaveBeenCalledTimes(1);\n });\n\n it('should handle root path correctly', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/' }],\n })\n );\n app.get('/', (c) => c.text('Home'));\n app.get('/other', (c) => c.text('Other'));\n\n // Root path should be protected\n const rootRes = await app.request('/');\n expect(rootRes.status).toBe(401);\n\n // Other paths should pass\n const otherRes = await app.request('/other');\n expect(otherRes.status).toBe(200);\n });\n\n it('should handle trailing slashes as different paths', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockResolvedValue(false),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/api/users' }, { path: '/api/users/' }],\n })\n );\n app.get('/api/users', (c) => c.text('Users'));\n app.get('/api/users/', (c) => c.text('Users with slash'));\n\n // Both paths should be protected when explicitly defined\n const withoutSlash = await app.request('/api/users');\n expect(withoutSlash.status).toBe(401);\n\n const withSlash = await app.request('/api/users/');\n expect(withSlash.status).toBe(401);\n });\n });\n\n describe('Complex authentication scenarios', () => {\n it('should support role-based authentication', async () => {\n const auth: AuthorizerConfig['auth'] = {\n isAuthenticated: jest.fn().mockImplementation(async (request: Request) => {\n const authHeader = request.headers.get('Authorization');\n const url = new URL(request.url);\n\n // Admin endpoints require admin token\n if (url.pathname.startsWith('/admin')) {\n return authHeader === 'Bearer admin-token';\n }\n\n // Regular endpoints accept any valid token\n return authHeader?.startsWith('Bearer ');\n }),\n };\n\n app.use(\n authorizer({\n auth,\n rules: [{ path: '/admin/*' }, { path: '/api/*' }],\n })\n );\n\n app.get('/admin/users', (c) => c.text('Admin Users'));\n app.get('/api/data', (c) => c.text('API Data'));\n\n // Admin endpoint with regular token should fail\n const adminWithRegularToken = await app.request('/admin/users', {\n headers: { Authorization: 'Bearer user-token' },\n });\n expect(adminWithRegularToken.status).toBe(401);\n\n // Admin endpoint with admin token should succeed\n const adminWithAdminToken = await app.request('/admin/users', {\n headers: { Authorization: 'Bearer admin-token' },\n });\n expect(adminWithAdminToken.status).toBe(200);\n\n // API endpoint with any token should succeed\n const apiWithUserToken = await app.request('/api/data', {\n headers: { Authorization: 'Bearer user-token' },\n });\n expect(apiWithUserToken.status).toBe(200);\n });\n });\n});\n"],"mappings":";AAAA,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAgC,kBAAkB;AAClD,SAAmB,oBAAoB;AAEvC,SAAS,cAAc,MAAM;AAC3B,MAAI;AAEJ,aAAW,MAAM;AACf,UAAM,IAAI,KAAU;AACpB,QAAI,IAAI,UAAU,CAAC;AACnB,QAAI,QAAQ,YAAY;AAAA,EAC1B,CAAC;AAED,WAAS,uBAAuB,MAAM;AACpC,OAAG,uDAAuD,YAAY;AACpE,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,IAAI;AAAA,MACnD;AAEA,UAAI,IAAI,WAAW,EAAE,KAAK,CAAC,CAAC;AAC5B,UAAI,IAAI,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC;AAEpC,YAAM,MAAM,MAAM,IAAI,QAAQ,OAAO;AACrC,aAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAC3B,aAAO,KAAK,eAAe,EAAE,IAAI,iBAAiB;AAAA,IACpD,CAAC;AAED,OAAG,oEAAoE,YAAY;AACjF,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,IAAI;AAAA,MACnD;AAEA,UAAI,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,MAAM,cAAc,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AAC/E,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,mBAAmB,CAAC;AAExD,YAAM,MAAM,MAAM,IAAI,QAAQ,YAAY;AAC1C,aAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAC3B,aAAO,MAAM,IAAI,KAAK,CAAC,EAAE,KAAK,mBAAmB;AACjD,aAAO,KAAK,eAAe,EAAE,sBAAsB,CAAC;AAAA,IACtD,CAAC;AAED,OAAG,oEAAoE,YAAY;AACjF,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,MAAM,cAAc,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AAC/E,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,mBAAmB,CAAC;AAExD,YAAM,MAAM,MAAM,IAAI,QAAQ,YAAY;AAC1C,aAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAE3B,aAAO,KAAK,eAAe,EAAE,sBAAsB,CAAC;AAAA,IACtD,CAAC;AAED,OAAG,wCAAwC,YAAY;AACrD,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AACA,YAAM,gBAAgB;AAEtB,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,cAAc;AAAA,UACd,OAAO,CAAC,EAAE,MAAM,cAAc,SAAS,CAAC,KAAK,EAAE,CAAC;AAAA,QAClD,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,mBAAmB,CAAC;AAExD,YAAM,MAAM,MAAM,IAAI,QAAQ,YAAY;AAC1C,aAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAAA,IAE7B,CAAC;AAAA,EACH,CAAC;AAED,WAAS,gBAAgB,MAAM;AAC7B,OAAG,8CAA8C,YAAY;AAC3D,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,cAAc,SAAS,CAAC,QAAQ,QAAQ,EAAE,CAAC;AAAA,QAC7D,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,aAAa,CAAC;AAClD,UAAI,KAAK,cAAc,CAAC,MAAM,EAAE,KAAK,gBAAgB,CAAC;AACtD,UAAI,OAAO,cAAc,CAAC,MAAM,EAAE,KAAK,kBAAkB,CAAC;AAC1D,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,aAAa,CAAC;AAGlD,YAAM,SAAS,MAAM,IAAI,QAAQ,cAAc,EAAE,QAAQ,MAAM,CAAC;AAChE,aAAO,OAAO,MAAM,EAAE,KAAK,GAAG;AAC9B,aAAO,MAAM,OAAO,KAAK,CAAC,EAAE,KAAK,aAAa;AAG9C,YAAM,UAAU,MAAM,IAAI,QAAQ,cAAc,EAAE,QAAQ,OAAO,CAAC;AAClE,aAAO,QAAQ,MAAM,EAAE,KAAK,GAAG;AAG/B,YAAM,YAAY,MAAM,IAAI,QAAQ,cAAc,EAAE,QAAQ,SAAS,CAAC;AACtE,aAAO,UAAU,MAAM,EAAE,KAAK,GAAG;AAGjC,YAAM,SAAS,MAAM,IAAI,QAAQ,cAAc,EAAE,QAAQ,MAAM,CAAC;AAChE,aAAO,OAAO,MAAM,EAAE,KAAK,GAAG;AAC9B,aAAO,MAAM,OAAO,KAAK,CAAC,EAAE,KAAK,aAAa;AAG9C,aAAO,KAAK,eAAe,EAAE,sBAAsB,CAAC;AAAA,IACtD,CAAC;AAED,OAAG,yDAAyD,YAAY;AACtE,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,aAAa,CAAC;AAAA;AAAA,QAChC,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC;AAC1C,UAAI,KAAK,cAAc,CAAC,MAAM,EAAE,KAAK,MAAM,CAAC;AAC5C,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC;AAC1C,UAAI,OAAO,cAAc,CAAC,MAAM,EAAE,KAAK,QAAQ,CAAC;AAGhD,YAAM,UAAU,CAAC,OAAO,QAAQ,OAAO,QAAQ;AAC/C,iBAAW,UAAU,SAAS;AAC5B,cAAM,MAAM,MAAM,IAAI,QAAQ,cAAc,EAAE,OAAO,CAAC;AACtD,eAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAAA,MAC7B;AAEA,aAAO,KAAK,eAAe,EAAE,sBAAsB,CAAC;AAAA,IACtD,CAAC;AAED,OAAG,wCAAwC,YAAY;AACrD,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,SAAS,CAAC;AAAA;AAAA,QAC5B,CAAC;AAAA,MACH;AACA,UAAI,QAAQ,aAAa,CAAC,MAAM,EAAE,KAAK,YAAY,CAAC;AAEpD,YAAM,MAAM,MAAM,IAAI,QAAQ,aAAa,EAAE,QAAQ,UAAU,CAAC;AAChE,aAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAC3B,aAAO,MAAM,IAAI,KAAK,CAAC,EAAE,KAAK,YAAY;AAC1C,aAAO,KAAK,eAAe,EAAE,IAAI,iBAAiB;AAAA,IACpD,CAAC;AAAA,EACH,CAAC;AAED,WAAS,iBAAiB,MAAM;AAC9B,OAAG,sCAAsC,YAAY;AACnD,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,aAAa,CAAC;AAAA,QAChC,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,OAAO,CAAC;AAC5C,UAAI,IAAI,kBAAkB,CAAC,MAAM,EAAE,KAAK,SAAS,CAAC;AAClD,UAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,QAAQ,CAAC;AAGvC,YAAM,WAAW,MAAM,IAAI,QAAQ,YAAY;AAC/C,aAAO,SAAS,MAAM,EAAE,KAAK,GAAG;AAGhC,YAAM,QAAQ,MAAM,IAAI,QAAQ,gBAAgB;AAChD,aAAO,MAAM,MAAM,EAAE,KAAK,GAAG;AAC7B,aAAO,MAAM,MAAM,KAAK,CAAC,EAAE,KAAK,SAAS;AAEzC,YAAM,YAAY,MAAM,IAAI,QAAQ,MAAM;AAC1C,aAAO,UAAU,MAAM,EAAE,KAAK,GAAG;AACjC,aAAO,MAAM,UAAU,KAAK,CAAC,EAAE,KAAK,QAAQ;AAAA,IAC9C,CAAC;AAED,OAAG,yCAAyC,YAAY;AACtD,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,SAAS,GAAG,EAAE,MAAM,aAAa,CAAC;AAAA,QACpD,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,WAAW,CAAC;AAChD,UAAI,IAAI,kBAAkB,CAAC,MAAM,EAAE,KAAK,UAAU,CAAC;AACnD,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,OAAO,CAAC;AAC5C,UAAI,IAAI,WAAW,CAAC,MAAM,EAAE,KAAK,QAAQ,CAAC;AAG1C,YAAM,cAAc,MAAM,IAAI,QAAQ,YAAY;AAClD,aAAO,YAAY,MAAM,EAAE,KAAK,GAAG;AAEnC,YAAM,aAAa,MAAM,IAAI,QAAQ,gBAAgB;AACrD,aAAO,WAAW,MAAM,EAAE,KAAK,GAAG;AAGlC,YAAM,WAAW,MAAM,IAAI,QAAQ,YAAY;AAC/C,aAAO,SAAS,MAAM,EAAE,KAAK,GAAG;AAGhC,YAAM,YAAY,MAAM,IAAI,QAAQ,SAAS;AAC7C,aAAO,UAAU,MAAM,EAAE,KAAK,GAAG;AACjC,aAAO,MAAM,UAAU,KAAK,CAAC,EAAE,KAAK,QAAQ;AAAA,IAC9C,CAAC;AAED,OAAG,6CAA6C,YAAY;AAC1D,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO;AAAA,YACL,EAAE,MAAM,YAAY;AAAA,YACpB,EAAE,MAAM,YAAY;AAAA,YACpB,EAAE,MAAM,aAAa;AAAA,YACrB,EAAE,MAAM,eAAe;AAAA,UACzB;AAAA,QACF,CAAC;AAAA,MACH;AACA,UAAI,IAAI,iBAAiB,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC;AAC5C,UAAI,IAAI,iBAAiB,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC;AAC5C,UAAI,IAAI,iBAAiB,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC;AAC5C,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,MAAM,CAAC;AAC3C,UAAI,IAAI,gBAAgB,CAAC,MAAM,EAAE,KAAK,aAAa,CAAC;AACpD,UAAI,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC;AAGzC,YAAM,QAAQ,MAAM,IAAI,QAAQ,eAAe;AAC/C,aAAO,MAAM,MAAM,EAAE,KAAK,GAAG;AAE7B,YAAM,QAAQ,MAAM,IAAI,QAAQ,eAAe;AAC/C,aAAO,MAAM,MAAM,EAAE,KAAK,GAAG;AAG7B,YAAM,QAAQ,MAAM,IAAI,QAAQ,eAAe;AAC/C,aAAO,MAAM,MAAM,EAAE,KAAK,GAAG;AAG7B,YAAM,UAAU,MAAM,IAAI,QAAQ,YAAY;AAC9C,aAAO,QAAQ,MAAM,EAAE,KAAK,GAAG;AAE/B,YAAM,gBAAgB,MAAM,IAAI,QAAQ,cAAc;AACtD,aAAO,cAAc,MAAM,EAAE,KAAK,GAAG;AAGrC,YAAM,SAAS,MAAM,IAAI,QAAQ,WAAW;AAC5C,aAAO,OAAO,MAAM,EAAE,KAAK,GAAG;AAAA,IAChC,CAAC;AAAA,EACH,CAAC;AAED,WAAS,2BAA2B,MAAM;AACxC,OAAG,yDAAyD,YAAY;AACtE,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,mBAAmB,OAAO,YAAqB;AACxE,gBAAM,aAAa,QAAQ,QAAQ,IAAI,eAAe;AACtD,iBAAO,eAAe;AAAA,QACxB,CAAC;AAAA,MACH;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,aAAa,CAAC;AAAA,QAChC,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,WAAW,CAAC;AAGhD,YAAM,aAAa,MAAM,IAAI,QAAQ,YAAY;AACjD,aAAO,WAAW,MAAM,EAAE,KAAK,GAAG;AAGlC,YAAM,kBAAkB,MAAM,IAAI,QAAQ,cAAc;AAAA,QACtD,SAAS,EAAE,eAAe,uBAAuB;AAAA,MACnD,CAAC;AACD,aAAO,gBAAgB,MAAM,EAAE,KAAK,GAAG;AAGvC,YAAM,gBAAgB,MAAM,IAAI,QAAQ,cAAc;AAAA,QACpD,SAAS,EAAE,eAAe,qBAAqB;AAAA,MACjD,CAAC;AACD,aAAO,cAAc,MAAM,EAAE,KAAK,GAAG;AACrC,aAAO,MAAM,cAAc,KAAK,CAAC,EAAE,KAAK,WAAW;AAEnD,aAAO,KAAK,eAAe,EAAE,sBAAsB,CAAC;AAAA,IACtD,CAAC;AAED,OAAG,oDAAoD,YAAY;AACjE,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,IAAI,MAAM,oBAAoB,CAAC;AAAA,MAC9E;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,aAAa,CAAC;AAAA,QAChC,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,WAAW,CAAC;AAEhD,YAAM,MAAM,MAAM,IAAI,QAAQ,YAAY;AAC1C,aAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAAA,IAE7B,CAAC;AAED,OAAG,8CAA8C,YAAY;AAC3D,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,mBAAmB,OAAO,YAAqB;AACxE,gBAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;AAC3C,iBAAO,QAAQ,SAAS,uBAAuB;AAAA,QACjD,CAAC;AAAA,MACH;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,aAAa,CAAC;AAAA,QAChC,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,WAAW,CAAC;AAGhD,YAAM,cAAc,MAAM,IAAI,QAAQ,YAAY;AAClD,aAAO,YAAY,MAAM,EAAE,KAAK,GAAG;AAGnC,YAAM,iBAAiB,MAAM,IAAI,QAAQ,cAAc;AAAA,QACrD,SAAS,EAAE,QAAQ,qCAAqC;AAAA,MAC1D,CAAC;AACD,aAAO,eAAe,MAAM,EAAE,KAAK,GAAG;AACtC,aAAO,MAAM,eAAe,KAAK,CAAC,EAAE,KAAK,WAAW;AAAA,IACtD,CAAC;AAED,OAAG,iDAAiD,YAAY;AAC9D,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,mBAAmB,OAAO,YAAqB;AACxE,gBAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,iBAAO,IAAI,aAAa,IAAI,SAAS,MAAM;AAAA,QAC7C,CAAC;AAAA,MACH;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,YAAY,CAAC;AAAA,QAC/B,CAAC;AAAA,MACH;AACA,UAAI,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,MAAM,CAAC;AAG1C,YAAM,WAAW,MAAM,IAAI,QAAQ,WAAW;AAC9C,aAAO,SAAS,MAAM,EAAE,KAAK,GAAG;AAGhC,YAAM,gBAAgB,MAAM,IAAI,QAAQ,+BAA+B;AACvE,aAAO,cAAc,MAAM,EAAE,KAAK,GAAG;AAGrC,YAAM,cAAc,MAAM,IAAI,QAAQ,6BAA6B;AACnE,aAAO,YAAY,MAAM,EAAE,KAAK,GAAG;AACnC,aAAO,MAAM,YAAY,KAAK,CAAC,EAAE,KAAK,MAAM;AAAA,IAC9C,CAAC;AAAA,EACH,CAAC;AAED,WAAS,kBAAkB,MAAM;AAC/B,OAAG,6CAA6C,YAAY;AAC1D,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO;AAAA,YACL,EAAE,MAAM,cAAc,SAAS,CAAC,QAAQ,OAAO,QAAQ,EAAE;AAAA,YACzD,EAAE,MAAM,eAAe;AAAA,YACvB,EAAE,MAAM,gBAAgB,SAAS,CAAC,KAAK,EAAE;AAAA,YACzC,EAAE,MAAM,cAAc,SAAS,CAAC,MAAM,EAAE;AAAA,UAC1C;AAAA,QACF,CAAC;AAAA,MACH;AAGA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,WAAW,CAAC;AAChD,UAAI,KAAK,cAAc,CAAC,MAAM,EAAE,KAAK,YAAY,CAAC;AAClD,UAAI,IAAI,uBAAuB,CAAC,MAAM,EAAE,KAAK,gBAAgB,CAAC;AAC9D,UAAI,IAAI,gBAAgB,CAAC,MAAM,EAAE,KAAK,SAAS,CAAC;AAChD,UAAI,KAAK,mBAAmB,CAAC,MAAM,EAAE,KAAK,SAAS,CAAC;AACpD,UAAI,IAAI,WAAW,CAAC,MAAM,EAAE,KAAK,QAAQ,CAAC;AAG1C,YAAM,cAAc,MAAM,IAAI,QAAQ,YAAY;AAClD,aAAO,YAAY,MAAM,EAAE,KAAK,GAAG;AAGnC,YAAM,eAAe,MAAM,IAAI,QAAQ,cAAc,EAAE,QAAQ,OAAO,CAAC;AACvE,aAAO,aAAa,MAAM,EAAE,KAAK,GAAG;AAGpC,YAAM,WAAW,MAAM,IAAI,QAAQ,qBAAqB;AACxD,aAAO,SAAS,MAAM,EAAE,KAAK,GAAG;AAGhC,YAAM,aAAa,MAAM,IAAI,QAAQ,cAAc;AACnD,aAAO,WAAW,MAAM,EAAE,KAAK,GAAG;AAGlC,YAAM,aAAa,MAAM,IAAI,QAAQ,mBAAmB,EAAE,QAAQ,OAAO,CAAC;AAC1E,aAAO,WAAW,MAAM,EAAE,KAAK,GAAG;AAGlC,YAAM,YAAY,MAAM,IAAI,QAAQ,SAAS;AAC7C,aAAO,UAAU,MAAM,EAAE,KAAK,GAAG;AAAA,IACnC,CAAC;AAED,OAAG,6CAA6C,YAAY;AAC1D,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,IAAI;AAAA,MACnD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO;AAAA,YACL,EAAE,MAAM,SAAS;AAAA;AAAA,YACjB,EAAE,MAAM,eAAe,SAAS,CAAC,KAAK,EAAE;AAAA;AAAA,UAC1C;AAAA,QACF,CAAC;AAAA,MACH;AACA,UAAI,IAAI,eAAe,CAAC,MAAM,EAAE,KAAK,YAAY,CAAC;AAClD,UAAI,KAAK,eAAe,CAAC,MAAM,EAAE,KAAK,iBAAiB,CAAC;AAGxD,YAAM,SAAS,MAAM,IAAI,QAAQ,aAAa;AAC9C,aAAO,OAAO,MAAM,EAAE,KAAK,GAAG;AAE9B,YAAM,UAAU,MAAM,IAAI,QAAQ,eAAe,EAAE,QAAQ,OAAO,CAAC;AACnE,aAAO,QAAQ,MAAM,EAAE,KAAK,GAAG;AAG/B,aAAO,KAAK,eAAe,EAAE,sBAAsB,CAAC;AAAA,IACtD,CAAC;AAAA,EACH,CAAC;AAED,WAAS,cAAc,MAAM;AAC3B,OAAG,oDAAoD,YAAY;AACjE,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC,CAAC;AACvC,UAAI,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC;AAExC,YAAM,MAAM,MAAM,IAAI,QAAQ,WAAW;AACzC,aAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAC3B,aAAO,KAAK,eAAe,EAAE,IAAI,iBAAiB;AAAA,IACpD,CAAC;AAED,OAAG,6CAA6C,YAAY;AAC1D,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO;AAAA,YACL,EAAE,MAAM,sBAAsB;AAAA,YAC9B,EAAE,MAAM,eAAe;AAAA,YACvB,EAAE,MAAM,kBAAkB;AAAA,YAC1B,EAAE,MAAM,wBAAwB;AAAA,UAClC;AAAA,QACF,CAAC;AAAA,MACH;AAEA,UAAI,IAAI,uBAAuB,CAAC,MAAM,EAAE,KAAK,OAAO,CAAC;AACrD,UAAI,IAAI,uBAAuB,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC;AACnD,UAAI,IAAI,mBAAmB,CAAC,MAAM,EAAE,KAAK,MAAM,CAAC;AAChD,UAAI,IAAI,yBAAyB,CAAC,MAAM,EAAE,KAAK,YAAY,CAAC;AAG5D,YAAM,WAAW,MAAM,IAAI,QAAQ,qBAAqB;AACxD,aAAO,SAAS,MAAM,EAAE,KAAK,GAAG;AAEhC,YAAM,SAAS,MAAM,IAAI,QAAQ,qBAAqB;AACtD,aAAO,OAAO,MAAM,EAAE,KAAK,GAAG;AAE9B,YAAM,UAAU,MAAM,IAAI,QAAQ,iBAAiB;AACnD,aAAO,QAAQ,MAAM,EAAE,KAAK,GAAG;AAE/B,YAAM,gBAAgB,MAAM,IAAI,QAAQ,uBAAuB;AAC/D,aAAO,cAAc,MAAM,EAAE,KAAK,GAAG;AAAA,IACvC,CAAC;AAED,OAAG,0DAA0D,YAAY;AACvE,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,cAAc,CAAC;AAAA,QACjC,CAAC;AAAA,MACH;AACA,UAAI,IAAI,eAAe,CAAC,MAAM,EAAE,KAAK,QAAQ,CAAC;AAG9C,YAAM,MAAM,MAAM,IAAI,QAAQ,mCAAmC;AACjE,aAAO,IAAI,MAAM,EAAE,KAAK,GAAG;AAC3B,aAAO,KAAK,eAAe,EAAE,sBAAsB,CAAC;AAAA,IACtD,CAAC;AAED,OAAG,qCAAqC,YAAY;AAClD,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;AAAA,QACvB,CAAC;AAAA,MACH;AACA,UAAI,IAAI,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,CAAC;AAClC,UAAI,IAAI,UAAU,CAAC,MAAM,EAAE,KAAK,OAAO,CAAC;AAGxC,YAAM,UAAU,MAAM,IAAI,QAAQ,GAAG;AACrC,aAAO,QAAQ,MAAM,EAAE,KAAK,GAAG;AAG/B,YAAM,WAAW,MAAM,IAAI,QAAQ,QAAQ;AAC3C,aAAO,SAAS,MAAM,EAAE,KAAK,GAAG;AAAA,IAClC,CAAC;AAED,OAAG,qDAAqD,YAAY;AAClE,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,kBAAkB,KAAK;AAAA,MACpD;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,aAAa,GAAG,EAAE,MAAM,cAAc,CAAC;AAAA,QACzD,CAAC;AAAA,MACH;AACA,UAAI,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,OAAO,CAAC;AAC5C,UAAI,IAAI,eAAe,CAAC,MAAM,EAAE,KAAK,kBAAkB,CAAC;AAGxD,YAAM,eAAe,MAAM,IAAI,QAAQ,YAAY;AACnD,aAAO,aAAa,MAAM,EAAE,KAAK,GAAG;AAEpC,YAAM,YAAY,MAAM,IAAI,QAAQ,aAAa;AACjD,aAAO,UAAU,MAAM,EAAE,KAAK,GAAG;AAAA,IACnC,CAAC;AAAA,EACH,CAAC;AAED,WAAS,oCAAoC,MAAM;AACjD,OAAG,4CAA4C,YAAY;AACzD,YAAM,OAAiC;AAAA,QACrC,iBAAiB,KAAK,GAAG,EAAE,mBAAmB,OAAO,YAAqB;AACxE,gBAAM,aAAa,QAAQ,QAAQ,IAAI,eAAe;AACtD,gBAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAG/B,cAAI,IAAI,SAAS,WAAW,QAAQ,GAAG;AACrC,mBAAO,eAAe;AAAA,UACxB;AAGA,iBAAO,YAAY,WAAW,SAAS;AAAA,QACzC,CAAC;AAAA,MACH;AAEA,UAAI;AAAA,QACF,WAAW;AAAA,UACT;AAAA,UACA,OAAO,CAAC,EAAE,MAAM,WAAW,GAAG,EAAE,MAAM,SAAS,CAAC;AAAA,QAClD,CAAC;AAAA,MACH;AAEA,UAAI,IAAI,gBAAgB,CAAC,MAAM,EAAE,KAAK,aAAa,CAAC;AACpD,UAAI,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,UAAU,CAAC;AAG9C,YAAM,wBAAwB,MAAM,IAAI,QAAQ,gBAAgB;AAAA,QAC9D,SAAS,EAAE,eAAe,oBAAoB;AAAA,MAChD,CAAC;AACD,aAAO,sBAAsB,MAAM,EAAE,KAAK,GAAG;AAG7C,YAAM,sBAAsB,MAAM,IAAI,QAAQ,gBAAgB;AAAA,QAC5D,SAAS,EAAE,eAAe,qBAAqB;AAAA,MACjD,CAAC;AACD,aAAO,oBAAoB,MAAM,EAAE,KAAK,GAAG;AAG3C,YAAM,mBAAmB,MAAM,IAAI,QAAQ,aAAa;AAAA,QACtD,SAAS,EAAE,eAAe,oBAAoB;AAAA,MAChD,CAAC;AACD,aAAO,iBAAiB,MAAM,EAAE,KAAK,GAAG;AAAA,IAC1C,CAAC;AAAA,EACH,CAAC;AACH,CAAC;","names":[]}
|
|
@@ -158,5 +158,171 @@ describe("CSRF Protection", () => {
|
|
|
158
158
|
res = await app.request("/public/data", { method: "POST" });
|
|
159
159
|
expect(res.status).toBe(200);
|
|
160
160
|
});
|
|
161
|
+
describe("Origin bypass", () => {
|
|
162
|
+
it("should allow requests from configured origins", async () => {
|
|
163
|
+
app.use((0, import_csrf.csrf)({ origin: ["https://example.com", "https://trusted.com"] }));
|
|
164
|
+
app.post("/test", (c) => c.text("OK"));
|
|
165
|
+
const res = await app.request("/test", {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { Origin: "https://example.com" }
|
|
168
|
+
});
|
|
169
|
+
expect(res.status).toBe(200);
|
|
170
|
+
expect(await res.text()).toBe("OK");
|
|
171
|
+
});
|
|
172
|
+
it("should allow requests from any configured origin", async () => {
|
|
173
|
+
app.use((0, import_csrf.csrf)({ origin: ["https://example.com", "https://trusted.com"] }));
|
|
174
|
+
app.post("/test", (c) => c.text("OK"));
|
|
175
|
+
const res = await app.request("/test", {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: { Origin: "https://trusted.com" }
|
|
178
|
+
});
|
|
179
|
+
expect(res.status).toBe(200);
|
|
180
|
+
});
|
|
181
|
+
it("should reject requests from non-configured origins", async () => {
|
|
182
|
+
app.use((0, import_csrf.csrf)({ origin: ["https://example.com"] }));
|
|
183
|
+
app.post("/test", (c) => c.text("OK"));
|
|
184
|
+
const res = await app.request("/test", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { Origin: "https://malicious.com" }
|
|
187
|
+
});
|
|
188
|
+
expect(res.status).toBe(403);
|
|
189
|
+
});
|
|
190
|
+
it("should reject requests without origin header", async () => {
|
|
191
|
+
app.use((0, import_csrf.csrf)({ origin: ["https://example.com"] }));
|
|
192
|
+
app.post("/test", (c) => c.text("OK"));
|
|
193
|
+
const res = await app.request("/test", { method: "POST" });
|
|
194
|
+
expect(res.status).toBe(403);
|
|
195
|
+
});
|
|
196
|
+
it("should handle empty origin list", async () => {
|
|
197
|
+
app.use((0, import_csrf.csrf)({ origin: [] }));
|
|
198
|
+
app.post("/test", (c) => c.text("OK"));
|
|
199
|
+
const res = await app.request("/test", {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: { Origin: "https://example.com" }
|
|
202
|
+
});
|
|
203
|
+
expect(res.status).toBe(403);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe("Sec-Fetch-Site bypass", () => {
|
|
207
|
+
it("should allow requests with configured sec-fetch-site values", async () => {
|
|
208
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin", "same-site"] }));
|
|
209
|
+
app.post("/test", (c) => c.text("OK"));
|
|
210
|
+
const res = await app.request("/test", {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: { "Sec-Fetch-Site": "same-origin" }
|
|
213
|
+
});
|
|
214
|
+
expect(res.status).toBe(200);
|
|
215
|
+
expect(await res.text()).toBe("OK");
|
|
216
|
+
});
|
|
217
|
+
it("should allow requests with any configured sec-fetch-site value", async () => {
|
|
218
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin", "same-site", "cross-origin"] }));
|
|
219
|
+
app.post("/test", (c) => c.text("OK"));
|
|
220
|
+
const res = await app.request("/test", {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: { "Sec-Fetch-Site": "cross-origin" }
|
|
223
|
+
});
|
|
224
|
+
expect(res.status).toBe(200);
|
|
225
|
+
});
|
|
226
|
+
it("should reject requests with non-configured sec-fetch-site values", async () => {
|
|
227
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin"] }));
|
|
228
|
+
app.post("/test", (c) => c.text("OK"));
|
|
229
|
+
const res = await app.request("/test", {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
232
|
+
});
|
|
233
|
+
expect(res.status).toBe(403);
|
|
234
|
+
});
|
|
235
|
+
it("should reject requests without sec-fetch-site header", async () => {
|
|
236
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin"] }));
|
|
237
|
+
app.post("/test", (c) => c.text("OK"));
|
|
238
|
+
const res = await app.request("/test", { method: "POST" });
|
|
239
|
+
expect(res.status).toBe(403);
|
|
240
|
+
});
|
|
241
|
+
it("should handle all valid sec-fetch-site values", async () => {
|
|
242
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin", "same-site", "none", "cross-origin"] }));
|
|
243
|
+
app.post("/test", (c) => c.text("OK"));
|
|
244
|
+
const validValues = ["same-origin", "same-site", "none", "cross-origin"];
|
|
245
|
+
for (const value of validValues) {
|
|
246
|
+
const res = await app.request("/test", {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: { "Sec-Fetch-Site": value }
|
|
249
|
+
});
|
|
250
|
+
expect(res.status).toBe(200);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
it("should handle empty sec-fetch-site list", async () => {
|
|
254
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: [] }));
|
|
255
|
+
app.post("/test", (c) => c.text("OK"));
|
|
256
|
+
const res = await app.request("/test", {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: { "Sec-Fetch-Site": "same-origin" }
|
|
259
|
+
});
|
|
260
|
+
expect(res.status).toBe(403);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
describe("Combined origin and sec-fetch-site bypass", () => {
|
|
264
|
+
it("should allow requests matching either origin or sec-fetch-site", async () => {
|
|
265
|
+
app.use(
|
|
266
|
+
(0, import_csrf.csrf)({
|
|
267
|
+
origin: ["https://example.com"],
|
|
268
|
+
secFetchSite: ["same-site"]
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
app.post("/test", (c) => c.text("OK"));
|
|
272
|
+
let res = await app.request("/test", {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: { Origin: "https://example.com" }
|
|
275
|
+
});
|
|
276
|
+
expect(res.status).toBe(200);
|
|
277
|
+
res = await app.request("/test", {
|
|
278
|
+
method: "POST",
|
|
279
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
280
|
+
});
|
|
281
|
+
expect(res.status).toBe(200);
|
|
282
|
+
});
|
|
283
|
+
it("should reject requests matching neither origin nor sec-fetch-site", async () => {
|
|
284
|
+
app.use(
|
|
285
|
+
(0, import_csrf.csrf)({
|
|
286
|
+
origin: ["https://example.com"],
|
|
287
|
+
secFetchSite: ["same-origin"]
|
|
288
|
+
})
|
|
289
|
+
);
|
|
290
|
+
app.post("/test", (c) => c.text("OK"));
|
|
291
|
+
const res = await app.request("/test", {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: {
|
|
294
|
+
Origin: "https://malicious.com",
|
|
295
|
+
"Sec-Fetch-Site": "cross-origin"
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
expect(res.status).toBe(403);
|
|
299
|
+
});
|
|
300
|
+
it("should work with origin, sec-fetch-site and ignore rules combined", async () => {
|
|
301
|
+
app.use(
|
|
302
|
+
(0, import_csrf.csrf)({
|
|
303
|
+
origin: ["https://example.com"],
|
|
304
|
+
secFetchSite: ["same-site"],
|
|
305
|
+
ignores: [{ path: "/webhook/*", methods: ["POST"] }]
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
app.post("/test", (c) => c.text("OK"));
|
|
309
|
+
app.post("/webhook/stripe", (c) => c.text("OK"));
|
|
310
|
+
app.post("/api/data", (c) => c.text("OK"));
|
|
311
|
+
let res = await app.request("/test", {
|
|
312
|
+
method: "POST",
|
|
313
|
+
headers: { Origin: "https://example.com" }
|
|
314
|
+
});
|
|
315
|
+
expect(res.status).toBe(200);
|
|
316
|
+
res = await app.request("/test", {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
319
|
+
});
|
|
320
|
+
expect(res.status).toBe(200);
|
|
321
|
+
res = await app.request("/webhook/stripe", { method: "POST" });
|
|
322
|
+
expect(res.status).toBe(200);
|
|
323
|
+
res = await app.request("/api/data", { method: "POST" });
|
|
324
|
+
expect(res.status).toBe(403);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
161
327
|
});
|
|
162
328
|
//# sourceMappingURL=csrf.test.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hono/__tests__/csrf.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { csrf } from '../csrf';\nimport { errorHandler, type Env } from '../handler';\n\ndescribe('CSRF Protection', () => {\n let app: Hono<Env>;\n\n beforeEach(() => {\n app = new Hono();\n app.onError(errorHandler);\n });\n\n it('should allow GET requests without CSRF token', async () => {\n app.use(csrf());\n app.get('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test');\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow HEAD requests without CSRF token', async () => {\n app.use(csrf());\n app.all('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(200);\n });\n\n it('should allow OPTIONS requests without CSRF token', async () => {\n app.use(csrf());\n app.options('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'OPTIONS' });\n expect(res.status).toBe(200);\n });\n\n it('should reject POST requests without CSRF token', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should reject POST requests with mismatched tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'header-token', Cookie: 'XSRF-TOKEN=cookie-token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should allow POST requests with matching tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-XSRF-TOKEN': 'matching-token',\n Cookie: 'XSRF-TOKEN=matching-token',\n },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should use custom cookie and header names', async () => {\n app.use(csrf({ cookieName: 'csrf-token', headerName: 'X-CSRF-Token' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-CSRF-Token': 'test-token',\n Cookie: 'csrf-token=test-token',\n },\n });\n expect(res.status).toBe(200);\n });\n\n it('should ignore specified paths', async () => {\n app.use(csrf({ ignores: [{ path: '/webhook/*', methods: ['POST'] }] }));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Ignored path should work without CSRF token\n const webhookRes = await app.request('/webhook/stripe', { method: 'POST' });\n expect(webhookRes.status).toBe(200);\n\n // Non-ignored path should require CSRF token\n const apiRes = await app.request('/api/data', { method: 'POST' });\n expect(apiRes.status).toBe(403);\n });\n\n it('should ignore all methods for a path when methods not specified', async () => {\n app.use(csrf({ ignores: [{ path: '/auth/apple/callback' }] }));\n app.post('/auth/apple/callback', (c) => c.text('OK'));\n app.put('/auth/apple/callback', (c) => c.text('OK'));\n\n const postRes = await app.request('/auth/apple/callback', { method: 'POST' });\n expect(postRes.status).toBe(200);\n\n const putRes = await app.request('/auth/apple/callback', { method: 'PUT' });\n expect(putRes.status).toBe(200);\n });\n\n it('should handle empty tokens safely', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': '', Cookie: 'XSRF-TOKEN=' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing cookie', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing header', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Cookie: 'XSRF-TOKEN=token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should use custom error message', async () => {\n app.use(csrf({ errorMessage: 'Custom CSRF error' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n const body = await res.json();\n expect(body.error.message).toBe('Custom CSRF error');\n });\n\n it('should work with custom safe methods', async () => {\n app.use(csrf({ safeMethods: ['GET'] }));\n app.all('/test', (c) => c.text('OK'));\n\n // HEAD is no longer safe, should require CSRF token\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(403);\n });\n\n it('should handle complex ignore patterns', async () => {\n app.use(\n csrf({\n ignores: [\n { path: '/api/v1/*', methods: ['GET', 'POST'] },\n { path: '/api/v2/*', methods: ['POST'] },\n { path: '/public/*' }, // All methods\n ],\n })\n );\n\n app.get('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v1/users', (c) => c.text('OK'));\n app.put('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v2/users', (c) => c.text('OK'));\n app.get('/api/v2/users', (c) => c.text('OK'));\n app.post('/public/data', (c) => c.text('OK'));\n\n // Ignored GET and POST for /api/v1/*\n let res = await app.request('/api/v1/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n res = await app.request('/api/v1/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // PUT not ignored for /api/v1/*\n res = await app.request('/api/v1/users', { method: 'PUT' });\n expect(res.status).toBe(403);\n\n // Only POST ignored for /api/v2/*\n res = await app.request('/api/v2/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // GET not ignored for /api/v2/* (but GET is safe by default)\n res = await app.request('/api/v2/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n // All methods ignored for /public/*\n res = await app.request('/public/data', { method: 'POST' });\n expect(res.status).toBe(200);\n });\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/hono/__tests__/csrf.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { csrf } from '../csrf';\nimport { type Env, errorHandler } from '../handler';\n\ndescribe('CSRF Protection', () => {\n let app: Hono<Env>;\n\n beforeEach(() => {\n app = new Hono();\n app.onError(errorHandler);\n });\n\n it('should allow GET requests without CSRF token', async () => {\n app.use(csrf());\n app.get('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test');\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow HEAD requests without CSRF token', async () => {\n app.use(csrf());\n app.all('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(200);\n });\n\n it('should allow OPTIONS requests without CSRF token', async () => {\n app.use(csrf());\n app.options('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'OPTIONS' });\n expect(res.status).toBe(200);\n });\n\n it('should reject POST requests without CSRF token', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should reject POST requests with mismatched tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'header-token', Cookie: 'XSRF-TOKEN=cookie-token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should allow POST requests with matching tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-XSRF-TOKEN': 'matching-token',\n Cookie: 'XSRF-TOKEN=matching-token',\n },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should use custom cookie and header names', async () => {\n app.use(csrf({ cookieName: 'csrf-token', headerName: 'X-CSRF-Token' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-CSRF-Token': 'test-token',\n Cookie: 'csrf-token=test-token',\n },\n });\n expect(res.status).toBe(200);\n });\n\n it('should ignore specified paths', async () => {\n app.use(csrf({ ignores: [{ path: '/webhook/*', methods: ['POST'] }] }));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Ignored path should work without CSRF token\n const webhookRes = await app.request('/webhook/stripe', { method: 'POST' });\n expect(webhookRes.status).toBe(200);\n\n // Non-ignored path should require CSRF token\n const apiRes = await app.request('/api/data', { method: 'POST' });\n expect(apiRes.status).toBe(403);\n });\n\n it('should ignore all methods for a path when methods not specified', async () => {\n app.use(csrf({ ignores: [{ path: '/auth/apple/callback' }] }));\n app.post('/auth/apple/callback', (c) => c.text('OK'));\n app.put('/auth/apple/callback', (c) => c.text('OK'));\n\n const postRes = await app.request('/auth/apple/callback', { method: 'POST' });\n expect(postRes.status).toBe(200);\n\n const putRes = await app.request('/auth/apple/callback', { method: 'PUT' });\n expect(putRes.status).toBe(200);\n });\n\n it('should handle empty tokens safely', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': '', Cookie: 'XSRF-TOKEN=' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing cookie', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing header', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Cookie: 'XSRF-TOKEN=token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should use custom error message', async () => {\n app.use(csrf({ errorMessage: 'Custom CSRF error' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n const body = await res.json();\n expect(body.error.message).toBe('Custom CSRF error');\n });\n\n it('should work with custom safe methods', async () => {\n app.use(csrf({ safeMethods: ['GET'] }));\n app.all('/test', (c) => c.text('OK'));\n\n // HEAD is no longer safe, should require CSRF token\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(403);\n });\n\n it('should handle complex ignore patterns', async () => {\n app.use(\n csrf({\n ignores: [\n { path: '/api/v1/*', methods: ['GET', 'POST'] },\n { path: '/api/v2/*', methods: ['POST'] },\n { path: '/public/*' }, // All methods\n ],\n })\n );\n\n app.get('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v1/users', (c) => c.text('OK'));\n app.put('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v2/users', (c) => c.text('OK'));\n app.get('/api/v2/users', (c) => c.text('OK'));\n app.post('/public/data', (c) => c.text('OK'));\n\n // Ignored GET and POST for /api/v1/*\n let res = await app.request('/api/v1/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n res = await app.request('/api/v1/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // PUT not ignored for /api/v1/*\n res = await app.request('/api/v1/users', { method: 'PUT' });\n expect(res.status).toBe(403);\n\n // Only POST ignored for /api/v2/*\n res = await app.request('/api/v2/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // GET not ignored for /api/v2/* (but GET is safe by default)\n res = await app.request('/api/v2/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n // All methods ignored for /public/*\n res = await app.request('/public/data', { method: 'POST' });\n expect(res.status).toBe(200);\n });\n\n describe('Origin bypass', () => {\n it('should allow requests from configured origins', async () => {\n app.use(csrf({ origin: ['https://example.com', 'https://trusted.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n // Request from allowed origin should work without CSRF token\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow requests from any configured origin', async () => {\n app.use(csrf({ origin: ['https://example.com', 'https://trusted.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://trusted.com' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests from non-configured origins', async () => {\n app.use(csrf({ origin: ['https://example.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://malicious.com' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should reject requests without origin header', async () => {\n app.use(csrf({ origin: ['https://example.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should handle empty origin list', async () => {\n app.use(csrf({ origin: [] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(403);\n });\n });\n\n describe('Sec-Fetch-Site bypass', () => {\n it('should allow requests with configured sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-origin' },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow requests with any configured sec-fetch-site value', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site', 'cross-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'cross-origin' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests with non-configured sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should reject requests without sec-fetch-site header', async () => {\n app.use(csrf({ secFetchSite: ['same-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should handle all valid sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site', 'none', 'cross-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const validValues = ['same-origin', 'same-site', 'none', 'cross-origin'];\n\n for (const value of validValues) {\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': value },\n });\n expect(res.status).toBe(200);\n }\n });\n\n it('should handle empty sec-fetch-site list', async () => {\n app.use(csrf({ secFetchSite: [] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-origin' },\n });\n expect(res.status).toBe(403);\n });\n });\n\n describe('Combined origin and sec-fetch-site bypass', () => {\n it('should allow requests matching either origin or sec-fetch-site', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-site'],\n })\n );\n app.post('/test', (c) => c.text('OK'));\n\n // Should work with matching origin\n let res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n\n // Should work with matching sec-fetch-site\n res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests matching neither origin nor sec-fetch-site', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-origin'],\n })\n );\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n Origin: 'https://malicious.com',\n 'Sec-Fetch-Site': 'cross-origin',\n },\n });\n expect(res.status).toBe(403);\n });\n\n it('should work with origin, sec-fetch-site and ignore rules combined', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-site'],\n ignores: [{ path: '/webhook/*', methods: ['POST'] }],\n })\n );\n\n app.post('/test', (c) => c.text('OK'));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Should work with origin bypass\n let res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n\n // Should work with sec-fetch-site bypass\n res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(200);\n\n // Should work with ignore rule\n res = await app.request('/webhook/stripe', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // Should fail without any bypass\n res = await app.request('/api/data', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n });\n});\n"],"mappings":"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","names":[]}
|
|
@@ -156,5 +156,171 @@ describe("CSRF Protection", () => {
|
|
|
156
156
|
res = await app.request("/public/data", { method: "POST" });
|
|
157
157
|
expect(res.status).toBe(200);
|
|
158
158
|
});
|
|
159
|
+
describe("Origin bypass", () => {
|
|
160
|
+
it("should allow requests from configured origins", async () => {
|
|
161
|
+
app.use(csrf({ origin: ["https://example.com", "https://trusted.com"] }));
|
|
162
|
+
app.post("/test", (c) => c.text("OK"));
|
|
163
|
+
const res = await app.request("/test", {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: { Origin: "https://example.com" }
|
|
166
|
+
});
|
|
167
|
+
expect(res.status).toBe(200);
|
|
168
|
+
expect(await res.text()).toBe("OK");
|
|
169
|
+
});
|
|
170
|
+
it("should allow requests from any configured origin", async () => {
|
|
171
|
+
app.use(csrf({ origin: ["https://example.com", "https://trusted.com"] }));
|
|
172
|
+
app.post("/test", (c) => c.text("OK"));
|
|
173
|
+
const res = await app.request("/test", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { Origin: "https://trusted.com" }
|
|
176
|
+
});
|
|
177
|
+
expect(res.status).toBe(200);
|
|
178
|
+
});
|
|
179
|
+
it("should reject requests from non-configured origins", async () => {
|
|
180
|
+
app.use(csrf({ origin: ["https://example.com"] }));
|
|
181
|
+
app.post("/test", (c) => c.text("OK"));
|
|
182
|
+
const res = await app.request("/test", {
|
|
183
|
+
method: "POST",
|
|
184
|
+
headers: { Origin: "https://malicious.com" }
|
|
185
|
+
});
|
|
186
|
+
expect(res.status).toBe(403);
|
|
187
|
+
});
|
|
188
|
+
it("should reject requests without origin header", async () => {
|
|
189
|
+
app.use(csrf({ origin: ["https://example.com"] }));
|
|
190
|
+
app.post("/test", (c) => c.text("OK"));
|
|
191
|
+
const res = await app.request("/test", { method: "POST" });
|
|
192
|
+
expect(res.status).toBe(403);
|
|
193
|
+
});
|
|
194
|
+
it("should handle empty origin list", async () => {
|
|
195
|
+
app.use(csrf({ origin: [] }));
|
|
196
|
+
app.post("/test", (c) => c.text("OK"));
|
|
197
|
+
const res = await app.request("/test", {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: { Origin: "https://example.com" }
|
|
200
|
+
});
|
|
201
|
+
expect(res.status).toBe(403);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
describe("Sec-Fetch-Site bypass", () => {
|
|
205
|
+
it("should allow requests with configured sec-fetch-site values", async () => {
|
|
206
|
+
app.use(csrf({ secFetchSite: ["same-origin", "same-site"] }));
|
|
207
|
+
app.post("/test", (c) => c.text("OK"));
|
|
208
|
+
const res = await app.request("/test", {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: { "Sec-Fetch-Site": "same-origin" }
|
|
211
|
+
});
|
|
212
|
+
expect(res.status).toBe(200);
|
|
213
|
+
expect(await res.text()).toBe("OK");
|
|
214
|
+
});
|
|
215
|
+
it("should allow requests with any configured sec-fetch-site value", async () => {
|
|
216
|
+
app.use(csrf({ secFetchSite: ["same-origin", "same-site", "cross-origin"] }));
|
|
217
|
+
app.post("/test", (c) => c.text("OK"));
|
|
218
|
+
const res = await app.request("/test", {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: { "Sec-Fetch-Site": "cross-origin" }
|
|
221
|
+
});
|
|
222
|
+
expect(res.status).toBe(200);
|
|
223
|
+
});
|
|
224
|
+
it("should reject requests with non-configured sec-fetch-site values", async () => {
|
|
225
|
+
app.use(csrf({ secFetchSite: ["same-origin"] }));
|
|
226
|
+
app.post("/test", (c) => c.text("OK"));
|
|
227
|
+
const res = await app.request("/test", {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
230
|
+
});
|
|
231
|
+
expect(res.status).toBe(403);
|
|
232
|
+
});
|
|
233
|
+
it("should reject requests without sec-fetch-site header", async () => {
|
|
234
|
+
app.use(csrf({ secFetchSite: ["same-origin"] }));
|
|
235
|
+
app.post("/test", (c) => c.text("OK"));
|
|
236
|
+
const res = await app.request("/test", { method: "POST" });
|
|
237
|
+
expect(res.status).toBe(403);
|
|
238
|
+
});
|
|
239
|
+
it("should handle all valid sec-fetch-site values", async () => {
|
|
240
|
+
app.use(csrf({ secFetchSite: ["same-origin", "same-site", "none", "cross-origin"] }));
|
|
241
|
+
app.post("/test", (c) => c.text("OK"));
|
|
242
|
+
const validValues = ["same-origin", "same-site", "none", "cross-origin"];
|
|
243
|
+
for (const value of validValues) {
|
|
244
|
+
const res = await app.request("/test", {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: { "Sec-Fetch-Site": value }
|
|
247
|
+
});
|
|
248
|
+
expect(res.status).toBe(200);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
it("should handle empty sec-fetch-site list", async () => {
|
|
252
|
+
app.use(csrf({ secFetchSite: [] }));
|
|
253
|
+
app.post("/test", (c) => c.text("OK"));
|
|
254
|
+
const res = await app.request("/test", {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: { "Sec-Fetch-Site": "same-origin" }
|
|
257
|
+
});
|
|
258
|
+
expect(res.status).toBe(403);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
describe("Combined origin and sec-fetch-site bypass", () => {
|
|
262
|
+
it("should allow requests matching either origin or sec-fetch-site", async () => {
|
|
263
|
+
app.use(
|
|
264
|
+
csrf({
|
|
265
|
+
origin: ["https://example.com"],
|
|
266
|
+
secFetchSite: ["same-site"]
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
app.post("/test", (c) => c.text("OK"));
|
|
270
|
+
let res = await app.request("/test", {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: { Origin: "https://example.com" }
|
|
273
|
+
});
|
|
274
|
+
expect(res.status).toBe(200);
|
|
275
|
+
res = await app.request("/test", {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
278
|
+
});
|
|
279
|
+
expect(res.status).toBe(200);
|
|
280
|
+
});
|
|
281
|
+
it("should reject requests matching neither origin nor sec-fetch-site", async () => {
|
|
282
|
+
app.use(
|
|
283
|
+
csrf({
|
|
284
|
+
origin: ["https://example.com"],
|
|
285
|
+
secFetchSite: ["same-origin"]
|
|
286
|
+
})
|
|
287
|
+
);
|
|
288
|
+
app.post("/test", (c) => c.text("OK"));
|
|
289
|
+
const res = await app.request("/test", {
|
|
290
|
+
method: "POST",
|
|
291
|
+
headers: {
|
|
292
|
+
Origin: "https://malicious.com",
|
|
293
|
+
"Sec-Fetch-Site": "cross-origin"
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
expect(res.status).toBe(403);
|
|
297
|
+
});
|
|
298
|
+
it("should work with origin, sec-fetch-site and ignore rules combined", async () => {
|
|
299
|
+
app.use(
|
|
300
|
+
csrf({
|
|
301
|
+
origin: ["https://example.com"],
|
|
302
|
+
secFetchSite: ["same-site"],
|
|
303
|
+
ignores: [{ path: "/webhook/*", methods: ["POST"] }]
|
|
304
|
+
})
|
|
305
|
+
);
|
|
306
|
+
app.post("/test", (c) => c.text("OK"));
|
|
307
|
+
app.post("/webhook/stripe", (c) => c.text("OK"));
|
|
308
|
+
app.post("/api/data", (c) => c.text("OK"));
|
|
309
|
+
let res = await app.request("/test", {
|
|
310
|
+
method: "POST",
|
|
311
|
+
headers: { Origin: "https://example.com" }
|
|
312
|
+
});
|
|
313
|
+
expect(res.status).toBe(200);
|
|
314
|
+
res = await app.request("/test", {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
317
|
+
});
|
|
318
|
+
expect(res.status).toBe(200);
|
|
319
|
+
res = await app.request("/webhook/stripe", { method: "POST" });
|
|
320
|
+
expect(res.status).toBe(200);
|
|
321
|
+
res = await app.request("/api/data", { method: "POST" });
|
|
322
|
+
expect(res.status).toBe(403);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
159
325
|
});
|
|
160
326
|
//# sourceMappingURL=csrf.test.mjs.map
|