Compare commits
1 Commits
feat/ms22-
...
chore/ms22
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f63727582 |
@@ -1,40 +0,0 @@
|
|||||||
import {
|
|
||||||
Controller,
|
|
||||||
ForbiddenException,
|
|
||||||
Get,
|
|
||||||
Param,
|
|
||||||
Req,
|
|
||||||
UnauthorizedException,
|
|
||||||
UseGuards,
|
|
||||||
} from "@nestjs/common";
|
|
||||||
import { AgentConfigService } from "./agent-config.service";
|
|
||||||
import { AgentConfigGuard, type AgentConfigRequest } from "./agent-config.guard";
|
|
||||||
|
|
||||||
@Controller("internal")
|
|
||||||
@UseGuards(AgentConfigGuard)
|
|
||||||
export class AgentConfigController {
|
|
||||||
constructor(private readonly agentConfigService: AgentConfigService) {}
|
|
||||||
|
|
||||||
// GET /api/internal/agent-config/:id
|
|
||||||
// Auth: Bearer token (validated against UserContainer.gatewayToken or SystemContainer.gatewayToken)
|
|
||||||
// Returns: assembled openclaw.json
|
|
||||||
//
|
|
||||||
// The :id param is the container record ID (cuid)
|
|
||||||
// Token must match the container requesting its own config
|
|
||||||
@Get("agent-config/:id")
|
|
||||||
async getAgentConfig(
|
|
||||||
@Param("id") id: string,
|
|
||||||
@Req() request: AgentConfigRequest
|
|
||||||
): Promise<object> {
|
|
||||||
const containerAuth = request.containerAuth;
|
|
||||||
if (!containerAuth) {
|
|
||||||
throw new UnauthorizedException("Missing container authentication context");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containerAuth.id !== id) {
|
|
||||||
throw new ForbiddenException("Token is not authorized for the requested container");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.agentConfigService.generateConfigForContainer(containerAuth.type, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
|
|
||||||
import type { Request } from "express";
|
|
||||||
import { AgentConfigService, type ContainerTokenValidation } from "./agent-config.service";
|
|
||||||
|
|
||||||
export interface AgentConfigRequest extends Request {
|
|
||||||
containerAuth?: ContainerTokenValidation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AgentConfigGuard implements CanActivate {
|
|
||||||
constructor(private readonly agentConfigService: AgentConfigService) {}
|
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
||||||
const request = context.switchToHttp().getRequest<AgentConfigRequest>();
|
|
||||||
const token = this.extractBearerToken(request.headers.authorization);
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new UnauthorizedException("Missing Bearer token");
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerAuth = await this.agentConfigService.validateContainerToken(token);
|
|
||||||
if (!containerAuth) {
|
|
||||||
throw new UnauthorizedException("Invalid container token");
|
|
||||||
}
|
|
||||||
|
|
||||||
request.containerAuth = containerAuth;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractBearerToken(headerValue: string | string[] | undefined): string | null {
|
|
||||||
const normalizedHeader = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
||||||
if (!normalizedHeader) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [scheme, token] = normalizedHeader.split(" ");
|
|
||||||
if (!scheme || !token || scheme.toLowerCase() !== "bearer") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { Module } from "@nestjs/common";
|
|
||||||
import { PrismaModule } from "../prisma/prisma.module";
|
|
||||||
import { CryptoModule } from "../crypto/crypto.module";
|
|
||||||
import { AgentConfigController } from "./agent-config.controller";
|
|
||||||
import { AgentConfigService } from "./agent-config.service";
|
|
||||||
import { AgentConfigGuard } from "./agent-config.guard";
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [PrismaModule, CryptoModule],
|
|
||||||
controllers: [AgentConfigController],
|
|
||||||
providers: [AgentConfigService, AgentConfigGuard],
|
|
||||||
exports: [AgentConfigService],
|
|
||||||
})
|
|
||||||
export class AgentConfigModule {}
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
||||||
import { AgentConfigService } from "./agent-config.service";
|
|
||||||
import { PrismaService } from "../prisma/prisma.service";
|
|
||||||
import { CryptoService } from "../crypto/crypto.service";
|
|
||||||
|
|
||||||
describe("AgentConfigService", () => {
|
|
||||||
let service: AgentConfigService;
|
|
||||||
|
|
||||||
const mockPrismaService = {
|
|
||||||
userAgentConfig: {
|
|
||||||
findUnique: vi.fn(),
|
|
||||||
},
|
|
||||||
llmProvider: {
|
|
||||||
findMany: vi.fn(),
|
|
||||||
},
|
|
||||||
userContainer: {
|
|
||||||
findUnique: vi.fn(),
|
|
||||||
findMany: vi.fn(),
|
|
||||||
},
|
|
||||||
systemContainer: {
|
|
||||||
findUnique: vi.fn(),
|
|
||||||
findMany: vi.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockCryptoService = {
|
|
||||||
isEncrypted: vi.fn((value: string) => value.startsWith("enc:")),
|
|
||||||
decrypt: vi.fn((value: string) => value.replace(/^enc:/, "")),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
|
|
||||||
service = new AgentConfigService(
|
|
||||||
mockPrismaService as unknown as PrismaService,
|
|
||||||
mockCryptoService as unknown as CryptoService
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("generateUserConfig returns valid openclaw.json structure", async () => {
|
|
||||||
mockPrismaService.userAgentConfig.findUnique.mockResolvedValue({
|
|
||||||
id: "cfg-1",
|
|
||||||
userId: "user-1",
|
|
||||||
primaryModel: "my-zai/glm-5",
|
|
||||||
});
|
|
||||||
|
|
||||||
mockPrismaService.userContainer.findUnique.mockResolvedValue({
|
|
||||||
id: "container-1",
|
|
||||||
userId: "user-1",
|
|
||||||
gatewayPort: 19001,
|
|
||||||
});
|
|
||||||
|
|
||||||
mockPrismaService.llmProvider.findMany.mockResolvedValue([
|
|
||||||
{
|
|
||||||
id: "provider-1",
|
|
||||||
userId: "user-1",
|
|
||||||
name: "my-zai",
|
|
||||||
displayName: "Z.ai",
|
|
||||||
type: "zai",
|
|
||||||
baseUrl: "https://api.z.ai/v1",
|
|
||||||
apiKey: "enc:secret-zai-key",
|
|
||||||
apiType: "openai-completions",
|
|
||||||
models: [{ id: "glm-5" }],
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const result = await service.generateUserConfig("user-1");
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
gateway: {
|
|
||||||
mode: "local",
|
|
||||||
port: 19001,
|
|
||||||
bind: "lan",
|
|
||||||
auth: { mode: "token" },
|
|
||||||
http: {
|
|
||||||
endpoints: {
|
|
||||||
chatCompletions: { enabled: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
agents: {
|
|
||||||
defaults: {
|
|
||||||
model: {
|
|
||||||
primary: "my-zai/glm-5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
models: {
|
|
||||||
providers: {
|
|
||||||
"my-zai": {
|
|
||||||
apiKey: "secret-zai-key",
|
|
||||||
baseUrl: "https://api.z.ai/v1",
|
|
||||||
models: {
|
|
||||||
"glm-5": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("generateUserConfig decrypts API keys correctly", async () => {
|
|
||||||
mockPrismaService.userAgentConfig.findUnique.mockResolvedValue({
|
|
||||||
id: "cfg-1",
|
|
||||||
userId: "user-1",
|
|
||||||
primaryModel: "openai-work/gpt-4.1",
|
|
||||||
});
|
|
||||||
|
|
||||||
mockPrismaService.userContainer.findUnique.mockResolvedValue({
|
|
||||||
id: "container-1",
|
|
||||||
userId: "user-1",
|
|
||||||
gatewayPort: 18789,
|
|
||||||
});
|
|
||||||
|
|
||||||
mockPrismaService.llmProvider.findMany.mockResolvedValue([
|
|
||||||
{
|
|
||||||
id: "provider-1",
|
|
||||||
userId: "user-1",
|
|
||||||
name: "openai-work",
|
|
||||||
displayName: "OpenAI Work",
|
|
||||||
type: "openai",
|
|
||||||
baseUrl: "https://api.openai.com/v1",
|
|
||||||
apiKey: "enc:encrypted-openai-key",
|
|
||||||
apiType: "openai-completions",
|
|
||||||
models: [{ id: "gpt-4.1" }],
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const result = await service.generateUserConfig("user-1");
|
|
||||||
|
|
||||||
expect(mockCryptoService.decrypt).toHaveBeenCalledWith("enc:encrypted-openai-key");
|
|
||||||
expect(result.models.providers["openai-work"]?.apiKey).toBe("encrypted-openai-key");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("generateUserConfig handles user with no providers", async () => {
|
|
||||||
mockPrismaService.userAgentConfig.findUnique.mockResolvedValue({
|
|
||||||
id: "cfg-1",
|
|
||||||
userId: "user-2",
|
|
||||||
primaryModel: "openai/gpt-4o-mini",
|
|
||||||
});
|
|
||||||
|
|
||||||
mockPrismaService.userContainer.findUnique.mockResolvedValue({
|
|
||||||
id: "container-2",
|
|
||||||
userId: "user-2",
|
|
||||||
gatewayPort: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
mockPrismaService.llmProvider.findMany.mockResolvedValue([]);
|
|
||||||
|
|
||||||
const result = await service.generateUserConfig("user-2");
|
|
||||||
|
|
||||||
expect(result.models.providers).toEqual({});
|
|
||||||
expect(result.gateway.port).toBe(18789);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("validateContainerToken returns correct type for user container", async () => {
|
|
||||||
mockPrismaService.userContainer.findMany.mockResolvedValue([
|
|
||||||
{
|
|
||||||
id: "user-container-1",
|
|
||||||
gatewayToken: "enc:user-token-1",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
mockPrismaService.systemContainer.findMany.mockResolvedValue([]);
|
|
||||||
|
|
||||||
const result = await service.validateContainerToken("user-token-1");
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
type: "user",
|
|
||||||
id: "user-container-1",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("validateContainerToken returns correct type for system container", async () => {
|
|
||||||
mockPrismaService.userContainer.findMany.mockResolvedValue([]);
|
|
||||||
mockPrismaService.systemContainer.findMany.mockResolvedValue([
|
|
||||||
{
|
|
||||||
id: "system-container-1",
|
|
||||||
gatewayToken: "enc:system-token-1",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const result = await service.validateContainerToken("system-token-1");
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
type: "system",
|
|
||||||
id: "system-container-1",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("validateContainerToken returns null for invalid token", async () => {
|
|
||||||
mockPrismaService.userContainer.findMany.mockResolvedValue([
|
|
||||||
{
|
|
||||||
id: "user-container-1",
|
|
||||||
gatewayToken: "enc:user-token-1",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockPrismaService.systemContainer.findMany.mockResolvedValue([
|
|
||||||
{
|
|
||||||
id: "system-container-1",
|
|
||||||
gatewayToken: "enc:system-token-1",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const result = await service.validateContainerToken("no-match");
|
|
||||||
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
|
||||||
import type { LlmProvider } from "@prisma/client";
|
|
||||||
import { timingSafeEqual } from "node:crypto";
|
|
||||||
import { PrismaService } from "../prisma/prisma.service";
|
|
||||||
import { CryptoService } from "../crypto/crypto.service";
|
|
||||||
|
|
||||||
const DEFAULT_GATEWAY_PORT = 18789;
|
|
||||||
const DEFAULT_PRIMARY_MODEL = "openai/gpt-4o-mini";
|
|
||||||
|
|
||||||
type ContainerType = "user" | "system";
|
|
||||||
|
|
||||||
export interface ContainerTokenValidation {
|
|
||||||
type: ContainerType;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpenClawModelMap = Record<string, Record<string, never>>;
|
|
||||||
|
|
||||||
interface OpenClawProviderConfig {
|
|
||||||
apiKey?: string;
|
|
||||||
baseUrl?: string;
|
|
||||||
models: OpenClawModelMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OpenClawConfig {
|
|
||||||
gateway: {
|
|
||||||
mode: "local";
|
|
||||||
port: number;
|
|
||||||
bind: "lan";
|
|
||||||
auth: { mode: "token" };
|
|
||||||
http: {
|
|
||||||
endpoints: {
|
|
||||||
chatCompletions: { enabled: true };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
agents: {
|
|
||||||
defaults: {
|
|
||||||
model: {
|
|
||||||
primary: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
models: {
|
|
||||||
providers: Record<string, OpenClawProviderConfig>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AgentConfigService {
|
|
||||||
constructor(
|
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
private readonly crypto: CryptoService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// Generate complete openclaw.json for a user container
|
|
||||||
async generateUserConfig(userId: string): Promise<OpenClawConfig> {
|
|
||||||
const [userAgentConfig, providers, userContainer] = await Promise.all([
|
|
||||||
this.prisma.userAgentConfig.findUnique({
|
|
||||||
where: { userId },
|
|
||||||
}),
|
|
||||||
this.prisma.llmProvider.findMany({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "asc",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
this.prisma.userContainer.findUnique({
|
|
||||||
where: { userId },
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!userContainer) {
|
|
||||||
throw new NotFoundException(`User container not found for user ${userId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const primaryModel =
|
|
||||||
userAgentConfig?.primaryModel ??
|
|
||||||
this.resolvePrimaryModelFromProviders(providers) ??
|
|
||||||
DEFAULT_PRIMARY_MODEL;
|
|
||||||
|
|
||||||
return this.buildOpenClawConfig(primaryModel, userContainer.gatewayPort, providers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate config for a system container
|
|
||||||
async generateSystemConfig(containerId: string): Promise<OpenClawConfig> {
|
|
||||||
const systemContainer = await this.prisma.systemContainer.findUnique({
|
|
||||||
where: { id: containerId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!systemContainer) {
|
|
||||||
throw new NotFoundException(`System container ${containerId} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.buildOpenClawConfig(
|
|
||||||
systemContainer.primaryModel || DEFAULT_PRIMARY_MODEL,
|
|
||||||
systemContainer.gatewayPort,
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateConfigForContainer(
|
|
||||||
type: ContainerType,
|
|
||||||
containerId: string
|
|
||||||
): Promise<OpenClawConfig> {
|
|
||||||
if (type === "system") {
|
|
||||||
return this.generateSystemConfig(containerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const userContainer = await this.prisma.userContainer.findUnique({
|
|
||||||
where: { id: containerId },
|
|
||||||
select: { userId: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!userContainer) {
|
|
||||||
throw new NotFoundException(`User container ${containerId} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.generateUserConfig(userContainer.userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate a container's bearer token
|
|
||||||
async validateContainerToken(token: string): Promise<ContainerTokenValidation | null> {
|
|
||||||
if (!token) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [userContainers, systemContainers] = await Promise.all([
|
|
||||||
this.prisma.userContainer.findMany({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
gatewayToken: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
this.prisma.systemContainer.findMany({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
gatewayToken: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const container of userContainers) {
|
|
||||||
const storedToken = this.decryptContainerToken(container.gatewayToken);
|
|
||||||
if (storedToken && this.tokensEqual(storedToken, token)) {
|
|
||||||
return { type: "user", id: container.id };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const container of systemContainers) {
|
|
||||||
const storedToken = this.decryptContainerToken(container.gatewayToken);
|
|
||||||
if (storedToken && this.tokensEqual(storedToken, token)) {
|
|
||||||
return { type: "system", id: container.id };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildOpenClawConfig(
|
|
||||||
primaryModel: string,
|
|
||||||
gatewayPort: number | null,
|
|
||||||
providers: LlmProvider[]
|
|
||||||
): OpenClawConfig {
|
|
||||||
return {
|
|
||||||
gateway: {
|
|
||||||
mode: "local",
|
|
||||||
port: gatewayPort ?? DEFAULT_GATEWAY_PORT,
|
|
||||||
bind: "lan",
|
|
||||||
auth: { mode: "token" },
|
|
||||||
http: {
|
|
||||||
endpoints: {
|
|
||||||
chatCompletions: { enabled: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
agents: {
|
|
||||||
defaults: {
|
|
||||||
model: {
|
|
||||||
primary: primaryModel,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
models: {
|
|
||||||
providers: this.buildProviderConfig(providers),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildProviderConfig(providers: LlmProvider[]): Record<string, OpenClawProviderConfig> {
|
|
||||||
const providerConfig: Record<string, OpenClawProviderConfig> = {};
|
|
||||||
|
|
||||||
for (const provider of providers) {
|
|
||||||
const config: OpenClawProviderConfig = {
|
|
||||||
models: this.extractModels(provider.models),
|
|
||||||
};
|
|
||||||
|
|
||||||
const apiKey = this.decryptIfNeeded(provider.apiKey);
|
|
||||||
if (apiKey) {
|
|
||||||
config.apiKey = apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (provider.baseUrl) {
|
|
||||||
config.baseUrl = provider.baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
providerConfig[provider.name] = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
return providerConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractModels(models: unknown): OpenClawModelMap {
|
|
||||||
const modelMap: OpenClawModelMap = {};
|
|
||||||
|
|
||||||
if (!Array.isArray(models)) {
|
|
||||||
return modelMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const modelEntry of models) {
|
|
||||||
if (typeof modelEntry === "string") {
|
|
||||||
modelMap[modelEntry] = {};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasModelId(modelEntry)) {
|
|
||||||
modelMap[modelEntry.id] = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return modelMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolvePrimaryModelFromProviders(providers: LlmProvider[]): string | null {
|
|
||||||
for (const provider of providers) {
|
|
||||||
const modelIds = Object.keys(this.extractModels(provider.models));
|
|
||||||
const firstModelId = modelIds[0];
|
|
||||||
|
|
||||||
if (firstModelId) {
|
|
||||||
return `${provider.name}/${firstModelId}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private decryptIfNeeded(value: string | null | undefined): string | undefined {
|
|
||||||
if (!value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.crypto.isEncrypted(value)) {
|
|
||||||
return this.crypto.decrypt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private decryptContainerToken(value: string): string | null {
|
|
||||||
try {
|
|
||||||
return this.decryptIfNeeded(value) ?? null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokensEqual(left: string, right: string): boolean {
|
|
||||||
const leftBuffer = Buffer.from(left, "utf8");
|
|
||||||
const rightBuffer = Buffer.from(right, "utf8");
|
|
||||||
|
|
||||||
if (leftBuffer.length !== rightBuffer.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return timingSafeEqual(leftBuffer, rightBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private hasModelId(modelEntry: unknown): modelEntry is { id: string } {
|
|
||||||
if (typeof modelEntry !== "object" || modelEntry === null || !("id" in modelEntry)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof (modelEntry as { id?: unknown }).id === "string";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@ import { TeamsModule } from "./teams/teams.module";
|
|||||||
import { ImportModule } from "./import/import.module";
|
import { ImportModule } from "./import/import.module";
|
||||||
import { ConversationArchiveModule } from "./conversation-archive/conversation-archive.module";
|
import { ConversationArchiveModule } from "./conversation-archive/conversation-archive.module";
|
||||||
import { RlsContextInterceptor } from "./common/interceptors/rls-context.interceptor";
|
import { RlsContextInterceptor } from "./common/interceptors/rls-context.interceptor";
|
||||||
import { AgentConfigModule } from "./agent-config/agent-config.module";
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -124,7 +123,6 @@ import { AgentConfigModule } from "./agent-config/agent-config.module";
|
|||||||
TeamsModule,
|
TeamsModule,
|
||||||
ImportModule,
|
ImportModule,
|
||||||
ConversationArchiveModule,
|
ConversationArchiveModule,
|
||||||
AgentConfigModule,
|
|
||||||
],
|
],
|
||||||
controllers: [AppController, CsrfController],
|
controllers: [AppController, CsrfController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
Reference in New Issue
Block a user