refactor: core layer quick wins
- Rename getRequestFingerprint to getRateLimitFingerprint in rate-limit.util.ts - Delete empty CoreConfigModule wrapper (importers use @nestjs/config directly) - Replace inline admin role check in csrf.controller.ts with @UseGuards(AdminGuard) - Move hashEmailForLogs() from support.logging.ts to core/logging/redaction.util.ts
This commit is contained in:
parent
6e51012d21
commit
1ac5e95e08
@ -1,9 +0,0 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
providers: [],
|
||||
exports: [],
|
||||
})
|
||||
export class CoreConfigModule {}
|
||||
@ -5,6 +5,8 @@
|
||||
* Prefer allowlists at call sites for best safety; this helper provides a safe baseline.
|
||||
*/
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
export type RedactOptions = {
|
||||
/**
|
||||
* Extra keys to redact (case-insensitive, substring match).
|
||||
@ -107,3 +109,12 @@ export function redactForLogs<T>(value: T, options: RedactOptions = {}): T {
|
||||
|
||||
return walk(value, 0) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash an email for logs (PII minimization).
|
||||
* Use a short, stable prefix to correlate events without storing the email itself.
|
||||
*/
|
||||
export function hashEmailForLogs(email: string): string {
|
||||
const normalized = email.trim().toLowerCase();
|
||||
return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
||||
}
|
||||
|
||||
@ -6,4 +6,4 @@ export {
|
||||
type RateLimitOptions,
|
||||
RATE_LIMIT_KEY,
|
||||
} from "./rate-limit.decorator.js";
|
||||
export { getRequestFingerprint } from "./rate-limit.util.js";
|
||||
export { getRateLimitFingerprint } from "./rate-limit.util.js";
|
||||
|
||||
@ -26,7 +26,7 @@ export function getRequestRateLimitIdentity(request: Request): {
|
||||
* @param request - Express request object
|
||||
* @returns SHA256 hash of IP + User-Agent (truncated to 32 chars)
|
||||
*/
|
||||
export function getRequestFingerprint(request: Request): string {
|
||||
export function getRateLimitFingerprint(request: Request): string {
|
||||
const { ip, userAgentHash } = getRequestRateLimitIdentity(request);
|
||||
const combined = `${ip}:${userAgentHash}`;
|
||||
return createHash("sha256").update(combined).digest("hex").slice(0, 32);
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { Controller, Get, Post, Req, Res, Inject, ForbiddenException } from "@nestjs/common";
|
||||
import { Controller, Get, Post, Req, Res, Inject, UseGuards } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import type { Request, Response } from "express";
|
||||
import { Logger } from "nestjs-pino";
|
||||
import { CsrfService } from "../services/csrf.service.js";
|
||||
import { Public } from "@bff/modules/auth/decorators/public.decorator.js";
|
||||
import { AdminGuard } from "../guards/admin.guard.js";
|
||||
import type { UserAuth } from "@customer-portal/domain/customer";
|
||||
|
||||
export type AuthenticatedRequest = Request & {
|
||||
@ -87,13 +88,10 @@ export class CsrfController {
|
||||
});
|
||||
}
|
||||
|
||||
@UseGuards(AdminGuard)
|
||||
@Get("stats")
|
||||
getCsrfStats(@Req() req: AuthenticatedRequest) {
|
||||
const userId = req.user?.id;
|
||||
const role = req.user?.role;
|
||||
if (role !== "ADMIN") {
|
||||
throw new ForbiddenException("Admin access required");
|
||||
}
|
||||
|
||||
this.logger.debug("CSRF stats requested", {
|
||||
userId,
|
||||
|
||||
@ -2,7 +2,7 @@ import { Controller, Post, Body, UseGuards, Res, Req, HttpCode } from "@nestjs/c
|
||||
import type { Request, Response } from "express";
|
||||
import { createZodDto } from "nestjs-zod";
|
||||
|
||||
import { RateLimitGuard, RateLimit, getRequestFingerprint } from "@bff/core/rate-limiting/index.js";
|
||||
import { RateLimitGuard, RateLimit, getRateLimitFingerprint } from "@bff/core/rate-limiting/index.js";
|
||||
import { SalesforceWriteThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-write-throttle.guard.js";
|
||||
import { Public } from "../../decorators/public.decorator.js";
|
||||
|
||||
@ -68,7 +68,7 @@ export class GetStartedController {
|
||||
@Body() body: SendVerificationCodeRequestDto,
|
||||
@Req() req: Request
|
||||
): Promise<SendVerificationCodeResponseDto> {
|
||||
const fingerprint = getRequestFingerprint(req);
|
||||
const fingerprint = getRateLimitFingerprint(req);
|
||||
return this.workflow.sendVerificationCode(body, fingerprint);
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ export class GetStartedController {
|
||||
@Body() body: VerifyCodeRequestDto,
|
||||
@Req() req: Request
|
||||
): Promise<VerifyCodeResponseDto> {
|
||||
const fingerprint = getRequestFingerprint(req);
|
||||
const fingerprint = getRateLimitFingerprint(req);
|
||||
return this.workflow.verifyCode(body, fingerprint);
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ export class GetStartedController {
|
||||
@Body() body: GuestEligibilityRequestDto,
|
||||
@Req() req: Request
|
||||
): Promise<GuestEligibilityResponseDto> {
|
||||
const fingerprint = getRequestFingerprint(req);
|
||||
const fingerprint = getRateLimitFingerprint(req);
|
||||
return this.workflow.guestEligibilityCheck(body, fingerprint);
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { CheckoutController } from "./controllers/checkout.controller.js";
|
||||
import { IntegrationsModule } from "@bff/integrations/integrations.module.js";
|
||||
import { MappingsModule } from "@bff/modules/id-mappings/mappings.module.js";
|
||||
import { UsersModule } from "@bff/modules/users/users.module.js";
|
||||
import { CoreConfigModule } from "@bff/core/config/config.module.js";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { ServicesModule } from "@bff/modules/services/services.module.js";
|
||||
import { CacheModule } from "@bff/infra/cache/cache.module.js";
|
||||
import { VerificationModule } from "@bff/modules/verification/verification.module.js";
|
||||
@ -52,7 +52,7 @@ import { SalesforceOrderFieldConfigModule } from "@bff/integrations/salesforce/c
|
||||
IntegrationsModule,
|
||||
MappingsModule,
|
||||
UsersModule,
|
||||
CoreConfigModule,
|
||||
ConfigModule,
|
||||
ServicesModule,
|
||||
CacheModule,
|
||||
VerificationModule,
|
||||
|
||||
@ -6,7 +6,7 @@ import { PublicServicesController } from "./public-services.controller.js";
|
||||
import { AccountServicesController } from "./account-services.controller.js";
|
||||
import { IntegrationsModule } from "@bff/integrations/integrations.module.js";
|
||||
import { MappingsModule } from "@bff/modules/id-mappings/mappings.module.js";
|
||||
import { CoreConfigModule } from "@bff/core/config/config.module.js";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { CacheModule } from "@bff/infra/cache/cache.module.js";
|
||||
import { QueueModule } from "@bff/infra/queue/queue.module.js";
|
||||
import { WorkflowModule } from "@bff/modules/shared/workflow/index.js";
|
||||
@ -22,7 +22,7 @@ import { ServicesCacheService } from "./application/services-cache.service.js";
|
||||
imports: [
|
||||
forwardRef(() => IntegrationsModule),
|
||||
MappingsModule,
|
||||
CoreConfigModule,
|
||||
ConfigModule,
|
||||
CacheModule,
|
||||
QueueModule,
|
||||
WorkflowModule,
|
||||
|
||||
@ -31,7 +31,7 @@ import {
|
||||
type AddCaseCommentResponse,
|
||||
} from "@customer-portal/domain/support";
|
||||
import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
|
||||
import { hashEmailForLogs } from "./support.logging.js";
|
||||
import { hashEmailForLogs } from "@bff/core/logging/redaction.util.js";
|
||||
import type { ActionMessageResponse } from "@customer-portal/domain/common";
|
||||
import { actionMessageResponseSchema } from "@customer-portal/domain/common";
|
||||
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
/**
|
||||
* Hash an email for logs (PII minimization).
|
||||
* Use a short, stable prefix to correlate events without storing the email itself.
|
||||
*/
|
||||
export const hashEmailForLogs = (email: string): string => {
|
||||
const normalized = email.trim().toLowerCase();
|
||||
return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
||||
};
|
||||
@ -18,7 +18,7 @@ import { SalesforceCaseService } from "@bff/integrations/salesforce/services/sal
|
||||
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
|
||||
import { SupportCacheService } from "./support-cache.service.js";
|
||||
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
|
||||
import { hashEmailForLogs } from "./support.logging.js";
|
||||
import { hashEmailForLogs } from "@bff/core/logging/redaction.util.js";
|
||||
|
||||
/**
|
||||
* Status values that indicate an open/active case
|
||||
|
||||
@ -3,7 +3,7 @@ import { ResidenceCardController } from "./residence-card.controller.js";
|
||||
import { ResidenceCardService } from "./residence-card.service.js";
|
||||
import { IntegrationsModule } from "@bff/integrations/integrations.module.js";
|
||||
import { MappingsModule } from "@bff/modules/id-mappings/mappings.module.js";
|
||||
import { CoreConfigModule } from "@bff/core/config/config.module.js";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { ServicesModule } from "@bff/modules/services/services.module.js";
|
||||
import { WorkflowModule } from "@bff/modules/shared/workflow/index.js";
|
||||
|
||||
@ -11,7 +11,7 @@ import { WorkflowModule } from "@bff/modules/shared/workflow/index.js";
|
||||
imports: [
|
||||
IntegrationsModule,
|
||||
MappingsModule,
|
||||
CoreConfigModule,
|
||||
ConfigModule,
|
||||
forwardRef(() => ServicesModule),
|
||||
WorkflowModule,
|
||||
],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user