84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
'use node';
|
|
|
|
import * as jose from "jose";
|
|
import type { GenericId } from "convex/values";
|
|
|
|
// JWT 验证配置
|
|
const JWT_CONFIG = {
|
|
// jwksUri: "https://kevisual.cn/root/convex/jwks.json",
|
|
jwksUri: "http://convex.kevisual.cn:3211/root/convex/jwks.json",
|
|
issuer: "https://kevisual.cn",
|
|
audience: "test-convex-app",
|
|
algorithms: ["RS256"],
|
|
};
|
|
|
|
// 创建 JWKS 缓存
|
|
let jwksCache: jose.JWTVerifyGetKey | null = null;
|
|
|
|
function getJWKS() {
|
|
if (!jwksCache) {
|
|
jwksCache = jose.createRemoteJWKSet(new URL(JWT_CONFIG.jwksUri));
|
|
}
|
|
return jwksCache;
|
|
}
|
|
|
|
export const createJwt = async (payload: jose.JWTPayload): Promise<string> => {
|
|
const secret = new TextEncoder().encode("your-256-bit-secret");
|
|
const token = await new jose.SignJWT(payload)
|
|
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
|
|
.setIssuedAt()
|
|
.setExpirationTime("2h")
|
|
.sign(secret);
|
|
return token;
|
|
}
|
|
// 验证 JWT token 并返回 payload
|
|
export async function verifyToken(token: string): Promise<jose.JWTPayload> {
|
|
const JWKS = getJWKS();
|
|
const { payload } = await jose.jwtVerify(token, JWKS, {
|
|
issuer: JWT_CONFIG.issuer,
|
|
audience: JWT_CONFIG.audience,
|
|
algorithms: JWT_CONFIG.algorithms,
|
|
});
|
|
return payload;
|
|
}
|
|
|
|
// 从请求 header 中提取 Bearer token
|
|
export function extractBearerToken(authHeader: string | null): string | null {
|
|
if (!authHeader) return null;
|
|
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
return match ? match[1] : null;
|
|
}
|
|
|
|
// 获取或创建用户(演示用)
|
|
export async function getOrCreateUser(
|
|
db: any,
|
|
tokenPayload: jose.JWTPayload
|
|
): Promise<GenericId<"users">> {
|
|
const externalId = tokenPayload.sub as string;
|
|
|
|
// 查找已存在的用户
|
|
const existingUser = await db
|
|
.query("users")
|
|
.withIndex("id", (q: any) => q.eq("id", externalId))
|
|
.first();
|
|
|
|
if (existingUser) {
|
|
// 更新最后登录时间
|
|
await db
|
|
.table("users")
|
|
.doc(existingUser._id)
|
|
.patch({ lastLoginAt: new Date().toISOString() });
|
|
return existingUser._id;
|
|
}
|
|
|
|
// 创建新用户
|
|
return await db
|
|
.table("users")
|
|
.insert({
|
|
id: externalId,
|
|
name: (tokenPayload.name as string) || "Unknown",
|
|
email: (tokenPayload.email as string) || "",
|
|
createdAt: new Date().toISOString(),
|
|
});
|
|
}
|