fix: configure Kong JWT auth flow with consumer credentials
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
28131491e2
commit
48e47975ca
|
|
@ -40,6 +40,8 @@ services:
|
||||||
build:
|
build:
|
||||||
context: ../../packages/gateway
|
context: ../../packages/gateway
|
||||||
container_name: it0-api-gateway
|
container_name: it0-api-gateway
|
||||||
|
environment:
|
||||||
|
- JWT_SECRET=${JWT_SECRET:-dev-jwt-secret}
|
||||||
ports:
|
ports:
|
||||||
- "18000:8000"
|
- "18000:8000"
|
||||||
- "18001:8001"
|
- "18001:8001"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,25 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { Sidebar } from '@/presentation/components/layout/sidebar';
|
import { Sidebar } from '@/presentation/components/layout/sidebar';
|
||||||
import { TopBar } from '@/presentation/components/layout/top-bar';
|
import { TopBar } from '@/presentation/components/layout/top-bar';
|
||||||
|
|
||||||
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
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 (
|
return (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default function LoginPage() {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await apiClient<LoginResponse>('/auth/login', {
|
const data = await apiClient<LoginResponse>('/api/v1/auth/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { email, password },
|
body: { email, password },
|
||||||
});
|
});
|
||||||
|
|
@ -32,6 +32,14 @@ export default function LoginPage() {
|
||||||
localStorage.setItem('refresh_token', data.refreshToken);
|
localStorage.setItem('refresh_token', data.refreshToken);
|
||||||
localStorage.setItem('user', JSON.stringify(data.user));
|
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');
|
router.push('/dashboard');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Login failed');
|
setError(err instanceof Error ? err.message : 'Login failed');
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
FROM kong:3.7
|
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_DATABASE=off
|
||||||
ENV KONG_DECLARATIVE_CONFIG=/etc/kong/kong.yml
|
ENV KONG_DECLARATIVE_CONFIG=/etc/kong/kong.yml
|
||||||
ENV KONG_PROXY_LISTEN=0.0.0.0:8000
|
ENV KONG_PROXY_LISTEN=0.0.0.0:8000
|
||||||
ENV KONG_ADMIN_LISTEN=0.0.0.0:8001
|
ENV KONG_ADMIN_LISTEN=0.0.0.0:8001
|
||||||
ENV KONG_LOG_LEVEL=info
|
ENV KONG_LOG_LEVEL=info
|
||||||
|
|
||||||
|
ENTRYPOINT ["/custom-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
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
_format_version: "3.0"
|
_format_version: "3.0"
|
||||||
|
|
||||||
|
consumers:
|
||||||
|
- username: it0-system
|
||||||
|
jwt_secrets:
|
||||||
|
- key: it0-auth
|
||||||
|
algorithm: HS256
|
||||||
|
secret: "${JWT_SECRET}"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: auth-service
|
- name: auth-service
|
||||||
url: http://auth-service:3001
|
url: http://auth-service:3001
|
||||||
|
|
@ -24,6 +31,14 @@ services:
|
||||||
- http
|
- http
|
||||||
- https
|
- 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
|
- name: ops-service
|
||||||
url: http://ops-service:3003
|
url: http://ops-service:3003
|
||||||
routes:
|
routes:
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,10 @@ export class AuthService {
|
||||||
roles: user.roles,
|
roles: user.roles,
|
||||||
};
|
};
|
||||||
|
|
||||||
const accessToken = this.jwtService.sign(jwtPayload);
|
const accessToken = this.jwtService.sign({
|
||||||
|
...jwtPayload,
|
||||||
|
kid: 'it0-auth',
|
||||||
|
});
|
||||||
|
|
||||||
const refreshToken = this.jwtService.sign(
|
const refreshToken = this.jwtService.sign(
|
||||||
{ sub: user.id, type: 'refresh' },
|
{ sub: user.id, type: 'refresh' },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue