Add SOC 2 readiness evidence endpoint
This commit is contained in:
parent
1b0f1c08d2
commit
f7160fceab
@ -28,6 +28,7 @@ import { NotificationsModule } from "./notifications/notifications.module";
|
||||
import { BillPayModule } from "./bill-pay/bill-pay.module";
|
||||
import { CreditScoreModule } from "./credit-score/credit-score.module";
|
||||
import { ViewModule } from "./view/view.module";
|
||||
import { ComplianceModule } from "./compliance/compliance.module";
|
||||
import { LoggerModule } from "nestjs-pino";
|
||||
import { JwtAuthGuard } from "./common/guards/jwt-auth.guard";
|
||||
import { BrowserUntrustedInterceptor } from "./common/browser-untrusted.interceptor";
|
||||
@ -88,6 +89,7 @@ import { BrowserUntrustedInterceptor } from "./common/browser-untrusted.intercep
|
||||
BillPayModule,
|
||||
CreditScoreModule,
|
||||
ViewModule,
|
||||
ComplianceModule,
|
||||
],
|
||||
providers: [
|
||||
// Apply rate limiting globally
|
||||
|
||||
14
src/compliance/compliance.controller.ts
Normal file
14
src/compliance/compliance.controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
10
src/compliance/compliance.module.ts
Normal file
10
src/compliance/compliance.module.ts
Normal 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 {}
|
||||
120
src/compliance/compliance.service.ts
Normal file
120
src/compliance/compliance.service.ts
Normal 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.",
|
||||
};
|
||||
}
|
||||
}
|
||||
38
test/compliance.service.spec.ts
Normal file
38
test/compliance.service.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user