295 lines
14 KiB
TypeScript
295 lines
14 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import { client } from "@/lib/auth-client";
|
|||
|
|
import { Button } from "@nice/ui/components/button";
|
|||
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@nice/ui/components/card";
|
|||
|
|
import { Input } from "@nice/ui/components/input";
|
|||
|
|
import { Label } from "@nice/ui/components/label";
|
|||
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@nice/ui/components/tabs";
|
|||
|
|
import { toast } from "@nice/ui/components/sonner";
|
|||
|
|
import { useState } from "react";
|
|||
|
|
|
|||
|
|
export default function BetterAuthDemo() {
|
|||
|
|
const [currentUser, setCurrentUser] = useState<any>(null);
|
|||
|
|
const [testCredentials, setTestCredentials] = useState({
|
|||
|
|
email: "test@example.com",
|
|||
|
|
password: "Test123456",
|
|||
|
|
name: "Test User"
|
|||
|
|
});
|
|||
|
|
const [discoveryInfo, setDiscoveryInfo] = useState<any>(null);
|
|||
|
|
|
|||
|
|
// 获取 OIDC Discovery 信息
|
|||
|
|
const handleGetDiscoveryInfo = async () => {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch('http://localhost:3001/api/auth/.well-known/openid-configuration');
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|||
|
|
}
|
|||
|
|
const info = await response.json();
|
|||
|
|
setDiscoveryInfo(info);
|
|||
|
|
toast.success("获取 Discovery 信息成功!");
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error("获取 Discovery 信息失败:", error);
|
|||
|
|
toast.error(`获取 Discovery 信息失败: ${error.message || "未知错误"}`);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 测试 Generic OAuth 配置
|
|||
|
|
const handleTestGenericOAuthConfig = async () => {
|
|||
|
|
try {
|
|||
|
|
const result = await client.signIn.oauth2({
|
|||
|
|
providerId: "fenghuo-oidc",
|
|||
|
|
callbackURL: "http://localhost:3000/auth/better-auth-callback",
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (result.data) {
|
|||
|
|
toast.success("OAuth 配置测试成功!");
|
|||
|
|
} else if (result.error) {
|
|||
|
|
console.error("OAuth 错误详情:", result.error);
|
|||
|
|
toast.error(`OAuth 配置测试失败: ${result.error.message}`);
|
|||
|
|
}
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error("OAuth 配置测试失败:", error);
|
|||
|
|
toast.error(`OAuth 配置测试失败: ${error.message || "未知错误"}`);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 检查当前会话
|
|||
|
|
const handleCheckSession = async () => {
|
|||
|
|
try {
|
|||
|
|
const session = await client.getSession();
|
|||
|
|
console.log("当前会话:", session);
|
|||
|
|
setCurrentUser(session.data);
|
|||
|
|
toast.success(session.data ? `会话存在: ${session.data.user.email}` : "无活跃会话");
|
|||
|
|
return session.data;
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error("检查会话失败:", error);
|
|||
|
|
toast.error(`检查会话失败: ${error.message}`);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 创建测试用户
|
|||
|
|
const handleCreateTestUser = async () => {
|
|||
|
|
try {
|
|||
|
|
const result = await client.signUp.email({
|
|||
|
|
email: testCredentials.email,
|
|||
|
|
password: testCredentials.password,
|
|||
|
|
name: testCredentials.name,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (result.data) {
|
|||
|
|
toast.success("测试用户创建成功!");
|
|||
|
|
setCurrentUser(result.data);
|
|||
|
|
} else if (result.error) {
|
|||
|
|
console.error("创建用户失败:", result.error);
|
|||
|
|
toast.error(`创建用户失败: ${result.error.message}`);
|
|||
|
|
}
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error("创建用户失败:", error);
|
|||
|
|
toast.error(`创建用户失败: ${error.message || "未知错误"}`);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 用邮箱密码登录
|
|||
|
|
const handleEmailSignIn = async () => {
|
|||
|
|
try {
|
|||
|
|
const result = await client.signIn.email({
|
|||
|
|
email: testCredentials.email,
|
|||
|
|
password: testCredentials.password,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (result.data) {
|
|||
|
|
toast.success("邮箱登录成功!");
|
|||
|
|
setCurrentUser(result.data);
|
|||
|
|
} else if (result.error) {
|
|||
|
|
console.error("邮箱登录失败:", result.error);
|
|||
|
|
toast.error(`邮箱登录失败: ${result.error.message}`);
|
|||
|
|
}
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error("邮箱登录失败:", error);
|
|||
|
|
toast.error(`邮箱登录失败: ${error.message || "未知错误"}`);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 登出
|
|||
|
|
const handleSignOut = async () => {
|
|||
|
|
try {
|
|||
|
|
await client.signOut();
|
|||
|
|
setCurrentUser(null);
|
|||
|
|
toast.success("已登出");
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error("登出失败:", error);
|
|||
|
|
toast.error(`登出失败: ${error.message}`);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="container mx-auto p-6 space-y-6">
|
|||
|
|
<div className="text-center">
|
|||
|
|
<h1 className="text-3xl font-bold mb-2">Better Auth 测试页面</h1>
|
|||
|
|
<p className="text-muted-foreground">测试认证功能</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<Tabs defaultValue="account" className="space-y-6">
|
|||
|
|
<TabsList className="grid w-full grid-cols-3">
|
|||
|
|
<TabsTrigger value="account">账户管理</TabsTrigger>
|
|||
|
|
<TabsTrigger value="discovery">OIDC 信息</TabsTrigger>
|
|||
|
|
<TabsTrigger value="oauth">OAuth 测试</TabsTrigger>
|
|||
|
|
</TabsList>
|
|||
|
|
|
|||
|
|
{/* 账户管理 Tab */}
|
|||
|
|
<TabsContent value="account" className="space-y-6">
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle>当前用户状态</CardTitle>
|
|||
|
|
<CardDescription>
|
|||
|
|
查看当前登录状态
|
|||
|
|
</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
<div className="flex gap-4 mb-4">
|
|||
|
|
<Button onClick={handleCheckSession} variant="outline">
|
|||
|
|
检查会话状态
|
|||
|
|
</Button>
|
|||
|
|
{currentUser && (
|
|||
|
|
<Button onClick={handleSignOut} variant="destructive">
|
|||
|
|
登出
|
|||
|
|
</Button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{currentUser ? (
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<h4 className="font-semibold">当前用户信息:</h4>
|
|||
|
|
<div className="bg-green-50 dark:bg-green-900/20 p-4 rounded-md">
|
|||
|
|
<div className="text-sm space-y-1">
|
|||
|
|
<div><strong>Email:</strong> {currentUser.user?.email}</div>
|
|||
|
|
<div><strong>Name:</strong> {currentUser.user?.name}</div>
|
|||
|
|
<div><strong>ID:</strong> {currentUser.user?.id}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-4 rounded-md">
|
|||
|
|
<div className="text-sm text-yellow-800 dark:text-yellow-200">
|
|||
|
|
⚠️ 未登录状态
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle>用户操作</CardTitle>
|
|||
|
|
<CardDescription>
|
|||
|
|
创建测试用户或登录
|
|||
|
|
</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="testEmail">邮箱</Label>
|
|||
|
|
<Input
|
|||
|
|
id="testEmail"
|
|||
|
|
type="email"
|
|||
|
|
value={testCredentials.email}
|
|||
|
|
onChange={(e) => setTestCredentials({ ...testCredentials, email: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="testPassword">密码</Label>
|
|||
|
|
<Input
|
|||
|
|
id="testPassword"
|
|||
|
|
type="password"
|
|||
|
|
value={testCredentials.password}
|
|||
|
|
onChange={(e) => setTestCredentials({ ...testCredentials, password: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="testName">姓名</Label>
|
|||
|
|
<Input
|
|||
|
|
id="testName"
|
|||
|
|
value={testCredentials.name}
|
|||
|
|
onChange={(e) => setTestCredentials({ ...testCredentials, name: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|||
|
|
<Button onClick={handleCreateTestUser} className="w-full">
|
|||
|
|
创建测试用户
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={handleEmailSignIn} variant="outline" className="w-full">
|
|||
|
|
邮箱密码登录
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</TabsContent>
|
|||
|
|
|
|||
|
|
{/* OIDC 信息 Tab */}
|
|||
|
|
<TabsContent value="discovery" className="space-y-6">
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle>OIDC Discovery 端点</CardTitle>
|
|||
|
|
<CardDescription>
|
|||
|
|
查看当前 OIDC Provider 的配置信息
|
|||
|
|
</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
<Button onClick={handleGetDiscoveryInfo} className="w-full">
|
|||
|
|
获取 Discovery 信息
|
|||
|
|
</Button>
|
|||
|
|
|
|||
|
|
{discoveryInfo && (
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<h4 className="font-semibold">Discovery 配置:</h4>
|
|||
|
|
<pre className="bg-muted p-4 rounded-md overflow-x-auto text-xs">
|
|||
|
|
{JSON.stringify(discoveryInfo, null, 2)}
|
|||
|
|
</pre>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</TabsContent>
|
|||
|
|
|
|||
|
|
{/* OAuth 测试 Tab */}
|
|||
|
|
<TabsContent value="oauth" className="space-y-6">
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle>OAuth2 登录测试</CardTitle>
|
|||
|
|
<CardDescription>
|
|||
|
|
测试 OAuth2 登录功能
|
|||
|
|
</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
<div className="text-sm text-muted-foreground mb-4">
|
|||
|
|
<p><strong>⚠️ 注意:</strong> 如果出现 "account_not_linked" 错误,请先在"账户管理"标签页中登录。</p>
|
|||
|
|
</div>
|
|||
|
|
<Button onClick={handleTestGenericOAuthConfig} className="w-full">
|
|||
|
|
测试 OAuth2 登录
|
|||
|
|
</Button>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle>配置信息</CardTitle>
|
|||
|
|
<CardDescription>
|
|||
|
|
当前 OAuth 配置
|
|||
|
|
</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="space-y-2 text-sm">
|
|||
|
|
<div><strong>Provider ID:</strong> <code>fenghuo-oidc</code></div>
|
|||
|
|
<div><strong>Discovery URL:</strong> <code>http://localhost:3001/api/auth/.well-known/openid-configuration</code></div>
|
|||
|
|
<div><strong>回调 URL:</strong> <code>http://localhost:3000/auth/better-auth-callback</code></div>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</TabsContent>
|
|||
|
|
</Tabs>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|