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:
barsa 2026-02-24 11:57:58 +09:00
parent 6e51012d21
commit 1ac5e95e08
12 changed files with 28 additions and 38 deletions

View File

@ -1,9 +0,0 @@
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
@Module({
imports: [ConfigModule],
providers: [],
exports: [],
})
export class CoreConfigModule {}

View File

@ -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);
}

View File

@ -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";

View File

@ -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);

View File

@ -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,

View File

@ -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);
}

View File

@ -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,

View File

@ -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,

View File

@ -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";

View File

@ -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);
};

View File

@ -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

View File

@ -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,
],