feat(sync): cn event coverage, bootstrap and outbox sender
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// services/api/src/shared/internal-sync.guard.ts —— /internal/** 服务间同步
|
||||
// 端点的 HMAC 守卫(P2 Task 3)。规则单一实现见 @photo/contracts internal-sync.ts
|
||||
// (与 services/sync 发送器共用,防两侧漂移):
|
||||
// X-Sync-Signature = hex(HMAC-SHA256(原始请求体, SYNC_INTERNAL_KEY))
|
||||
// X-Sync-Timestamp = Unix 秒(±300s 防重放)
|
||||
// 失败路径(头缺失/签名不符/时间戳超窗/密钥未配置)一律同码 401 SYNC_AUTH_FAILED,
|
||||
// 不区分具体原因(不向调用方泄露校验进度)。原始请求体依赖 NestFactory 的
|
||||
// rawBody: true(body-parser verify 钩子在 JSON 解析前捕获 Buffer 到 req.rawBody,
|
||||
// 两个部署入口 main-cn/main-ov 均已开启);未经解析器捕获的请求无 rawBody → 401。
|
||||
// 使用方式:@UseGuards(InternalSyncGuard)(按控制器/路由启用,不注册 APP_GUARD)。
|
||||
// 守卫无依赖(纯 env + 纯函数校验),任意模块可直接引用。
|
||||
import {
|
||||
type CanActivate,
|
||||
type ExecutionContext,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
SYNC_INTERNAL_KEY_ENV,
|
||||
SYNC_SIGNATURE_HEADER,
|
||||
SYNC_TIMESTAMP_HEADER,
|
||||
verifySyncSignature,
|
||||
} from '@photo/contracts';
|
||||
import { ApiException } from '../api-exception';
|
||||
import { ErrorCodes } from '@photo/contracts';
|
||||
|
||||
/** 经 body-parser verify 捕获的原始请求体(NestFactory rawBody: true)。 */
|
||||
export interface RequestWithRawBody {
|
||||
rawBody?: Buffer;
|
||||
headers: Record<string, string | string[] | undefined>;
|
||||
}
|
||||
|
||||
function syncUnauthorized(): ApiException {
|
||||
return new ApiException(
|
||||
ErrorCodes.SYNC_AUTH_FAILED,
|
||||
'Internal sync signature is missing, invalid, or expired',
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class InternalSyncGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const req = context.switchToHttp().getRequest<RequestWithRawBody>();
|
||||
// rawBody 缺失(未走 JSON 解析器的请求体/入口未开 rawBody)以空串参与校验,
|
||||
// 结果恒 false——与签名不符同一出口,不单独暴露「原始体缺失」这一内部细节。
|
||||
const ok = verifySyncSignature({
|
||||
key: process.env[SYNC_INTERNAL_KEY_ENV] ?? '',
|
||||
rawBody: req.rawBody ?? '',
|
||||
timestampHeader: req.headers[SYNC_TIMESTAMP_HEADER],
|
||||
signatureHeader: req.headers[SYNC_SIGNATURE_HEADER],
|
||||
});
|
||||
if (!ok) {
|
||||
throw syncUnauthorized();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user