repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | import { describe, it, expect, vi, beforeEach } from 'vitest'; |
| #2 | import { isDevMode, extractJWT } from './middleware'; |
| #3 | import type { MoltbotEnv } from '../types'; |
| #4 | import type { Context } from 'hono'; |
| #5 | import type { AppEnv } from '../types'; |
| #6 | import { createMockEnv } from '../test-utils'; |
| #7 | |
| #8 | describe('isDevMode', () => { |
| #9 | it('returns true when DEV_MODE is "true"', () => { |
| #10 | const env = createMockEnv({ DEV_MODE: 'true' }); |
| #11 | expect(isDevMode(env)).toBe(true); |
| #12 | }); |
| #13 | |
| #14 | it('returns false when DEV_MODE is undefined', () => { |
| #15 | const env = createMockEnv(); |
| #16 | expect(isDevMode(env)).toBe(false); |
| #17 | }); |
| #18 | |
| #19 | it('returns false when DEV_MODE is "false"', () => { |
| #20 | const env = createMockEnv({ DEV_MODE: 'false' }); |
| #21 | expect(isDevMode(env)).toBe(false); |
| #22 | }); |
| #23 | |
| #24 | it('returns false when DEV_MODE is any other value', () => { |
| #25 | const env = createMockEnv({ DEV_MODE: 'yes' }); |
| #26 | expect(isDevMode(env)).toBe(false); |
| #27 | }); |
| #28 | |
| #29 | it('returns false when DEV_MODE is empty string', () => { |
| #30 | const env = createMockEnv({ DEV_MODE: '' }); |
| #31 | expect(isDevMode(env)).toBe(false); |
| #32 | }); |
| #33 | }); |
| #34 | |
| #35 | describe('extractJWT', () => { |
| #36 | // Helper to create a mock context |
| #37 | function createMockContext(options: { |
| #38 | jwtHeader?: string; |
| #39 | cookies?: string; |
| #40 | }): Context<AppEnv> { |
| #41 | const headers = new Headers(); |
| #42 | if (options.jwtHeader) { |
| #43 | headers.set('CF-Access-JWT-Assertion', options.jwtHeader); |
| #44 | } |
| #45 | if (options.cookies) { |
| #46 | headers.set('Cookie', options.cookies); |
| #47 | } |
| #48 | |
| #49 | return { |
| #50 | req: { |
| #51 | header: (name: string) => headers.get(name), |
| #52 | raw: { |
| #53 | headers, |
| #54 | }, |
| #55 | }, |
| #56 | } as unknown as Context<AppEnv>; |
| #57 | } |
| #58 | |
| #59 | it('extracts JWT from CF-Access-JWT-Assertion header', () => { |
| #60 | const jwt = 'header.payload.signature'; |
| #61 | const c = createMockContext({ jwtHeader: jwt }); |
| #62 | expect(extractJWT(c)).toBe(jwt); |
| #63 | }); |
| #64 | |
| #65 | it('extracts JWT from CF_Authorization cookie', () => { |
| #66 | const jwt = 'cookie.payload.signature'; |
| #67 | const c = createMockContext({ cookies: `CF_Authorization=${jwt}` }); |
| #68 | expect(extractJWT(c)).toBe(jwt); |
| #69 | }); |
| #70 | |
| #71 | it('extracts JWT from CF_Authorization cookie with other cookies', () => { |
| #72 | const jwt = 'cookie.payload.signature'; |
| #73 | const c = createMockContext({ |
| #74 | cookies: `other=value; CF_Authorization=${jwt}; another=test` |
| #75 | }); |
| #76 | expect(extractJWT(c)).toBe(jwt); |
| #77 | }); |
| #78 | |
| #79 | it('prefers header over cookie', () => { |
| #80 | const headerJwt = 'header.jwt.token'; |
| #81 | const cookieJwt = 'cookie.jwt.token'; |
| #82 | const c = createMockContext({ |
| #83 | jwtHeader: headerJwt, |
| #84 | cookies: `CF_Authorization=${cookieJwt}` |
| #85 | }); |
| #86 | expect(extractJWT(c)).toBe(headerJwt); |
| #87 | }); |
| #88 | |
| #89 | it('returns null when no JWT present', () => { |
| #90 | const c = createMockContext({}); |
| #91 | expect(extractJWT(c)).toBeNull(); |
| #92 | }); |
| #93 | |
| #94 | it('returns null when cookie header exists but no CF_Authorization', () => { |
| #95 | const c = createMockContext({ cookies: 'other=value; session=abc123' }); |
| #96 | expect(extractJWT(c)).toBeNull(); |
| #97 | }); |
| #98 | |
| #99 | it('handles cookie with whitespace', () => { |
| #100 | const jwt = 'spaced.payload.signature'; |
| #101 | const c = createMockContext({ cookies: ` CF_Authorization=${jwt} ` }); |
| #102 | expect(extractJWT(c)).toBe(jwt); |
| #103 | }); |
| #104 | }); |
| #105 | |
| #106 | describe('createAccessMiddleware', () => { |
| #107 | // Import the function dynamically to allow mocking |
| #108 | let createAccessMiddleware: typeof import('./middleware').createAccessMiddleware; |
| #109 | |
| #110 | beforeEach(async () => { |
| #111 | vi.resetModules(); |
| #112 | const module = await import('./middleware'); |
| #113 | createAccessMiddleware = module.createAccessMiddleware; |
| #114 | }); |
| #115 | |
| #116 | // Helper to create a mock context with full implementation |
| #117 | function createFullMockContext(options: { |
| #118 | env?: Partial<MoltbotEnv>; |
| #119 | jwtHeader?: string; |
| #120 | cookies?: string; |
| #121 | }): { c: Context<AppEnv>; jsonMock: ReturnType<typeof vi.fn>; htmlMock: ReturnType<typeof vi.fn>; redirectMock: ReturnType<typeof vi.fn>; setMock: ReturnType<typeof vi.fn> } { |
| #122 | const headers = new Headers(); |
| #123 | if (options.jwtHeader) { |
| #124 | headers.set('CF-Access-JWT-Assertion', options.jwtHeader); |
| #125 | } |
| #126 | if (options.cookies) { |
| #127 | headers.set('Cookie', options.cookies); |
| #128 | } |
| #129 | |
| #130 | const jsonMock = vi.fn().mockReturnValue(new Response()); |
| #131 | const htmlMock = vi.fn().mockReturnValue(new Response()); |
| #132 | const redirectMock = vi.fn().mockReturnValue(new Response()); |
| #133 | const setMock = vi.fn(); |
| #134 | |
| #135 | const c = { |
| #136 | req: { |
| #137 | header: (name: string) => headers.get(name), |
| #138 | raw: { headers }, |
| #139 | }, |
| #140 | env: createMockEnv(options.env), |
| #141 | json: jsonMock, |
| #142 | html: htmlMock, |
| #143 | redirect: redirectMock, |
| #144 | set: setMock, |
| #145 | } as unknown as Context<AppEnv>; |
| #146 | |
| #147 | return { c, jsonMock, htmlMock, redirectMock, setMock }; |
| #148 | } |
| #149 | |
| #150 | it('skips auth and sets dev user when DEV_MODE is true', async () => { |
| #151 | const { c, setMock } = createFullMockContext({ env: { DEV_MODE: 'true' } }); |
| #152 | const middleware = createAccessMiddleware({ type: 'json' }); |
| #153 | const next = vi.fn(); |
| #154 | |
| #155 | await middleware(c, next); |
| #156 | |
| #157 | expect(next).toHaveBeenCalled(); |
| #158 | expect(setMock).toHaveBeenCalledWith('accessUser', { email: 'dev@localhost', name: 'Dev User' }); |
| #159 | }); |
| #160 | |
| #161 | it('returns 500 JSON error when CF Access not configured', async () => { |
| #162 | const { c, jsonMock } = createFullMockContext({ env: {} }); |
| #163 | const middleware = createAccessMiddleware({ type: 'json' }); |
| #164 | const next = vi.fn(); |
| #165 | |
| #166 | await middleware(c, next); |
| #167 | |
| #168 | expect(next).not.toHaveBeenCalled(); |
| #169 | expect(jsonMock).toHaveBeenCalledWith( |
| #170 | expect.objectContaining({ error: 'Cloudflare Access not configured' }), |
| #171 | 500 |
| #172 | ); |
| #173 | }); |
| #174 | |
| #175 | it('returns 500 HTML error when CF Access not configured', async () => { |
| #176 | const { c, htmlMock } = createFullMockContext({ env: {} }); |
| #177 | const middleware = createAccessMiddleware({ type: 'html' }); |
| #178 | const next = vi.fn(); |
| #179 | |
| #180 | await middleware(c, next); |
| #181 | |
| #182 | expect(next).not.toHaveBeenCalled(); |
| #183 | expect(htmlMock).toHaveBeenCalledWith( |
| #184 | expect.stringContaining('Admin UI Not Configured'), |
| #185 | 500 |
| #186 | ); |
| #187 | }); |
| #188 | |
| #189 | it('returns 401 JSON error when JWT is missing', async () => { |
| #190 | const { c, jsonMock } = createFullMockContext({ |
| #191 | env: { CF_ACCESS_TEAM_DOMAIN: 'team.cloudflareaccess.com', CF_ACCESS_AUD: 'aud123' } |
| #192 | }); |
| #193 | const middleware = createAccessMiddleware({ type: 'json' }); |
| #194 | const next = vi.fn(); |
| #195 | |
| #196 | await middleware(c, next); |
| #197 | |
| #198 | expect(next).not.toHaveBeenCalled(); |
| #199 | expect(jsonMock).toHaveBeenCalledWith( |
| #200 | expect.objectContaining({ error: 'Unauthorized' }), |
| #201 | 401 |
| #202 | ); |
| #203 | }); |
| #204 | |
| #205 | it('returns 401 HTML error when JWT is missing', async () => { |
| #206 | const { c, htmlMock } = createFullMockContext({ |
| #207 | env: { CF_ACCESS_TEAM_DOMAIN: 'team.cloudflareaccess.com', CF_ACCESS_AUD: 'aud123' } |
| #208 | }); |
| #209 | const middleware = createAccessMiddleware({ type: 'html' }); |
| #210 | const next = vi.fn(); |
| #211 | |
| #212 | await middleware(c, next); |
| #213 | |
| #214 | expect(next).not.toHaveBeenCalled(); |
| #215 | expect(htmlMock).toHaveBeenCalledWith( |
| #216 | expect.stringContaining('Unauthorized'), |
| #217 | 401 |
| #218 | ); |
| #219 | }); |
| #220 | |
| #221 | it('redirects when JWT is missing and redirectOnMissing is true', async () => { |
| #222 | const { c, redirectMock } = createFullMockContext({ |
| #223 | env: { CF_ACCESS_TEAM_DOMAIN: 'team.cloudflareaccess.com', CF_ACCESS_AUD: 'aud123' } |
| #224 | }); |
| #225 | const middleware = createAccessMiddleware({ type: 'html', redirectOnMissing: true }); |
| #226 | const next = vi.fn(); |
| #227 | |
| #228 | await middleware(c, next); |
| #229 | |
| #230 | expect(next).not.toHaveBeenCalled(); |
| #231 | expect(redirectMock).toHaveBeenCalledWith('https://team.cloudflareaccess.com', 302); |
| #232 | }); |
| #233 | }); |
| #234 |