Skip to content

Commit b94e113

Browse files
amitpaz1claude
andcommitted
fix: complete 8 remaining items from 30-idea audit
Security: - HSTS header auto-enables in production (sec-001) - Docker production compose: network isolation, redis requirepass, postgres/redis no longer exposed to host (sec-005) Performance: - Auth middleware now uses cached validateApiKey(), eliminating redundant per-request DB queries (perf-001) - Webhook delivery logs Promise.allSettled failures (perf-003) UI/UX: - AuditLog, ApiKeys, Webhooks pages show skeleton screens during data loading instead of generic spinners (uiux-004) Code Quality: - RequestList migrated to shared ResponsiveTable component (cq-003) Documentation: - Added AgentGateEmitter API docs to events.md covering on/once/ onAll/off/emit/emitSync and utility methods (doc-003) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8307ede commit b94e113

10 files changed

Lines changed: 236 additions & 139 deletions

File tree

docker-compose.production.yml

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ services:
2323
POSTGRES_USER: ${POSTGRES_USER:-agentgate}
2424
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-agentgate}
2525
POSTGRES_DB: ${POSTGRES_DB:-agentgate}
26-
ports:
27-
- "${POSTGRES_PORT:-5432}:5432"
26+
# Not exposed to host — only accessible within the internal network
27+
expose:
28+
- "5432"
2829
volumes:
2930
- postgres-data:/var/lib/postgresql/data
31+
networks:
32+
- agentgate-internal
3033
healthcheck:
3134
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-agentgate} -d ${POSTGRES_DB:-agentgate}"]
3235
interval: 10s
@@ -40,13 +43,16 @@ services:
4043
redis:
4144
image: redis:7-alpine
4245
container_name: agentgate-redis
43-
ports:
44-
- "${REDIS_PORT:-6379}:6379"
46+
# Not exposed to host — only accessible within the internal network
47+
expose:
48+
- "6379"
4549
volumes:
4650
- redis-data:/data
47-
command: redis-server --appendonly yes
51+
networks:
52+
- agentgate-internal
53+
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-changeme}
4854
healthcheck:
49-
test: ["CMD", "redis-cli", "ping"]
55+
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-changeme}", "ping"]
5056
interval: 10s
5157
timeout: 5s
5258
retries: 3
@@ -69,7 +75,7 @@ services:
6975
DATABASE_URL: postgresql://${POSTGRES_USER:-agentgate}:${POSTGRES_PASSWORD:-agentgate}@postgres:5432/${POSTGRES_DB:-agentgate}
7076

7177
# Redis
72-
REDIS_URL: redis://redis:6379
78+
REDIS_URL: redis://:${REDIS_PASSWORD:-changeme}@redis:6379
7379
RATE_LIMIT_BACKEND: redis
7480

7581
# Security
@@ -116,6 +122,8 @@ services:
116122
CHANNEL_ROUTES: ${CHANNEL_ROUTES:-[]}
117123
ports:
118124
- "${SERVER_PORT:-3000}:3000"
125+
networks:
126+
- agentgate-internal
119127
depends_on:
120128
postgres:
121129
condition: service_healthy
@@ -137,6 +145,8 @@ services:
137145
container_name: agentgate-dashboard
138146
ports:
139147
- "${DASHBOARD_PORT:-8080}:80"
148+
networks:
149+
- agentgate-internal
140150
depends_on:
141151
server:
142152
condition: service_healthy
@@ -151,3 +161,7 @@ services:
151161
volumes:
152162
postgres-data:
153163
redis-data:
164+
165+
networks:
166+
agentgate-internal:
167+
driver: bridge

docker-compose.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ services:
3636
- redis-data:/data
3737
networks:
3838
- agentgate-internal
39-
command: redis-server --appendonly yes
39+
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-agentgate}
4040
healthcheck:
41-
test: ["CMD", "redis-cli", "ping"]
41+
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-agentgate}", "ping"]
4242
interval: 10s
4343
timeout: 5s
4444
retries: 3
@@ -63,7 +63,7 @@ services:
6363
DATABASE_URL: postgresql://${POSTGRES_USER:-agentgate}:${POSTGRES_PASSWORD:-agentgate}@postgres:5432/${POSTGRES_DB:-agentgate}
6464

6565
# Redis
66-
REDIS_URL: redis://redis:6379
66+
REDIS_URL: redis://:${REDIS_PASSWORD:-agentgate}@redis:6379
6767
RATE_LIMIT_BACKEND: redis
6868

