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