Files
Edu/packages/shared-ts/src/federation/router-auth.guard.ts
SpecialX 5fcb831a18 feat(shared-ts): federation shared utilities for apollo subgraphs
- RouterAuthGuard: validate Router-Authorization header (ADR-036)

- DataLoader factory: request-scoped batching (ADR-035)

- ScopeTokenService: Redis-backed scope token (ADR-041)

- GraphqlContext: build context from HTTP headers

- FederationExceptionFilter: HTTP-to-GraphQL error mapping
2026-07-14 23:46:51 +08:00

77 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* RouterAuthGuard - Apollo Router 信任凭证校验
*
* 强制约束v2.1 §3.7 / ADR-036
* Apollo Router 请求子图时必须携带 Router-Authorization Header
* 各服务 NestJS Guard 拦截并校验,拒绝任何非 Router 发起的 GraphQL 请求。
*
* 部署模式:
* - 生产ROUTER_AUTH_SECRET 环境变量配置共享密钥
* - 开发DEV_MODE=true 时跳过校验(仅限本地)
*/
import {
CanActivate,
ExecutionContext,
Injectable,
ForbiddenException,
Logger,
} from "@nestjs/common";
export const ROUTER_AUTH_HEADER = "router-authorization";
export interface RouterAuthConfig {
/** 共享密钥(生产环境由 Secret 注入) */
secret: string;
/** 开发模式跳过校验 */
devMode?: boolean;
}
@Injectable()
export class RouterAuthGuard implements CanActivate {
private readonly logger = new Logger(RouterAuthGuard.name);
private readonly config: RouterAuthConfig;
constructor(config: RouterAuthConfig) {
this.config = config;
}
canActivate(ctx: ExecutionContext): boolean {
if (this.config.devMode === true) {
return true;
}
const req = ctx.switchToHttp().getRequest<{
headers: Record<string, string | undefined>;
url: string;
}>();
// 健康检查端点豁免
if (req.url?.startsWith("/health") || req.url?.startsWith("/ready")) {
return true;
}
const routerAuth = req.headers[ROUTER_AUTH_HEADER];
const expected = this.config.secret;
if (!expected) {
this.logger.error(
`${ROUTER_AUTH_HEADER} secret not configured (ROUTER_AUTH_SECRET env required)`,
);
throw new ForbiddenException(
"Router authorization not configured on server",
);
}
if (!routerAuth || routerAuth !== expected) {
this.logger.warn(
`Direct GraphQL access denied (path=${req.url}); must go through Apollo Router`,
);
throw new ForbiddenException(
"Direct GraphQL access denied; must go through Apollo Router",
);
}
return true;
}
}