From ba7ddf2d821d8ce92cf02ab51ea9065d7663db23 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 26 Apr 2026 12:44:38 +0200 Subject: [PATCH] Add requireAuth to workspaces, conversations, connectors, query routes These routes were publicly accessible without authentication. Now all use the requireAuth middleware. Replaced manual JWT parsing in workspaces.ts and query.ts with c.get('user') from the middleware. Closes #14 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/routes/connectors.ts | 5 ++++- src/routes/conversations.ts | 5 ++++- src/routes/query.ts | 27 +++++------------------- src/routes/workspaces.ts | 41 ++++++++++--------------------------- 4 files changed, 24 insertions(+), 54 deletions(-) diff --git a/src/routes/connectors.ts b/src/routes/connectors.ts index 24711c4..f8e5302 100644 --- a/src/routes/connectors.ts +++ b/src/routes/connectors.ts @@ -5,6 +5,7 @@ import type { RowDataPacket } from 'mysql2' import pino from 'pino' import { getPool } from '../db/pool.js' import { parseBody } from '../middleware/validate.js' +import { requireAuth, type AuthEnv } from '../middleware/auth.js' import type { IdRow } from '../db/types.js' /** Slack auth.test API response */ @@ -72,7 +73,9 @@ const mcpSaveSchema = z.object({ const log = pino({ name: 'routes/connectors' }) -const connectors = new Hono() +const connectors = new Hono() + +connectors.use('/api/connectors/*', requireAuth) // Bind a Slack channel to a workspace (uses the org-wide bot) connectors.post('/api/connectors/slack-channel', async (c) => { diff --git a/src/routes/conversations.ts b/src/routes/conversations.ts index 6c3749d..fa4b170 100644 --- a/src/routes/conversations.ts +++ b/src/routes/conversations.ts @@ -3,6 +3,7 @@ import { randomBytes } from 'crypto' import type { RowDataPacket } from 'mysql2' import pino from 'pino' import { getPool } from '../db/pool.js' +import { requireAuth, type AuthEnv } from '../middleware/auth.js' import type { ConversationRow, ConversationStatsRow, TotalRow } from '../db/types.js' /** JOIN of conversations + conversation_stats for list queries */ @@ -69,7 +70,9 @@ interface StatsStatusRow extends RowDataPacket { const log = pino({ name: 'routes/conversations' }) -const conversations = new Hono() +const conversations = new Hono() + +conversations.use('/api/workspaces/*/conversations*', requireAuth) conversations.get('/api/workspaces/:id/conversations', async (c) => { const db = getPool() diff --git a/src/routes/query.ts b/src/routes/query.ts index a571b3b..763144a 100644 --- a/src/routes/query.ts +++ b/src/routes/query.ts @@ -1,23 +1,13 @@ import { Hono } from 'hono' -import { getCookie } from 'hono/cookie' import type { RowDataPacket } from 'mysql2' -import jwt from 'jsonwebtoken' import { z } from 'zod' import pino from 'pino' import { getPool } from '../db/pool.js' import { runAgent } from '../core/agent.js' -import { JWT_SECRET } from '../config.js' import { parseBody } from '../middleware/validate.js' +import { requireAuth, type AuthUser, type AuthEnv } from '../middleware/auth.js' import type { WorkspaceRow } from '../db/types.js' -/** JWT payload for authenticated users */ -interface SessionPayload { - id: string - name: string - email: string - org_id: string - org_role: string -} /** Connection row subset needed for agent dispatch */ interface ConnectionConfigRow extends RowDataPacket { @@ -37,7 +27,9 @@ const queryBodySchema = z.object({ const log = pino({ name: 'routes/query' }) -const query = new Hono() +const query = new Hono() + +query.use('/api/workspaces/*/query', requireAuth) query.post('/api/workspaces/:id/query', async (c) => { const db = getPool() @@ -55,16 +47,7 @@ query.post('/api/workspaces/:id/query', async (c) => { if (!parsed.success) return parsed.response const queryText = parsed.data.query - // Get user from JWT (optional) - let user: SessionPayload | null = null - const token = getCookie(c, 'supaproxy_session') - if (token) { - try { - user = jwt.verify(token, JWT_SECRET) as SessionPayload - } catch (err) { - log.debug({ error: (err as Error).message }, 'JWT verification failed for query session') - } - } + const user = c.get('user') as AuthUser const { findOrCreateConversation, getConversationHistory } = await import('../core/conversation.js') const sessionId = parsed.data.session_id || `api:${user?.id || 'anon'}:${wsId}:${Date.now()}` diff --git a/src/routes/workspaces.ts b/src/routes/workspaces.ts index fc3c921..0fe163e 100644 --- a/src/routes/workspaces.ts +++ b/src/routes/workspaces.ts @@ -1,27 +1,17 @@ import { Hono } from 'hono' -import { getCookie } from 'hono/cookie' -import jwt from 'jsonwebtoken' import { randomBytes } from 'crypto' import { z } from 'zod' import pino from 'pino' import type { RowDataPacket } from 'mysql2' import { getPool } from '../db/pool.js' -import { JWT_SECRET, DEFAULT_MODEL } from '../config.js' +import { DEFAULT_MODEL } from '../config.js' import { parseBody } from '../middleware/validate.js' +import { requireAuth, type AuthUser, type AuthEnv } from '../middleware/auth.js' import type { IdRow, TotalRow, } from '../db/types.js' -// ── JWT payload ── - -interface JwtPayload { - org_id: string - user_id: string - email: string - iat?: number - exp?: number -} // ── Inline row types for JOIN / aggregate queries ── @@ -266,7 +256,12 @@ const updateWorkspaceSchema = z.object({ const log = pino({ name: 'routes/workspaces' }) -const workspaces = new Hono() +const workspaces = new Hono() + +workspaces.use('/api/workspaces/*', requireAuth) +workspaces.use('/api/workspaces', requireAuth) +workspaces.use('/api/teams', requireAuth) +workspaces.use('/api/connections/*', requireAuth) // List teams workspaces.get('/api/teams', async (c) => { @@ -282,23 +277,9 @@ workspaces.post('/api/workspaces', async (c) => { if (!result.success) return result.response const { name, team_id, team_name, system_prompt, org_id } = result.data - // Resolve org_id - let resolvedOrgId = org_id - if (!resolvedOrgId) { - const token = getCookie(c, 'supaproxy_session') - if (token) { - try { - const payload = jwt.verify(token, JWT_SECRET) as JwtPayload - resolvedOrgId = payload.org_id - } catch (err) { - log.debug({ error: (err as Error).message }, 'JWT verification failed when resolving org_id') - } - } - } - if (!resolvedOrgId) { - const [orgs] = await db.execute('SELECT id FROM organisations LIMIT 1') - resolvedOrgId = orgs[0]?.id - } + // Resolve org_id from request body or authenticated user + const user = c.get('user') as AuthUser + const resolvedOrgId = org_id || user.org_id // Resolve or create team let resolvedTeamId = team_id