0.3 增加登录模块

This commit is contained in:
liaibo
2026-01-18 05:13:53 +08:00
parent df2e9f5339
commit 90a38b48cf
86 changed files with 2041 additions and 148 deletions
+71 -5
View File
@@ -1,5 +1,9 @@
import type { AuthContext } from "@/lib/auth/types";
import { getDevUser, isDevAuthEnabled } from "@/lib/auth/devUser";
import { isConvexEnabled } from "@/lib/convex/enabled";
import { convexAuthNextjsToken } from "@convex-dev/auth/nextjs/server";
import { fetchQuery } from "convex/nextjs";
import { api } from "@/lib/convex/api";
export class HttpError extends Error {
status: number;
@@ -11,17 +15,79 @@ export class HttpError extends Error {
}
}
export function getAuthContext(): AuthContext {
if (isDevAuthEnabled()) return getDevUser();
// 说明:后续接入真实鉴权时,在这里替换为 Supabase/Convex Auth 的校验逻辑。
/**
* 获取认证上下文
*
* 优先级:
* 1. Convex Auth(如果启用)- 服务端:从 Convex Auth cookies 获取并查询当前用户;客户端:从 hooks 获取
* 2. 开发用户(如果启用)
* 3. 抛出错误(未配置)
*/
export async function getAuthContext(): Promise<AuthContext> {
// 1. 检查是否启用 Convex Auth
if (isConvexEnabled()) {
// 说明:开发模式下允许使用固定用户快速跑通链路(例如迁移测试)。
if (isDevAuthEnabled()) {
return getDevUser();
}
const token = await convexAuthNextjsToken();
if (!token) {
throw new Error("未登录,请先登录");
}
// 说明:从 Convex Auth token 获取当前用户(users 表由 authTables 提供)。
// 这里不强依赖具体字段结构,避免升级 authTables 后类型不兼容。
const user = (await fetchQuery(api.users.currentUser, {}, { token })) as unknown;
if (!user || typeof user !== "object") {
throw new Error("未登录,请先登录");
}
const record = user as Record<string, unknown>;
const id = record["_id"];
const email = record["email"];
const name = record["name"];
if (typeof id !== "string" || !id) {
throw new Error("未登录,请先登录");
}
return {
userId: id,
email: typeof email === "string" ? email : undefined,
name: typeof name === "string" ? name : undefined,
};
}
// 2. 检查是否启用开发用户模式
if (isDevAuthEnabled()) {
return getDevUser();
}
// 3. 未配置认证
throw new Error("Auth is not configured");
}
export function requireAuthContext(): AuthContext {
/**
* 要求已认证的上下文
*
* 如果未认证,抛出 401 错误
*/
export async function requireAuthContext(): Promise<AuthContext> {
try {
return getAuthContext();
return await getAuthContext();
} catch (err) {
const message = err instanceof Error ? err.message : "Unauthorized";
throw new HttpError(401, message);
}
}
/**
* 获取当前用户 ID
*
* 用于服务端组件和 API 路由
*/
export async function getCurrentUserId(): Promise<string> {
const auth = await requireAuthContext();
return auth.userId;
}
+1 -1
View File
@@ -1,4 +1,4 @@
export function isConvexEnabled(): boolean {
return process.env.USE_CONVEX === "1";
return process.env.NEXT_PUBLIC_USE_CONVEX === "1";
}
+2 -3
View File
@@ -3,9 +3,8 @@ import type { AuthContext } from "@/lib/auth/types";
import { requireAuthContext } from "@/lib/auth/authContext";
import { getConvexHttpClient } from "@/lib/convex/server";
export function getAuthedConvexClient(): { auth: AuthContext; client: ConvexHttpClient } {
const auth = requireAuthContext();
export async function getAuthedConvexClient(): Promise<{ auth: AuthContext; client: ConvexHttpClient }> {
const auth = await requireAuthContext();
const client = getConvexHttpClient();
return { auth, client };
}
+3 -2
View File
@@ -13,10 +13,11 @@ export function getConvexHttpClient(): ConvexHttpClient {
const client = new ConvexHttpClient(url);
const adminKey = process.env.CONVEX_SELF_HOSTED_ADMIN_KEY;
if (adminKey) {
client.setAdminAuth(adminKey);
// 说明:ConvexHttpClient 的类型声明可能未暴露 setAdminAuth(但运行期存在)。
// 这里用最小侵入方式兼容自托管管理密钥。
(client as any).setAdminAuth(adminKey);
}
cached = client;
return client;
}