From 48e47975ca561d65cc714a3d51fb59c163802c0e Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 21 Feb 2026 23:20:06 -0800 Subject: [PATCH] fix: configure Kong JWT auth flow with consumer credentials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add kid claim to auth-service JWT for Kong validation - Add Kong consumer with JWT credential (shared secret via env) - Add agent-config route to Kong for /api/v1/agent-config - Kong Dockerfile uses entrypoint script to inject JWT_SECRET at runtime - Fix frontend login path (/auth/login → /api/v1/auth/login) - Extract tenantId from JWT on login and store as current_tenant - Add auth guard in admin layout (redirect to /login if no token) - Pass JWT_SECRET env var to Kong container in docker-compose Co-Authored-By: Claude Opus 4.6 --- deploy/docker/docker-compose.yml | 2 ++ it0-web-admin/src/app/(admin)/layout.tsx | 16 ++++++++++++++++ it0-web-admin/src/app/(auth)/login/page.tsx | 10 +++++++++- packages/gateway/Dockerfile | 6 +++++- packages/gateway/config/docker-entrypoint.sh | 4 ++++ packages/gateway/config/kong.yml | 15 +++++++++++++++ .../src/application/services/auth.service.ts | 5 ++++- 7 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 packages/gateway/config/docker-entrypoint.sh diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index 3475353..96bd93f 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -40,6 +40,8 @@ services: build: context: ../../packages/gateway container_name: it0-api-gateway + environment: + - JWT_SECRET=${JWT_SECRET:-dev-jwt-secret} ports: - "18000:8000" - "18001:8001" diff --git a/it0-web-admin/src/app/(admin)/layout.tsx b/it0-web-admin/src/app/(admin)/layout.tsx index def72c8..1f64028 100644 --- a/it0-web-admin/src/app/(admin)/layout.tsx +++ b/it0-web-admin/src/app/(admin)/layout.tsx @@ -1,9 +1,25 @@ 'use client'; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; import { Sidebar } from '@/presentation/components/layout/sidebar'; import { TopBar } from '@/presentation/components/layout/top-bar'; export default function AdminLayout({ children }: { children: React.ReactNode }) { + const router = useRouter(); + const [ready, setReady] = useState(false); + + useEffect(() => { + const token = localStorage.getItem('access_token'); + if (!token) { + router.replace('/login'); + } else { + setReady(true); + } + }, [router]); + + if (!ready) return null; + return (
diff --git a/it0-web-admin/src/app/(auth)/login/page.tsx b/it0-web-admin/src/app/(auth)/login/page.tsx index 7c6168e..d916a43 100644 --- a/it0-web-admin/src/app/(auth)/login/page.tsx +++ b/it0-web-admin/src/app/(auth)/login/page.tsx @@ -23,7 +23,7 @@ export default function LoginPage() { setError(null); try { - const data = await apiClient('/auth/login', { + const data = await apiClient('/api/v1/auth/login', { method: 'POST', body: { email, password }, }); @@ -32,6 +32,14 @@ export default function LoginPage() { localStorage.setItem('refresh_token', data.refreshToken); localStorage.setItem('user', JSON.stringify(data.user)); + // Decode tenantId from JWT and store as current tenant + try { + const payload = JSON.parse(atob(data.accessToken.split('.')[1])); + if (payload.tenantId) { + localStorage.setItem('current_tenant', JSON.stringify({ id: payload.tenantId })); + } + } catch { /* ignore decode errors */ } + router.push('/dashboard'); } catch (err) { setError(err instanceof Error ? err.message : 'Login failed'); diff --git a/packages/gateway/Dockerfile b/packages/gateway/Dockerfile index d145b55..16abdf1 100644 --- a/packages/gateway/Dockerfile +++ b/packages/gateway/Dockerfile @@ -1,9 +1,13 @@ FROM kong:3.7 -COPY config/kong.yml /etc/kong/kong.yml +COPY config/kong.yml /etc/kong/kong.yml.tpl +COPY config/docker-entrypoint.sh /custom-entrypoint.sh +RUN chmod +x /custom-entrypoint.sh ENV KONG_DATABASE=off ENV KONG_DECLARATIVE_CONFIG=/etc/kong/kong.yml ENV KONG_PROXY_LISTEN=0.0.0.0:8000 ENV KONG_ADMIN_LISTEN=0.0.0.0:8001 ENV KONG_LOG_LEVEL=info + +ENTRYPOINT ["/custom-entrypoint.sh"] diff --git a/packages/gateway/config/docker-entrypoint.sh b/packages/gateway/config/docker-entrypoint.sh new file mode 100644 index 0000000..c35a1c3 --- /dev/null +++ b/packages/gateway/config/docker-entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# Substitute JWT_SECRET into Kong declarative config at runtime +sed "s/\${JWT_SECRET}/${JWT_SECRET:-dev-jwt-secret}/g" /etc/kong/kong.yml.tpl > /etc/kong/kong.yml +exec /docker-entrypoint.sh kong docker-start diff --git a/packages/gateway/config/kong.yml b/packages/gateway/config/kong.yml index 4f3424f..4535a70 100644 --- a/packages/gateway/config/kong.yml +++ b/packages/gateway/config/kong.yml @@ -1,5 +1,12 @@ _format_version: "3.0" +consumers: + - username: it0-system + jwt_secrets: + - key: it0-auth + algorithm: HS256 + secret: "${JWT_SECRET}" + services: - name: auth-service url: http://auth-service:3001 @@ -24,6 +31,14 @@ services: - http - https + - name: agent-config-service + url: http://agent-service:3002 + routes: + - name: agent-config-routes + paths: + - /api/v1/agent-config + strip_path: false + - name: ops-service url: http://ops-service:3003 routes: diff --git a/packages/services/auth-service/src/application/services/auth.service.ts b/packages/services/auth-service/src/application/services/auth.service.ts index 9c825a7..4271af8 100644 --- a/packages/services/auth-service/src/application/services/auth.service.ts +++ b/packages/services/auth-service/src/application/services/auth.service.ts @@ -153,7 +153,10 @@ export class AuthService { roles: user.roles, }; - const accessToken = this.jwtService.sign(jwtPayload); + const accessToken = this.jwtService.sign({ + ...jwtPayload, + kid: 'it0-auth', + }); const refreshToken = this.jwtService.sign( { sub: user.id, type: 'refresh' },