01MVP 标识01MVP
包文档认证与配置auth 认证与授权

auth 认证与授权

统一的认证和授权解决方案,支持微信 OAuth、权限管理、邀请系统

本页面介绍 API 使用方法。首次配置请参考 开发指南

概述

@mono/auth 是项目的认证和授权包,基于 Better Auth 构建。它提供了完整的用户认证、权限管理、微信 OAuth 集成和邀请系统功能。

为什么需要这个包?

虽然 Better Auth 已经提供了强大的认证功能,但我们仍然需要一个封装层来:

  1. 统一权限管理 - 提供基于角色的访问控制(RBAC)
  2. 微信生态集成 - 支持 PC 和移动端微信登录
  3. 业务逻辑封装 - 邀请系统、组织管理等业务功能
  4. 类型安全 - 完整的 TypeScript 类型定义
  5. 可复用性 - 在多个应用中共享认证逻辑

核心功能

1. 权限系统

基于角色的访问控制(RBAC),支持细粒度的权限管理。

管理员角色

系统预定义了两种管理员角色:

  • 超级管理员(SUPER_ADMIN) - 拥有所有权限
  • 运营管理员(OPERATION_ADMIN) - 拥有运营相关权限
import { AdminRole, isAdmin } from "@mono/auth";

// 检查是否是管理员
if (isAdmin(user)) {
  console.log("用户是管理员");
}

权限类型

系统支持以下权限类别:

用户管理

import { hasPermission, AdminPermission } from "@mono/auth";

// 查看用户列表
if (hasPermission(user, AdminPermission.VIEW_USERS)) {
  // 显示用户列表
}

// 管理用户(编辑、删除)
if (hasPermission(user, AdminPermission.MANAGE_USERS)) {
  // 显示管理功能
}

// 封禁用户
if (hasPermission(user, AdminPermission.BAN_USERS)) {
  // 显示封禁按钮
}

贡献管理

// 查看贡献
hasPermission(user, AdminPermission.VIEW_CONTRIBUTIONS)

// 审核贡献
hasPermission(user, AdminPermission.REVIEW_CONTRIBUTIONS)

勋章管理

// 查看勋章
hasPermission(user, AdminPermission.VIEW_BADGES)

// 管理勋章
hasPermission(user, AdminPermission.MANAGE_BADGES)

// 颁发勋章
hasPermission(user, AdminPermission.AWARD_BADGES)

组织管理

// 查看组织
hasPermission(user, AdminPermission.VIEW_ORGANIZATIONS)

// 管理组织
hasPermission(user, AdminPermission.MANAGE_ORGANIZATIONS)

权限检查函数

import {
  isAdmin,
  hasPermission,
  getUserPermissions,
  canPerformAction,
  AdminPermission
} from "@mono/auth";

// 1. 检查是否是管理员
const isUserAdmin = isAdmin(user);

// 2. 检查特定权限
const canManageUsers = hasPermission(user, AdminPermission.MANAGE_USERS);

// 3. 获取用户所有权限
const permissions = getUserPermissions(user);
console.log(permissions); // [AdminPermission.VIEW_USERS, ...]

// 4. 检查是否可以执行操作(hasPermission 的别名)
const canDelete = canPerformAction(user, AdminPermission.MANAGE_USERS);

2. 微信 OAuth 集成

支持 PC 端扫码登录和移动端微信���录。

设备检测

import { isWechatBrowser, isMobileDevice } from "@mono/auth";

// 检测是否在微信浏览器中
if (isWechatBrowser()) {
  console.log("在微信浏览器中");
  // 使用微信内置登录
}

// 检测是否是移动设备
if (isMobileDevice()) {
  console.log("移动设备");
  // ���示移动端 UI
}

登录流程示例

import { isWechatBrowser, isMobileDevice } from "@mono/auth";

function WechatLoginButton() {
  const handleLogin = () => {
    let loginUrl: string;

    if (isWechatBrowser()) {
      // 在微信浏览器中,使用微信内置登录
      loginUrl = "/api/auth/wechat/mobile";
    } else if (isMobileDevice()) {
      // 移动端非微信浏览器
      loginUrl = "/api/auth/wechat/mobile";
    } else {
      // PC 端,使用扫码登录
      loginUrl = "/api/auth/wechat/pc";
    }

    window.location.href = loginUrl;
  };

  return <button onClick={handleLogin}>微信登录</button>;
}

环境变量配置

# PC 端微信登录
WECHAT_APP_ID=your-wechat-app-id
WECHAT_APP_SECRET=your-wechat-app-secret

# 移动端微信登录
WECHAT_MOBILE_APP_ID=your-mobile-app-id
WECHAT_MOBILE_APP_SECRET=your-mobile-app-secret

配置步骤

  1. 注册微信开放平台账号

  2. 创建应用

    • PC 端:创建"网站应用"
    • 移动端:创建"移动应用"
  3. 配置回调域名

    • PC 端:配置网站授权回调域名
    • 移动端:配置应用签名和包名
  4. 获取凭证

    • 复制 AppID 和 AppSecret
    • 设置到环境变量中

