Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/routes/connectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -72,7 +73,9 @@ const mcpSaveSchema = z.object({

const log = pino({ name: 'routes/connectors' })

const connectors = new Hono()
const connectors = new Hono<AuthEnv>()

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) => {
Expand Down
5 changes: 4 additions & 1 deletion src/routes/conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -69,7 +70,9 @@ interface StatsStatusRow extends RowDataPacket {

const log = pino({ name: 'routes/conversations' })

const conversations = new Hono()
const conversations = new Hono<AuthEnv>()

conversations.use('/api/workspaces/*/conversations*', requireAuth)

conversations.get('/api/workspaces/:id/conversations', async (c) => {
const db = getPool()
Expand Down
27 changes: 5 additions & 22 deletions src/routes/query.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -37,7 +27,9 @@ const queryBodySchema = z.object({

const log = pino({ name: 'routes/query' })

const query = new Hono()
const query = new Hono<AuthEnv>()

query.use('/api/workspaces/*/query', requireAuth)

query.post('/api/workspaces/:id/query', async (c) => {
const db = getPool()
Expand All @@ -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()}`
Expand Down
41 changes: 11 additions & 30 deletions src/routes/workspaces.ts
Original file line number Diff line number Diff line change
@@ -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 ──

Expand Down Expand Up @@ -266,7 +256,12 @@ const updateWorkspaceSchema = z.object({

const log = pino({ name: 'routes/workspaces' })

const workspaces = new Hono()
const workspaces = new Hono<AuthEnv>()

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) => {
Expand All @@ -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<IdRow[]>('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
Expand Down