6969
# Security

docs/events.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,82 @@ const base = createBaseEvent("request.created", "my-plugin");
6767

6868
---
6969

70+
## Subscribing to Events
71+
72+
Use the `AgentGateEmitter` class to subscribe to events. The global singleton is available via `getGlobalEmitter()`, or create an isolated instance with `createEmitter()`.
73+
74+
```typescript
75+
import { getGlobalEmitter, createEmitter } from "@agentgate/core";
76+
77+
const emitter = getGlobalEmitter();
78+
```
79+
80+
### `emitter.on(eventType, listener)`
81+
82+
Subscribe to events of a specific type. Returns an unsubscribe function.
83+
84+
```typescript
85+
const unsub = emitter.on("request.created", (event) => {
86+
console.log("New request:", event.payload.requestId);
87+
});
88+
89+
// Later: unsubscribe
90+
unsub();
91+
```
92+
93+
### `emitter.once(eventType, listener)`
94+
95+
Subscribe to a single event — the listener is automatically removed after the first emission.
96+
97+
```typescript
98+
emitter.once("request.decided", (event) => {
99+
console.log("First decision:", event.payload.status);
100+
});
101+
```
102+
103+
### `emitter.onAll(listener)`
104+
105+
Subscribe to all events regardless of type. Useful for logging and audit.
106+
107+
```typescript
108+
const unsub = emitter.onAll((event) => {
109+
console.log(`[${event.type}]`, event.eventId);
110+
});
111+
```
112+
113+
### `emitter.off(eventType, listener)` / `emitter.offAll(listener)`
114+
115+
Explicitly unsubscribe a listener (alternative to calling the function returned by `on`).
116+
117+
### `emitter.emit(event)` / `emitter.emitSync(event)`
118+
119+
Emit an event. `emit` is async and awaits all listeners; `emitSync` is fire-and-forget.
120+
121+
```typescript
122+
import { createBaseEvent, EventNames } from "@agentgate/core";
123+
124+
const event = {
125+
...createBaseEvent(EventNames.SYSTEM_ERROR, "my-plugin"),
126+
payload: { message: "Something went wrong" },
127+
};
128+
129+
// Await all listeners
130+
await emitter.emit(event);
131+
132+
// Or fire-and-forget
133+
emitter.emitSync(event);
134+
```
135+
136+
### Utility Methods
137+
138+
| Method | Description |
139+
|--------|-------------|
140+
| `listenerCount(eventType?)` | Number of listeners (specific type + wildcards, or total) |
141+
| `removeAllListeners(eventType?)` | Remove all listeners (optionally for a specific event type) |
142+
| `eventTypes()` | List all event types with registered listeners |
143+
144+
---
145+
70146
## Event Payload Types
71147

72148
Each event type carries a typed `payload`. All fields are listed below.

packages/dashboard/src/pages/ApiKeys.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { adminApi } from '../api';
33
import { ResponsiveTable, type Column } from '../components/ResponsiveTable';
44
import { useToast } from '../components/Toast';
55
import { Modal } from '../components/Modal';
6+
import { ApiKeysSkeleton } from '../components/Skeleton';
67
import { ConfirmDialog } from '../components/ConfirmDialog';
78

89
interface ApiKey {
@@ -198,6 +199,8 @@ export default function ApiKeys() {
198199
},
199200
];
200201

202+
if (loading) return <ApiKeysSkeleton />;
203+
201204
if (error) {
202205
return (
203206
<div className="p-4 sm:p-6">

packages/dashboard/src/pages/AuditLog.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useSearchParams, useNavigate } from 'react-router-dom';
33
import { api, type AuditEntryWithRequest, type ApprovalStatus } from '../api';
44
import { StatusBadge } from '../components/StatusBadge';
55
import { ResponsiveTable, type Column } from '../components/ResponsiveTable';
6+
import { AuditLogSkeleton } from '../components/Skeleton';
67

78
const EVENT_TYPES = [
89
{ value: '', label: 'All Events' },
@@ -185,6 +186,8 @@ export default function AuditLog() {
185186
},
186187
];
187188

189+
if (loading) return <AuditLogSkeleton />;
190+
188191
return (
189192
<div className="space-y-6">
190193
{/* Header */}

0 commit comments

Comments
 (0)