docs: concepts

This commit is contained in:
Bereket Engida
2024-09-22 15:53:47 +03:00
parent 829af5710e
commit 70d94d394b
18 changed files with 1197 additions and 235 deletions

View File

@@ -1,97 +1,117 @@
import { cn } from "@/lib/utils";
import { SVGProps } from "react";
export function MaterialSymbolsLightContactMailOutlineRounded(
props?: SVGProps<SVGSVGElement>
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
{...props}
>
<path
fill="currentColor"
d="M15 11.192h5q.348 0 .578-.23t.23-.577v-3q0-.348-.23-.578T20 6.577h-5q-.348 0-.578.23t-.23.577v3q0 .349.23.578q.23.23.578.23m2.5-2.019l1.889-1.32q.18-.13.377-.018q.196.113.196.324q0 .064-.156.293l-1.847 1.282q-.217.143-.459.143t-.46-.143l-1.846-1.282q-.044-.044-.155-.293q0-.211.196-.324q.196-.112.377.019zM2.615 19.615q-.69 0-1.153-.462T1 18V6q0-.69.463-1.153t1.152-.463h18.77q.69 0 1.153.463T23 6v12q0 .69-.462 1.153t-1.153.463zm12.17-1h6.6q.23 0 .423-.192T22 18V6q0-.23-.192-.423q-.193-.192-.424-.192H2.616q-.231 0-.424.192T2 6v12q0 .23.192.423t.423.193h.6q1.05-1.356 2.554-2.178T9 15.616t3.23.822t2.555 2.178M9 14.23q1.039 0 1.77-.731t.73-1.77t-.73-1.768T9 9.23t-1.77.73t-.73 1.77t.73 1.769t1.77.73m-4.45 4.386h8.9q-.87-.95-2.022-1.475q-1.153-.525-2.428-.525t-2.425.525t-2.025 1.475M9 13.23q-.617 0-1.059-.441q-.441-.442-.441-1.06t.441-1.058T9 10.231t1.059.441t.441 1.059t-.441 1.059q-.442.44-1.059.44M12 12"
></path>
</svg>
);
}
export const Icons = {
nextJS: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m4-14h-1.35v4H16zM9.346 9.71l6.059 7.828l1.054-.809L9.683 8H8v7.997h1.346z"
></path>
</svg>
),
nuxt: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M200.662 81.35L0 430.65h130.774l139.945-239.803zm134.256 40.313l-39.023 69.167l138.703 239.82H512zm-51.596 91.052L155.924 430.651h253.485z"
></path>
</svg>
),
svelteKit: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 426 512"
>
<path
fill="currentColor"
d="M403.508 229.23C491.235 87.7 315.378-58.105 190.392 23.555L71.528 99.337c-57.559 37.487-82.55 109.513-47.45 183.53c-87.761 133.132 83.005 289.03 213.116 205.762l118.864-75.782c64.673-42.583 79.512-116.018 47.45-183.616m-297.592-80.886l118.69-75.739c77.973-46.679 167.756 34.942 135.388 110.992c-19.225-15.274-40.65-24.665-56.923-28.894c6.186-24.57-22.335-42.796-42.174-30.106l-118.95 75.48c-29.411 20.328 1.946 62.138 31.014 44.596l45.33-28.895c101.725-57.403 198 80.425 103.38 147.975l-118.692 75.739C131.455 485.225 34.11 411.96 67.592 328.5c17.786 13.463 36.677 23.363 56.923 28.894c-4.47 28.222 24.006 41.943 42.476 30.365L285.64 312.02c29.28-21.955-2.149-61.692-30.97-44.595l-45.504 28.894c-100.56 58.77-199.076-80.42-103.25-147.975"
></path>
</svg>
),
solidStart: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 128 128"
>
<path
fill="currentColor"
d="M61.832 4.744c-3.205.058-6.37.395-9.45 1.07l-2.402.803c-4.806 1.603-8.813 4.005-11.216 7.21l-1.602 2.404l-12.017 20.828l.166.031c-4.785 5.823-5.007 14.07-.166 21.6c1.804 2.345 4.073 4.431 6.634 6.234l-15.445 4.982L.311 97.946s42.46 32.044 75.306 24.033l2.403-.801c5.322-1.565 9.292-4.48 11.683-8.068l.334.056l16.022-28.84c3.204-5.608 2.404-12.016-1.602-18.425a36 36 0 0 0-7.059-6.643l15.872-5.375l14.42-24.033S92.817 4.19 61.831 4.744z"
></path>
</svg>
),
react: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 15 15"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M5.315 1.837c-.4-.116-.695-.085-.91.032c-.216.116-.404.347-.526.745c-.122.401-.163.936-.104 1.582q.015.157.037.321a14 14 0 0 1 1.676-.311a13 13 0 0 1 1.275-1.54l-.066-.053c-.508-.402-.98-.66-1.382-.776m2.185.14q-.09-.076-.182-.148C6.746 1.377 6.16 1.04 5.594.876C5.024.711 4.441.711 3.928.99s-.833.767-1.005 1.334c-.172.564-.21 1.238-.144 1.965q.023.255.065.523q-.256.09-.49.192c-.671.287-1.246.642-1.66 1.062C.278 6.487 0 7 0 7.584S.278 8.68.694 9.103c.414.42.989.774 1.66 1.062q.235.1.49.192a9 9 0 0 0-.065.523c-.066.726-.028 1.4.144 1.965c.172.567.492 1.056 1.005 1.333c.513.278 1.097.279 1.666.114c.566-.165 1.152-.5 1.724-.953l.182-.149q.09.076.182.149c.572.452 1.158.788 1.724.953c.569.165 1.153.164 1.666-.114c.513-.277.833-.766 1.005-1.333c.172-.564.21-1.239.144-1.965a9 9 0 0 0-.065-.523q.255-.09.49-.192c.671-.288 1.246-.643 1.66-1.062c.416-.422.694-.936.694-1.52c0-.582-.278-1.096-.694-1.518c-.414-.42-.989-.775-1.66-1.062a9 9 0 0 0-.49-.192q.04-.268.065-.523c.066-.727.028-1.4-.144-1.965c-.172-.567-.492-1.056-1.005-1.334S9.975.711 9.406.876c-.566.164-1.152.5-1.724.953zm0 1.365q-.338.346-.672.755a17 17 0 0 1 1.344 0a11 11 0 0 0-.672-.755m2.012.864c-.41-.574-.84-1.092-1.275-1.54l.065-.053c.51-.402.98-.66 1.383-.776c.399-.116.695-.085.91.032c.216.116.404.347.525.745c.122.401.164.936.105 1.582q-.015.158-.037.32a14 14 0 0 0-1.676-.31m-.563.944a15.6 15.6 0 0 0-2.898 0A15.6 15.6 0 0 0 4.72 7.584a15.7 15.7 0 0 0 1.33 2.433a15.6 15.6 0 0 0 2.9 0a15.6 15.6 0 0 0 1.33-2.433A15.7 15.7 0 0 0 8.95 5.15m1.824 1.138a17 17 0 0 0-.527-.956q.39.075.752.168q-.094.385-.225.788m0 2.591a17 17 0 0 1-.527.957q.39-.075.752-.169a12 12 0 0 0-.225-.788m1.18.487a14 14 0 0 0-.588-1.782c.246-.61.443-1.209.588-1.782q.154.058.3.12c.596.256 1.047.547 1.341.845c.292.296.406.572.406.817s-.114.52-.406.816c-.294.299-.745.59-1.341.846a8 8 0 0 1-.3.12m-.765 1.285a14 14 0 0 1-1.676.311c-.41.574-.84 1.091-1.275 1.54l.066.052c.508.403.98.66 1.382.777c.399.116.695.085.91-.032s.404-.348.525-.746c.123-.4.164-.936.105-1.582a7 7 0 0 0-.037-.32M7.5 11.826q.338-.346.672-.755a17 17 0 0 1-1.344 0q.334.408.672.755m-2.746-1.99a17 17 0 0 1-.527-.957q-.13.404-.225.788q.361.094.752.169m-.942.815a14 14 0 0 0 1.676.311c.41.574.839 1.091 1.275 1.54l-.066.052c-.508.403-.98.66-1.382.777c-.4.116-.695.085-.911-.032s-.403-.348-.525-.746c-.122-.4-.163-.936-.104-1.582a8 8 0 0 1 .037-.32m-.765-1.285c.145-.574.341-1.172.588-1.782a14 14 0 0 1-.588-1.782q-.155.058-.3.12c-.596.256-1.047.547-1.341.845c-.292.296-.406.572-.406.817s.114.52.406.816c.294.299.745.59 1.341.846q.146.061.3.12m.955-3.865q.094.384.225.787a17 17 0 0 1 .527-.956q-.39.075-.752.169M6 7.584a1.5 1.5 0 1 1 3 0a1.5 1.5 0 0 1-3 0m1.5-.5a.5.5 0 1 0 0 1a.5.5 0 0 0 0-1"
clipRule="evenodd"
></path>
</svg>
),
hono: (props?: SVGProps<any>) => (
<svg
className={cn(props?.className)}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 256 330"
>
<path
fill="#9E9494"
className="dark:fill-white"
d="M134.129.029q1.315-.17 2.319.662a1256 1256 0 0 1 69.573 93.427q24.141 36.346 41.082 76.862q27.055 72.162-28.16 125.564q-48.313 40.83-111.318 31.805q-75.312-15.355-102.373-87.133Q-1.796 217.85.614 193.51q4.014-41.896 19.878-80.838q6.61-15.888 17.228-29.154a382 382 0 0 1 16.565 21.203q3.66 3.825 7.62 7.289Q92.138 52.013 134.13.029"
opacity=".993"
></path>
<path
fill="#3C2020"
d="M129.49 53.7q36.47 42.3 65.93 90.114a187.3 187.3 0 0 1 15.24 33.13q12.507 49.206-26.836 81.169q-38.05 26.774-83.488 15.902q-48.999-15.205-56.653-65.929q-1.857-15.993 3.314-31.142a225.4 225.4 0 0 1 17.89-35.78l19.878-29.155a5510 5510 0 0 0 44.726-58.31"
></path>
</svg>
),
nextJS: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m4-14h-1.35v4H16zM9.346 9.71l6.059 7.828l1.054-.809L9.683 8H8v7.997h1.346z"
></path>
</svg>
),
nuxt: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M200.662 81.35L0 430.65h130.774l139.945-239.803zm134.256 40.313l-39.023 69.167l138.703 239.82H512zm-51.596 91.052L155.924 430.651h253.485z"
></path>
</svg>
),
svelteKit: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 426 512"
>
<path
fill="currentColor"
d="M403.508 229.23C491.235 87.7 315.378-58.105 190.392 23.555L71.528 99.337c-57.559 37.487-82.55 109.513-47.45 183.53c-87.761 133.132 83.005 289.03 213.116 205.762l118.864-75.782c64.673-42.583 79.512-116.018 47.45-183.616m-297.592-80.886l118.69-75.739c77.973-46.679 167.756 34.942 135.388 110.992c-19.225-15.274-40.65-24.665-56.923-28.894c6.186-24.57-22.335-42.796-42.174-30.106l-118.95 75.48c-29.411 20.328 1.946 62.138 31.014 44.596l45.33-28.895c101.725-57.403 198 80.425 103.38 147.975l-118.692 75.739C131.455 485.225 34.11 411.96 67.592 328.5c17.786 13.463 36.677 23.363 56.923 28.894c-4.47 28.222 24.006 41.943 42.476 30.365L285.64 312.02c29.28-21.955-2.149-61.692-30.97-44.595l-45.504 28.894c-100.56 58.77-199.076-80.42-103.25-147.975"
></path>
</svg>
),
solidStart: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 128 128"
>
<path
fill="currentColor"
d="M61.832 4.744c-3.205.058-6.37.395-9.45 1.07l-2.402.803c-4.806 1.603-8.813 4.005-11.216 7.21l-1.602 2.404l-12.017 20.828l.166.031c-4.785 5.823-5.007 14.07-.166 21.6c1.804 2.345 4.073 4.431 6.634 6.234l-15.445 4.982L.311 97.946s42.46 32.044 75.306 24.033l2.403-.801c5.322-1.565 9.292-4.48 11.683-8.068l.334.056l16.022-28.84c3.204-5.608 2.404-12.016-1.602-18.425a36 36 0 0 0-7.059-6.643l15.872-5.375l14.42-24.033S92.817 4.19 61.831 4.744z"
></path>
</svg>
),
react: (props?: SVGProps<any>) => (
<svg
className={props?.className}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 15 15"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M5.315 1.837c-.4-.116-.695-.085-.91.032c-.216.116-.404.347-.526.745c-.122.401-.163.936-.104 1.582q.015.157.037.321a14 14 0 0 1 1.676-.311a13 13 0 0 1 1.275-1.54l-.066-.053c-.508-.402-.98-.66-1.382-.776m2.185.14q-.09-.076-.182-.148C6.746 1.377 6.16 1.04 5.594.876C5.024.711 4.441.711 3.928.99s-.833.767-1.005 1.334c-.172.564-.21 1.238-.144 1.965q.023.255.065.523q-.256.09-.49.192c-.671.287-1.246.642-1.66 1.062C.278 6.487 0 7 0 7.584S.278 8.68.694 9.103c.414.42.989.774 1.66 1.062q.235.1.49.192a9 9 0 0 0-.065.523c-.066.726-.028 1.4.144 1.965c.172.567.492 1.056 1.005 1.333c.513.278 1.097.279 1.666.114c.566-.165 1.152-.5 1.724-.953l.182-.149q.09.076.182.149c.572.452 1.158.788 1.724.953c.569.165 1.153.164 1.666-.114c.513-.277.833-.766 1.005-1.333c.172-.564.21-1.239.144-1.965a9 9 0 0 0-.065-.523q.255-.09.49-.192c.671-.288 1.246-.643 1.66-1.062c.416-.422.694-.936.694-1.52c0-.582-.278-1.096-.694-1.518c-.414-.42-.989-.775-1.66-1.062a9 9 0 0 0-.49-.192q.04-.268.065-.523c.066-.727.028-1.4-.144-1.965c-.172-.567-.492-1.056-1.005-1.334S9.975.711 9.406.876c-.566.164-1.152.5-1.724.953zm0 1.365q-.338.346-.672.755a17 17 0 0 1 1.344 0a11 11 0 0 0-.672-.755m2.012.864c-.41-.574-.84-1.092-1.275-1.54l.065-.053c.51-.402.98-.66 1.383-.776c.399-.116.695-.085.91.032c.216.116.404.347.525.745c.122.401.164.936.105 1.582q-.015.158-.037.32a14 14 0 0 0-1.676-.31m-.563.944a15.6 15.6 0 0 0-2.898 0A15.6 15.6 0 0 0 4.72 7.584a15.7 15.7 0 0 0 1.33 2.433a15.6 15.6 0 0 0 2.9 0a15.6 15.6 0 0 0 1.33-2.433A15.7 15.7 0 0 0 8.95 5.15m1.824 1.138a17 17 0 0 0-.527-.956q.39.075.752.168q-.094.385-.225.788m0 2.591a17 17 0 0 1-.527.957q.39-.075.752-.169a12 12 0 0 0-.225-.788m1.18.487a14 14 0 0 0-.588-1.782c.246-.61.443-1.209.588-1.782q.154.058.3.12c.596.256 1.047.547 1.341.845c.292.296.406.572.406.817s-.114.52-.406.816c-.294.299-.745.59-1.341.846a8 8 0 0 1-.3.12m-.765 1.285a14 14 0 0 1-1.676.311c-.41.574-.84 1.091-1.275 1.54l.066.052c.508.403.98.66 1.382.777c.399.116.695.085.91-.032s.404-.348.525-.746c.123-.4.164-.936.105-1.582a7 7 0 0 0-.037-.32M7.5 11.826q.338-.346.672-.755a17 17 0 0 1-1.344 0q.334.408.672.755m-2.746-1.99a17 17 0 0 1-.527-.957q-.13.404-.225.788q.361.094.752.169m-.942.815a14 14 0 0 0 1.676.311c.41.574.839 1.091 1.275 1.54l-.066.052c-.508.403-.98.66-1.382.777c-.4.116-.695.085-.911-.032s-.403-.348-.525-.746c-.122-.4-.163-.936-.104-1.582a8 8 0 0 1 .037-.32m-.765-1.285c.145-.574.341-1.172.588-1.782a14 14 0 0 1-.588-1.782q-.155.058-.3.12c-.596.256-1.047.547-1.341.845c-.292.296-.406.572-.406.817s.114.52.406.816c.294.299.745.59 1.341.846q.146.061.3.12m.955-3.865q.094.384.225.787a17 17 0 0 1 .527-.956q-.39.075-.752.169M6 7.584a1.5 1.5 0 1 1 3 0a1.5 1.5 0 0 1-3 0m1.5-.5a.5.5 0 1 0 0 1a.5.5 0 0 0 0-1"
clipRule="evenodd"
></path>
</svg>
),
hono: (props?: SVGProps<any>) => (
<svg
className={cn(props?.className)}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 256 330"
>
<path
fill="#9E9494"
className="dark:fill-white"
d="M134.129.029q1.315-.17 2.319.662a1256 1256 0 0 1 69.573 93.427q24.141 36.346 41.082 76.862q27.055 72.162-28.16 125.564q-48.313 40.83-111.318 31.805q-75.312-15.355-102.373-87.133Q-1.796 217.85.614 193.51q4.014-41.896 19.878-80.838q6.61-15.888 17.228-29.154a382 382 0 0 1 16.565 21.203q3.66 3.825 7.62 7.289Q92.138 52.013 134.13.029"
opacity=".993"
></path>
<path
fill="#3C2020"
d="M129.49 53.7q36.47 42.3 65.93 90.114a187.3 187.3 0 0 1 15.24 33.13q12.507 49.206-26.836 81.169q-38.05 26.774-83.488 15.902q-48.999-15.205-56.653-65.929q-1.857-15.993 3.314-31.142a225.4 225.4 0 0 1 17.89-35.78l19.878-29.155a5510 5510 0 0 0 44.726-58.31"
></path>
</svg>
),
MailOutlineRounded: MaterialSymbolsLightContactMailOutlineRounded,
};

