mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-25 08:31:37 -05:00
feat: new client and more
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.52.2",
|
||||
"react-qr-code": "^2.0.15",
|
||||
"sonner": "^1.5.0",
|
||||
"tailwind-merge": "^2.5.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
||||
Binary file not shown.
@@ -11,8 +11,9 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { authClient } from "@/lib/client";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { useState } from "react";
|
||||
import { Key } from "lucide-react";
|
||||
|
||||
export default function Page() {
|
||||
const [email, setEmail] = useState("");
|
||||
@@ -59,7 +60,7 @@ export default function Page() {
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full" onClick={async () => {
|
||||
const res = await authClient.signInCredential({
|
||||
await authClient.signIn.credential({
|
||||
body: {
|
||||
email,
|
||||
password,
|
||||
@@ -73,14 +74,22 @@ export default function Page() {
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={async () => {
|
||||
await authClient.signInOAuth({
|
||||
provider: "github",
|
||||
callbackURL: "http://localhost:3000/",
|
||||
await authClient.signIn.oauth({
|
||||
body: {
|
||||
provider: "github",
|
||||
callbackURL: "http://localhost:3000",
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Login with Github
|
||||
</Button>
|
||||
<Button variant="secondary" className="gap-2" onClick={async () => {
|
||||
|
||||
}}>
|
||||
<Key size={16} />
|
||||
Login with Passkey
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useState } from "react";
|
||||
import { authClient } from "@/lib/client";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
|
||||
export default function SignUpForm() {
|
||||
const [firstName, setFirstName] = useState("");
|
||||
|
||||
77
dev/next-app/src/app/(auth)/two-factor/otp/page.tsx
Normal file
77
dev/next-app/src/app/(auth)/two-factor/otp/page.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card"
|
||||
import { AlertCircle, CheckCircle2, Mail } from "lucide-react"
|
||||
import { authClient } from '@/lib/auth-client'
|
||||
|
||||
export default function Component() {
|
||||
const [otp, setOtp] = useState('')
|
||||
const [isOtpSent, setIsOtpSent] = useState(false)
|
||||
const [message, setMessage] = useState('')
|
||||
const [isError, setIsError] = useState(false)
|
||||
const [isValidated, setIsValidated] = useState(false)
|
||||
|
||||
// In a real app, this email would come from your authentication context
|
||||
const userEmail = "user@example.com"
|
||||
|
||||
const requestOTP = async () => {
|
||||
await authClient.twoFactor.sendOtp();
|
||||
// In a real app, this would call your backend API to send the OTP
|
||||
setMessage('OTP sent to your email')
|
||||
setIsError(false)
|
||||
setIsOtpSent(true)
|
||||
}
|
||||
|
||||
const validateOTP = async () => {
|
||||
await authClient.twoFactor.verifyOtp({
|
||||
body: {
|
||||
code: otp,
|
||||
}
|
||||
})
|
||||
}
|
||||
return (
|
||||
<main className='flex flex-col items-center justify-center min-h-screen'>
|
||||
<Card className="w-[350px]">
|
||||
<CardHeader>
|
||||
<CardTitle>Two-Factor Authentication</CardTitle>
|
||||
<CardDescription>Verify your identity with a one-time password</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid w-full items-center gap-4">
|
||||
{!isOtpSent ? (
|
||||
<Button onClick={requestOTP} className="w-full">
|
||||
<Mail className="mr-2 h-4 w-4" /> Send OTP to Email
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<Label htmlFor="otp">One-Time Password</Label>
|
||||
<Input
|
||||
id="otp"
|
||||
placeholder="Enter 6-digit OTP"
|
||||
value={otp}
|
||||
onChange={(e) => setOtp(e.target.value)}
|
||||
maxLength={6}
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={validateOTP} disabled={otp.length !== 6 || isValidated}>
|
||||
Validate OTP
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{message && (
|
||||
<div className={`flex items-center gap-2 mt-4 ${isError ? 'text-destructive' : 'text-primary'}`}>
|
||||
{isError ? <AlertCircle className="h-4 w-4" /> : <CheckCircle2 className="h-4 w-4" />}
|
||||
<p className="text-sm">{message}</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { AlertCircle, CheckCircle2 } from "lucide-react"
|
||||
import { authClient } from "@/lib/client"
|
||||
import { authClient } from "@/lib/auth-client"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function Component() {
|
||||
const [totpCode, setTotpCode] = useState("")
|
||||
@@ -19,10 +20,10 @@ export default function Component() {
|
||||
setError("TOTP code must be 6 digits")
|
||||
return
|
||||
}
|
||||
authClient.verifyTotp({
|
||||
authClient.twoFactor.verify({
|
||||
body: {
|
||||
code: totpCode,
|
||||
callbackURL: "/"
|
||||
with: "totp",
|
||||
}
|
||||
}).then((res) => {
|
||||
console.log(res)
|
||||
@@ -78,8 +79,12 @@ export default function Component() {
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="text-sm text-muted-foreground">
|
||||
Protect your account with TOTP-based authentication
|
||||
<CardFooter className="text-sm text-muted-foreground gap-2">
|
||||
<Link href="/two-factor/otp">
|
||||
<Button variant="link" size="sm">
|
||||
Switch to Email Verification
|
||||
</Button>
|
||||
</Link>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</main>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default async function Home() {
|
||||
<Button>signin</Button>
|
||||
</Link>
|
||||
)}
|
||||
<Organization />
|
||||
{/* <Organization /> */}
|
||||
<Client />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import { setCounter } from "@/server/counter";
|
||||
import { Button } from "./ui/button";
|
||||
import { client } from "@/lib/client";
|
||||
import { client } from "@/lib/auth-client";
|
||||
|
||||
export async function AddCount() {
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { authClient } from "@/lib/client";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { useAuthStore } from "better-auth/react"
|
||||
import { Button } from "./ui/button";
|
||||
import QRCode from "react-qr-code";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Card, CardContent, CardHeader } from "./ui/card";
|
||||
import { Dialog, DialogContent, DialogTrigger } from "./ui/dialog";
|
||||
export function Client() {
|
||||
const session = useAuthStore(authClient.$session)
|
||||
const [uri, setUri] = useState<string>()
|
||||
const session = authClient.useSession()
|
||||
type S = NonNullable<typeof session>
|
||||
const a: S['user'] = {
|
||||
id: "1",
|
||||
@@ -14,23 +19,59 @@ export function Client() {
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user?.twoFactorEnabled) {
|
||||
authClient.twoFactor.getTotpUri().then((res) => {
|
||||
if (res.data) {
|
||||
setUri(res.data.totpURI)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [session])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
session ? <div>
|
||||
<Button onClick={async () => {
|
||||
if (session.user.twoFactorEnabled) {
|
||||
await authClient.disableTotp()
|
||||
} else {
|
||||
await authClient.enableTotp()
|
||||
}
|
||||
}}>
|
||||
{
|
||||
session.user.twoFactorEnabled ? "Disable" : "Enable"
|
||||
}
|
||||
</Button>
|
||||
</div> : null
|
||||
}
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
|
||||
</CardHeader>
|
||||
<CardContent className="flex items-center justify-center gap-2">
|
||||
{
|
||||
session ? <div>
|
||||
<Button onClick={async () => {
|
||||
if (session.user?.twoFactorEnabled) {
|
||||
await authClient.twoFactor.disable()
|
||||
} else {
|
||||
await authClient.twoFactor.enable()
|
||||
}
|
||||
}}>
|
||||
{
|
||||
session.user?.twoFactorEnabled ? "Disable 2FA" : "Enable 2FA"
|
||||
}
|
||||
</Button>
|
||||
</div> : null
|
||||
}
|
||||
{
|
||||
uri ? <Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">
|
||||
View TOTP URI
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="flex flex-col items-center justify-center">
|
||||
{
|
||||
uri ? <div>
|
||||
<p>
|
||||
URI to scan
|
||||
</p>
|
||||
<QRCode value={uri} />
|
||||
</div> : null
|
||||
}
|
||||
</DialogContent>
|
||||
</Dialog> : null
|
||||
}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "./ui/table";
|
||||
import { authClient } from "@/lib/client";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { useAuthStore } from "better-auth/react";
|
||||
|
||||
export const Organization = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { authClient } from "@/lib/client";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
export const SignOut = () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createAuthClient } from "better-auth/client";
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
import { auth } from "./auth";
|
||||
|
||||
export const authClient = createAuthClient<typeof auth>({
|
||||
@@ -1,6 +1,6 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { github, passkey } from "better-auth/provider";
|
||||
import { twoFactor } from "better-auth/plugins";
|
||||
import { organization, twoFactor } from "better-auth/plugins";
|
||||
|
||||
export const auth = betterAuth({
|
||||
basePath: "/api/auth",
|
||||
@@ -9,10 +9,6 @@ export const auth = betterAuth({
|
||||
clientId: process.env.GITHUB_CLIENT_ID as string,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
||||
}),
|
||||
passkey({
|
||||
rpID: "localhost",
|
||||
rpName: "Better Auth",
|
||||
}),
|
||||
],
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
@@ -23,9 +19,15 @@ export const auth = betterAuth({
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [
|
||||
organization(),
|
||||
twoFactor({
|
||||
issuer: "BetterAuth",
|
||||
twoFactorURL: "/two-factor",
|
||||
otpOptions: {
|
||||
async sendOTP(user, otp) {
|
||||
console.log({ user, otp });
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user