Commit ea537a02 by luoqi

refactor(cleanup): CORS 收口到 config + auth TTL memoize(审计 H12/A11/G6)

- main.ts CORS origins 改读 config.get('cors').origins(消除与 configuration.ts 的重复解析,
  同时救活原本无人读的 cors config 字段);行为不变
- auth.service: 4 处 parseDurationToSeconds(jwt.expiresIn) 收口为 memoized getter accessExpiresInSeconds

service tsc 通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 4523c4ad
import 'reflect-metadata'; import 'reflect-metadata';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { apiReference } from '@scalar/nestjs-api-reference'; import { apiReference } from '@scalar/nestjs-api-reference';
import { cleanupOpenApiDoc } from 'nestjs-zod'; import { cleanupOpenApiDoc } from 'nestjs-zod';
...@@ -15,10 +16,7 @@ async function bootstrap() { ...@@ -15,10 +16,7 @@ async function bootstrap() {
app.use(helmet({ contentSecurityPolicy: false })); app.use(helmet({ contentSecurityPolicy: false }));
const corsOrigins = (process.env.CORS_ORIGINS ?? '') const corsOrigins = app.get(ConfigService).getOrThrow<{ origins: string[] }>('cors').origins;
.split(',')
.map((o) => o.trim())
.filter(Boolean);
app.enableCors({ app.enableCors({
origin: corsOrigins.length > 0 ? corsOrigins : true, origin: corsOrigins.length > 0 ? corsOrigins : true,
credentials: false, credentials: false,
......
...@@ -58,6 +58,14 @@ export class AuthService { ...@@ -58,6 +58,14 @@ export class AuthService {
private readonly config: ConfigService, private readonly config: ConfigService,
) {} ) {}
/** access token TTL(秒)— 进程内固定,memoize 一次(收口多处 parseDurationToSeconds) */
private _accessExpiresInSeconds?: number;
private get accessExpiresInSeconds(): number {
return (this._accessExpiresInSeconds ??= parseDurationToSeconds(
this.config.getOrThrow<string>('jwt.expiresIn'),
));
}
async exchangeToken(req: TokenExchangeRequest): Promise<TokenExchangeResponse> { async exchangeToken(req: TokenExchangeRequest): Promise<TokenExchangeResponse> {
const host = await this.prisma.host.findUnique({ where: { appId: req.appId } }); const host = await this.prisma.host.findUnique({ where: { appId: req.appId } });
if (!host || !host.active) { if (!host || !host.active) {
...@@ -99,7 +107,7 @@ export class AuthService { ...@@ -99,7 +107,7 @@ export class AuthService {
codeTtl, codeTtl,
); );
const expiresIn = parseDurationToSeconds(this.config.getOrThrow<string>('jwt.expiresIn')); const expiresIn = this.accessExpiresInSeconds;
return { return {
accessToken, accessToken,
...@@ -114,7 +122,7 @@ export class AuthService { ...@@ -114,7 +122,7 @@ export class AuthService {
const raw = await this.redis.getDel(CODE_PREFIX + code); const raw = await this.redis.getDel(CODE_PREFIX + code);
if (!raw) throw new BizError(ApiCode.AUTH_INVALID_CODE); if (!raw) throw new BizError(ApiCode.AUTH_INVALID_CODE);
const parsed = JSON.parse(raw) as { accessToken: string; refreshToken: string }; const parsed = JSON.parse(raw) as { accessToken: string; refreshToken: string };
const expiresIn = parseDurationToSeconds(this.config.getOrThrow<string>('jwt.expiresIn')); const expiresIn = this.accessExpiresInSeconds;
return { return {
accessToken: parsed.accessToken, accessToken: parsed.accessToken,
refreshToken: parsed.refreshToken, refreshToken: parsed.refreshToken,
...@@ -173,7 +181,7 @@ export class AuthService { ...@@ -173,7 +181,7 @@ export class AuthService {
dictionary: payload.dictionary, dictionary: payload.dictionary,
}); });
const expiresIn = parseDurationToSeconds(this.config.getOrThrow<string>('jwt.expiresIn')); const expiresIn = this.accessExpiresInSeconds;
return { accessToken, refreshToken: newRefreshToken, expiresIn }; return { accessToken, refreshToken: newRefreshToken, expiresIn };
} }
...@@ -182,7 +190,7 @@ export class AuthService { ...@@ -182,7 +190,7 @@ export class AuthService {
): Promise<string> { ): Promise<string> {
return this.jwt.signAsync(payload, { return this.jwt.signAsync(payload, {
secret: this.config.getOrThrow<string>('jwt.secret'), secret: this.config.getOrThrow<string>('jwt.secret'),
expiresIn: parseDurationToSeconds(this.config.getOrThrow<string>('jwt.expiresIn')), expiresIn: this.accessExpiresInSeconds,
}); });
} }
...@@ -313,7 +321,7 @@ export class AuthService { ...@@ -313,7 +321,7 @@ export class AuthService {
role: req.role, role: req.role,
dictionary, dictionary,
}); });
const expiresIn = parseDurationToSeconds(this.config.getOrThrow<string>('jwt.expiresIn')); const expiresIn = this.accessExpiresInSeconds;
this.logger.log( this.logger.log(
`mock-login: tenant=${req.tenant}(${preset.tenantNameZh}) role=${req.role} ` + `mock-login: tenant=${req.tenant}(${preset.tenantNameZh}) role=${req.role} ` +
......
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts"; import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment