Merge branch 'main' of https://git.xiongxiao.me/abearxiong/test-convex
This commit is contained in:
12
convex/_generated/api.d.ts
vendored
12
convex/_generated/api.d.ts
vendored
@@ -9,7 +9,13 @@
|
||||
*/
|
||||
|
||||
import type * as abcv from "../abcv.js";
|
||||
<<<<<<< HEAD
|
||||
import type * as xiong from "../xiong.js";
|
||||
=======
|
||||
import type * as actions_jwt from "../actions/jwt.js";
|
||||
import type * as actions_redis from "../actions/redis.js";
|
||||
import type * as http from "../http.js";
|
||||
>>>>>>> 5657bffd39687b05f44247a0318fee65990aa3db
|
||||
|
||||
import type {
|
||||
ApiFromModules,
|
||||
@@ -19,7 +25,13 @@ import type {
|
||||
|
||||
declare const fullApi: ApiFromModules<{
|
||||
abcv: typeof abcv;
|
||||
<<<<<<< HEAD
|
||||
xiong: typeof xiong;
|
||||
=======
|
||||
"actions/jwt": typeof actions_jwt;
|
||||
"actions/redis": typeof actions_redis;
|
||||
http: typeof http;
|
||||
>>>>>>> 5657bffd39687b05f44247a0318fee65990aa3db
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,10 @@ export const get = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const auth = await ctx.auth.getUserIdentity();
|
||||
console.log("Query abcv.get called", auth);
|
||||
console.log("Query abcv.get called, auth:", auth);
|
||||
if (auth) {
|
||||
console.log("Authenticated user ID:", auth.subject);
|
||||
}
|
||||
return await ctx.db.query("abcv").collect();
|
||||
},
|
||||
});
|
||||
|
||||
83
convex/actions/jwt.ts
Normal file
83
convex/actions/jwt.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
'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(),
|
||||
});
|
||||
}
|
||||
22
convex/actions/redis.ts
Normal file
22
convex/actions/redis.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
'use node';
|
||||
|
||||
import { query, mutation, action } from "../_generated/server";
|
||||
import { Kevisual } from '@kevisual/ai/browser'
|
||||
import { v } from "convex/values";
|
||||
import { Redis } from "ioredis";
|
||||
const redisClient = new Redis({
|
||||
host: process.env.REDIS_HOST,
|
||||
password: process.env.REDIS_PASSWORD,
|
||||
});
|
||||
let time: any = null;
|
||||
export const isConnected = action({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const result = await redisClient.ping();
|
||||
if (time === null) {
|
||||
time = new Date();
|
||||
}
|
||||
console.log("Redis PING at", new Date().toISOString(), "since", time);
|
||||
return result === "PONG";
|
||||
},
|
||||
});
|
||||
13
convex/auth.config.ts
Normal file
13
convex/auth.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { AuthConfig } from "convex/server";
|
||||
|
||||
export default {
|
||||
providers: [
|
||||
{
|
||||
type: "customJwt",
|
||||
applicationID: "convex-app",
|
||||
issuer: "https://convex.kevisual.cn",
|
||||
jwks: "https://api-convex.kevisual.cn/root/convex/jwks.json",
|
||||
algorithm: "RS256",
|
||||
},
|
||||
],
|
||||
};
|
||||
38
convex/http.ts
Normal file
38
convex/http.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { httpRouter } from "convex/server";
|
||||
import { httpAction } from "./_generated/server";
|
||||
const http = httpRouter();
|
||||
|
||||
http.route({
|
||||
path: "/auth",
|
||||
method: "POST",
|
||||
handler: httpAction(async (ctx, request) => {
|
||||
// 处理请求并返回响应
|
||||
return new Response(JSON.stringify({ message: "Hello from custom endpoint!" }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}),
|
||||
})
|
||||
|
||||
http.route({
|
||||
path: '/root/convex/jwks.json',
|
||||
method: 'GET',
|
||||
handler: httpAction(async (ctx, request) => {
|
||||
// 返回 JWKS 数据
|
||||
const jwks = {
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"n": "km4cjJJOMFkl2G5qWMuFmWwF7rmeqRYzYdR8SddKeeMW0e9yIf5pv2Mfwv0aMJUpb-_j3j9M7whx_SEGc_2jx1vxCu1AlYURhnnLTWdsR-ZRPr2LK9UstMrgpWV425R2RliqXTDTYlSxUUlD9nPue_tqbfwN2aM9MCarm67xK_ZCcKRlW9o9L2-9UMfzRA7uiy4VQtOemP0PTXp-E9RxNiMPOQXIRls9wTW_EkDT3dGy7JCZhj7_qib3T8k9m84SwU7wI2R_3IH4DcHSMAn1BRRMXZ1_wPhbP39laNtdJgbDjGCqUccGhLUaoo2WGkZ52eb7NPqamp0K1Dh2jwTIJQ",
|
||||
"e": "AQAB",
|
||||
"kid": "kid-key-1"
|
||||
}
|
||||
]
|
||||
};
|
||||
return new Response(JSON.stringify(jwks), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}),
|
||||
})
|
||||
export default http;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineSchema, defineTable } from "convex/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
import { authTables } from "@convex-dev/auth/server";
|
||||
// Define a messages table with an index.
|
||||
export default defineSchema({
|
||||
abcv: defineTable({
|
||||
@@ -9,4 +9,16 @@ export default defineSchema({
|
||||
xiong: defineTable({
|
||||
name: v.string(),
|
||||
}),
|
||||
users: defineTable({
|
||||
id: v.string(), // 外部系统的用户 ID
|
||||
name: v.string(),
|
||||
createdAt: v.string(),
|
||||
lastLoginAt: v.optional(v.string()),
|
||||
}).index("id", ["id"]),
|
||||
sessions: defineTable({
|
||||
userId: v.id("users"),
|
||||
createdAt: v.string(),
|
||||
expiresAt: v.optional(v.string()),
|
||||
token: v.optional(v.string()),
|
||||
}).index("token", ["token"]),
|
||||
});
|
||||
Reference in New Issue
Block a user