import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { jwtVerify, SignJWT } from "jose";
import { DEFAULT_JWT_SECRET } from "./auth-validation.schema";
import { InternalTokenPayload, TokenPayload } from "./token.decorator";
export interface GenerateTokenOptions {
expiresIn?: string;
audience?: string;
subject: string;
}
@Injectable()
export class JwtService {
constructor(private configService: ConfigService) {
if (
this.configService.get<string>("JWT_SECRET") === DEFAULT_JWT_SECRET
) {
console.warn(
"Using default JWT secret. This is not secure for production environments.",
);
}
}
/**
* Generate a JWT token for integrated OAuth2 server
*/
async generateToken(
payload: InternalTokenPayload,
options: GenerateTokenOptions,
): Promise<string> {
if (this.isUsingExternalOIDC()) {
throw new Error(
"Token generation is not available when using external OIDC provider. Use your external OIDC provider for token generation.",
);
}
const secret = this.configService.getOrThrow<string>("JWT_SECRET");
const issuer = this.configService.getOrThrow<string>("JWT_ISSUER");
const expiresIn =
options.expiresIn ||
this.configService.getOrThrow<string>("JWT_EXPIRES_IN");
const secretKey = new TextEncoder().encode(secret);
const jwt = new SignJWT({
...payload,
})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setIssuer(issuer)
.setSubject(options.subject)
.setExpirationTime(expiresIn);
if (options.audience) {
jwt.setAudience(options.audience);
}
return await jwt.sign(secretKey);
}
/**
* Verify a JWT token (for additional validation if needed)
*/
async verifyToken(token: string): Promise<TokenPayload> {
if (this.isUsingExternalOIDC()) {
throw new Error(
"Token verification is handled by external OIDC provider.",
);
}
const secret = this.configService.getOrThrow<string>("JWT_SECRET");
const issuer = this.configService.getOrThrow<string>("JWT_ISSUER");
const secretKey = new TextEncoder().encode(secret);
try {
const { payload } = (await jwtVerify(token, secretKey, {
issuer,
algorithms: ["HS256"],
})) as { payload: TokenPayload };
return payload;
} catch (error) {
throw new Error(`Invalid token: ${error.message}`);
}
}
/**
* Check if the service is using external OIDC provider
*/
isUsingExternalOIDC(): boolean {
return this.configService.get<string>("OIDC") !== undefined;
}
}