auth 认证与授权
统一的认证和授权解决方案,支持微信 OAuth、权限管理、邀请系统
本页面介绍 API 使用方法。首次配置请参考 开发指南。
概述
@mono/auth 是项目的认证和授权包,基于 Better Auth 构建。它提供了完整的用户认证、权限管理、微信 OAuth 集成和邀请系统功能。
为什么需要这个包?
虽然 Better Auth 已经提供了强大的认证功能,但我们仍然需要一个封装层来:
- 统一权限管理 - 提供基于角色的访问控制(RBAC)
- 微信生态集成 - 支持 PC 和移动端微信登录
- 业务逻辑封装 - 邀请系统、组织管理等业务功能
- 类型安全 - 完整的 TypeScript 类型定义
- 可复用性 - 在多个应用中共享认证逻辑
核心功能
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配置步骤
-
注册微信开放平台账号
- 访问 微信开放平台
- 完成开发者认证
-
创建应用
- PC 端:创建"网站应用"
- 移动端:创建"移动应用"
-
配置回调域名
- PC 端:配置网站授权回调域名
- 移动端:配置应用签名和包名
-
获取凭证
- 复制 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 />;
}权限层级
组织权限有三个层级:
- 全局管理员 -
user.role === "super_admin"或"admin" - 组织所有者 -
member.role === "owner" - 组织管理员 -
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
解决方案:
- 检查用户对象是否包含
role字段 - 确认角色值是否正确(
super_admin或operation_admin) - 检查是否使用了正确的权限枚举
// 调试代码
console.log("User role:", user.role);
console.log("User object:", user);
console.log("Permissions:", getUserPermissions(user));微信登录失败
问题:微信登录跳转后返回错误
解决方案:
- 检查环境变量是否正确设置
- 确认回调域名已在微信开放平台配置
- 检查应用是否已审核通过
- 验证 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
解决方案:
- 检查组织对象是否包含
members数组 - 确认成员对象包含
userId和role字段 - 验证用户 ID 是否匹配
// 调试代码
console.log("Organization:", organization);
console.log("Members:", organization?.members);
console.log("User ID:", user?.id);API 参考
完整的 API 文档请参考 README.md。