feat: docs example trial

This commit is contained in:
Bereket Engida
2024-09-21 11:04:44 +03:00
parent 0fbc3dc8d1
commit 5ca615a3e4
23 changed files with 17972 additions and 19568 deletions

View File

@@ -1,5 +1,6 @@
{ {
"name": "express", "name": "@better-auth/dev-express",
"private": true,
"module": "index.ts", "module": "index.ts",
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {

1
docs/.gitignore vendored
View File

@@ -26,3 +26,4 @@ yarn-error.log*
.env*.local .env*.local
.vercel .vercel
next-env.d.ts next-env.d.ts
certificates

View File

@@ -1,39 +0,0 @@
import { AnimatePresence } from "@/components/ui/fade-in"
const ChangelogOne = () => {
return (
<AnimatePresence>
<div className="flex flex-col gap-4 items-start justify-center max-w-full md:max-w-2xl">
<img src="https://camo.githubusercontent.com/3282afc585d07e52e883ac2345467841e5c9cbe3befdec9dd6f84c603748e0d4/68747470733a2f2f726573656e642e636f6d2f5f6e6578742f696d6167653f75726c3d253246737461746963253246706f737473253246776562686f6f6b732e6a706726773d36343026713d3735" className="w-full h-[400px] object-cover rounded-lg" />
<div className="flex flex-col gap-2">
<h2 className="text-2xl font-bold tracking-tighter">
Commit message suggestions
</h2>
</div>
<p className="text-gray-600 dark:text-gray-300 text-[0.855rem]">
In the latest release, I've added support for commit message and description suggestions via an integration with OpenAI. Commit looks at all of your changes, and feeds that into the machine with a bit of prompt-tuning to get back a commit message that does a surprisingly good job at describing the intent of your changes.
It's also been a pretty helpful way to remind myself what the hell I was working on at the end of the day yesterday when I get back to my computer and realize I didn't commit any of my work.
</p>
<div className="flex flex-col gap-2">
<h4 className="text-xl tracking-tighter"> Improvement</h4>
</div>
<ul className="list-disc ml-10 text-[0.855rem] text-gray-600 dark:text-gray-300">
<li>
Added commit message and description suggestions powered by OpenAI
</li>
<li>
Started commit message and description suggestions powered by OpenAI
</li>
<li>
Restored message and description suggestions powered by OpenAI
</li>
<li>
Added commit message and description suggestions powered by OpenAI
</li>
</ul>
</div>
</AnimatePresence >
)
}
export default ChangelogOne

View File

@@ -0,0 +1,33 @@
import { AnimatePresence } from "@/components/ui/fade-in";
const listOfFeatures = ["The first better-auth public beta release"];
const bugFixes = [""];
const ChangelogOne = () => {
return (
<AnimatePresence>
<div className="flex flex-col gap-4 items-start justify-center max-w-full md:max-w-2xl">
<div className="flex flex-col gap-2">
<h2 className="text-2xl font-bold tracking-tighter">
Public Beta Release
</h2>
<p>
The first public beta release of better-auth is now available. This
release includes a lot of new features and improvements.
</p>
</div>
<p className="text-gray-600 dark:text-gray-300 text-[0.855rem]"></p>
<div className="flex flex-col gap-2">
<h4 className="text-xl tracking-tighter">Features</h4>
</div>
<ul className="list-disc ml-10 text-[0.855rem] text-gray-600 dark:text-gray-300">
{listOfFeatures.map((change, i) => (
<li key={i}>{change}</li>
))}
</ul>
</div>
</AnimatePresence>
);
};
export default ChangelogOne;

View File

@@ -1,23 +1,8 @@
import ChangelogOne from "./2024-08-09"; import ChangelogOne from "./2024-09-28";
export const Logs = [ export const Logs = [
{ {
name: "Changelog 1", name: "Changelog 1",
date: "2024-09-09", date: "2024-09-28",
component: ChangelogOne,
},
{
name: "Changelog 1",
date: "2024-09-09",
component: ChangelogOne,
},
{
name: "Changelog 1",
date: "2024-09-09",
component: ChangelogOne,
},
{
name: "Changelog 1",
date: "2024-09-09",
component: ChangelogOne, component: ChangelogOne,
}, },
]; ];

View File

@@ -1,83 +1,112 @@
import { Layout } from './_components/_layout' import { Layout } from "./_components/_layout";
import { useId } from 'react' import { useId } from "react";
import { Intro, IntroFooter } from './_components/changelog-layout' import { Intro, IntroFooter } from "./_components/changelog-layout";
import { StarField } from './_components/stat-field' import { StarField } from "./_components/stat-field";
function Timeline() { function Timeline() {
let id = useId() let id = useId();
return ( return (
<div className='pointer-events-none absolute inset-0 z-50 overflow-hidden lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem] lg:overflow-visible'> <div className="pointer-events-none absolute inset-0 z-50 overflow-hidden lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem] lg:overflow-visible">
<svg className='absolute left-[max(0px,calc(50%-18.125rem))] top-0 h-full w-1.5 lg:left-full lg:ml-1 xl:left-auto xl:right-1 xl:ml-0' aria-hidden='true'> <svg
className="absolute left-[max(0px,calc(50%-18.125rem))] top-0 h-full w-1.5 lg:left-full lg:ml-1 xl:left-auto xl:right-1 xl:ml-0"
aria-hidden="true"
>
<defs> <defs>
<pattern id={id} width='6' height='8' patternUnits='userSpaceOnUse'> <pattern id={id} width="6" height="8" patternUnits="userSpaceOnUse">
<path d='M0 0H6M0 8H6' className='stroke-sky-900/10 xl:stroke-white/10 dark:stroke-white/10' fill='none' /> <path
d="M0 0H6M0 8H6"
className="stroke-sky-900/10 xl:stroke-white/10 dark:stroke-white/10"
fill="none"
/>
</pattern> </pattern>
</defs> </defs>
<rect width='100%' height='100%' fill={`url(#${id})`} /> <rect width="100%" height="100%" fill={`url(#${id})`} />
</svg> </svg>
</div> </div>
) );
} }
function Glow() { function Glow() {
let id = useId() let id = useId();
return ( return (
<div className='absolute inset-0 -z-10 overflow-hidden bg-gradient-to-tr from-transparent via-stone-950/5 to-transparent/10 lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem]'> <div className="absolute inset-0 -z-10 overflow-hidden bg-gradient-to-tr from-transparent via-stone-950/5 to-transparent/10 lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem]">
<svg className='absolute -bottom-48 left-[-40%] h-[80rem] w-[180%] lg:-right-40 lg:bottom-auto lg:left-auto lg:top-[-40%] lg:h-[180%] lg:w-[80rem]' aria-hidden='true'> <svg
className="absolute -bottom-48 left-[-40%] h-[80rem] w-[180%] lg:-right-40 lg:bottom-auto lg:left-auto lg:top-[-40%] lg:h-[180%] lg:w-[80rem]"
aria-hidden="true"
>
<defs> <defs>
<radialGradient id={`${id}-desktop`} cx='100%'> <radialGradient id={`${id}-desktop`} cx="100%">
<stop offset='0%' stopColor='rgba(41, 37, 36, 0.4)' /> <stop offset="0%" stopColor="rgba(41, 37, 36, 0.4)" />
<stop offset='53.95%' stopColor='rgba(28, 25, 23, 0.09)' /> <stop offset="53.95%" stopColor="rgba(28, 25, 23, 0.09)" />
<stop offset='100%' stopColor='rgba(0, 0, 0, 0)' /> <stop offset="100%" stopColor="rgba(0, 0, 0, 0)" />
</radialGradient> </radialGradient>
<radialGradient id={`${id}-mobile`} cy='100%'> <radialGradient id={`${id}-mobile`} cy="100%">
<stop offset='0%' stopColor='rgba(41, 37, 36, 0.3)' /> <stop offset="0%" stopColor="rgba(41, 37, 36, 0.3)" />
<stop offset='53.95%' stopColor='rgba(28, 25, 23, 0.09)' /> <stop offset="53.95%" stopColor="rgba(28, 25, 23, 0.09)" />
<stop offset='100%' stopColor='rgba(0, 0, 0, 0)' /> <stop offset="100%" stopColor="rgba(0, 0, 0, 0)" />
</radialGradient> </radialGradient>
</defs> </defs>
<rect width='100%' height='100%' fill={`url(#${id}-desktop)`} className='hidden lg:block' /> <rect
<rect width='100%' height='100%' fill={`url(#${id}-mobile)`} className='lg:hidden' /> width="100%"
height="100%"
fill={`url(#${id}-desktop)`}
className="hidden lg:block"
/>
<rect
width="100%"
height="100%"
fill={`url(#${id}-mobile)`}
className="lg:hidden"
/>
</svg> </svg>
<div className='absolute inset-x-0 bottom-0 right-0 h-px bg-white/5 mix-blend-overlay lg:left-auto lg:top-0 lg:h-auto lg:w-px' /> <div className="absolute inset-x-0 bottom-0 right-0 h-px bg-white/5 mix-blend-overlay lg:left-auto lg:top-0 lg:h-auto lg:w-px" />
</div> </div>
) );
} }
function FixedSidebar({ main, footer }: { main: React.ReactNode; footer: React.ReactNode }) { function FixedSidebar({
main,
footer,
}: {
main: React.ReactNode;
footer: React.ReactNode;
}) {
return ( return (
<div className='relative flex-none overflow-hidden px-10 lg:pointer-events-none md:fixed lg:inset-0 lg:z-40 lg:flex lg:px-0'> <div className="relative flex-none overflow-hidden px-10 lg:pointer-events-none md:fixed lg:inset-0 lg:z-40 lg:flex lg:px-0">
<Glow /> <Glow />
<div className='relative flex w-full lg:pointer-events-auto lg:mr-[calc(max(2rem,50%-35rem)+40rem)] lg:min-w-[32rem] lg:overflow-y-auto lg:overflow-x-hidden lg:pl-[max(4rem,calc(50%-44rem))]'> <div className="relative flex w-full lg:pointer-events-auto lg:mr-[calc(max(2rem,50%-35rem)+40rem)] lg:min-w-[32rem] lg:overflow-y-auto lg:overflow-x-hidden lg:pl-[max(4rem,calc(50%-44rem))]">
<div className='mx-auto pr-20 max-w-lg lg:mx-auto lg:flex lg:max-w-4xl lg:flex-col lg:before:flex-1 lg:before:pt-6'> <div className="mx-auto pr-20 max-w-lg lg:mx-auto lg:flex lg:max-w-4xl lg:flex-col lg:before:flex-1 lg:before:pt-6">
<div className='pb-16 pt-20 sm:pb-20 sm:pt-32 lg:py-20'> <div className="pb-16 pt-20 sm:pb-20 sm:pt-32 lg:py-20">
<div className='relative '> <div className="relative ">
<StarField className='-right-44 top-14' /> <StarField className="-right-44 top-14" />
{main} {main}
</div> </div>
</div> </div>
<div className='flex flex-1 items-end justify-center pb-4 lg:justify-start lg:pb-6'>{footer}</div> <div className="flex flex-1 items-end justify-center pb-4 lg:justify-start lg:pb-6">
{footer}
</div> </div>
</div> </div>
</div> </div>
) </div>
);
} }
const ChangeLogLayout = ({ children }: { children: React.ReactNode }) => { const ChangeLogLayout = ({ children }: { children: React.ReactNode }) => {
return ( return (
<div className='h-full font-sans overflow-y-auto'> <div className="h-full font-sans overflow-y-auto">
<FixedSidebar main={<Intro />} footer={<IntroFooter />} /> <FixedSidebar main={<Intro />} footer={<IntroFooter />} />
<div /> <div />
<div className='relative overflow-y-auto flex gap-2 justify-start items-start'> <div className="relative overflow-y-auto flex gap-2 justify-start items-start">
<Timeline /> <Timeline />
<div className='space-y-20 py-20 w-[80%] mx-auto md:mx-0 md:ml-auto md:w-1/2 h-screen sm:space-y-20 sm:py-20'>{children}</div> <div className="space-y-20 py-20 w-[80%] mx-auto md:mx-0 md:ml-auto md:w-1/2 h-screen sm:space-y-20 sm:py-20">
{children}
</div> </div>
</div> </div>
) </div>
} );
};
export default ChangeLogLayout export default ChangeLogLayout;

View File

@@ -1,24 +1,25 @@
import { AnimatePresence } from "@/components/ui/fade-in" import { AnimatePresence } from "@/components/ui/fade-in";
import { Logs } from "./_logs" import { Logs } from "./_logs";
import { FormattedDate } from "./_components/fmt-dates" import { FormattedDate } from "./_components/fmt-dates";
const ChangelogPage = () => { const ChangelogPage = () => {
return ( return (
<div> <div>
<div className='mt-10 overflow-visible h-full flex flex-col gap-10'> <div className="mt-10 overflow-visible h-full flex flex-col gap-10">
{Logs.map((log) => { {Logs.map((log) => {
return ( return (
<div className="relative my-5 h-auto"> <div className="relative my-5 h-auto">
<div className="sticky top-2 flex-1 h-full"> <div className="sticky top-2 flex-1 h-full">
<FormattedDate className="absolute md:-left-32 left-0 text-sm -top-8 md:top-0 font-light" date={log.date} /> <FormattedDate
className="absolute md:-left-32 left-0 text-sm -top-8 md:top-0 font-light"
date={log.date}
/>
</div> </div>
<log.component /> <log.component />
</div> </div>
) );
})} })}
</div> </div>
</div> </div>
) );
} };
export default ChangelogPage export default ChangelogPage;

View File

@@ -10,20 +10,16 @@ import {
Webhook, Webhook,
XIcon, XIcon,
} from "lucide-react"; } from "lucide-react";
import { import { LockClosedIcon } from "@radix-ui/react-icons";
LockClosedIcon,
} from "@radix-ui/react-icons";
import Link from "next/link"; import Link from "next/link";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import React, { useState } from "react"; import React, { useState } from "react";
import { TechStackDisplay } from "./display-techstack"; import { TechStackDisplay } from "./display-techstack";
import { Ripple } from "./ripple"; import { Ripple } from "./ripple";
export default function Features() { export default function Features() {
return ( return (
<div className="md:max-w-[1300px] mt-10 mx-auto font-geist relative md:border-l-0 md:border-[1.2px] rounded-none -pr-2"> <div className="md:w-10/12 mt-10 mx-auto font-geist relative md:border-l-0 md:border-[1.2px] rounded-none -pr-2">
<Plus className="absolute top-[-17px] left-[-17px] text-black/20 dark:text-white/30 w-8 h-8" /> <Plus className="absolute top-[-17px] left-[-17px] text-black/20 dark:text-white/30 w-8 h-8" />
<div className="grid grid-cols-1 md:grid-cols-3 md:mx-0 grid-rows-4 md:grid-rows-4 w-full"> <div className="grid grid-cols-1 md:grid-cols-3 md:mx-0 grid-rows-4 md:grid-rows-4 w-full">
<div className="relative items-start justify-start border-l-[1.2px] border-t-[1.2px] md:border-t-0 transform-gpu flex flex-col p-10 overflow-clip"> <div className="relative items-start justify-start border-l-[1.2px] border-t-[1.2px] md:border-t-0 transform-gpu flex flex-col p-10 overflow-clip">
@@ -31,7 +27,9 @@ export default function Features() {
<div className="flex gap-2 items-center my-1"> <div className="flex gap-2 items-center my-1">
<PlugZap2Icon className="w-4 h-4" /> <PlugZap2Icon className="w-4 h-4" />
<p className="text-gray-600 dark:text-gray-400">Framework Agnostic </p> <p className="text-gray-600 dark:text-gray-400">
Framework Agnostic{" "}
</p>
</div> </div>
<div className="mt-2"> <div className="mt-2">
<div className="max-w-full"> <div className="max-w-full">
@@ -42,12 +40,10 @@ export default function Features() {
</div> </div>
</div> </div>
<p className="text-left text-sm mt-2 text-muted-foreground"> <p className="text-left text-sm mt-2 text-muted-foreground">
Supports your favorite frontend, backend and meta frameworks, including React, Vue, Svelte, Solid, Next.js, Nuxt.js, Hono, and more{" "} Supports your favorite frontend, backend and meta frameworks,
<a including React, Vue, Svelte, Solid, Next.js, Nuxt.js, Hono, and
className="text-gray-50" more{" "}
href=".docs" <a className="text-gray-50" href=".docs" target="_blank">
target="_blank"
>
Learn more Learn more
</a> </a>
</p> </p>
@@ -69,12 +65,9 @@ export default function Features() {
</div> </div>
</div> </div>
<p className="text-left text-sm mt-2 text-muted-foreground"> <p className="text-left text-sm mt-2 text-muted-foreground">
Builtin support for email and password authentication, with secure password hashing and account management features{" "} Builtin support for email and password authentication, with secure
<a password hashing and account management features{" "}
className="text-gray-50" <a className="text-gray-50" href="/docs" target="_blank">
href="/docs"
target="_blank"
>
Learn more Learn more
</a> </a>
</p> </p>
@@ -96,12 +89,9 @@ export default function Features() {
</div> </div>
</div> </div>
<p className="text-left text-sm mt-2 text-muted-foreground"> <p className="text-left text-sm mt-2 text-muted-foreground">
Allow users to sign in with their accounts, including Github, Google, Discord, Twitter, and more.{" "} Allow users to sign in with their accounts, including Github,
<a Google, Discord, Twitter, and more.{" "}
className="text-gray-50" <a className="text-gray-50" href="#" target="_blank">
href="#"
target="_blank"
>
Learn more Learn more
</a> </a>
</p> </p>
@@ -121,23 +111,20 @@ export default function Features() {
</div> </div>
</div> </div>
<p className="text-left text-sm mt-2 text-muted-foreground"> <p className="text-left text-sm mt-2 text-muted-foreground">
With our built-in two factor authentication plugin, you can add an extra layer of security to your account. With our built-in two factor authentication plugin, you can add an
{" "} extra layer of security to your account.{" "}
<Link <Link className="text-gray-50" href="/docs" target="_blank">
className="text-gray-50"
href="/docs"
target="_blank"
>
Learn more Learn more
</Link> </Link>
</p> </p>
</div> </div>
</div> </div>
<div className="items-start justify-staart border-l-[1.2px] border-t-[1.2px] flex flex-col p-10 "> <div className="items-start justify-staart border-l-[1.2px] border-t-[1.2px] flex flex-col p-10 ">
<div className="flex gap-2 items-center my-1"> <div className="flex gap-2 items-center my-1">
<RabbitIcon className="w-4 h-4" /> <RabbitIcon className="w-4 h-4" />
<p className="text-gray-600 dark:text-gray-400">Organization & Access Control </p> <p className="text-gray-600 dark:text-gray-400">
Organization & Access Control{" "}
</p>
</div> </div>
<div className="mt-2"> <div className="mt-2">
<div className="max-w-full"> <div className="max-w-full">
@@ -148,12 +135,9 @@ export default function Features() {
</div> </div>
</div> </div>
<p className="text-left text-sm mt-2 text-muted-foreground"> <p className="text-left text-sm mt-2 text-muted-foreground">
Manage users and their access to resources within your application.{" "} Manage users and their access to resources within your
<a application.{" "}
className="text-gray-50" <a className="text-gray-50" href="/docs" target="_blank">
href="/docs"
target="_blank"
>
Learn more Learn more
</a> </a>
</p> </p>
@@ -163,7 +147,9 @@ export default function Features() {
<Plus className="absolute bottom-[-15px] right-[-15px] text-black/20 dark:text-white/40 w-8 h-8" /> <Plus className="absolute bottom-[-15px] right-[-15px] text-black/20 dark:text-white/40 w-8 h-8" />
<div className="flex gap-2 items-center my-1"> <div className="flex gap-2 items-center my-1">
<PlugIcon className="w-4 h-4" /> <PlugIcon className="w-4 h-4" />
<p className="text-gray-600 dark:text-gray-400">Plugin Ecosystem </p> <p className="text-gray-600 dark:text-gray-400">
Plugin Ecosystem{" "}
</p>
</div> </div>
<div className="max-w-full"> <div className="max-w-full">
<div className="flex gap-3 "> <div className="flex gap-3 ">
@@ -174,12 +160,9 @@ export default function Features() {
</div> </div>
<div className="mt-2"> <div className="mt-2">
<p className="text-left text-sm mt-2 text-muted-foreground"> <p className="text-left text-sm mt-2 text-muted-foreground">
Enhance your application with our official plugins and those created by the community.{" "} Enhance your application with our official plugins and those
<a created by the community.{" "}
className="text-gray-50" <a className="text-gray-50" href="/docs" target="_blank">
href="/docs"
target="_blank"
>
Learn more Learn more
</a> </a>
</p> </p>
@@ -197,9 +180,7 @@ export default function Features() {
</p> </p>
</div> </div>
<p className="text-4xl md:text-4xl mt-4 tracking-tighter font-normal max-w-md mx-auto text-center"> <p className="text-4xl md:text-4xl mt-4 tracking-tighter font-normal max-w-md mx-auto text-center">
<strong> <strong>Roll your own auth with confidence in minutes!</strong>
Roll your own auth with confidence in minutes!
</strong>
</p> </p>
<div className="flex mt-[10px] z-20 justify-center items-start"> <div className="flex mt-[10px] z-20 justify-center items-start">
<TechStackDisplay <TechStackDisplay
@@ -210,7 +191,6 @@ export default function Features() {
"solidStart", "solidStart",
"react", "react",
"hono", "hono",
]} ]}
/> />
</div> </div>
@@ -222,8 +202,6 @@ export default function Features() {
<Ripple /> <Ripple />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -17,7 +17,7 @@ export default function ArticleLayout() {
const pathname = usePathname(); const pathname = usePathname();
function getDefaultValue() { function getDefaultValue() {
const defaultValue = contents.findIndex((item) => const defaultValue = contents.findIndex((item) =>
item.list.some((listItem) => listItem.href === pathname), item.list.some((listItem) => listItem.href === pathname)
); );
return defaultValue === -1 ? 0 : defaultValue; return defaultValue === -1 ? 0 : defaultValue;
} }
@@ -43,7 +43,7 @@ export default function ArticleLayout() {
<AccordionItem value={`item-${i}`} key={item.title}> <AccordionItem value={`item-${i}`} key={item.title}>
<AccordionTrigger className="border-b border-lines px-5 py-2.5 text-left"> <AccordionTrigger className="border-b border-lines px-5 py-2.5 text-left">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{item.Icon && <item.Icon />} {item.Icon && <item.Icon className="w-5 h-5" />}
{item.title} {item.title}
</div> </div>
</AccordionTrigger> </AccordionTrigger>

View File

@@ -1,4 +1,6 @@
import { import {
AppWindow,
Clock,
Key, Key,
LucideAArrowDown, LucideAArrowDown,
LucideIcon, LucideIcon,
@@ -348,6 +350,17 @@ export const contents: Content[] = [
href: "/docs/plugins/1st-party-plugins", href: "/docs/plugins/1st-party-plugins",
icon: LucideAArrowDown, icon: LucideAArrowDown,
}, },
{
title: "Two Factor",
icon: ScanFace,
href: "/docs/plugins/2fa",
},
{
title: "Username",
icon: UserSquare2,
href: "/docs/plugins/username",
},
{ {
title: "Passkey", title: "Passkey",
href: "/docs/plugins/passkey", href: "/docs/plugins/passkey",
@@ -365,16 +378,6 @@ export const contents: Content[] = [
</svg> </svg>
), ),
}, },
{
title: "Two Factor",
icon: ScanFace,
href: "/docs/plugins/2fa",
},
{
title: "Username",
icon: UserSquare2,
href: "/docs/plugins/username",
},
{ {
title: "Authorization", title: "Authorization",
group: true, group: true,
@@ -392,6 +395,11 @@ export const contents: Content[] = [
href: "/docs/plugins/1st-party-plugins", href: "/docs/plugins/1st-party-plugins",
icon: LucideAArrowDown, icon: LucideAArrowDown,
}, },
{
title: "Rate Limit",
icon: Clock,
href: "/docs/plugins/rate-limit",
},
{ {
title: "Bearer", title: "Bearer",
icon: Key, icon: Key,
@@ -399,4 +407,15 @@ export const contents: Content[] = [
}, },
], ],
}, },
{
title: "Examples",
list: [
{
title: "Next JS",
href: "/docs/examples/next-js",
icon: Icons.nextJS,
},
],
Icon: AppWindow,
},
]; ];

View File

@@ -0,0 +1,20 @@
---
title: Next js example
descirption: Better auth next js example
---
<iframe src="https://codesandbox.io/p/github/better-auth/better-auth/draft/dry-wind?workspaceId=f8670ba9-e253-4b60-91a6-369d03522afe&embed=1"
style={
{
width: "100%",
height: "500px",
border: 0,
borderRadius: "4px",
overflow: "hidden"
}
}
title="Bekacru/better-auth-demo/draft/awesome-chihiro"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
module="/demo/nextjs"
></iframe>

View File

@@ -0,0 +1,73 @@
---
title: Hono Integration
description: Hono Integration Guide
---
This integration guide is assuming you are using Hono with node server.
## Installation
First, install Better Auth
```package-install
npm install better-auth
```
## Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
**Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
```
**Set Secret**
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
<GenerateSecret/>
## Configure Server
### Create Better Auth instance
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
```ts twoslash title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
```
<Callout type="warn">
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
</Callout>
### Mount the handler
We need to mount the handler to hono endpoint.
```ts
import { Hono } from "hono";
import { auth } from "./auth";
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";
const app = new Hono();
app.on(["POST", "GET"], "/api/auth/**", (c) => {
return auth.handler(c.req.raw);
});
serve(app);
```

View File

@@ -1,5 +1,5 @@
--- ---
title: Nuxt.js title: Nuxt.js Integration
description: Learn how to integrate Better Auth with Nuxt.js description: Learn how to integrate Better Auth with Nuxt.js
--- ---

View File

@@ -0,0 +1,64 @@
---
title: Solid Start Integration
description: Solid Start integratons guide
---
## Installation
First, install Better Auth
```package-install
npm install better-auth
```
## Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
**Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
```
**Set Secret**
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
<GenerateSecret/>
## Configure Server
### Create Better Auth instance
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
```ts twoslash title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
```
<Callout type="warn">
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
</Callout>
### Mount the handler
We need to mount the handler to Solid Start server. Put the following code in your `*auth.ts` file inside `/routes/api/auth` folder.
```ts title="*auth.ts"
import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";
export const { GET, POST } = toSolidStartHandler(auth);
```

View File

@@ -0,0 +1,148 @@
---
title: Svelte Kit Integration
description: Learn how to integrate Better Auth with Svelte Kit
---
Better Auth has first class support for Svelte Kit. It provides utilities to make it easier to use Better Auth with Svelte Kit.
## Installation
First, install Better Auth
```package-install
npm install better-auth
```
## Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
**Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
```
**Set Secret**
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
<GenerateSecret/>
## Configure Server
### Create Better Auth instance
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
```ts twoslash title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
```
<Callout type="warn">
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
</Callout>
### Mount the handler
We need to mount the handler to svelte kit server hook.
```ts
import { auth } from "$lib/auth";
import { svelteKitHandler } from "better-auth/svelte-kit";
export async function handle({ event, resolve }) {
return svelteKitHandler({ event, resolve, auth });
}
```
### Migrate the database
Run the following command to create the necessary tables in your database:
```bash
npx better-auth migrate
```
## Create a client
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
```ts twoslash title="client.ts"
import { createAuthClient } from "better-auth/svelte" // make sure to import from better-auth/svlete
export const client = createAuthClient({
//you can pass client configuration here
})
```
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
Some of the actinos are reactive. The client use [nano-store](https://github.com/nanostores/nanostores) to store the state and refelct changes when there is a change like a user signing in or out affecting the session state.
### Example usage
```svelte
<script lang="ts">
import { client } from "$lib/client";
const session = client.$session;
</script>
<div>
{#if $session}
<div>
<p>
{$session?.user.name}
</p>
<button
on:click={async () => {
await client.signOut();
}}
>
Signout
</button>
</div>
{:else}
<button
on:click={async () => {
await client.signIn.social({
provider: "github",
});
}}
>
Continue with github
</button>
{/if}
</div>
```
### Example: Getting Session on a loader
```ts title="+page.ts"
import { auth } from "$lib/auth";
export async function load(request: Request) {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
return {
status: 401,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
error: "Unauthorized",
}),
};
}
return session;
}
```

View File

@@ -0,0 +1,387 @@
---
title: Two-Factor Authentication (2FA)
description: Enhance your app's security with two-factor authentication
---
`OTP` `TOTP` `Backup Codes` `Trusted Devices`
## What is Two-Factor Authentication?
Two-Factor Authentication (2FA) adds an extra security step when users log in. Instead of just using a password, they'll need to provide a second form of verification. This makes it much harder for unauthorized people to access accounts, even if they've somehow gotten the password.
This plugin offers two main methods of 2FA:
1. **OTP (One-Time Password)**: A temporary code sent to the user's email or phone.
2. **TOTP (Time-based One-Time Password)**: A code generated by an app on the user's device.
**Additional features include:**
- Generating backup codes for account recovery
- Enabling/disabling 2FA
- Managing trusted devices
## Initial Setup
To get started with Two-Factor Authentication, follow these steps:
<Steps>
<Step>
### Add the plugin to your auth config:
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
export const auth = await betterAuth({
// ... other config options
plugins: [
twoFactor({
issuer: "my-app"
})
]
})
```
</Step>
<Step>
### Migrate your database:
```bash
npx better-auth migrate
```
</Step>
<Step>
### Add the client plugin:
```ts title="client.ts"
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [
twoFactorClient({
twoFactorPage: "/two-factor" //redirect for two factor verification if enabled // [!code highlight]
})
]
})
```
</Step>
</Steps>
## Usage
### Enabling 2FA
To enable two-factor authentication for a user:
```ts title="two-factor.ts"
const enableTwoFactor = async() => {
const { data, error } = await client.twoFactor.enable()
if (data) {
// 2FA has been enabled successfully
}
}
```
### Sign In with 2FA
When a user with 2FA enabled tries to sign in, you'll need to verify their 2FA code. If they have 2FA enabled, they'll be redirected to the `twoFactorPage` where they can enter their 2FA code.
```ts
const signin = async () => {
const { data, error } = await client.signIn.email({
email: "user@example.com",
password: "password123",
})
}
```
By default, the user will be redirected to the `twoFactorPage` if they have 2FA enabled. If you want to handle the 2FA verification in place, you can use the `options` parameter.
```ts
const signin = async () => {
const { data, error } = await client.signIn.email({
email: "user@example.com",
password: "password123",
options: {
async onSuccess(context) {
if (context.data.twoFactorRedirect) {
// Handle the 2FA verification in place
}
}
}
})
}
```
### TOTP
TOTP is a time-based one-time password algorithm that generates a code based on the current time. It's a more secure method than OTP because it takes into account the time it takes to generate the code.
#### Getting TOTP URI
After enabling 2FA, you can get the TOTP URI to display to the user.
```ts
const { data, error } = await client.twoFactor.getTotpUri()
if (data) {
// Use data.totpURI to generate a QR code or display to the user
}
```
#### Generating a QR Code
You can use a library like `qrcode` to generate a QR code from the TOTP URI.
```ts
import { toCanvas } from "qrcode"
toCanvas(document.getElementById('canvas'), data.totpURI)
```
#### Verifying TOTP
After the user has entered their 2FA code, you can verify it
```ts
const verifyTotp = async (code: string) => {
const { data, error } = await client.twoFactor.verifyTotp({ code })
}
```
### OTP
OTP is a one-time password algorithm that generates a code based on the current time. It's a more secure method than TOTP because it takes into account the time it takes to generate the code.
Before using OTP, you need to setup `sendOTP` function.
```ts title="auth.ts" twoslash
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
export const auth = await betterAuth({
database: {
provider: "sqlite",
url: "./db.sqlite",
},
plugins: [
twoFactor({
otpOptions: {
async sendOTP(user, otp) {
console.log({ user, otp });
},
},
})
]
})
```
#### Sending OTP
sending otp is done by calling `sendOtp` function. This functino will call your `sendOTP` function that you provide in the `otpOptions` with the otp code and the user.
```ts
const { data, error } = await client.twoFactor.sendOtp()
if (data) {
// Show the OTP to the user
}
```
#### Verifying OTP
After the user has entered their OTP code, you can verify it
```ts
const verifyOtp = async (code: string) => {
const { data, error } = await client.twoFactor.verifyOtp({ code })
}
```
### Backup Codes
Backup codes are generated and stored in the database when the user enabled two factor authentication. This can be used to recover access to the account if the user loses access to their phone or email.
#### Generating Backup Codes
Generate backup codes for account recovery:
```ts
const { data, error } = await client.twoFactor.generateBackupCodes()
if (data) {
// Show the backup codes to the user
}
```
#### Using Backup Codes
Backup codes can be used to recover access to the account if the user loses access to their phone or email.
```ts
const { data, error } = await client.twoFactor.verifyBackupCode({code: ""})
if (data) {
// 2FA verified and account recovered
}
```
### Trusted Devices
You can mark a device as trusted by passing `trustDevice` to `verifyTotp` or `verifyOtp`.
```ts
const verify2FA = async (code: string) => {
const { data, error } = await client.twoFactor.verifyTotp({
code,
callbackURL: "/dashboard",
trustDevice: true // Mark this device as trusted
})
if (data) {
// 2FA verified and device trusted
}
}
```
When `trustDevice` is set to `true`, the current device will be remembered for 60 days. During this period, the user won't be prompted for 2FA on subsequent sign-ins from this device. The trust period is refreshed each time the user signs in successfully.
<Callout type="info">
Trusted devices enhance user convenience but should be used carefully, as they slightly reduce security. Encourage users to only trust personal, secure devices.
</Callout>
## Configuration
### Server
To configure the two factor plugin, you need to add the following code to your better auth instance.
```ts title="auth.ts" twoslash
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
export const auth = await betterAuth({
database: {
provider: "sqlite",
url: "./db.sqlite",
},
plugins: [ // [!code highlight]
twoFactor({ // [!code highlight]
issuer: "my-app" // [!code highlight]
}) // [!code highlight]
] // [!code highlight]
})
```
**Issuer**: The issuer is the name of your application. It's used to generate totp codes. It'll be displayed in the authenticator apps.
**TOTP options**
these are options for TOTP.
<TypeTable
type={{
digits:{
description: 'The number of digits the otp to be',
type: 'number',
default: 6,
},
period: {
description: 'The period for otp in seconds.',
type: 'number',
default: 30,
},
}}
/>
**OTP options**
these are options for OTP.
<TypeTable
type={{
sendOTP: {
description: "a function that sends the otp to the user's email or phone number. It takes two parameters: user and otp",
type: "function",
},
period: {
description: 'The period for otp in seconds.',
type: 'number',
default: 30,
},
}}
/>
**Backup Code Options**
backup codes are generated and stored in the database when the user enabled two factor authentication. This can be used to recover access to the account if the user loses access to their phone or email.
<TypeTable
type={{
amount: {
description: "The amount of backup codes to generate",
type: "number",
default: 10,
},
length: {
description: "The length of the backup codes",
type: "number",
default: 10,
},
customBackupCodesGenerate: {
description: "A function that generates custom backup codes. It takes no parameters and returns an array of strings.",
type: "function",
},
}}
/>
### Client
To use the two factor plugin in the client, you need to add it on your plugins list.
```ts title="client.ts" twoslash
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [
twoFactorClient({ // [!code highlight]
twoFactorPage: "/two-factor" // [!code highlight]
}) // [!code highlight]
] // [!code highlight]
})
```
**Options**
`twoFactorPage`: The page to redirect the user to after they have enabled 2-Factor. This is the page where the user will be redirected to verify their 2-Factor code.
`redirect`: If set to `false`, the user will not be redirected to the `twoFactorPage` after they have enabled 2-Factor.
## Database Schema
Two factores requires additional fields on the user table. If you use better auth's migration system, it will automatically create this table for you.
```ts
const schema = {
user: {
fields: {
twoFactorEnabled: {
type: "boolean",
required: false,
defaultValue: false,
},
twoFactorSecret: {
type: "string",
required: false,
},
twoFactorBackupCodes: {
type: "string",
required: false,
returned: false,
},
},
},
}
```

View File

@@ -0,0 +1,4 @@
---
title: Organization
descirption: The organization plugin allows you to manage your organization's members and teams.
---

View File

@@ -0,0 +1,158 @@
---
title: Passkey
description: Passkey
---
Passkeys are a secure, passwordless authentication method using cryptographic key pairs, supported by WebAuthn and FIDO2 standards in web browsers. They replace passwords with unique key pairs: a private key stored on the users device and a public key shared with the website. Users can log in using biometrics, PINs, or security keys, providing strong, phishing-resistant authentication without traditional passwords.
The passkey plugin implementation is powered by [simple-web-authn](https://simplewebauthn.dev/) behind the scenes.
## Quick setup
<Steps>
<Step>
### Add the plugin to your auth config
To add the passkey plugin to your auth config, you need to import the plugin and pass it to the `plugins` option of the auth instance.
**Options**
`rpID`: A unique identifier for your website. 'localhost' is okay for local dev
`rpName`: Human-readable title for your website
`origin`: The URL at which registrations and authentications should occur. 'http://localhost' and 'http://localhost:PORT' are also valid.Do NOT include any trailing /
```ts title="auth.ts" twoslash
import { betterAuth } from "better-auth"
import { passkey } from "better-auth/plugins"
export const auth = await betterAuth({
database: {
provider: "sqlite",
url: "./db.sqlite",
},
plugins: [ // [!code highlight]
passkey({ // [!code highlight]
rpID: "localhost", // [!code highlight]
rpName: "BetterAuth", // [!code highlight]
origin: "http://localhost:3000", // [!code highlight]
}), // [!code highlight]
], // [!code highlight]
})
```
</Step>
<Step>
### Add the client plugin
```ts title="client.ts" twoslash
import { createAuthClient } from "better-auth/client"
import { passkeyClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [ // [!code highlight]
passkeyClient() // [!code highlight]
] // [!code highlight]
})
```
</Step>
<Step>
### Add a passkey
To add a passkey make sure a user is authenticated and then call the `passkey.addPasskey` function provided by the client.
```ts title="client.ts" twoslash
import { createAuthClient } from "better-auth/client"
import { passkeyClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [ // [!code highlight]
passkeyClient() // [!code highlight]
] // [!code highlight]
})
// ---cut---
const data = await client.passkey.addPasskey()
```
This will prompt the user to register a passkey. And it'll add the passkey to the user's account.
</Step>
<Step>
### Signin with a passkey
To signin with a passkey you can use the passkeySignIn method. This will prompt the user to sign in with their passkey.
Signin method accepts:
`autoFill`: Browser autofill, a.k.a. Conditional UI. [read more](https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui)
`callbackURL`: The URL to redirect to after the user has signed in. (optional)
```ts title="client.ts" twoslash
import { createAuthClient } from "better-auth/client"
import { passkeyClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [ // [!code highlight]
passkeyClient() // [!code highlight]
] // [!code highlight]
})
// ---cut---
const data = await client.signIn.passkey()
```
</Step>
</Steps>
## Passkey Configuration
**rpID**: A unique identifier for your website. 'localhost' is okay for local dev.
**rpName**: Human-readable title for your website.
**origin**: The URL at which registrations and authentications should occur. 'http://localhost' and 'http://localhost:PORT' are also valid. Do NOT include any trailing /.
## Database Schema
Passkey requires a database table called `passkey` with the following fields. If you use better auth's migration system, it will automatically create this table for you.
```ts
const schema = {
passkey: {
fields: {
publicKey: {
type: "string",
},
userId: {
type: "string",
references: {
model: "user",
field: "id",
},
},
webauthnUserID: {
type: "string",
},
counter: {
type: "number",
},
deviceType: {
type: "string",
},
backedUp: {
type: "boolean",
},
transports: {
type: "string",
required: false,
},
createdAt: {
type: "date",
defaultValue: new Date(),
required: false,
},
},
},
}
```

View File

@@ -0,0 +1,4 @@
---
title: Rate Limit
description: The rate limit plugin allows you to limit the number of requests a user can make to your auth server.
---

View File

@@ -0,0 +1,119 @@
---
title: Username
description: Username plugin
---
The username plugin wraps the email and password authenticator and adds username support. This allows users to sign in and sign up with their username instead of their email.
## Qiuck setup
<Steps>
<Step>
### Add Plugin to the server
```ts title="auth.ts" twoslash
import { betterAuth } from "better-auth"
import { username } from "better-auth/plugins"
const auth = await betterAuth({
database: {
provider: "sqlite",
url: "./db.sqlite",
},
plugins: [ // [!code highlight]
username() // [!code highlight]
] // [!code highlight]
})
```
</Step>
<Step>
### Add the client plugin
```ts title="client.ts" twoslash
import { createAuthClient } from "better-auth/client"
import { usernameClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [ // [!code highlight]
usernameClient() // [!code highlight]
] // [!code highlight]
})
```
</Step>
<Step>
### Signup with username
To signup a user with username, you can use the `signUp.username` function provided by the client. The `signUp` function takes an object with the following properties:
- `username`: The username of the user.
- `email`: The email address of the user.
- `password`: The password of the user. It should be at least 8 characters long and max 32 by default.
- `name`: The name of the user.
- `image`: The image of the user. (optional)
- `callbackURL`: The url to redirect to after the user has signed up. (optional)
```ts title="client.ts" twoslash
import { createAuthClient } from "better-auth/client"
import { usernameClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [ // [!code highlight]
usernameClient() // [!code highlight]
] // [!code highlight]
})
// ---cut---
const data = await client.signUp.username({
username: "test",
email: "test@email.com",
password: "password1234",
name: "test",
image: "https://example.com/image.png",
})
```
</Step>
<Step>
### Signin with username
To signin a user with username, you can use the `signIn.username` function provided by the client. The `signIn` function takes an object with the following properties:
- `username`: The username of the user.
- `password`: The password of the user.
- `callbackURL`: The url to redirect to after the user has signed in. (optional)
```ts title="client.ts" twoslash
import { createAuthClient } from "better-auth/client"
import { usernameClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [ // [!code highlight]
usernameClient() // [!code highlight]
] // [!code highlight]
})
// ---cut---
const data = await client.signIn.username({
username: "test",
password: "password1234",
})
```
</Step>
</Steps>
## Configuration
The username plugin doesn't require any configuration. It just needs to be added to the server and client.
## Database Schema
The username plugin requires a `username` field in the user table. If you're using better auth migration tool it will automatically add the `username` field to the user table. If not you can add it manually.
```ts
const shcmea = {
user: {
username: {
type: "string",
unique: true,
required: false
}
}
}
```

View File

@@ -31,5 +31,6 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
AnimatePresence, AnimatePresence,
TypeTable, TypeTable,
Features, Features,
iframe: (props) => <iframe {...props} className="w-full h-[500px]" />,
}; };
} }

34844
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +0,0 @@
Basic Concepts
## Auth Server
the auth instance you create with `betterAuth` has 2 important properties:
- `auth.handler`
- `auth.api`
the `auth.handler` is a web standard handler that you mount on your server that handler api requests.
`auth.api` is a list of methods that you can call directly on the server, to interact with the auth server. Like `getSession` to get the current session. Plugins may add additional methods to the api.
**Example: Getting the current session on the server**
```ts
/**
* Consider this as a random route endpoint that we want to protect
*/
export const POST = (request: Request)=> {
const session = await auth.api.getSession({
headers: request.headers // get session requires the headers to be passed
})
}
```
## Client
The client side of the library lets you interact with the auth server and includes built-in state management for specific methods, such as useSession.
You can import the client and use it to call these methods directly or export each method individually from the client.
If you add new plugins, they may also introduce their own methods. For instance, using the twoFactor plugin will add methods like twoFactor.enable. Check out the example below to see how to use the client:
```tsx
const client = createAuthClient()
export const { signIn, signUp, signOut, useSession } = client
export function SignUp(){
async function handleSubmit(data){
await signUp.email({
name: data.name,
email: data.email,
password: data.password,
})
}
//...your component
}
```