fix(agent): scope instance list to requesting user (multi-user isolation)

GET /api/v1/agent/instances was returning all instances regardless of user.
Now decodes JWT: non-admin users only see their own instances; admins see all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-09 08:58:28 -07:00
parent f186c57afb
commit 29c433c7c3
1 changed files with 22 additions and 2 deletions

View File

@ -6,9 +6,11 @@ import {
Delete, Delete,
Body, Body,
Param, Param,
Req,
NotFoundException, NotFoundException,
BadRequestException, BadRequestException,
} from '@nestjs/common'; } from '@nestjs/common';
import { Request } from 'express';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { AgentInstanceRepository } from '../../../infrastructure/repositories/agent-instance.repository'; import { AgentInstanceRepository } from '../../../infrastructure/repositories/agent-instance.repository';
import { AgentInstanceDeployService } from '../../../infrastructure/services/agent-instance-deploy.service'; import { AgentInstanceDeployService } from '../../../infrastructure/services/agent-instance-deploy.service';
@ -28,9 +30,17 @@ export class AgentInstanceController {
} }
@Get() @Get()
async list() { async list(@Req() req: Request) {
const jwt = this.decodeJwt(req.headers?.['authorization'] as string | undefined);
const userId = jwt?.sub;
const roles: string[] = jwt?.roles ?? [];
// Admins see all instances; regular users only see their own
if (!userId || roles.includes('admin')) {
return this.instanceRepo.findAll(); return this.instanceRepo.findAll();
} }
const instances = await this.instanceRepo.findByUserId(userId);
return instances.map((inst) => this.sanitize(inst));
}
@Get('user/:userId') @Get('user/:userId')
async listByUser(@Param('userId') userId: string) { async listByUser(@Param('userId') userId: string) {
@ -210,4 +220,14 @@ export class AgentInstanceController {
const { openclawToken, openclawTokenIv, ...safe } = inst; const { openclawToken, openclawTokenIv, ...safe } = inst;
return { ...safe, hasToken: !!openclawToken }; return { ...safe, hasToken: !!openclawToken };
} }
private decodeJwt(authHeader: string | undefined): Record<string, any> | null {
if (!authHeader?.startsWith('Bearer ')) return null;
try {
const payload = authHeader.slice(7).split('.')[1];
return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
} catch {
return null;
}
}
} }