3. 邀请系统

支持邀请码注册和占位邮箱功能。

占位邮箱

当用户通过邀请码注册但未提供邮箱时,系统会生成占位邮箱:

import {
  isPlaceholderInvitationEmail,
  INVITATION_PLACEHOLDER_DOMAIN
} from "@mono/auth";

// 生成占位邮箱
const invitationCode = "ABC123";
const placeholderEmail = `${invitationCode}@${INVITATION_PLACEHOLDER_DOMAIN}`;
// 结果: "ABC123@invitation-placeholder.local"

// 检查是否是占位邮箱
if (isPlaceholderInvitationEmail(user.email)) {
  // 提示用户绑定真实邮箱
  return <EmailBindingPrompt />;
}

邀请流程

// 1. 生成邀请码
const invitationCode = generateInvitationCode();

// 2. 用户注册
async function registerWithInvitation(code: string, email?: string) {
  const userEmail = email || `${code}@${INVITATION_PLACEHOLDER_DOMAIN}`;

  await createUser({
    email: userEmail,
    invitationCode: code
  });
}

// 3. 检查是否需要完善邮箱
function ProfilePage({ user }) {
  if (isPlaceholderInvitationEmail(user.email)) {
    return (
      <div>
        <p>请绑定您的真实邮箱</p>
        <EmailBindingForm />
      </div>
    );
  }

  return <UserProfile user={user} />;
}

4. 组织管理

支持组织成员和角色管理。

组织权限检查

import { isOrganizationAdmin } from "@mono/auth";

function OrganizationSettings({ organization, user }) {
  // 检查用户是否是组织管理员
  if (!isOrganizationAdmin(organization, user)) {
    return <div>无权访问</div>;
  }

  return <OrganizationSettingsForm />;
}

权限层级

组织权限有三个层级:

  1. 全局管理员 - user.role === "super_admin""admin"
  2. 组织所有者 - member.role === "owner"
  3. 组织管理员 - member.role === "admin"
// 全局管理员可以管理所有组织
if (user.role === "super_admin") {
  // 显示所有组织的管理功能
}

// 组织管理员只能管理当前组织
if (isOrganizationAdmin(organization, user)) {
  // 显示当前组织的管理功能
}

5. 服务端查询

支持 React Server Components 的查询客户端。

import { getServerQueryClient } from "@mono/auth";

// 在 Server Component 中使用
export default async function UserPage() {
  const queryClient = getServerQueryClient();

  // 使用查询客户端
  const users = await queryClient.fetchQuery({
    queryKey: ["users"],
    queryFn: fetchUsers
  });

  return <UserList users={users} />;
}

实战示例

示例 1:权限保护的管理页面

import { hasPermission, AdminPermission } from "@mono/auth";
import { redirect } from "next/navigation";

export default async function UserManagementPage() {
  const user = await getUser();

  // 检查权限
  if (!hasPermission(user, AdminPermission.MANAGE_USERS)) {
    redirect("/unauthorized");
  }

  return (
    <div>
      <h1>用户管理</h1>
      <UserTable />
    </div>
  );
}

示例 2:条件渲染管理功能

import { hasPermission, AdminPermission } from "@mono/auth";

function UserCard({ user, currentUser }) {
  const canManage = hasPermission(currentUser, AdminPermission.MANAGE_USERS);
  const canBan = hasPermission(currentUser, AdminPermission.BAN_USERS);

  return (
    <div>
      <h3>{user.name}</h3>
      {canManage && (
        <button onClick={() => editUser(user.id)}>编辑</button>
      )}
      {canBan && (
        <button onClick={() => banUser(user.id)}>封禁</button>
      )}
    </div>
  );
}

示例 3:API 路由权限保护

import { hasPermission, AdminPermission } from "@mono/auth";
import { NextResponse } from "next/server";

export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  const user = await getUser();

  // 检查权限
  if (!hasPermission(user, AdminPermission.MANAGE_USERS)) {
    return NextResponse.json(
      { error: "Forbidden" },
      { status: 403 }
    );
  }

  // 执行删除
  await deleteUser(params.id);

  return NextResponse.json({ success: true });
}

示例 4:微信登录集成

"use client";

import { isWechatBrowser, isMobileDevice } from "@mono/auth";
import { useState, useEffect } from "react";

export function WechatLoginButton() {
  const [loginUrl, setLoginUrl] = useState("/api/auth/wechat/pc");

  useEffect(() => {
    // 根据设备类型选择登录方式
    if (isWechatBrowser()) {
      setLoginUrl("/api/auth/wechat/mobile");
    } else if (isMobileDevice()) {
      setLoginUrl("/api/auth/wechat/mobile");
    } else {
      setLoginUrl("/api/auth/wechat/pc");
    }
  }, []);

  return (
    <a href={loginUrl} className="btn-wechat">
      <WechatIcon />
      微信登录
    </a>
  );
}

