chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
function positiveInteger(
|
||||
config: ConfigService,
|
||||
key: string,
|
||||
fallback: number,
|
||||
): number {
|
||||
const value = Number(config.get<string>(key) ?? fallback);
|
||||
if (!Number.isInteger(value) || value <= 0) {
|
||||
throw new Error(`${key} must be a positive integer`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function requiredSecret(config: ConfigService, key: string): string {
|
||||
const value = config.get<string>(key);
|
||||
if (!value || value.length < 64) {
|
||||
throw new Error(`${key} must contain at least 64 characters`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function cookieName(config: ConfigService, key: string, fallback: string): string {
|
||||
const value = config.get<string>(key) ?? fallback;
|
||||
if (!/^[A-Za-z0-9_-]{1,80}$/.test(value)) {
|
||||
throw new Error(`${key} must be a valid cookie name`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AuthConfigService {
|
||||
readonly accessTokenSecret: string;
|
||||
readonly refreshTokenPepper: string;
|
||||
readonly accessTokenTtlSeconds: number;
|
||||
readonly refreshTokenTtlSeconds: number;
|
||||
readonly maxLoginAttempts: number;
|
||||
readonly lockoutSeconds: number;
|
||||
readonly webOrigin: string;
|
||||
readonly accessCookieName: string;
|
||||
readonly refreshCookieName: string;
|
||||
readonly csrfCookieName: string;
|
||||
|
||||
constructor(config: ConfigService) {
|
||||
this.accessTokenSecret = requiredSecret(config, 'JWT_ACCESS_SECRET');
|
||||
this.refreshTokenPepper = requiredSecret(config, 'REFRESH_TOKEN_PEPPER');
|
||||
if (this.accessTokenSecret === this.refreshTokenPepper) {
|
||||
throw new Error('JWT_ACCESS_SECRET and REFRESH_TOKEN_PEPPER must differ');
|
||||
}
|
||||
this.accessTokenTtlSeconds = positiveInteger(
|
||||
config,
|
||||
'ACCESS_TOKEN_TTL_SECONDS',
|
||||
900,
|
||||
);
|
||||
this.refreshTokenTtlSeconds = positiveInteger(
|
||||
config,
|
||||
'REFRESH_TOKEN_TTL_SECONDS',
|
||||
604800,
|
||||
);
|
||||
this.maxLoginAttempts = positiveInteger(
|
||||
config,
|
||||
'AUTH_MAX_LOGIN_ATTEMPTS',
|
||||
5,
|
||||
);
|
||||
this.lockoutSeconds = positiveInteger(
|
||||
config,
|
||||
'AUTH_LOCKOUT_SECONDS',
|
||||
900,
|
||||
);
|
||||
|
||||
const webOrigin = config.get<string>('WEB_ORIGIN');
|
||||
if (!webOrigin) throw new Error('Missing required environment variable: WEB_ORIGIN');
|
||||
|
||||
const parsedOrigin = new URL(webOrigin).origin;
|
||||
if (parsedOrigin !== webOrigin || !webOrigin.startsWith('https://')) {
|
||||
throw new Error('WEB_ORIGIN must be an HTTPS origin without a path');
|
||||
}
|
||||
this.webOrigin = parsedOrigin;
|
||||
|
||||
this.accessCookieName = cookieName(
|
||||
config,
|
||||
'ACCESS_COOKIE_NAME',
|
||||
'dhv2_access',
|
||||
);
|
||||
this.refreshCookieName = cookieName(
|
||||
config,
|
||||
'REFRESH_COOKIE_NAME',
|
||||
'dhv2_refresh',
|
||||
);
|
||||
this.csrfCookieName = cookieName(config, 'CSRF_COOKIE_NAME', 'dhv2_csrf');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user