View File

@@ -199,7 +199,10 @@ export default function Hero() {
code={code}
language={"javascript"}
theme={{
...themes.synthwave84,
...(theme.resolvedTheme === "dark"
? themes.synthwave84
: themes.oneLight),
plain: {
backgroundColor: "transparent",
},

View File

@@ -4,6 +4,7 @@ import {
Key,
LucideAArrowDown,
LucideIcon,
Mailbox,
PlusCircle,
ScanFace,
Users2,
@@ -99,6 +100,62 @@ export const contents: Content[] = [
{
title: "Concepts",
list: [
{
title: "Account Linking",
href: "/docs/concepts/account-linking",
icon: (props?: SVGProps<any>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path
className="fill-foreground"
d="M21.4 7.5c.8.8.8 2.1 0 2.8l-2.8 2.8l-7.8-7.8l2.8-2.8c.8-.8 2.1-.8 2.8 0l1.8 1.8l3-3l1.4 1.4l-3 3zm-5.8 5.8l-1.4-1.4l-2.8 2.8l-2.1-2.1l2.8-2.8l-1.4-1.4l-2.8 2.8l-1.5-1.4l-2.8 2.8c-.8.8-.8 2.1 0 2.8l1.8 1.8l-4 4l1.4 1.4l4-4l1.8 1.8c.8.8 2.1.8 2.8 0l2.8-2.8l-1.4-1.4z"
></path>
</svg>
),
},
{
href: "/docs/concepts/api",
title: "API",
icon: () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
className="fill-foreground"
fillRule="evenodd"
d="M2.6 13.25a1.35 1.35 0 0 0-1.35 1.35v6.8c0 .746.604 1.35 1.35 1.35h18.8a1.35 1.35 0 0 0 1.35-1.35v-6.8a1.35 1.35 0 0 0-1.35-1.35zm3.967 5.25a.75.75 0 0 0-1.114-1.003l-.01.011a.75.75 0 0 0 1.114 1.004zM2.6 1.25A1.35 1.35 0 0 0 1.25 2.6v6.8c0 .746.604 1.35 1.35 1.35h18.8a1.35 1.35 0 0 0 1.35-1.35V2.6a1.35 1.35 0 0 0-1.35-1.35zM6.567 6.5a.75.75 0 0 0-1.114-1.003l-.01.011a.75.75 0 1 0 1.114 1.004z"
clipRule="evenodd"
></path>
</svg>
),
},
{
title: "Client",
href: "/docs/concepts/client",
icon: () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
className="stroke-foreground"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 17h4v4H3zm7 0h4v4h-4zm7 0h4v4h-4zm0-7h4v4h-4zm0-7h4v4h-4zm-7 7h4v4h-4zm0-7h4v4h-4zM3 3h4v4H3z"
></path>
</svg>
),
},
{
title: "Database",
icon: (props?: SVGProps<any>) => (
@@ -115,7 +172,29 @@ export const contents: Content[] = [
></path>
</svg>
),
href: "/docs/database",
href: "/docs/concepts/database",
},
{
href: "/docs/concepts/plugins",
title: "Plugins",
icon: (props?: SVGProps<any>) => (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<g fill="none">
<path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
<path
fill="currentColor"
d="M2 9a3 3 0 0 1 3-3h2.853c.297 0 .48-.309.366-.583A2.5 2.5 0 0 1 8.083 5c-.331-1.487.792-3 2.417-3c1.626 0 2.748 1.513 2.417 3a2.5 2.5 0 0 1-.136.417c-.115.274.069.583.366.583H15a3 3 0 0 1 3 3v1.853c0 .297.308.48.583.366c.135-.056.273-.104.417-.136c1.487-.331 3 .791 3 2.417s-1.513 2.748-3 2.417a2.5 2.5 0 0 1-.417-.136c-.274-.115-.583.069-.583.366V19a3 3 0 0 1-3 3h-1.893c-.288 0-.473-.291-.39-.566q.063-.21.085-.434a2.31 2.31 0 1 0-4.604 0q.021.224.086.434c.082.275-.103.566-.39.566H5a3 3 0 0 1-3-3v-2.893c0-.288.291-.473.566-.39q.21.063.434.085a2.31 2.31 0 1 0 0-4.604q-.224.021-.434.086c-.275.082-.566-.103-.566-.39z"
></path>
</g>
</svg>
),
},
],
Icon: () => (
@@ -404,6 +483,11 @@ export const contents: Content[] = [
icon: UserSquare2,
href: "/docs/plugins/username",
},
{
title: "Magic Link",
href: "/docs/plugins/magic-link",
icon: Mailbox,
},
{
title: "Passkey",
href: "/docs/plugins/passkey",
@@ -415,8 +499,8 @@ export const contents: Content[] = [
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M18 16.663a3.5 3.5 0 0 1-2-3.163a3.5 3.5 0 1 1 4.5 3.355V17l1.146 1.146a.5.5 0 0 1 0 .708L20.5 20l1.161 1.161a.5.5 0 0 1 .015.692l-1.823 1.984a.5.5 0 0 1-.722.015l-.985-.984a.5.5 0 0 1-.146-.354zM20.5 13a1 1 0 1 0-2 0a1 1 0 0 0 2 0M17 17.242v3.69c-1.36.714-3.031 1.07-5 1.07c-3.42 0-5.944-1.073-7.486-3.237a2.75 2.75 0 0 1-.51-1.596v-.92a2.25 2.25 0 0 1 2.249-2.25h8.775A4.5 4.5 0 0 0 17 17.243M12 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10"
className="fill-foreground"
d="M3.25 9.65q-.175-.125-.213-.312t.113-.388q1.55-2.125 3.888-3.3t4.987-1.175q2.65 0 5 1.138T20.95 8.9q.175.225.113.4t-.213.3q-.15.125-.35.113t-.35-.213q-1.375-1.95-3.537-2.987t-4.588-1.038q-2.425 0-4.55 1.038T3.95 9.5q-.15.225-.35.25t-.35-.1m11.6 12.325q-2.6-.65-4.25-2.588T8.95 14.65q0-1.25.9-2.1t2.175-.85q1.275 0 2.175.85t.9 2.1q0 .825.625 1.388t1.475.562q.85 0 1.45-.562t.6-1.388q0-2.9-2.125-4.875T12.05 7.8q-2.95 0-5.075 1.975t-2.125 4.85q0 .6.113 1.5t.537 2.1q.075.225-.012.4t-.288.25q-.2.075-.387-.012t-.263-.288q-.375-.975-.537-1.937T3.85 14.65q0-3.325 2.413-5.575t5.762-2.25q3.375 0 5.8 2.25t2.425 5.575q0 1.25-.887 2.087t-2.163.838q-1.275 0-2.187-.837T14.1 14.65q0-.825-.612-1.388t-1.463-.562q-.85 0-1.463.563T9.95 14.65q0 2.425 1.438 4.05t3.712 2.275q.225.075.3.25t.025.375q-.05.175-.2.3t-.375.075M6.5 4.425q-.2.125-.4.063t-.3-.263q-.1-.2-.05-.362T6 3.575q1.4-.75 2.925-1.15t3.1-.4q1.6 0 3.125.388t2.95 1.112q.225.125.263.3t-.038.35q-.075.175-.25.275t-.425-.025q-1.325-.675-2.738-1.037t-2.887-.363q-1.45 0-2.85.338T6.5 4.425m2.95 17.2q-1.475-1.55-2.262-3.162T6.4 14.65q0-2.275 1.65-3.838t3.975-1.562q2.325 0 4 1.563T17.7 14.65q0 .225-.137.363t-.363.137q-.2 0-.35-.137t-.15-.363q0-1.875-1.388-3.137t-3.287-1.263q-1.9 0-3.262 1.263T7.4 14.65q0 2.025.7 3.438t2.05 2.837q.15.15.15.35t-.15.35q-.15.15-.35.15t-.35-.15m7.55-1.7q-2.225 0-3.863-1.5T11.5 14.65q0-.2.138-.35t.362-.15q.225 0 .363.15t.137.35q0 1.875 1.35 3.075t3.15 1.2q.15 0 .425-.025t.575-.075q.225-.05.388.063t.212.337q.05.2-.075.35t-.325.2q-.45.125-.787.138t-.413.012"
></path>
</svg>
),
@@ -448,12 +532,6 @@ export const contents: Content[] = [
icon: Key,
href: "/docs/plugins/bearer",
},
// {
// title: "Guide",
// group: true,
// href: "/docs/plugins/guide",
// icon: LucideAArrowDown,
// },
],
},
];

View File

@@ -102,12 +102,6 @@ To enable email verification, you need to configure the email and password authe
import { betterAuth } from "better-auth"
export const auth = await betterAuth({
// ---cut-start---
database: {
provider: "sqlite",
url: "./db.sqlite"
},
// ---cut-end---
emailAndPassword: {
enabled: true,
async sendVerificationEmail(email, url){
@@ -120,9 +114,6 @@ export const auth = await betterAuth({
on the client side you can use `sendVerificationEmail` function to send verification link to user.
```ts title="client.ts"
import { createAuthClient } from "better-auth/client"
const client = createAuthClient()
// ---cut---
const verifyEmail = async () => {
const data = await client.sendVerificationEmail({
email: "test@example.com",
@@ -140,10 +131,6 @@ to reset a password first you need to provider `sendResetPasswordToken` function
- `token`: The token that was generated.
```ts title="auth.ts"
async function sendResetEmail(email: string, url: string){
// send email to user
}
// ---cut---
import { betterAuth } from "better-auth"
export const auth = await betterAuth({
@@ -165,9 +152,6 @@ export const auth = await betterAuth({
once you configured your server you can call `forgetPassword` function to send reset password link to user.
```ts title="client.ts"
import { createAuthClient } from "better-auth/client"
const client = createAuthClient()
// ---cut---
const forgetPassword = async () => {
const data = await client.forgetPassword({
email: "test@example.com",
@@ -181,15 +165,6 @@ When user click on the link in the email he will be redirected to the reset pass
- `newPassword`: The new password of the user.
```ts title="client.ts"
import { createAuthClient } from "better-auth/client"
const client = createAuthClient()
function useSearchParams() {
return {
get: (key: string) => "" as string | null,
}
}
// ---cut---
const token = useSearchParams().get("token") // get token from url
const resetPassword = async () => {
if(!token) return
@@ -200,8 +175,30 @@ const resetPassword = async () => {
}
```
### Configuration Options
### Configuration
**Password**
Better auth stores passwords inside the `account` table with `providerId` set to `credential`.
**Password Hashing**: Better Auth uses `scrypt` to hash passwords. The `scrypt` algorithm is designed to be slow and memory-intensive to make it difficult for attackers to brute force passwords. OWSAP recommends using `scrypt if `argon2id` is not available. We decided to use `scrypt` because it's natively supported by Node.js.
You can pass custom password hashing algothim by setting `passwordHasher` option in the `auth` configuration.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { scrypt } from "scrypt"
export const auth = betterAuth({
//...rest of the options
emailAndPassword: {
password: {
hash: // your custom password hashing function
verify: // your custom password verification function
}
}
})
```
<TypeTable
type={{
@@ -227,6 +224,20 @@ const resetPassword = async () => {
},
sendVerificationEmail: {
description: 'send verification email. It takes a functions that takes two parameters: email and url.',
},
password: {
description: 'password configuration',
type: 'object',
properties: {
hash: {
description: 'custom password hashing function',
type: 'function',
},
verify: {
description: 'custom password verification function',
type: 'function',
}
}
}
}}
/>

View File

@@ -0,0 +1,13 @@
---
title: Account Linking
description: Account linking allows users to link multiple authentication methods to the same account
---
Account linking allows users to link multiple authentication methods to the same account. This is useful when users want to sign in using different methods.
Better auth automatically handles account linking when users sign in using different methods. This works by checking if the provider returns `emailVerified` field. If the field is present, Better Auth will link the account to the existing user account.
<Callout>
more sophisticated account linking feature will be added in the future.
</Callout>

View File

@@ -0,0 +1,67 @@
---
title: API
description: Better Auth API
---
When you create a new Better Auth instance, you get an object called `api` with a set of functions that you can use to interact with the server.
Any endpoint added on the server will be available on the `api` object.
## Calling API on the Server
Better auth uses a library called [better-call](https://github.com/bekacru/better-call) to make API endpoints. It's a library made by the same team behind Better Auth and is designed to work seamlessly with it.Better call endpoints allow you to call `rest` api handlers as if they were normal functions.
```ts title="server.ts"
import { betterAuth } from "better-auth";
export const auth = await betterAuth({
database: {
provider: "sqlite",
url: "./db.sqlite",
},
plugins: [
// add your plugins here
]
})
// calling sign in email on the server
await auth.api.signInEmail({
body: {
email: "",
password: ""
}
})
```
**Example: Getting the current Session**
```ts title="server.ts"
import { headers } from "next/server";
// calling get session on the server
await auth.api.getSession({
headers: headers()
})
```
## Handling Errors
Unlike the client, the server doesn't return error as a value, instead it throws an error. You can use a try/catch block to handle errors.
```ts title="server.ts"
try {
await auth.api.signInEmail({
body: {
email: "",
password: ""
}
})
} catch (error) {
console.error(error)
}
```
<Callout>
On some cases, the server will return an error object or just a value that indicates non successful value. Mostly, the server will throw an error.
</Callout>

View File

@@ -0,0 +1,286 @@
---
title: Client
description: Better Auth client library for authentication
---
Better auth provides a client library that you can use with different popular frontend frameworks such as React, Vue, Svelte, and more. The client library provides a set of functions to interact with the Better Auth server.
## Installation
If you haven't already, install better-auth.
```package-install
npm i better-auth
```
## Create Client Instance
Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React). Call the function to create your client. Pass the base URL of your auth server. If the auth server is running on the same domain as your client, you can skip this step.
<Callout type="info">
If you're using a different base path other than `/api/auth`, make sure to pass the whole URL, including the path. (e.g., `http://localhost:3000/custom-path/auth`)
</Callout>
<Tabs items={["react", "vue", "svelte", "solid",
"vanilla"]} defaultValue="react">
<Tab value="vanilla">
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/client"
export const client = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
</Tab>
<Tab value="react" title="lib/auth-client.ts">
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/react"
export const client = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
</Tab>
<Tab value="vue" title="lib/auth-client.ts">
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/vue"
export const client = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
</Tab>
<Tab value="svelte" title="lib/auth-client.ts">
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/svelte"
export const client = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
</Tab>
<Tab value="solid" title="lib/auth-client.ts">
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/solid"
export const client = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
</Tab>
</Tabs>
## Usage
Once you've created your client instance, you can use the client to interact with the Better Auth server. The client provides a set of functions by default and they can be extended with plugins.
**Example: Sign In**
```ts title="client.ts"
import { createAuthClient } from "better-auth/client"
const client = createAuthClient()
await client.signIn.email({
email: "test@user.com",
password: "password1234"
})
```
### Session
The client provides a `useSession` hook to access the current user session. The `useSession` hook returns the current user session and updates automatically when the session changes.
Better Auth provides a `useSession` hook to easily access session data on the client side. This hook is implemented in a reactive way for each supported framework, ensuring that any changes to the session (such as signing out) are immediately reflected in your UI.
<Tabs items={["React", "Vue","Svelte", "Solid"]} defaultValue="React">
<Tab value="React">
```tsx title="user.tsx"
//make sure you're using the react client
import { createAuthClient } from "better-auth/react"
const { useSession } = createAuthClient() // [!code highlight]
export function User(){
const {
data: session,
isPending, //loading state
error //error object
} = useSession()
returns (
//...
)
}
```
</Tab>
<Tab value="Vue">
```vue title="user.vue"
<template>
<div>
<button v-if="!client.useSession().value" @click="() => client.signIn.social({
provider: 'github'
})">
Continue with github
</button>
<div>
<pre>{{ client.useSession().value }}</pre>
<button v-if="client.useSession().value" @click="client.signOut()">
Sign out
</button>
</div>
</div>
</template>
```
</Tab>
<Tab value="Svelte">
```svelte title="user.svelte"
<script lang="ts">
import { client } from "$lib/client";
const session = client.useSsession;
</script>
<div
style="display: flex; flex-direction: column; gap: 10px; border-radius: 10px; border: 1px solid #4B453F; padding: 20px; margin-top: 10px;"
>
<div>
{#if $session}
<div>
<p>
{$session?.user.name}
</p>
<p>
{$session?.user.email}
</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>
</div>
```
</Tab>
<Tab value="Solid">
```tsx title="user.tsx"
import { client } from "~/lib/client";
import { Show } from 'solid-js';
export default function Home() {
const session = client.useSession()
return (
<Show
when={session()}
fallback={<button onClick={toggle}>Log in</button>}
>
<button onClick={toggle}>Log out</button>
</Show>
);
}
```
</Tab>
</Tabs>
### Fetch Options
The client uses a libray called [better fetch](https://better-fetch.vercel.app) to make requests to the server.
Better fetch is a wrapper around the native fetch API that provides a more convenient way to make requests. It's created by the same team behind Better Auth and is designed to work seamlessly with it.
You can pass any defualt fetch options to the client by passing `fetchOptions` object to the `createAuthClient` function.
```ts title="client.ts"
import { createAuthClient } from "better-auth/client"
const client = createAuthClient({
fetchOptions: {
//any better-fetch options
},
})
```
You can also pass fetch options to most of the client functions. For example, you can pass `fetchOptions` to the `signIn` function to customize the request.
```ts title="client.ts"
await client.signIn.email({
email: "email@email.com",
password: "password1234",
fetchOptions: {
onSuccess(ctx){
//
}
},
})
```
### Handling Errors
Most of the client functions return a response object with the following properties:
- `data`: The response data.
- `error`: The error object if there was an error.
the error object contains the following properties:
- `message`: The error message. (e.g., "Invalid email or password")
- `status`: The HTTP status code.
- `statusText`: The HTTP status text.
```ts title="client.ts"
const { data, error } = await client.signIn.email({
email: "email@email.com",
password: "password1234"
})
if(error){
//handle error
}
```
If the functions accepte a `fetchOptions` object, you can pass an `onError` function to handle errors.
```ts title="client.ts"
await client.signIn.email({
email: "email@email.com",
password: "password1234",
fetchOptions: {
onError(ctx){
//handle error
}
}
})
```
Hooks like `useSession` also return an error object if there was an error fetching the session. On top of that, they also return a `isPending` property to indicate if the request is still pending.
```ts title="client.ts"
const { data, error, isPending } = useSession()
if(error){
//handle error
}
```
### Plugins
You can extend the client with plugins to add more functionality. Plugins can add new functions to the client or modify existing ones.
**Example: Magic Link Plugin**
```ts title="client.ts"
import { createAuthClient } from "better-auth/client"
import { magicLinkClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [
magicLinkClient()
]
})
```

View File

@@ -0,0 +1,123 @@
---
title: Database
description: Learn how to use a database with BetterAuth
---
Better auth requires a database connection to store data. Better auth comes with a built in database mangment using <Link href="https://kysely.dev/"> Kysley </Link> to manage the database. The database will be used to store data such as users, sessions, and more. Plugins can also define their own database tables to store data.
You can pass a database connection to better auth by passing a `database` object to the `betterAuth` function. The `database` object should have a `provider` key that specifies the database provider to use. By default, better auth support `postgres`, `mysql` and `sqlite`. You can also pass a kysley dialect directly to use other databases supported by kysley. The `database` object can also have a `url` key that specifies the database connection to use. If you're using `sqlite`, you can pass a file path in the `url` key.
**Example: Sqlite**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = await betterAuth({
database: {
provider: "sqlite",
url: "path/to/database.sqlite"
}
})
```
**Example: Postgres**
```ts title="auth.ts"
import { betterAuth } from "better-auth
export const auth = await betterAuth({
database: {
provider: "postgres",
url: "postgres://user:password@localhost:5432/database"
}
})
```
**Exmaple: Mysql**
```ts title="auth.ts"
import { betterAuth } from "better-auth
export const auth = await betterAuth({
database: {
provider: "mysql",
url: "mysql://user:password@localhost:3306/database"
}
})
```
**Exmaple: Custom Dialect using libsql**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { LibsqlDialect } from "@libsql/kysely-libsql";
export const auth = await betterAuth({
database: new LibsqlDialect({
url: process.env.TURSO_DATABASE_URL || "",
authToken: process.env.TURSO_AUTH_TOKEN || "",
}),
})
```
<Callout>
See <Link href="https://kysely.dev/docs/dialects" target="_blank"> Kysley Dialects </Link> for more dialects supported by kysley.
</Callout>
## Running Migrations
Better auth includes a CLI tool to manage database migrations. Use the `migrate` command to create or update tables as needed.
The cli checks your database and prompts you to add missing tables or update existing ones with new columns.
```bash
npx better-auth migrate
```
## Core Schema
Better auth requires the following tables to be present in the database:
**user**:
- `id`: The unique identifier of the user.
- `email`: The email address of the user.
- `name`: The name of the user.
- `image`: The image of the user.
**session**:
- `id`: The unique identifier of the session. Also used as the session token.
- `userId`: The id of the user.
- `expiresAt`: The time when the session expires.
- `ipAddress`: The IP address of the user.
- `userAgent`: The user agent of the user.
**account**:
- `id`: The unique identifier of the account.
- `userId`: The id of the user.
- `accountId`: The id of the account. (optional)
- `providerId`: The id of the provider. (optional)
- `accessToken`: The access token of the account. Returned by the provider. (optional)
- `refreshToken`: The refresh token of the account. Returned by the provider. (optional)
- `accessTokenExpiresAt`: The time when the access token expires. (optional)
- `refreshTokenExpiresAt`: The time when the refresh token expires. (optional)
- `password`: The password of the account. Mainly used for email and password authentication. (optional)
## Plugins Schema
Plugins can define their own tables in the database to store additional data. They can also add columns to the core tables to store additional data. For example, the two factor authentication plugin adds the following columns to the `user` table:
- `twoFactorEnabled`: Whether two factor authentication is enabled for the user.
- `twoFactorSecret`: The secret key used to generate TOTP codes.
- `twoFactorBackupCodes`: Encrypted backup codes for account recovery.
To add additional tables and columns to the database, you can run the `migrate` command provided by better auth. The `migrate` command will run the migrations defined by the plugins to add the required tables and columns to the database.
```bash
npx better-auth migrate
```
<Callout>
The `migrate` command should be run whenever you add plugins that require additional tables or columns in the database.
</Callout>
## Extending the Schema
Better auth currently does not provide a direct method to extend the core schema. However, you can add extra columns to the database and manage them independently. For instance, to store additional user data, you can either create a new table or add columns to the `user` table.
We suggest creating a new table for additional data to keep the core tables clean and maintainable. You can link this new table to the `user` table using a foreign key.

View File

@@ -0,0 +1,324 @@
---
title: Plugins
description: Learn how to use plugins with BetterAuth
---
Plugins are a key part of Better Auth, letting you extend its functionality. Use them to add new authentication methods, features, or customize behavior.
Better Auth offers many built-in plugins ready to use. Check the plugins section for details. You can also create your own plugins.
## Using a Plugin
Plugins can be a server-side plugin, a client-side plugin, or both.
To add a plugin on the server, include it in the `plugins` array in the Better Auth configuration. The plugin will initialize with the provided options.
```ts title="server.ts"
import { betterAuth } from "better-auth";
export const auth = await betterAuth({
database: {
provider: "sqlite",
url: "./db.sqlite",
},
plugins: [
// Add your plugins here
]
})
```
Client plugins are added when creating the client. Include the plugin in the `plugins` array when creating the client. Most clients require both server and client plugins to work correctly.
```ts title="client.ts"
import { createAuthClient } from "better-auth/client";
const client = createAuthClient({
plugins: [
// Add your client plugins here
]
})
```
## Creating a Plugin
To create a plugin you need to pass an object that satsfies the `BetterAuthPlugin` interface.
The only required key is `id`, which is a unique identifier for the plugin.
```ts title="plugin.ts"
const myPlugin = {
id: "my-plugin",
}
```
### Endpoints
To add endpoints to the server, you can pass `endpoints` that requires an object with the key being any `string` and teh value being `AuthEndpoint`.
To create an Auth Endpoint you'll need to import `createAuthEndpoint` from `better-auth`.
Better auth uses a library called <Link href="https://github.com/bekacru/better-call"> Better Call </Link> to create endpoints. Better call is a simple web framework made by the same author of Better Auth.
```ts title="plugin.ts"
import { createAuthEndpoint } from "better-auth";
const myPlugin = {
id: "my-plugin",
endpoints: {
getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
}, async(req)=>{
})
}
}
```
Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the `req` object, it'll provide you with an object called `context` that give you access better-auth specefic contexts including `options`, `db`, `baseURL` and more.
**Context Object**
- `appName`: The name of the application. Defaults to "Better Auth".
- `options`: The options passed to the Better Auth instance.
- `tables`: Core tables defination. It is an object which has the table name as the key and the schema defination as the value.
- `baseURL`: the baseURL of the auth server. This includes the path. For example, if the server is running on `http://localhost:3000`, the baseURL will be `http://localhost:3000/api/auth` by default unless changed by the user.
- `session`: The session configuration. Includes `updateAge` and `expiresIn` values.
- `secret`: The secret key used for various purposes. This is defined by the user.
- `authCookie`: The defualt cookie configuration for core auth cookies.
- `logger`: The logger instance used by Better Auth.
- `db`: The kysely instance used by Better Auth to interact with the database.
- `password`: The password configuration. Includes `hash` and `verify` values for hashing and verifying passwords.
- `adapter`: This is the same as db but it give you `orm` like functions to interact with the database. (we recommend using this over `db` unless you need raw sql queries or for peroframnce reasons)
- `intenralAdapter`: this are intenral db calls that are used by better auth. You can use this calls for example to create session instead of using `adapter` directly. `intenralAdapter.createSession(userId)`
- `createAuthCookie`: This is a helper function that let's you get a cookie `name` and `options` for either to `set` or `get` cookies. It implements things like `__secure` prefix and `__host` prefix for cookies based on
For other properties, you can check the <Link href="https://github.com/bekacru/better-call">Better Call</Link> documentation.
**Rules**
- Makes sure you use kebab-case for the endpoint path.
- Make sure to only use `POST` or `GET` methods for the endpoints.
- Any function that modifies a data should be a `POST` method.
- Any function that fetches data should be a `GET` method.
- Make sure to use the `createAuthEndpoint` function to create the endpoint.
- Make sure your paths are unique to avoid conflicts with other plugins.
### Schema
You can define a database schema for your plugin by passing a `schema` object. The schema object should have the table name as the key and the schema definition as the value.
```ts title="plugin.ts"
const myPlugin = {
id: "my-plugin",
schema: {
myTable: {
fields: {
name: {
type: "string"
}
}
}
}
}
```
**Fields**
By default better-auth will create an `id` field for each table. You can add additional fields to the table by adding them to the `fields` object.
The key is the column name and the value is the column definition. The column definition can have the following properties:
`type`: The type of the filed. It can be `string`, `number`, `boolean`, `date`.
`required`: if the field should be required on a new record. (default: `false`)
`returned`: if the field should be returned when fetching records. (default: `true`)
`unique`: if the field should be unique. (default: `false`)
`reference`: if the field is a reference to another table. (default: `null`) It takes an object with the following properties:
- `model`: The table name to reference.
- `field`: The field name to reference.
- `onDelete`: The action to take when the referenced record is deleted. (default: `null`)
**Other Schema Properties**
`disableMigration`: if the table should not be migrated. (default: `false`)
```ts title="plugin.ts"
const myPlugin = (opts: PluginOptions)=>{
return {
id: "my-plugin",
schema: {
rateLimit: {
fields: {
key: {
type: "string",
},
},
disableMigration: opts.storage.provider !== "database", // [!code highlight]
},
},
} satisfies BetterAuthPlugin
}
```
### Middleware
You can add middleware to the server by passing a `middleware` array. The middleware array should contain an array of middleware functions created by `createAuthMiddelware`.
`createAuthMiddelware` wraps around `createMiddleware` from Better Call. It takes a function that returns a middleware function. The function will be called with the context object.
```ts title="plugin.ts"
const myPlugin = {
id: "my-plugin",
middleware: [
createAuthMiddelware(async(ctx)=>{
//do something
})
]
}
```
If you throw an `APIError` from the middleware or returned a `Response` object, the request will be stopped and the response will be sent to the client.
### Hooks
Hooks and middleware are similar but middlewares are handled by better-call and hooks are handled by better-auth. Hooks allows you to execture a code `before` or `after` a specific action. Hooks takes an array of `after` or `before` hooks.
```ts title="plugin.ts"
const myPlugin = {
id: "my-plugin",
hooks: {
before: {
[{
matcher: (context)=>{
return context.req.method === "POST"
},
handler: createAuthMiddelware(async(ctx)=>{
//do something before the request
})
}]
},
after: [{
matcher: (context)=>{
return context.req.method === "POST"
},
handler: createAuthMiddelware(async(ctx)=>{
//this is the returned response object from the actual endpoint
const returned = ctx.context.returned
//do something after the request
})
}]
}
} satisfies BetterAuthPlugin
```
### Client
If your endpoints needs to be called from the client, you'll need to also create a client plugin. Better auth clients can infer the endpoints from the server plugins. You can also add additional client side logic.
**Endpoint Inference**
Endpoints are inferred from the server plugin by adding a `$InferServerPlugin` key to the client plugin.
The client infers the `path` as an object and converts kebab-case to camelCase. For example, `/my-plugin/hello-world` becomes `myPlugin.helloWorld`.
```ts title="client-plugin.ts"
import type { AuthClientPlugin } from "better-auth/client";
import type { myPlugin } from ".//plugin";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
} satisfies AuthClientPlugin
```
**getActions**
If you need to add additional methods or what not to the client you can use the `getActions` function. This function is called with the `fetch` function from the client.
Better auth uses <Link href="https://bette-fetch.vercel.app"> Better fetch </Link> to make requests. Better fetch is a simple fetch wrapper made by the same author of Better Auth.
```ts title="client-plugin.ts"
import type { AuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";
import { BetteFetchOptions } from "better-fetch";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
getActions: ($fetch)=>{
return {
myCustomAction: async (data: {
foo: string,
fetchOptions?: BetteFetchOptions
})=>{
const res = $fetch("/custom/action", {
method: "POST",
body: {
foo: data.foo
}
...data.fetchOptions
})
return res
}
}
}
} satisfies AuthClientPlugin
```
<Callout>
As a general rule, make sure to accept only one argument in the function and make sure you also optionally accept a `fetchOptions` object. This is to make sure the user can pass additional options to the fetch call. And return and object with the `data` and `error` key.
</Callout>
**getAtoms**
This is only useful if you want to provide `hooks` like `useSession`.
Get atoms is called with the `fetch` function from better fetch and it should return an object with the atoms. This atoms should be created using <Link href="https://github.com/nanostores/nanostores">nanostore</Link>. The atoms will be resolved by each framework `useStore` hook provided by nanostore.
```ts title="client-plugin.ts"
import { atom } from "nanostores";
import type { AuthClientPlugin } from "better-auth/client";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
getAtoms: ($fetch)=>{
const myAtom = atom<null>()
return {
myAtom
}
}
} satisfies AuthClientPlugin
```
See built in plugins for examples of how to use atoms properly.
**pathMethods**
by default, infered paths use `GET` method if they don't require a body and `POST` if they do. You can override this by passing a `pathMethods` object. The key should be the path and the value should be the method ("POST" | "GET").
```ts title="client-plugin.ts"
import type { AuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
pathMethods: {
"/my-plugin/hello-world": "POST"
}
} satisfies AuthClientPlugin
```
**fetchPlugins**
If you need to use better fetch plugins you can pass them to the `fetchPlugins` array. You can read more about better fetch plugins in the <Link href="https://better-fetch.vercel.app/docs/plugins">better fetch documentation</Link>.
**atomListners**
This is only useful if you want to provide `hooks` like `useSession` and you want to listen to the atoms and re-evalue the atoms when the atom changes.
You can see how this is used in the built in plugins.

View File

@@ -1,54 +0,0 @@
---
title: Database
description: Learn how to use a database with BetterAuth
---
Better auth requires a database connection to store data. Better auth comes with a built in database mangment using <Link href="https://kysely.dev/"> Kysley </Link> to manage the database. The database will be used to store data such as users, sessions, and more. Plugins can also define their own database tables to store data.
You can pass a database connection to better auth by passing a `database` object to the `betterAuth` function. The `database` object should have a `provider` key that specifies the database provider to use. By default, better auth support `postgres`, `mysql` and `sqlite`. You can also pass a kysley dialect directly to use other databases supported by kysley. The `database` object can also have a `url` key that specifies the database connection to use. If you're using `sqlite`, you can pass a file path in the `url` key.
**Example: Sqlite**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = await betterAuth({
database: {
provider: "sqlite",
url: "path/to/database.sqlite"
}
})
```
**Example: Postgres**
```ts title="auth.ts"
import { betterAuth } from "better-auth
export const auth = await betterAuth({
database: {
provider: "postgres",
url: "postgres://user:password@localhost:5432/database"
}
})
```
**Exmaple: Mysql**
```ts title="auth.ts"
import { betterAuth } from "better-auth
export const auth = await betterAuth({
database: {
provider: "mysql",
url: "mysql://user:password@localhost:3306/database"
}
})
```
**Exmaple: Custom Dialect**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { LibsqlDialect } from "@libsql/kysely-libsql";
export const auth = await betterAuth({
database: new LibsqlDialect({
url: process.env.TURSO_DATABASE_URL || "",
authToken: process.env.TURSO_AUTH_TOKEN || "",
}),
})
```

View File

@@ -24,7 +24,7 @@ This plugin offers two main methods of 2FA:
<Steps>
<Step>
### Add the plugin to your auth config
### Add the plugin to your auth config
Add the two-factor plugin to your auth configuration and specify your app name as the issuer.
@@ -161,9 +161,7 @@ const verifyTotp = async (code: string) => {
### 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.
OTP is a one-time password sent to the user's email or phone.
Before using OTP, you need to setup `sendOTP` function.
```ts title="auth.ts" twoslash
@@ -179,7 +177,7 @@ export const auth = await betterAuth({
twoFactor({
otpOptions: {
async sendOTP(user, otp) {
console.log({ user, otp });
// send otp to user
},
},
})

View File

@@ -0,0 +1,96 @@
---
title: Magic link
description: Magic link plugin
---
Magic link or email link is a way to authenticate users without a password. When a user enters their email, a link is sent to their email. When the user clicks on the link, they are authenticated.
## Installation
<Steps>
<Step>
### Add the server Plugin
Add the magic link plugin to your server:
```ts title="server.ts"
import { betterAuth } from "better-auth";
import { magicLink } from "better-auth/plugin";
export const auth = await betterAuth({
database: {
provider: "sqlite",
url: "./db.sqlite",
},
plugins: [
magicLink({
sendMagicLink: async (data: {
email: string,
token: string,
url: string
}) => {
// send email to user
}
})
]
})
```
</Step>
<Step>
### Add the client Plugin
Add the magic link plugin to your client:
```ts title="client.ts"
import { createAuthClient } from "better-auth/client";
import { magicLinkClient } from "better-auth/client/plugins";
const client = createAuthClient({
plugins: [
magicLinkClient()
]
});
```
</Step>
</Steps>
## Usage
### Sign In with Magic Link
To sign in with a magic link, you need to call `signIn.magicLink` with the user's email address. The `sendMagicLink` function is called to send the magic link to the user's email.
```ts title="magic-link.ts"
const { data, error } = await client.signIn.magicLink({
email: "user@email.com",
callbackURL: "/dashboard" //redirect after successful login (optional)
});
```
### Verify Magic Link
When you send the URL generated by the `sendMagicLink` function to a user, clicking the link will authenticate them and redirect them to the `callbackURL` specified in the `signIn.magicLink` function. If an error occurs, the user will be redirected to the `callbackURL` with an error query parameter.
<Callout type="warn">
If no `callbackURL` is provided, the user will not be redirected and will see a JSON response instead. Therefore, always ensure to provide a `callbackURL` when sending the URL directly to the user.
</Callout>
If you want to handle the verification manually, (e.g, if you send the user a differnt url), you can use the `verify` function.
```ts title="magic-link.ts"
const { data, error } = await client.magicLink.verify({
query: {
token
}
});
```
## Configuration Options
**sendMagicLink**: The `sendMagicLink` function is called when a user requests a magic link. It takes an object with the following properties:
- `email`: The email address of the user.
- `url`: The url to be sent to the user. This url contains the token.
- `token`: The token if you want to send the token with custom url.
**expiresIn**: specifies the time in seconds after which the magic link will expire. The default value is `300` seconds (5 minutes).

View File

@@ -209,27 +209,4 @@ export const auth = await betterAuth({
}),
],
})
```
### Custom Rate Limiter
you can also pass a custom rate limiter that'll overtake the default plugins rate llimiting implementation.
The custom rate limiter function should accept a Request object and return a boolean value indicating whether the request should be allowed or not.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { rateLimiter } from "better-auth/plugins"
export const auth = await betterAuth({
//
plugins: [
rateLimit({
enabled: true,
rateLimiter: async (request) => { // [!code highlight]
return true; // [!code highlight]
}, // [!code highlight]
}),
],
})
```

View File

@@ -92,3 +92,8 @@ export type FieldAttributeConfig<T extends FieldType = FieldType> = {
*/
validator?: ZodSchema;
};
export type PluginFieldAttribute = Omit<
FieldAttribute,
"transform" | "defaultValue" | "hashValue"
>;

View File

@@ -7,17 +7,22 @@ import { createEmailVerificationToken } from "../../api/routes";
import { logger } from "../../utils/logger";
import { validateJWT, type JWT } from "oslo/jwt";
import { setSessionCookie } from "../../utils/cookies";
import { off } from "process";
interface MagicLinkOptions {
/**
* Time in seconds until the magic link expires.
* @default (60 * 60) // 1 hour
* @default (60 * 5) // 5 minutes
*/
expiresIn?: number;
/**
* Send magic link implementation.
*/
sendMagicLink: (email: string, url: string) => Promise<void> | void;
sendMagicLink: (data: {
email: string;
url: string;
token: string;
}) => Promise<void> | void;
}
export const magicLink = (options: MagicLinkOptions) => {
@@ -53,7 +58,11 @@ export const magicLink = (options: MagicLinkOptions) => {
ctx.body.callbackURL || ctx.body.currentURL
}`;
try {
await options.sendMagicLink(email, url);
await options.sendMagicLink({
email,
url,
token,
});
} catch (e) {
ctx.context.logger.error("Failed to send magic link", e);
throw new APIError("INTERNAL_SERVER_ERROR", {
@@ -86,6 +95,9 @@ export const magicLink = (options: MagicLinkOptions) => {
);
} catch (e) {
ctx.context.logger.error("Failed to verify email", e);
if (callbackURL) {
throw ctx.redirect(`${callbackURL}?error=INVALID_TOKEN`);
}
return ctx.json(null, {
status: 400,
statusText: "INVALID_TOKEN",
@@ -102,6 +114,9 @@ export const magicLink = (options: MagicLinkOptions) => {
parsed.email,
);
if (!user) {
if (callbackURL) {
throw ctx.redirect(`${callbackURL}?error=USER_NOT_FOUND`);
}
return ctx.json(null, {
status: 400,
statusText: "USER_NOT_FOUND",
@@ -115,6 +130,9 @@ export const magicLink = (options: MagicLinkOptions) => {
ctx.headers,
);
if (!session) {
if (callbackURL) {
throw ctx.redirect(`${callbackURL}?error=SESSION_NOT_CREATED`);
}
return ctx.json(null, {
status: 400,
statusText: "SESSION NOT CREATED",

View File

@@ -7,14 +7,15 @@ import { magicLinkClient } from "./client";
describe("magic link", async () => {
let verificationEmail = {
email: "",
token: "",
url: "",
};
const { auth, customFetchImpl, testUser, sessionSetter } =
await getTestInstance({
plugins: [
magicLink({
async sendMagicLink(email, url) {
verificationEmail = { email, url };
async sendMagicLink(data) {
verificationEmail = data;
},
}),
],

View File

@@ -117,24 +117,6 @@ export const rateLimiter = (options: RateLimitOptions) => {
],
...options,
} satisfies RateLimitOptions;
const schema =
opts.storage.provider === "database"
? ({
rateLimit: {
fields: {
key: {
type: "string",
},
count: {
type: "number",
},
lastRequest: {
type: "number",
},
},
},
} as const)
: undefined;
function createDBStorage(ctx: GenericEndpointContext) {
const db = ctx.context.db;
@@ -263,6 +245,21 @@ export const rateLimiter = (options: RateLimitOptions) => {
}),
},
],
schema,
schema: {
rateLimit: {
fields: {
key: {
type: "string",
},
count: {
type: "number",
},
lastRequest: {
type: "number",
},
},
disableMigration: opts.storage.provider !== "database",
},
},
} satisfies BetterAuthPlugin;
};

View File

@@ -28,7 +28,6 @@ export const twoFactorClient = (
},
],
pathMethods: {
"enable/totp": "POST",
"/two-factor/disable": "POST",
"/two-factor/enable": "POST",
"/two-factor/send-otp": "POST",