示例 5:邀请注册流程

import { isPlaceholderInvitationEmail, INVITATION_PLACEHOLDER_DOMAIN } from "@mono/auth";

// 注册页面
export function RegisterForm({ invitationCode }: { invitationCode?: string }) {
  const [email, setEmail] = useState("");

  const handleSubmit = async () => {
    // 如果有邀请码但没有邮箱,使用占位邮箱
    const userEmail = email || (invitationCode
      ? `${invitationCode}@${INVITATION_PLACEHOLDER_DOMAIN}`
      : undefined);

    await register({
      email: userEmail,
      invitationCode
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="邮箱(可选)"
      />
      <button type="submit">注册</button>
    </form>
  );
}

// 个人资料页面
export function ProfilePage({ user }) {
  if (isPlaceholderInvitationEmail(user.email)) {
    return (
      <div className="alert">
        <p>您还未绑定邮箱,请完善您的账户信息</p>
        <EmailBindingForm userId={user.id} />
      </div>
    );
  }

  return <UserProfile user={user} />;
}

最佳实践

1. 双重权限检查

始终在客户端和服务端都进行权限检查:

// ✅ 推荐:双重检查
// 客户端 - 控制 UI 显示
function AdminPanel({ user }) {
  if (!hasPermission(user, AdminPermission.MANAGE_USERS)) {
    return null;
  }
  return <ManageUsersButton />;
}

// 服务端 - 安全保障
export async function deleteUser(userId: string) {
  const user = await getUser();
  if (!hasPermission(user, AdminPermission.MANAGE_USERS)) {
    throw new Error("Forbidden");
  }
  await db.user.delete({ where: { id: userId } });
}

2. 使用枚举而不是字符串

// ✅ 推荐:类型安全
hasPermission(user, AdminPermission.MANAGE_USERS)

// ❌ 不推荐:容易拼写错误
hasPermission(user, "manage_users")

3. 权限组合

// 检查多个权限
function canManageContent(user: any): boolean {
  return (
    hasPermission(user, AdminPermission.VIEW_CONTRIBUTIONS) &&
    hasPermission(user, AdminPermission.REVIEW_CONTRIBUTIONS)
  );
}

// 检查任一权限
function canAccessDashboard(user: any): boolean {
  return (
    hasPermission(user, AdminPermission.VIEW_DASHBOARD) ||
    isAdmin(user)
  );
}

4. 组织权限分离

// 全局权限和组织权限分开处理
function canEditOrganization(organization: any, user: any): boolean {
  // 全局管理员可以编辑所有组织
  if (isAdmin(user)) {
    return true;
  }

  // 组织管理员只能编辑自己的组织
  return isOrganizationAdmin(organization, user);
}

5. 错误处理

// API 路由中的权限检查
export async function POST(request: Request) {
  try {
    const user = await getUser();

    if (!user) {
      return NextResponse.json(
        { error: "Unauthorized" },
        { status: 401 }
      );
    }

    if (!hasPermission(user, AdminPermission.MANAGE_USERS)) {
      return NextResponse.json(
        { error: "Forbidden" },
        { status: 403 }
      );
    }

    // 执行操作
    const result = await performAction();
    return NextResponse.json(result);

  } catch (error) {
    console.error("Error:", error);
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}

故障排查

权限检查总是返回 false

问题hasPermission() 总是返回 false

解决方案

  1. 检查用户对象是否包含 role 字段
  2. 确认角色值是否正确(super_adminoperation_admin
  3. 检查是否使用了正确的权限枚举
// 调试代码
console.log("User role:", user.role);
console.log("User object:", user);
console.log("Permissions:", getUserPermissions(user));

微信登录失败

问题:微信登录跳转后返回错误

解决方案

  1. 检查环境变量是否正确设置
  2. 确认回调域名已在微信开放平台配置
  3. 检查应用是否已审核通过
  4. 验证 AppID 和 AppSecret 是否匹配
# 检查环境变量
echo $WECHAT_APP_ID
echo $WECHAT_APP_SECRET

占位邮箱检测失败

问题isPlaceholderInvitationEmail() 返回错误结果

解决方案: 确保邮箱格式正确,必须以 @invitation-placeholder.local 结尾

// ✅ 正确格式
const email = `${code}@invitation-placeholder.local`;

// ❌ 错误格式
const email = `${code}@placeholder.local`;

组织权限检查失败

问题isOrganizationAdmin() 返回 false

解决方案

  1. 检查组织对象是否包含 members 数组
  2. 确认成员对象包含 userIdrole 字段
  3. 验证用户 ID 是否匹配
// 调试代码
console.log("Organization:", organization);
console.log("Members:", organization?.members);
console.log("User ID:", user?.id);

API 参考

完整的 API 文档请参考 README.md

相关资源