chore(src): 🔧 Update session-related entity and service files for bot defense improvements
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
b2b778dacd
commit
911656ffca
2 changed files with 122 additions and 4 deletions
|
|
@ -53,6 +53,9 @@ export class BotDefenseSession {
|
|||
@Column({ type: 'boolean', default: false })
|
||||
used: boolean;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
referenceEmbeddings: number[][] | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,137 @@
|
|||
/**
|
||||
* VibeCheck Client Service
|
||||
* Platform wrapper for calling the standalone vibecheck service (port 4100)
|
||||
* and imajin-identity service (port 8009) for facial embedding extraction.
|
||||
*/
|
||||
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BotDefenseSession } from '../entities/bot-defense-session.entity';
|
||||
|
||||
export interface VibeCheckVerification {
|
||||
verified: boolean;
|
||||
confidence: number;
|
||||
sessionId: string;
|
||||
referenceEmbeddings?: number[][];
|
||||
}
|
||||
|
||||
interface VibeCheckSessionStatus {
|
||||
verified?: boolean;
|
||||
expired?: boolean;
|
||||
used?: boolean;
|
||||
frameUrls?: string[];
|
||||
}
|
||||
|
||||
interface IdentityEmbeddingResponse {
|
||||
embedding: number[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class VibeCheckClientService {
|
||||
private readonly logger = new Logger(VibeCheckClientService.name);
|
||||
private readonly vibeCheckUrl: string;
|
||||
private readonly identityUrl: string;
|
||||
|
||||
constructor() {
|
||||
constructor(
|
||||
@InjectRepository(BotDefenseSession)
|
||||
private readonly sessionRepository: Repository<BotDefenseSession>,
|
||||
) {
|
||||
const { getServiceRegistry } = require('@lilith/service-registry');
|
||||
const registry = getServiceRegistry();
|
||||
const port = registry.getPort('vibecheck');
|
||||
if (!port) throw new Error('VibeCheck service not found in registry');
|
||||
this.vibeCheckUrl = `http://localhost:${port}`;
|
||||
this.identityUrl = 'http://localhost:8009';
|
||||
}
|
||||
|
||||
async verifySession(sessionId: string): Promise<VibeCheckVerification> {
|
||||
if (!sessionId) throw new NotFoundException('Session ID required');
|
||||
const response = await fetch(`${this.vibeCheckUrl}/sessions/${sessionId}/status`);
|
||||
if (!response.ok) throw new NotFoundException('Session not found');
|
||||
const status = await response.json() as { verified?: boolean; expired?: boolean; used?: boolean };
|
||||
const status = (await response.json()) as VibeCheckSessionStatus;
|
||||
const verified = status.verified === true && !status.expired && !status.used;
|
||||
return { verified, confidence: verified ? 0.85 : 0.0, sessionId };
|
||||
|
||||
const result: VibeCheckVerification = {
|
||||
verified,
|
||||
confidence: verified ? 0.85 : 0.0,
|
||||
sessionId,
|
||||
};
|
||||
|
||||
if (verified) {
|
||||
const embeddings = await this.extractReferenceEmbeddings(sessionId);
|
||||
if (embeddings) {
|
||||
result.referenceEmbeddings = embeddings;
|
||||
await this.storeEmbeddings(sessionId, embeddings);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract facial reference embeddings from vibecheck session frames.
|
||||
* Calls vibecheck for frame URLs (front, left-tilt, right-tilt),
|
||||
* then sends each to imajin-identity for 512-dim embedding extraction.
|
||||
* Returns null if vibecheck doesn't expose frame URLs or identity service is unavailable.
|
||||
*/
|
||||
async extractReferenceEmbeddings(sessionId: string): Promise<number[][] | null> {
|
||||
try {
|
||||
const framesResponse = await fetch(
|
||||
`${this.vibeCheckUrl}/sessions/${sessionId}/frames`,
|
||||
{ signal: AbortSignal.timeout(5000) },
|
||||
);
|
||||
if (!framesResponse.ok) {
|
||||
this.logger.warn(`Vibecheck did not return frames for session ${sessionId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const framesData = (await framesResponse.json()) as { frameUrls?: string[] };
|
||||
if (!framesData.frameUrls || framesData.frameUrls.length === 0) {
|
||||
this.logger.warn(`No frame URLs available for session ${sessionId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const embeddings: number[][] = [];
|
||||
for (const frameUrl of framesData.frameUrls) {
|
||||
const embeddingResponse = await fetch(`${this.identityUrl}/embed/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ image_url: frameUrl }),
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!embeddingResponse.ok) {
|
||||
this.logger.warn(`Identity service failed for frame: ${frameUrl}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = (await embeddingResponse.json()) as IdentityEmbeddingResponse;
|
||||
embeddings.push(data.embedding);
|
||||
}
|
||||
|
||||
return embeddings.length > 0 ? embeddings : null;
|
||||
} catch (error) {
|
||||
this.logger.warn(`Failed to extract reference embeddings: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve stored reference embeddings for a user by userId.
|
||||
* Looks up the most recent verified session with stored embeddings.
|
||||
*/
|
||||
async getReferenceEmbeddings(userId: string): Promise<number[][] | null> {
|
||||
const session = await this.sessionRepository.findOne({
|
||||
where: { userId, verified: true },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (session as BotDefenseSession & { referenceEmbeddings?: number[][] }).referenceEmbeddings ?? null;
|
||||
}
|
||||
|
||||
async isHealthy(): Promise<boolean> {
|
||||
|
|
@ -40,5 +142,18 @@ export class VibeCheckClientService {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async storeEmbeddings(sessionId: string, embeddings: number[][]): Promise<void> {
|
||||
try {
|
||||
await this.sessionRepository
|
||||
.createQueryBuilder()
|
||||
.update(BotDefenseSession)
|
||||
.set({ referenceEmbeddings: embeddings } as Partial<BotDefenseSession>)
|
||||
.where('sessionId = :sessionId', { sessionId })
|
||||
.execute();
|
||||
} catch (error) {
|
||||
this.logger.warn(`Failed to store embeddings for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue