54 lines
1.5 KiB
TypeScript
54 lines
1.5 KiB
TypeScript
|
|
import { Injectable } from "@nestjs/common";
|
||
|
|
import { ConfigService } from "@nestjs/config";
|
||
|
|
import { SignJWT, decodeJwt, jwtVerify, errors, type JWTPayload } from "jose";
|
||
|
|
import { parseJwtExpiry } from "../../utils/jwt-expiry.util.js";
|
||
|
|
|
||
|
|
@Injectable()
|
||
|
|
export class JoseJwtService {
|
||
|
|
private readonly secretKey: Uint8Array;
|
||
|
|
|
||
|
|
constructor(private readonly configService: ConfigService) {
|
||
|
|
const secret = configService.get<string>("JWT_SECRET");
|
||
|
|
if (!secret) {
|
||
|
|
throw new Error("JWT_SECRET is required in environment variables");
|
||
|
|
}
|
||
|
|
this.secretKey = new TextEncoder().encode(secret);
|
||
|
|
}
|
||
|
|
|
||
|
|
async sign(payload: JWTPayload, expiresIn: string): Promise<string> {
|
||
|
|
const expiresInSeconds = parseJwtExpiry(expiresIn);
|
||
|
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
||
|
|
|
||
|
|
return new SignJWT(payload)
|
||
|
|
.setProtectedHeader({ alg: "HS256" })
|
||
|
|
.setIssuedAt(nowSeconds)
|
||
|
|
.setExpirationTime(nowSeconds + expiresInSeconds)
|
||
|
|
.sign(this.secretKey);
|
||
|
|
}
|
||
|
|
|
||
|
|
async verify<T extends JWTPayload>(token: string): Promise<T> {
|
||
|
|
const { payload } = await jwtVerify(token, this.secretKey);
|
||
|
|
return payload as T;
|
||
|
|
}
|
||
|
|
|
||
|
|
async verifyAllowExpired<T extends JWTPayload>(token: string): Promise<T | null> {
|
||
|
|
try {
|
||
|
|
return await this.verify<T>(token);
|
||
|
|
} catch (err) {
|
||
|
|
if (err instanceof errors.JWTExpired) {
|
||
|
|
return this.decode<T>(token);
|
||
|
|
}
|
||
|
|
throw err;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
decode<T extends JWTPayload>(token: string): T | null {
|
||
|
|
try {
|
||
|
|
return decodeJwt(token) as T;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|