From 5340835ef212418fa8bc3b43613085ff4cc5cb4b Mon Sep 17 00:00:00 2001 From: Bereket Engida Date: Wed, 30 Oct 2024 09:07:11 +0300 Subject: [PATCH] example: add 2fa and clean up tanstack example --- examples/tanstack-example/.env.example | 6 + examples/tanstack-example/README.md | 10 +- examples/tanstack-example/app.config.ts | 14 +- .../app/components/login-form.tsx | 49 +- .../app/components/register-form.tsx | 2 +- .../app/components/ui/avatar.tsx | 50 + .../app/components/ui/dialog.tsx | 122 ++ .../tanstack-example/app/lib/auth-client.ts | 9 + examples/tanstack-example/app/lib/auth.ts | 23 + .../tanstack-example/app/lib/client/auth.ts | 5 - examples/tanstack-example/app/lib/icons/X.tsx | 4 + .../app/lib/icons/iconWithClassName.ts | 14 + .../tanstack-example/app/lib/server/auth.ts | 9 - .../tanstack-example/app/routeTree.gen.ts | 31 +- .../tanstack-example/app/routes/__root.tsx | 19 +- .../tanstack-example/app/routes/api/auth/$.ts | 2 +- .../app/routes/auth/two-factor.tsx | 84 ++ .../tanstack-example/app/routes/index.tsx | 250 +++- examples/tanstack-example/appconfig.ts | 12 - .../2024-10-29T08-42-28.630Z.sql | 7 - examples/tanstack-example/package.json | 7 + packages/better-auth/src/plugins/jwt/utils.ts | 63 + pnpm-lock.yaml | 1328 +++++++++++++---- 23 files changed, 1724 insertions(+), 396 deletions(-) create mode 100644 examples/tanstack-example/.env.example create mode 100644 examples/tanstack-example/app/components/ui/avatar.tsx create mode 100644 examples/tanstack-example/app/components/ui/dialog.tsx create mode 100644 examples/tanstack-example/app/lib/auth-client.ts create mode 100644 examples/tanstack-example/app/lib/auth.ts delete mode 100644 examples/tanstack-example/app/lib/client/auth.ts create mode 100644 examples/tanstack-example/app/lib/icons/X.tsx create mode 100644 examples/tanstack-example/app/lib/icons/iconWithClassName.ts delete mode 100644 examples/tanstack-example/app/lib/server/auth.ts create mode 100644 examples/tanstack-example/app/routes/auth/two-factor.tsx delete mode 100644 examples/tanstack-example/appconfig.ts delete mode 100644 examples/tanstack-example/better-auth_migrations/2024-10-29T08-42-28.630Z.sql create mode 100644 packages/better-auth/src/plugins/jwt/utils.ts diff --git a/examples/tanstack-example/.env.example b/examples/tanstack-example/.env.example new file mode 100644 index 0000000000..8eb3eeae80 --- /dev/null +++ b/examples/tanstack-example/.env.example @@ -0,0 +1,6 @@ +BETTER_AUTH_SECRET= +DISCORD_CLIENT_ID= +DISCORD_CLIENT_SECRET= +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +BETTER_AUTH_URL= \ No newline at end of file diff --git a/examples/tanstack-example/README.md b/examples/tanstack-example/README.md index 76198bccc3..714c9639dd 100644 --- a/examples/tanstack-example/README.md +++ b/examples/tanstack-example/README.md @@ -3,13 +3,15 @@ An example of using Better Auth with [TanStack Start](https://tanstack.com/start ## Setup -To install dependencies: +1. Move .env.example to .env and provide necessary variables + +2. install dependencies: ```bash pnpm install ``` -To migrate Better-Auth: +3. migrate required tables: ```bash pnpx @better-auth/cli migrate @@ -22,6 +24,4 @@ pnpm dev ``` ## Preview -![Sign In Preview](./preview.webp) - - +![Sign In Preview](./preview.webp) \ No newline at end of file diff --git a/examples/tanstack-example/app.config.ts b/examples/tanstack-example/app.config.ts index 557a912b38..38b3f4c907 100644 --- a/examples/tanstack-example/app.config.ts +++ b/examples/tanstack-example/app.config.ts @@ -2,11 +2,11 @@ import { defineConfig } from "@tanstack/start/config"; import viteTsConfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - vite: { - plugins: [ - viteTsConfigPaths({ - projects: ["./tsconfig.json"], - }), - ], - }, + vite: { + plugins: [ + viteTsConfigPaths({ + projects: ["./tsconfig.json"], + }), + ], + }, }); diff --git a/examples/tanstack-example/app/components/login-form.tsx b/examples/tanstack-example/app/components/login-form.tsx index c35cc23c0b..d2d3825ac5 100644 --- a/examples/tanstack-example/app/components/login-form.tsx +++ b/examples/tanstack-example/app/components/login-form.tsx @@ -12,7 +12,7 @@ import { } from "~/components/ui/card"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; -import { signIn } from "~/lib/client/auth"; +import { signIn } from "~/lib/auth-client"; export function LoginForm() { function handleSubmit(e: React.FormEvent) { @@ -66,6 +66,53 @@ export function LoginForm() { Sign In +
+ + +
Don't have an account?{" "} diff --git a/examples/tanstack-example/app/components/register-form.tsx b/examples/tanstack-example/app/components/register-form.tsx index da59cad597..eefbc9f03d 100644 --- a/examples/tanstack-example/app/components/register-form.tsx +++ b/examples/tanstack-example/app/components/register-form.tsx @@ -12,7 +12,7 @@ import { } from "~/components/ui/card"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; -import { signUp } from "~/lib/client/auth"; +import { signUp } from "~/lib/auth-client"; export function RegisterForm() { function handleSubmit(e: React.FormEvent) { diff --git a/examples/tanstack-example/app/components/ui/avatar.tsx b/examples/tanstack-example/app/components/ui/avatar.tsx new file mode 100644 index 0000000000..05e961cd38 --- /dev/null +++ b/examples/tanstack-example/app/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "~/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/examples/tanstack-example/app/components/ui/dialog.tsx b/examples/tanstack-example/app/components/ui/dialog.tsx new file mode 100644 index 0000000000..9850daa418 --- /dev/null +++ b/examples/tanstack-example/app/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "~/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/examples/tanstack-example/app/lib/auth-client.ts b/examples/tanstack-example/app/lib/auth-client.ts new file mode 100644 index 0000000000..d90fce6f23 --- /dev/null +++ b/examples/tanstack-example/app/lib/auth-client.ts @@ -0,0 +1,9 @@ +import { twoFactorClient } from "better-auth/plugins"; +import { createAuthClient } from "better-auth/react"; + +export const { useSession, signIn, signOut, signUp, twoFactor } = createAuthClient({ + baseURL: "http://localhost:3000", + plugins: [twoFactorClient({ + twoFactorPage: "/auth/two-factor", + })] +}); diff --git a/examples/tanstack-example/app/lib/auth.ts b/examples/tanstack-example/app/lib/auth.ts new file mode 100644 index 0000000000..8cea4375eb --- /dev/null +++ b/examples/tanstack-example/app/lib/auth.ts @@ -0,0 +1,23 @@ +import { twoFactor } from 'better-auth/plugins'; +import { betterAuth } from "better-auth"; +import Database from "better-sqlite3"; + +export const auth = betterAuth({ + database: new Database("data.db"), + emailAndPassword: { + enabled: true, + }, + socialProviders: { + discord: { + enabled: true, + clientId: process.env.DISCORD_CLIENT_ID!, + clientSecret: process.env.DISCORD_CLIENT_SECRET!, + }, + github: { + enabled: true, + clientId: process.env.GITHUB_CLIENT_ID!, + clientSecret: process.env.GITHUB_CLIENT_SECRET!, + }, + }, + plugins: [twoFactor()] +}); diff --git a/examples/tanstack-example/app/lib/client/auth.ts b/examples/tanstack-example/app/lib/client/auth.ts deleted file mode 100644 index 46ff103613..0000000000 --- a/examples/tanstack-example/app/lib/client/auth.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createAuthClient } from "better-auth/react"; - -export const { useSession, signIn, signOut, signUp } = createAuthClient({ - baseURL: "http://localhost:3000", -}); diff --git a/examples/tanstack-example/app/lib/icons/X.tsx b/examples/tanstack-example/app/lib/icons/X.tsx new file mode 100644 index 0000000000..f3ba2a487c --- /dev/null +++ b/examples/tanstack-example/app/lib/icons/X.tsx @@ -0,0 +1,4 @@ +import { X } from 'lucide-react-native'; +import { iconWithClassName } from './iconWithClassName'; +iconWithClassName(X); +export { X }; \ No newline at end of file diff --git a/examples/tanstack-example/app/lib/icons/iconWithClassName.ts b/examples/tanstack-example/app/lib/icons/iconWithClassName.ts new file mode 100644 index 0000000000..aec4575d5e --- /dev/null +++ b/examples/tanstack-example/app/lib/icons/iconWithClassName.ts @@ -0,0 +1,14 @@ +import type { LucideIcon } from 'lucide-react-native'; +import { cssInterop } from 'nativewind'; + +export function iconWithClassName(icon: LucideIcon) { +cssInterop(icon, { + className: { + target: 'style', + nativeStyleToProp: { + color: true, + opacity: true, + }, + }, +}); +} \ No newline at end of file diff --git a/examples/tanstack-example/app/lib/server/auth.ts b/examples/tanstack-example/app/lib/server/auth.ts deleted file mode 100644 index f546bdd3c1..0000000000 --- a/examples/tanstack-example/app/lib/server/auth.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { betterAuth } from "better-auth"; -import Database from "better-sqlite3"; - -export const auth = betterAuth({ - database: new Database("data.db"), - emailAndPassword: { - enabled: true, - }, -}); diff --git a/examples/tanstack-example/app/routeTree.gen.ts b/examples/tanstack-example/app/routeTree.gen.ts index bf4275411e..f7087562cf 100644 --- a/examples/tanstack-example/app/routeTree.gen.ts +++ b/examples/tanstack-example/app/routeTree.gen.ts @@ -12,6 +12,7 @@ import { Route as rootRoute } from './routes/__root' import { Route as IndexImport } from './routes/index' +import { Route as AuthTwoFactorImport } from './routes/auth/two-factor' import { Route as AuthSignupImport } from './routes/auth/signup' import { Route as AuthSigninImport } from './routes/auth/signin' @@ -23,6 +24,12 @@ const IndexRoute = IndexImport.update({ getParentRoute: () => rootRoute, } as any) +const AuthTwoFactorRoute = AuthTwoFactorImport.update({ + id: '/auth/two-factor', + path: '/auth/two-factor', + getParentRoute: () => rootRoute, +} as any) + const AuthSignupRoute = AuthSignupImport.update({ id: '/auth/signup', path: '/auth/signup', @@ -60,6 +67,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthSignupImport parentRoute: typeof rootRoute } + '/auth/two-factor': { + id: '/auth/two-factor' + path: '/auth/two-factor' + fullPath: '/auth/two-factor' + preLoaderRoute: typeof AuthTwoFactorImport + parentRoute: typeof rootRoute + } } } @@ -69,12 +83,14 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/auth/signin': typeof AuthSigninRoute '/auth/signup': typeof AuthSignupRoute + '/auth/two-factor': typeof AuthTwoFactorRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/auth/signin': typeof AuthSigninRoute '/auth/signup': typeof AuthSignupRoute + '/auth/two-factor': typeof AuthTwoFactorRoute } export interface FileRoutesById { @@ -82,14 +98,15 @@ export interface FileRoutesById { '/': typeof IndexRoute '/auth/signin': typeof AuthSigninRoute '/auth/signup': typeof AuthSignupRoute + '/auth/two-factor': typeof AuthTwoFactorRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/auth/signin' | '/auth/signup' + fullPaths: '/' | '/auth/signin' | '/auth/signup' | '/auth/two-factor' fileRoutesByTo: FileRoutesByTo - to: '/' | '/auth/signin' | '/auth/signup' - id: '__root__' | '/' | '/auth/signin' | '/auth/signup' + to: '/' | '/auth/signin' | '/auth/signup' | '/auth/two-factor' + id: '__root__' | '/' | '/auth/signin' | '/auth/signup' | '/auth/two-factor' fileRoutesById: FileRoutesById } @@ -97,12 +114,14 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute AuthSigninRoute: typeof AuthSigninRoute AuthSignupRoute: typeof AuthSignupRoute + AuthTwoFactorRoute: typeof AuthTwoFactorRoute } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AuthSigninRoute: AuthSigninRoute, AuthSignupRoute: AuthSignupRoute, + AuthTwoFactorRoute: AuthTwoFactorRoute, } export const routeTree = rootRoute @@ -119,7 +138,8 @@ export const routeTree = rootRoute "children": [ "/", "/auth/signin", - "/auth/signup" + "/auth/signup", + "/auth/two-factor" ] }, "/": { @@ -130,6 +150,9 @@ export const routeTree = rootRoute }, "/auth/signup": { "filePath": "auth/signup.tsx" + }, + "/auth/two-factor": { + "filePath": "auth/two-factor.tsx" } } } diff --git a/examples/tanstack-example/app/routes/__root.tsx b/examples/tanstack-example/app/routes/__root.tsx index 7d6076f2c0..8012349668 100644 --- a/examples/tanstack-example/app/routes/__root.tsx +++ b/examples/tanstack-example/app/routes/__root.tsx @@ -3,7 +3,7 @@ import { Outlet, ScrollRestoration } from "@tanstack/react-router"; import { Body, Head, Html, Meta, Scripts } from "@tanstack/start"; import type * as React from "react"; import { useEffect, useState } from "react"; -import { signOut, useSession } from "~/lib/client/auth"; +import { signOut, useSession } from "~/lib/auth-client"; import globalStylesheet from "~/lib/style/global.css?url"; import "~/lib/style/global.css"; import { DoorOpen, LoaderCircle, Moon, Sun } from "lucide-react"; @@ -42,9 +42,9 @@ export const Route = createRootRoute({ function RootComponent() { const [theme, setTheme] = useState<"light" | "dark">("light"); - const [loading, setLoading] = useState(true); - const { data, isPending } = useSession(); + const { data, isPending, error } = useSession(); const { navigate } = useRouter(); + console.log() useEffect(() => { if (!data?.user) { @@ -61,11 +61,6 @@ function RootComponent() { ); }, [data, navigate]); - useEffect(() => { - if (!isPending) { - setLoading(false); - } - }, [isPending]); useEffect(() => { const root = window.document.documentElement; @@ -77,12 +72,7 @@ function RootComponent() { return ( - {loading ? ( -
- -
- ) : ( - <> + <>