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.
54 lines
1.6 KiB
54 lines
1.6 KiB
import { SignJWT, jwtVerify, type JWTPayload } from "jose";
|
|
import { JWT_EXPIRY_SECONDS } from "@/lib/constants/auth";
|
|
import { unauthorized } from "@/modules/auth/errors";
|
|
|
|
export const EMBED_DEMO_ROLE = "embed_demo" as const;
|
|
export const EMBED_DEMO_COOKIE = "embed_demo_session";
|
|
export const EMBED_DEMO_DEFAULT_PASSWORD = "123456";
|
|
|
|
export type EmbedDemoJwtPayload = {
|
|
sub: string;
|
|
role: typeof EMBED_DEMO_ROLE;
|
|
};
|
|
|
|
function getJwtSecret(): Uint8Array {
|
|
const secret = process.env.JWT_SECRET;
|
|
if (!secret) {
|
|
throw new Error("JWT_SECRET 未配置");
|
|
}
|
|
return new TextEncoder().encode(secret);
|
|
}
|
|
|
|
export function getEmbedDemoPassword(): string {
|
|
return process.env.EMBED_DEMO_PASSWORD?.trim() || EMBED_DEMO_DEFAULT_PASSWORD;
|
|
}
|
|
|
|
function toEmbedPayload(payload: JWTPayload): EmbedDemoJwtPayload {
|
|
if (typeof payload.sub !== "string" || payload.role !== EMBED_DEMO_ROLE) {
|
|
throw unauthorized("无效的演示会话");
|
|
}
|
|
return { sub: payload.sub, role: EMBED_DEMO_ROLE };
|
|
}
|
|
|
|
export async function signEmbedDemoToken(customerId: string): Promise<string> {
|
|
return new SignJWT({ role: EMBED_DEMO_ROLE })
|
|
.setProtectedHeader({ alg: "HS256" })
|
|
.setSubject(customerId)
|
|
.setIssuedAt()
|
|
.setExpirationTime(`${JWT_EXPIRY_SECONDS}s`)
|
|
.sign(getJwtSecret());
|
|
}
|
|
|
|
export async function verifyEmbedDemoToken(
|
|
token: string,
|
|
): Promise<EmbedDemoJwtPayload> {
|
|
try {
|
|
const { payload } = await jwtVerify(token, getJwtSecret(), {
|
|
algorithms: ["HS256"],
|
|
});
|
|
return toEmbedPayload(payload);
|
|
} catch {
|
|
throw unauthorized("演示会话无效或已过期");
|
|
}
|
|
}
|