You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
49 lines
1.3 KiB
49 lines
1.3 KiB
import { SignJWT, jwtVerify, decodeJwt } from "jose";
|
|
import type { JWTPayload } from "jose";
|
|
|
|
const rawSecret = process.env.JWT_SECRET;
|
|
if (!rawSecret) {
|
|
if (process.env.NODE_ENV === "production") {
|
|
throw new Error("JWT_SECRET environment variable is required in production");
|
|
}
|
|
rawSecret = "dev-secret-change-in-production";
|
|
}
|
|
const JWT_SECRET = new TextEncoder().encode(rawSecret);
|
|
const ACCESS_TOKEN_EXPIRY = "15m";
|
|
|
|
export interface AccessTokenPayload extends JWTPayload {
|
|
userId: number;
|
|
sessionId: string;
|
|
role: string;
|
|
}
|
|
|
|
export async function signAccessToken(payload: {
|
|
userId: number;
|
|
sessionId: string;
|
|
role: string;
|
|
}): Promise<string> {
|
|
return new SignJWT(payload as Record<string, unknown>)
|
|
.setProtectedHeader({ alg: "HS256" })
|
|
.setIssuedAt()
|
|
.setExpirationTime(ACCESS_TOKEN_EXPIRY)
|
|
.sign(JWT_SECRET);
|
|
}
|
|
|
|
export async function verifyAccessToken(
|
|
token: string
|
|
): Promise<AccessTokenPayload | null> {
|
|
try {
|
|
const { payload } = await jwtVerify(token, JWT_SECRET);
|
|
return payload as AccessTokenPayload;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function decodeAccessTokenNoVerify(token: string): AccessTokenPayload | null {
|
|
try {
|
|
return decodeJwt(token) as AccessTokenPayload;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|