diff --git a/.env.lan b/.env.lan index 3e79bd5..072676a 100644 --- a/.env.lan +++ b/.env.lan @@ -4,6 +4,8 @@ # Database DATABASE_URL=postgresql://agenthub:xo9QpEShrrxndZWB6pjuevfqUSzLZMj0@postgres:5432/agenthub +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 POSTGRES_USER=agenthub POSTGRES_PASSWORD=xo9QpEShrrxndZWB6pjuevfqUSzLZMj0 POSTGRES_DB=agenthub diff --git a/Dockerfile b/Dockerfile index b504821..2fd6537 100644 --- a/Dockerfile +++ b/Dockerfile @@ -84,4 +84,4 @@ HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \ ENTRYPOINT ["/usr/bin/tini", "--"] # Start the application -CMD ["node", "dist/src/server.js"] +CMD ["node", "dist/server.js"] diff --git a/src/routes/rooms.ts b/src/routes/rooms.ts index 008c72b..33ca436 100644 --- a/src/routes/rooms.ts +++ b/src/routes/rooms.ts @@ -19,8 +19,8 @@ const _AddMemberSchema = z.object({ export async function registerRoomRoutes(app: FastifyInstance, pool: Pool) { const db = drizzle(pool); - // POST /rooms - Create room (admin only) - app.post('/rooms', async (request, reply) => { + // POST /api/v1/rooms - Create room (admin only) + app.post('/api/v1/rooms', async (request, reply) => { // TODO: Add proper auth middleware - for now assume agentId from JWT/session const agentId = request.headers['x-agent-id'] as string | undefined; if (!agentId) { @@ -80,8 +80,8 @@ export async function registerRoomRoutes(app: FastifyInstance, pool: Pool) { } }); - // GET /rooms - List all rooms accessible by current agent - app.get('/rooms', async (request, reply) => { + // GET /api/v1/rooms - List all rooms accessible by current agent + app.get('/api/v1/rooms', async (request, reply) => { const agentId = request.headers['x-agent-id'] as string | undefined; if (!agentId) { return reply.code(401).send({ error: 'Missing x-agent-id header' }); @@ -111,8 +111,8 @@ export async function registerRoomRoutes(app: FastifyInstance, pool: Pool) { }); }); - // GET /rooms/:id - Get single room - app.get('/rooms/:id', async (request, reply) => { + // GET /api/v1/rooms/:id - Get single room + app.get('/api/v1/rooms/:id', async (request, reply) => { const agentId = request.headers['x-agent-id'] as string | undefined; if (!agentId) { return reply.code(401).send({ error: 'Missing x-agent-id header' }); @@ -145,8 +145,8 @@ export async function registerRoomRoutes(app: FastifyInstance, pool: Pool) { }); }); - // DELETE /rooms/:id - Delete room (admin only) - app.delete('/rooms/:id', async (request, reply) => { + // DELETE /api/v1/rooms/:id - Delete room (admin only) + app.delete('/api/v1/rooms/:id', async (request, reply) => { const agentId = request.headers['x-agent-id'] as string | undefined; if (!agentId) { return reply.code(401).send({ error: 'Missing x-agent-id header' }); @@ -178,8 +178,8 @@ export async function registerRoomRoutes(app: FastifyInstance, pool: Pool) { return reply.code(204).send(); }); - // POST /rooms/:id/members/:agentId - Add member (admin only) - app.post('/rooms/:id/members/:memberId', async (request, reply) => { + // POST /api/v1/rooms/:id/members/:agentId - Add member (admin only) + app.post('/api/v1/rooms/:id/members/:memberId', async (request, reply) => { const agentId = request.headers['x-agent-id'] as string | undefined; if (!agentId) { return reply.code(401).send({ error: 'Missing x-agent-id header' }); @@ -225,8 +225,8 @@ export async function registerRoomRoutes(app: FastifyInstance, pool: Pool) { } }); - // DELETE /rooms/:id/members/:agentId - Remove member (admin only) - app.delete('/rooms/:id/members/:memberId', async (request, reply) => { + // DELETE /api/v1/rooms/:id/members/:agentId - Remove member (admin only) + app.delete('/api/v1/rooms/:id/members/:memberId', async (request, reply) => { const agentId = request.headers['x-agent-id'] as string | undefined; if (!agentId) { return reply.code(401).send({ error: 'Missing x-agent-id header' }); @@ -247,8 +247,8 @@ export async function registerRoomRoutes(app: FastifyInstance, pool: Pool) { return reply.code(204).send(); }); - // GET /rooms/:id/messages - Get messages with cursor pagination - app.get('/rooms/:id/messages', async (request, reply) => { + // GET /api/v1/rooms/:id/messages - Get messages with cursor pagination + app.get('/api/v1/rooms/:id/messages', async (request, reply) => { const agentId = request.headers['x-agent-id'] as string | undefined; if (!agentId) { return reply.code(401).send({ error: 'Missing x-agent-id header' }); diff --git a/src/routes/sessions.ts b/src/routes/sessions.ts index 0e80e8c..08df413 100644 --- a/src/routes/sessions.ts +++ b/src/routes/sessions.ts @@ -24,11 +24,12 @@ export async function registerSessionRoutes( const body = createSessionSchema.parse(request.body); // Extract prefix from token (format: ah_live_XXXX_secret) - const parts = body.apiToken.split('_'); - if (parts.length !== 4 || parts[0] !== 'ah' || parts[1] !== 'live') { + // prefix is always exactly 12 chars: "ah_live_" + 4 base64url chars + // base64url chars can include '_', so split('_') is unreliable + if (!body.apiToken.startsWith('ah_live_') || body.apiToken.length < 14 || body.apiToken[12] !== '_') { return reply.status(401).send({ error: 'Invalid token format' }); } - const prefix = `${parts[0]}_${parts[1]}_${parts[2]}`; + const prefix = body.apiToken.slice(0, 12); // Find active token by prefix const [token] = await db.select().from(apiTokens).where(eq(apiTokens.prefix, prefix)); diff --git a/test/j5-messaging-validation.js b/test/j5-messaging-validation.js index d7125ae..7650462 100644 --- a/test/j5-messaging-validation.js +++ b/test/j5-messaging-validation.js @@ -81,12 +81,12 @@ async function main() { success('AgentHub is healthy'); console.log(''); - // Step 2: Create Alexia + // Step 2: Create Alexia (admin role needed to create rooms) step('Step 2/10: Create agent Alexia'); const alexia = await request('POST', '/api/v1/agents', { name: `alexia-j5-${Date.now()}`, displayName: 'Alexia', - role: 'agent', + role: 'admin', }); alexiaId = alexia.id; if (!alexiaId) error(`Failed to create Alexia: ${JSON.stringify(alexia)}`); @@ -107,13 +107,9 @@ async function main() { // Step 4: Generate API tokens step('Step 4/10: Generate API tokens'); - const tokenAlexia = await request('POST', '/api/v1/tokens', { - agentId: alexiaId, - }); - const tokenAlan = await request('POST', '/api/v1/tokens', { - agentId: alanId, - }); - if (!tokenAlexia.token || !tokenAlan.token) { + const tokenAlexia = await request('POST', `/api/v1/agents/${alexiaId}/tokens`, {}); + const tokenAlan = await request('POST', `/api/v1/agents/${alanId}/tokens`, {}); + if (!tokenAlexia.secret || !tokenAlan.secret) { error('Failed to generate API tokens'); } success(`Tokens generated`); @@ -122,10 +118,10 @@ async function main() { // Step 5: Exchange for JWTs step('Step 5/10: Exchange tokens for JWTs'); const sessionAlexia = await request('POST', '/api/v1/sessions', { - apiToken: tokenAlexia.token, + apiToken: tokenAlexia.secret, }); const sessionAlan = await request('POST', '/api/v1/sessions', { - apiToken: tokenAlan.token, + apiToken: tokenAlan.secret, }); jwtAlexia = sessionAlexia.jwt; jwtAlan = sessionAlan.jwt; diff --git a/tsconfig.build.json b/tsconfig.build.json index 5621b1f..8aa48a4 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "noEmit": false, + "rootDir": "src", "outDir": "dist", "module": "ESNext", "moduleResolution": "Bundler",