Add SOC 2 readiness evidence endpoint

This commit is contained in:
MOHAN 2026-07-16 22:42:33 +05:30
parent 1b0f1c08d2
commit f7160fceab
5 changed files with 184 additions and 0 deletions

View File

@ -28,6 +28,7 @@ import { NotificationsModule } from "./notifications/notifications.module";
import { BillPayModule } from "./bill-pay/bill-pay.module"; import { BillPayModule } from "./bill-pay/bill-pay.module";
import { CreditScoreModule } from "./credit-score/credit-score.module"; import { CreditScoreModule } from "./credit-score/credit-score.module";
import { ViewModule } from "./view/view.module"; import { ViewModule } from "./view/view.module";
import { ComplianceModule } from "./compliance/compliance.module";
import { LoggerModule } from "nestjs-pino"; import { LoggerModule } from "nestjs-pino";
import { JwtAuthGuard } from "./common/guards/jwt-auth.guard"; import { JwtAuthGuard } from "./common/guards/jwt-auth.guard";
import { BrowserUntrustedInterceptor } from "./common/browser-untrusted.interceptor"; import { BrowserUntrustedInterceptor } from "./common/browser-untrusted.interceptor";
@ -88,6 +89,7 @@ import { BrowserUntrustedInterceptor } from "./common/browser-untrusted.intercep
BillPayModule, BillPayModule,
CreditScoreModule, CreditScoreModule,
ViewModule, ViewModule,
ComplianceModule,
], ],
providers: [ providers: [
// Apply rate limiting globally // Apply rate limiting globally

View File

@ -0,0 +1,14 @@
import { Controller, Get } from "@nestjs/common";
import { CurrentUser } from "../common/decorators/current-user.decorator";
import { ComplianceService } from "./compliance.service";
@Controller("compliance")
export class ComplianceController {
constructor(private readonly complianceService: ComplianceService) {}
@Get("soc2/readiness")
async soc2Readiness(@CurrentUser() userId: string) {
return this.complianceService.getSoc2Readiness(userId);
}
}

View File

@ -0,0 +1,10 @@
import { Module } from "@nestjs/common";
import { ComplianceController } from "./compliance.controller";
import { ComplianceService } from "./compliance.service";
@Module({
controllers: [ComplianceController],
providers: [ComplianceService],
})
export class ComplianceModule {}

View File

@ -0,0 +1,120 @@
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
export type ControlStatus = "implemented" | "partial" | "external_required";
export interface Soc2Control {
id: string;
trustServiceCriterion: string;
status: ControlStatus;
control: string;
evidence: string[];
}
export interface Soc2ReadinessReport {
framework: "SOC 2";
readinessStatus: "technical_baseline_ready";
certificationStatus: "not_certified";
generatedAt: string;
evidenceSummary: {
auditLogCount: number;
exportLogCount: number;
abuseEventCount: number;
activeSessionCount: number;
};
controls: Soc2Control[];
pendingExternalControls: string[];
note: string;
}
@Injectable()
export class ComplianceService {
constructor(private readonly prisma: PrismaService) {}
async getSoc2Readiness(userId: string): Promise<Soc2ReadinessReport> {
const [auditLogCount, exportLogCount, abuseEventCount, activeSessionCount] = await Promise.all([
this.prisma.auditLog.count({ where: { userId } }),
this.prisma.exportLog.count({ where: { userId } }),
this.prisma.abuseEvent.count({ where: { userId } }),
this.prisma.session.count({ where: { userId, revokedAt: null, expiresAt: { gt: new Date() } } }),
]);
const controls: Soc2Control[] = [
{
id: "CC6.1",
trustServiceCriterion: "Logical access",
status: "implemented",
control: "Authenticated access is bound to a server-side session with IP, user-agent, refresh token, and rotating nonce checks.",
evidence: [
"JwtAuthGuard validates session ID, IP hash, user-agent hash, and per-request nonce hash.",
`${activeSessionCount} active session(s) currently recorded for this user.`,
],
},
{
id: "CC6.7",
trustServiceCriterion: "Data confidentiality",
status: "implemented",
control: "Sensitive tokens and raw transaction payloads are encrypted before storage, and browser APIs return presentation data.",
evidence: [
"Plaid tokens and raw transaction payloads use server-side encryption services.",
"Browser-facing finance routes are served through sanitized view APIs.",
],
},
{
id: "CC7.2",
trustServiceCriterion: "Security monitoring",
status: "implemented",
control: "Abuse and anomaly signals are recorded for risky usage patterns.",
evidence: [`${abuseEventCount} abuse/risk event(s) currently recorded for this user.`],
},
{
id: "CC7.3",
trustServiceCriterion: "Auditability",
status: "implemented",
control: "Security-relevant account, export, and collaboration actions write audit evidence.",
evidence: [
`${auditLogCount} audit event(s) currently recorded for this user.`,
`${exportLogCount} export audit record(s) currently recorded for this user.`,
],
},
{
id: "CC8.1",
trustServiceCriterion: "Change management",
status: "partial",
control: "Repository-level CI and committed implementation history exist, but formal approval and release controls remain operational processes.",
evidence: ["Technical CI coverage can support SOC 2 evidence, but policy attestation is outside the application runtime."],
},
{
id: "P1.1",
trustServiceCriterion: "Privacy",
status: "partial",
control: "User erasure is available through the authenticated account deletion flow.",
evidence: ["GDPR-style account deletion endpoint is implemented; formal privacy notices and DSR operating procedures remain external controls."],
},
];
return {
framework: "SOC 2",
readinessStatus: "technical_baseline_ready",
certificationStatus: "not_certified",
generatedAt: new Date().toISOString(),
evidenceSummary: {
auditLogCount,
exportLogCount,
abuseEventCount,
activeSessionCount,
},
controls,
pendingExternalControls: [
"Independent SOC 2 auditor engagement and examination period",
"Board/management-approved security, access control, incident response, vendor, and change management policies",
"Recurring access reviews with retained evidence",
"Vendor risk reviews for infrastructure, email, payment, and data-provider subprocessors",
"Incident response tabletop or equivalent drill evidence",
"Employee security training and onboarding/offboarding records",
],
note: "This endpoint reports application control evidence only. It does not represent SOC 2 certification or audit opinion.",
};
}
}

View File

@ -0,0 +1,38 @@
import { ComplianceService } from "../src/compliance/compliance.service";
const createService = () => {
const prisma = {
auditLog: { count: jest.fn() },
exportLog: { count: jest.fn() },
abuseEvent: { count: jest.fn() },
session: { count: jest.fn() },
};
return { service: new ComplianceService(prisma as any), prisma };
};
describe("ComplianceService", () => {
it("returns SOC 2 readiness evidence without claiming certification", async () => {
const { service, prisma } = createService();
prisma.auditLog.count.mockResolvedValue(12);
prisma.exportLog.count.mockResolvedValue(3);
prisma.abuseEvent.count.mockResolvedValue(2);
prisma.session.count.mockResolvedValue(1);
const result = await service.getSoc2Readiness("user_1");
expect(result.readinessStatus).toBe("technical_baseline_ready");
expect(result.certificationStatus).toBe("not_certified");
expect(result.evidenceSummary).toEqual({
auditLogCount: 12,
exportLogCount: 3,
abuseEventCount: 2,
activeSessionCount: 1,
});
expect(result.controls).toEqual(expect.arrayContaining([
expect.objectContaining({ id: "CC6.1", status: "implemented" }),
expect.objectContaining({ id: "CC8.1", status: "partial" }),
]));
expect(result.pendingExternalControls.length).toBeGreaterThan(0);
});
});