fix: add auth checks on all GET endpoints, timing-safe comparisons, and XSS sanitization
This commit is contained in:
parent
84ba833454
commit
1ee506b4cf
|
|
@ -1,6 +1,20 @@
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import type { NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
|
|
||||||
|
/** Edge-compatible constant-time string comparison. */
|
||||||
|
function safeCompare(a: string, b: string): boolean {
|
||||||
|
if (typeof a !== 'string' || typeof b !== 'string') return false
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const bufA = encoder.encode(a)
|
||||||
|
const bufB = encoder.encode(b)
|
||||||
|
if (bufA.length !== bufB.length) return false
|
||||||
|
let result = 0
|
||||||
|
for (let i = 0; i < bufA.length; i++) {
|
||||||
|
result |= bufA[i] ^ bufB[i]
|
||||||
|
}
|
||||||
|
return result === 0
|
||||||
|
}
|
||||||
|
|
||||||
function envFlag(name: string): boolean {
|
function envFlag(name: string): boolean {
|
||||||
const raw = process.env[name]
|
const raw = process.env[name]
|
||||||
if (raw === undefined) return false
|
if (raw === undefined) return false
|
||||||
|
|
@ -65,13 +79,13 @@ export function middleware(request: NextRequest) {
|
||||||
// API routes: accept session cookie OR API key
|
// API routes: accept session cookie OR API key
|
||||||
if (pathname.startsWith('/api/')) {
|
if (pathname.startsWith('/api/')) {
|
||||||
const apiKey = request.headers.get('x-api-key')
|
const apiKey = request.headers.get('x-api-key')
|
||||||
if (sessionToken || (apiKey && apiKey === process.env.API_KEY)) {
|
if (sessionToken || (apiKey && safeCompare(apiKey, process.env.API_KEY || ''))) {
|
||||||
return NextResponse.next()
|
return NextResponse.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backward compat: accept legacy cookie during migration
|
// Backward compat: accept legacy cookie during migration
|
||||||
const legacyCookie = request.cookies.get('mission-control-auth')
|
const legacyCookie = request.cookies.get('mission-control-auth')
|
||||||
if (legacyCookie?.value === process.env.AUTH_SECRET) {
|
if (safeCompare(legacyCookie?.value || '', process.env.AUTH_SECRET || '')) {
|
||||||
return NextResponse.next()
|
return NextResponse.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +99,7 @@ export function middleware(request: NextRequest) {
|
||||||
|
|
||||||
// Backward compat: accept legacy cookie
|
// Backward compat: accept legacy cookie
|
||||||
const legacyCookie = request.cookies.get('mission-control-auth')
|
const legacyCookie = request.cookies.get('mission-control-auth')
|
||||||
if (legacyCookie?.value === process.env.AUTH_SECRET) {
|
if (safeCompare(legacyCookie?.value || '', process.env.AUTH_SECRET || '')) {
|
||||||
return NextResponse.next()
|
return NextResponse.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getDatabase, Activity } from '@/lib/db';
|
import { getDatabase, Activity } from '@/lib/db';
|
||||||
|
import { requireRole } from '@/lib/auth'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/activities - Get activity stream or stats
|
* GET /api/activities - Get activity stream or stats
|
||||||
* Query params: type, actor, entity_type, limit, offset, since, hours (for stats)
|
* Query params: type, actor, entity_type, limit, offset, since, hours (for stats)
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { searchParams, pathname } = new URL(request.url);
|
const { searchParams, pathname } = new URL(request.url);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const { id } = await params
|
const { id } = await params
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { NextRequest, NextResponse } from "next/server"
|
import { NextRequest, NextResponse } from "next/server"
|
||||||
import { getDatabase, Message } from "@/lib/db"
|
import { getDatabase, Message } from "@/lib/db"
|
||||||
|
import { requireRole } from '@/lib/auth'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/agents/comms - Inter-agent communication stats and timeline
|
* GET /api/agents/comms - Inter-agent communication stats and timeline
|
||||||
* Query params: limit, offset, since, agent
|
* Query params: limit, offset, since, agent
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ import { getUserFromRequest, requireRole } from '@/lib/auth';
|
||||||
* Query params: status, role, limit, offset
|
* Query params: status, role, limit, offset
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { createUser, getUserFromRequest } from '@/lib/auth'
|
import { createUser, getUserFromRequest , requireRole } from '@/lib/auth'
|
||||||
import { getDatabase, logAuditEvent } from '@/lib/db'
|
import { getDatabase, logAuditEvent } from '@/lib/db'
|
||||||
|
|
||||||
function makeUsernameFromEmail(email: string): string {
|
function makeUsernameFromEmail(email: string): string {
|
||||||
|
|
@ -20,6 +20,9 @@ function ensureUniqueUsername(base: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
const user = getUserFromRequest(request)
|
const user = getUserFromRequest(request)
|
||||||
if (!user || user.role !== 'admin') {
|
if (!user || user.role !== 'admin') {
|
||||||
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
|
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { getUserFromRequest, updateUser } from '@/lib/auth'
|
import { getUserFromRequest, updateUser , requireRole } from '@/lib/auth'
|
||||||
import { logAuditEvent } from '@/lib/db'
|
import { logAuditEvent } from '@/lib/db'
|
||||||
import { verifyPassword } from '@/lib/password'
|
import { verifyPassword } from '@/lib/password'
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
const user = getUserFromRequest(request)
|
const user = getUserFromRequest(request)
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { getUserFromRequest, getAllUsers, createUser, updateUser, deleteUser } from '@/lib/auth'
|
import { getUserFromRequest, getAllUsers, createUser, updateUser, deleteUser , requireRole } from '@/lib/auth'
|
||||||
import { logAuditEvent } from '@/lib/db'
|
import { logAuditEvent } from '@/lib/db'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/auth/users - List all users (admin only)
|
* GET /api/auth/users - List all users (admin only)
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
const user = getUserFromRequest(request)
|
const user = getUserFromRequest(request)
|
||||||
if (!user || user.role !== 'admin') {
|
if (!user || user.role !== 'admin') {
|
||||||
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
|
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { getDatabase } from '@/lib/db'
|
import { getDatabase } from '@/lib/db'
|
||||||
|
import { requireRole } from '@/lib/auth'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/chat/conversations - List conversations derived from messages
|
* GET /api/chat/conversations - List conversations derived from messages
|
||||||
* Query params: agent (filter by participant), limit, offset
|
* Query params: agent (filter by participant), limit, offset
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const { id } = await params
|
const { id } = await params
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,9 @@ function extractReplyText(waitPayload: any): string | null {
|
||||||
* Query params: conversation_id, from_agent, to_agent, limit, offset, since
|
* Query params: conversation_id, from_agent, to_agent, limit, offset, since
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,9 @@ function mapOpenClawJob(job: OpenClawCronJob): CronJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'admin')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const action = searchParams.get('action')
|
const action = searchParams.get('action')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import { NextRequest , NextResponse } from 'next/server'
|
||||||
import { eventBus, ServerEvent } from '@/lib/event-bus'
|
import { eventBus, ServerEvent } from '@/lib/event-bus'
|
||||||
|
import { requireRole } from '@/lib/auth'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
export const runtime = 'nodejs'
|
export const runtime = 'nodejs'
|
||||||
|
|
@ -7,7 +9,10 @@ export const runtime = 'nodejs'
|
||||||
* GET /api/events - Server-Sent Events stream for real-time DB mutations.
|
* GET /api/events - Server-Sent Events stream for real-time DB mutations.
|
||||||
* Clients connect via EventSource and receive JSON-encoded events.
|
* Clients connect via EventSource and receive JSON-encoded events.
|
||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
|
|
||||||
// Cleanup function, set in start(), called in cancel()
|
// Cleanup function, set in start(), called in cancel()
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,9 @@ async function readLogFile(filePath: string, source: string, maxLines: number):
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const action = searchParams.get('action') || 'recent'
|
const action = searchParams.get('action') || 'recent'
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,9 @@ export async function POST(request: NextRequest) {
|
||||||
* GET /api/notifications/deliver - Get delivery status and statistics
|
* GET /api/notifications/deliver - Get delivery status and statistics
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import { requireRole } from '@/lib/auth';
|
||||||
* Query params: recipient, unread_only, type, limit, offset
|
* Query params: recipient, unread_only, type, limit, offset
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,10 @@ export interface Pipeline {
|
||||||
/**
|
/**
|
||||||
* GET /api/pipelines - List all pipelines with enriched step data
|
* GET /api/pipelines - List all pipelines with enriched step data
|
||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const pipelines = db.prepare(
|
const pipelines = db.prepare(
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ interface PipelineRun {
|
||||||
* GET /api/pipelines/run - Get pipeline runs
|
* GET /api/pipelines/run - Get pipeline runs
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ import { getDatabase, db_helpers } from '@/lib/db'
|
||||||
import { requireRole } from '@/lib/auth'
|
import { requireRole } from '@/lib/auth'
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import { NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { getAllGatewaySessions } from '@/lib/sessions'
|
import { getAllGatewaySessions } from '@/lib/sessions'
|
||||||
|
import { requireRole } from '@/lib/auth'
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
try {
|
try {
|
||||||
const gatewaySessions = getAllGatewaySessions()
|
const gatewaySessions = getAllGatewaySessions()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,9 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
// Get spawn history
|
// Get spawn history
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const limit = parseInt(searchParams.get('limit') || '50')
|
const limit = parseInt(searchParams.get('limit') || '50')
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,9 @@ export async function POST(request: NextRequest) {
|
||||||
* Query params: limit, offset
|
* Query params: limit, offset
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer');
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@ import { runCommand, runOpenClaw, runClawdbot } from '@/lib/command'
|
||||||
import { config } from '@/lib/config'
|
import { config } from '@/lib/config'
|
||||||
import { getDatabase } from '@/lib/db'
|
import { getDatabase } from '@/lib/db'
|
||||||
import { getAllGatewaySessions, getAgentLiveStatuses } from '@/lib/sessions'
|
import { getAllGatewaySessions, getAgentLiveStatuses } from '@/lib/sessions'
|
||||||
|
import { requireRole } from '@/lib/auth'
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const action = searchParams.get('action') || 'overview'
|
const action = searchParams.get('action') || 'overview'
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
|
const auth = requireRole(request, 'viewer');
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
|
const auth = requireRole(request, 'viewer');
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ function hasAegisApproval(db: ReturnType<typeof getDatabase>, taskId: number): b
|
||||||
* Query params: status, assigned_to, priority, limit, offset
|
* Query params: status, assigned_to, priority, limit, offset
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer');
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,9 @@ function filterByTimeframe(records: TokenUsageRecord[], timeframe: string): Toke
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const action = searchParams.get('action') || 'list'
|
const action = searchParams.get('action') || 'list'
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,10 @@ export interface WorkflowTemplate {
|
||||||
/**
|
/**
|
||||||
* GET /api/workflows - List all workflow templates
|
* GET /api/workflows - List all workflow templates
|
||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET(request: NextRequest) {
|
||||||
|
const auth = requireRole(request, 'viewer')
|
||||||
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const templates = db.prepare('SELECT * FROM workflow_templates ORDER BY use_count DESC, updated_at DESC').all() as WorkflowTemplate[]
|
const templates = db.prepare('SELECT * FROM workflow_templates ORDER BY use_count DESC, updated_at DESC').all() as WorkflowTemplate[]
|
||||||
|
|
|
||||||
|
|
@ -321,8 +321,13 @@ export function MemoryBrowserPanel() {
|
||||||
elements.push(<div key={`${i}-space`} className="mb-2"></div>)
|
elements.push(<div key={`${i}-space`} className="mb-2"></div>)
|
||||||
} else if (trimmedLine.length > 0) {
|
} else if (trimmedLine.length > 0) {
|
||||||
if (inList) inList = false
|
if (inList) inList = false
|
||||||
// Handle inline formatting
|
// Handle inline formatting — escape HTML entities first to prevent XSS
|
||||||
let content = trimmedLine
|
let content = trimmedLine
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
// Simple bold formatting
|
// Simple bold formatting
|
||||||
content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||||
// Simple italic formatting
|
// Simple italic formatting
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,21 @@
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes, timingSafeEqual } from 'crypto'
|
||||||
import { getDatabase } from './db'
|
import { getDatabase } from './db'
|
||||||
import { hashPassword, verifyPassword } from './password'
|
import { hashPassword, verifyPassword } from './password'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant-time string comparison to prevent timing attacks.
|
||||||
|
*/
|
||||||
|
export function safeCompare(a: string, b: string): boolean {
|
||||||
|
if (typeof a !== 'string' || typeof b !== 'string') return false
|
||||||
|
const bufA = Buffer.from(a)
|
||||||
|
const bufB = Buffer.from(b)
|
||||||
|
if (bufA.length !== bufB.length) {
|
||||||
|
timingSafeEqual(bufA, bufA)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return timingSafeEqual(bufA, bufB)
|
||||||
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
|
|
@ -202,7 +216,7 @@ export function getUserFromRequest(request: Request): User | null {
|
||||||
|
|
||||||
// Check API key - return synthetic user
|
// Check API key - return synthetic user
|
||||||
const apiKey = request.headers.get('x-api-key')
|
const apiKey = request.headers.get('x-api-key')
|
||||||
if (apiKey && apiKey === process.env.API_KEY) {
|
if (apiKey && safeCompare(apiKey, process.env.API_KEY || '')) {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
id: 0,
|
||||||
username: 'api',
|
username: 'api',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue