From f53ce02cebb36be004c5cf54a91ce497e1bb2faa Mon Sep 17 00:00:00 2001 From: Bereket Engida Date: Sat, 24 Aug 2024 23:34:12 +0300 Subject: [PATCH] feat: new client and more --- dev/next-app/package.json | 1 + dev/next-app/prisma/db.sqlite | Bin 53248 -> 53248 bytes dev/next-app/src/app/(auth)/sign-in/page.tsx | 19 ++- dev/next-app/src/app/(auth)/sign-up/page.tsx | 2 +- .../src/app/(auth)/two-factor/otp/page.tsx | 77 +++++++++ .../src/app/(auth)/two-factor/page.tsx | 15 +- dev/next-app/src/app/page.tsx | 2 +- dev/next-app/src/components/add-count.tsx | 2 +- dev/next-app/src/components/client.tsx | 79 ++++++--- dev/next-app/src/components/org.tsx | 2 +- dev/next-app/src/components/signout.tsx | 2 +- .../src/lib/{client.ts => auth-client.ts} | 2 +- dev/next-app/src/lib/auth.ts | 12 +- packages/better-auth/package.json | 3 +- packages/better-auth/src/adapters/schema.ts | 52 ++++++ packages/better-auth/src/api/index.ts | 22 ++- .../src/api/routes/confirm-email.ts | 14 ++ .../src/api/routes/forget-password.ts | 46 ++++++ packages/better-auth/src/api/routes/signin.ts | 2 +- packages/better-auth/src/client/base.ts | 153 +++++------------- .../better-auth/src/client/client-utils.ts | 43 +++-- .../better-auth/src/client/client.test.ts | 27 ++++ packages/better-auth/src/client/index.ts | 82 +--------- .../better-auth/src/client/path-to-object.ts | 46 ++++++ packages/better-auth/src/client/preact.ts | 21 +++ packages/better-auth/src/client/proxy.ts | 115 +++++-------- packages/better-auth/src/client/react.ts | 24 +++ .../better-auth/src/client/session-atom.ts | 10 +- packages/better-auth/src/client/solid.ts | 2 - packages/better-auth/src/client/type.ts | 97 +---------- packages/better-auth/src/client/vue.ts | 21 +++ .../plugins/two-factor/backup-codes/index.ts | 56 +++---- .../src/plugins/two-factor/index.ts | 67 +------- .../src/plugins/two-factor/otp/index.ts | 41 ++--- .../src/plugins/two-factor/totp/index.ts | 124 ++------------ ...ify-middleware.ts => two-fa-middleware.ts} | 23 ++- .../src/plugins/two-factor/types.ts | 9 +- packages/better-auth/src/providers/passkey.ts | 3 + .../src/test-utils/test-instance.ts | 14 +- packages/better-auth/src/types/options.ts | 6 - packages/better-auth/src/utils/cookies.ts | 2 +- packages/better-auth/tsup.config.ts | 2 +- pnpm-lock.yaml | 51 +++++- todo.md | 2 + 44 files changed, 730 insertions(+), 665 deletions(-) create mode 100644 dev/next-app/src/app/(auth)/two-factor/otp/page.tsx rename dev/next-app/src/lib/{client.ts => auth-client.ts} (71%) create mode 100644 packages/better-auth/src/api/routes/confirm-email.ts create mode 100644 packages/better-auth/src/api/routes/forget-password.ts create mode 100644 packages/better-auth/src/client/client.test.ts create mode 100644 packages/better-auth/src/client/path-to-object.ts delete mode 100644 packages/better-auth/src/client/solid.ts rename packages/better-auth/src/plugins/two-factor/{verify-middleware.ts => two-fa-middleware.ts} (88%) create mode 100644 todo.md diff --git a/dev/next-app/package.json b/dev/next-app/package.json index 0b2e705a10..efd64c2bbf 100644 --- a/dev/next-app/package.json +++ b/dev/next-app/package.json @@ -32,6 +32,7 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.52.2", + "react-qr-code": "^2.0.15", "sonner": "^1.5.0", "tailwind-merge": "^2.5.0", "tailwindcss-animate": "^1.0.7", diff --git a/dev/next-app/prisma/db.sqlite b/dev/next-app/prisma/db.sqlite index 5f659c0a8cdd07f211b7b02275b6b95835e81146..4bfc5a953d123f112a246b606a3d493e2229366a 100644 GIT binary patch literal 53248 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 diff --git a/dev/next-app/src/app/(auth)/sign-in/page.tsx b/dev/next-app/src/app/(auth)/sign-in/page.tsx index e97846a4df..051eff804c 100644 --- a/dev/next-app/src/app/(auth)/sign-in/page.tsx +++ b/dev/next-app/src/app/(auth)/sign-in/page.tsx @@ -11,8 +11,9 @@ import { } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { authClient } from "@/lib/client"; +import { authClient } from "@/lib/auth-client"; import { useState } from "react"; +import { Key } from "lucide-react"; export default function Page() { const [email, setEmail] = useState(""); @@ -59,7 +60,7 @@ export default function Page() { /> +
Don't have an account?{" "} diff --git a/dev/next-app/src/app/(auth)/sign-up/page.tsx b/dev/next-app/src/app/(auth)/sign-up/page.tsx index 360f1e3fce..ac9ee8b36e 100644 --- a/dev/next-app/src/app/(auth)/sign-up/page.tsx +++ b/dev/next-app/src/app/(auth)/sign-up/page.tsx @@ -13,7 +13,7 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useState } from "react"; -import { authClient } from "@/lib/client"; +import { authClient } from "@/lib/auth-client"; export default function SignUpForm() { const [firstName, setFirstName] = useState(""); diff --git a/dev/next-app/src/app/(auth)/two-factor/otp/page.tsx b/dev/next-app/src/app/(auth)/two-factor/otp/page.tsx new file mode 100644 index 0000000000..0a2815454f --- /dev/null +++ b/dev/next-app/src/app/(auth)/two-factor/otp/page.tsx @@ -0,0 +1,77 @@ +'use client' + +import { useState } from 'react' +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card" +import { AlertCircle, CheckCircle2, Mail } from "lucide-react" +import { authClient } from '@/lib/auth-client' + +export default function Component() { + const [otp, setOtp] = useState('') + const [isOtpSent, setIsOtpSent] = useState(false) + const [message, setMessage] = useState('') + const [isError, setIsError] = useState(false) + const [isValidated, setIsValidated] = useState(false) + + // In a real app, this email would come from your authentication context + const userEmail = "user@example.com" + + const requestOTP = async () => { + await authClient.twoFactor.sendOtp(); + // In a real app, this would call your backend API to send the OTP + setMessage('OTP sent to your email') + setIsError(false) + setIsOtpSent(true) + } + + const validateOTP = async () => { + await authClient.twoFactor.verifyOtp({ + body: { + code: otp, + } + }) + } + return ( +
+ + + Two-Factor Authentication + Verify your identity with a one-time password + + +
+ {!isOtpSent ? ( + + ) : ( + <> +
+ + setOtp(e.target.value)} + maxLength={6} + /> +
+ + + )} +
+ {message && ( +
+ {isError ? : } +

{message}

+
+ )} +
+
+
+ ) +} \ No newline at end of file diff --git a/dev/next-app/src/app/(auth)/two-factor/page.tsx b/dev/next-app/src/app/(auth)/two-factor/page.tsx index ef5705a06c..0486cc93f6 100644 --- a/dev/next-app/src/app/(auth)/two-factor/page.tsx +++ b/dev/next-app/src/app/(auth)/two-factor/page.tsx @@ -6,7 +6,8 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { AlertCircle, CheckCircle2 } from "lucide-react" -import { authClient } from "@/lib/client" +import { authClient } from "@/lib/auth-client" +import Link from "next/link" export default function Component() { const [totpCode, setTotpCode] = useState("") @@ -19,10 +20,10 @@ export default function Component() { setError("TOTP code must be 6 digits") return } - authClient.verifyTotp({ + authClient.twoFactor.verify({ body: { code: totpCode, - callbackURL: "/" + with: "totp", } }).then((res) => { console.log(res) @@ -78,8 +79,12 @@ export default function Component() {
)} - - Protect your account with TOTP-based authentication + + + + diff --git a/dev/next-app/src/app/page.tsx b/dev/next-app/src/app/page.tsx index ec9a66e418..a9514f198c 100644 --- a/dev/next-app/src/app/page.tsx +++ b/dev/next-app/src/app/page.tsx @@ -28,7 +28,7 @@ export default async function Home() { )} - + {/* */} ); diff --git a/dev/next-app/src/components/add-count.tsx b/dev/next-app/src/components/add-count.tsx index dc36a18060..1862519155 100644 --- a/dev/next-app/src/components/add-count.tsx +++ b/dev/next-app/src/components/add-count.tsx @@ -1,7 +1,7 @@ "use client"; import { setCounter } from "@/server/counter"; import { Button } from "./ui/button"; -import { client } from "@/lib/client"; +import { client } from "@/lib/auth-client"; export async function AddCount() { return ( diff --git a/dev/next-app/src/components/client.tsx b/dev/next-app/src/components/client.tsx index 5692265b72..1c42185310 100644 --- a/dev/next-app/src/components/client.tsx +++ b/dev/next-app/src/components/client.tsx @@ -1,10 +1,15 @@ "use client"; -import { authClient } from "@/lib/client"; +import { authClient } from "@/lib/auth-client"; import { useAuthStore } from "better-auth/react" import { Button } from "./ui/button"; +import QRCode from "react-qr-code"; +import { useEffect, useState } from "react"; +import { Card, CardContent, CardHeader } from "./ui/card"; +import { Dialog, DialogContent, DialogTrigger } from "./ui/dialog"; export function Client() { - const session = useAuthStore(authClient.$session) + const [uri, setUri] = useState() + const session = authClient.useSession() type S = NonNullable const a: S['user'] = { id: "1", @@ -14,23 +19,59 @@ export function Client() { createdAt: new Date(), updatedAt: new Date(), } + + useEffect(() => { + if (session?.user?.twoFactorEnabled) { + authClient.twoFactor.getTotpUri().then((res) => { + if (res.data) { + setUri(res.data.totpURI) + } + }) + } + }, [session]) + return ( -
- { - session ?
- -
: null - } -
+ + + + + + + { + session ?
+ +
: null + } + { + uri ? + + + + + { + uri ?
+

+ URI to scan +

+ +
: null + } +
+
: null + } +
+
) } \ No newline at end of file diff --git a/dev/next-app/src/components/org.tsx b/dev/next-app/src/components/org.tsx index 7bdfaaa060..557e7432b6 100644 --- a/dev/next-app/src/components/org.tsx +++ b/dev/next-app/src/components/org.tsx @@ -26,7 +26,7 @@ import { TableHeader, TableRow, } from "./ui/table"; -import { authClient } from "@/lib/client"; +import { authClient } from "@/lib/auth-client"; import { useAuthStore } from "better-auth/react"; export const Organization = () => { diff --git a/dev/next-app/src/components/signout.tsx b/dev/next-app/src/components/signout.tsx index 5f73191e69..9c5a576f24 100644 --- a/dev/next-app/src/components/signout.tsx +++ b/dev/next-app/src/components/signout.tsx @@ -1,6 +1,6 @@ "use client"; -import { authClient } from "@/lib/client"; +import { authClient } from "@/lib/auth-client"; import { Button } from "./ui/button"; export const SignOut = () => { diff --git a/dev/next-app/src/lib/client.ts b/dev/next-app/src/lib/auth-client.ts similarity index 71% rename from dev/next-app/src/lib/client.ts rename to dev/next-app/src/lib/auth-client.ts index ee1f84fe3b..e111bb5ea3 100644 --- a/dev/next-app/src/lib/client.ts +++ b/dev/next-app/src/lib/auth-client.ts @@ -1,4 +1,4 @@ -import { createAuthClient } from "better-auth/client"; +import { createAuthClient } from "better-auth/react"; import { auth } from "./auth"; export const authClient = createAuthClient({ diff --git a/dev/next-app/src/lib/auth.ts b/dev/next-app/src/lib/auth.ts index 7fe349049a..f59c674cd4 100644 --- a/dev/next-app/src/lib/auth.ts +++ b/dev/next-app/src/lib/auth.ts @@ -1,6 +1,6 @@ import { betterAuth } from "better-auth"; import { github, passkey } from "better-auth/provider"; -import { twoFactor } from "better-auth/plugins"; +import { organization, twoFactor } from "better-auth/plugins"; export const auth = betterAuth({ basePath: "/api/auth", @@ -9,10 +9,6 @@ export const auth = betterAuth({ clientId: process.env.GITHUB_CLIENT_ID as string, clientSecret: process.env.GITHUB_CLIENT_SECRET as string, }), - passkey({ - rpID: "localhost", - rpName: "Better Auth", - }), ], database: { provider: "sqlite", @@ -23,9 +19,15 @@ export const auth = betterAuth({ enabled: true, }, plugins: [ + organization(), twoFactor({ issuer: "BetterAuth", twoFactorURL: "/two-factor", + otpOptions: { + async sendOTP(user, otp) { + console.log({ user, otp }); + }, + }, }), ], }); diff --git a/packages/better-auth/package.json b/packages/better-auth/package.json index 3a8381cf9c..1660ae3f10 100644 --- a/packages/better-auth/package.json +++ b/packages/better-auth/package.json @@ -52,12 +52,13 @@ "@simplewebauthn/browser": "^10.0.0", "@simplewebauthn/server": "^10.0.1", "arctic": "^1.9.2", - "better-call": "^0.1.28", + "better-call": "^0.1.33", "chalk": "^5.3.0", "commander": "^12.1.0", "consola": "^3.2.3", "dotenv": "^16.4.5", "jiti": "^1.21.6", + "jose": "^5.7.0", "kysely": "^0.27.4", "mysql2": "^3.11.0", "nanostores": "^0.11.2", diff --git a/packages/better-auth/src/adapters/schema.ts b/packages/better-auth/src/adapters/schema.ts index b9f8657f2b..ca025a6739 100644 --- a/packages/better-auth/src/adapters/schema.ts +++ b/packages/better-auth/src/adapters/schema.ts @@ -1,4 +1,6 @@ import { z } from "zod"; +import { BetterAuthOptions } from "../types"; +import { FieldAttribute } from "../db"; export const accountSchema = z.object({ id: z.string(), @@ -39,3 +41,53 @@ export interface MigrationTable { name: string; timestamp: string; } + +export function parseData>( + data: T, + schema: { + fields: Record; + }, +) { + const fields = schema.fields; + const parsedData: Record = {}; + for (const key in data) { + const field = fields[key]; + if (!field) { + parsedData[key] = data[key]; + continue; + } + if (field.returned === false) { + continue; + } + parsedData[key] = data[key]; + } + return parsedData as T; +} + +export function getAllFields(options: BetterAuthOptions, table: string) { + let schema: Record = {}; + for (const plugin of options.plugins || []) { + if (plugin.schema && plugin.schema[table]) { + schema = { + ...schema, + ...plugin.schema[table].fields, + }; + } + } + return schema; +} + +export function parseUser(options: BetterAuthOptions, user: User) { + const schema = getAllFields(options, "user"); + return parseData(user, { fields: schema }); +} + +export function parseAccount(options: BetterAuthOptions, account: Account) { + const schema = getAllFields(options, "account"); + return parseData(account, { fields: schema }); +} + +export function parseSession(options: BetterAuthOptions, session: Session) { + const schema = getAllFields(options, "session"); + return parseData(session, { fields: schema }); +} diff --git a/packages/better-auth/src/api/index.ts b/packages/better-auth/src/api/index.ts index c835612762..a191dcd8f2 100644 --- a/packages/better-auth/src/api/index.ts +++ b/packages/better-auth/src/api/index.ts @@ -10,6 +10,7 @@ import { AuthContext } from "../init"; import { csrfMiddleware } from "./middlewares/csrf"; import { getCSRFToken } from "./routes/csrf"; import { signUpCredential } from "./routes/signup"; +import { parseAccount, parseSession, parseUser } from "../adapters/schema"; export const router = (ctx: C) => { const pluginEndpoints = ctx.options.plugins?.reduce( @@ -101,8 +102,25 @@ export const router = (ctx: C) => { }, ...middlewares, ], - onError(e) { - ctx.logger.error(e); + /** + * this is to remove any sensitive data from the response + */ + async transformResponse(res) { + const body = await res.json(); + if (body?.user) { + body.user = parseUser(ctx.options, body.user); + } + if (body?.session) { + body.session = parseSession(ctx.options, body.session); + } + if (body?.account) { + body.account = parseAccount(ctx.options, body.account); + } + return new Response(body ? JSON.stringify(body) : null, { + headers: res.headers, + status: res.status, + statusText: res.statusText, + }); }, }); }; diff --git a/packages/better-auth/src/api/routes/confirm-email.ts b/packages/better-auth/src/api/routes/confirm-email.ts new file mode 100644 index 0000000000..8f6b208fbe --- /dev/null +++ b/packages/better-auth/src/api/routes/confirm-email.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; +import { createAuthEndpoint } from "../call"; + +export const confirmEmail = createAuthEndpoint( + "/confirm-email", + { + method: "POST", + body: z.object({ + token: z.string(), + email: z.string().email(), + }), + }, + async (ctx) => {}, +); diff --git a/packages/better-auth/src/api/routes/forget-password.ts b/packages/better-auth/src/api/routes/forget-password.ts new file mode 100644 index 0000000000..ffd2a3cd7b --- /dev/null +++ b/packages/better-auth/src/api/routes/forget-password.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; +import { createAuthEndpoint } from "../call"; +import { createJWT } from "oslo/jwt"; +import { TimeSpan } from "oslo"; + +export const forgetPassword = createAuthEndpoint( + "/send-forget-password", + { + method: "POST", + body: z.object({ + email: z.string().email(), + }), + }, + async (ctx) => { + const { email } = ctx.body; + const user = await ctx.context.internalAdapter.findUserByEmail(email); + if (!user) { + return ctx.json( + { + error: "User not found", + }, + { + status: 400, + statusText: "USER_NOT_FOUND", + body: { + message: "User not found", + }, + }, + ); + } + const token = await createJWT( + "HS256", + Buffer.from(ctx.context.secret), + { + email: user.user.email, + }, + { + expiresIn: new TimeSpan(1, "h"), + issuer: "better-auth", + subject: "forget-password", + audiences: [user.user.email], + includeIssuedTimestamp: true, + }, + ); + }, +); diff --git a/packages/better-auth/src/api/routes/signin.ts b/packages/better-auth/src/api/routes/signin.ts index b36964a9d0..5c37135557 100644 --- a/packages/better-auth/src/api/routes/signin.ts +++ b/packages/better-auth/src/api/routes/signin.ts @@ -7,7 +7,7 @@ import { oAuthProviderList } from "../../providers"; import { Argon2id } from "oslo/password"; export const signInOAuth = createAuthEndpoint( - "/signin/oauth", + "/sign-in/oauth", { method: "POST", query: z diff --git a/packages/better-auth/src/client/base.ts b/packages/better-auth/src/client/base.ts index d41f176e40..03970509e7 100644 --- a/packages/better-auth/src/client/base.ts +++ b/packages/better-auth/src/client/base.ts @@ -1,125 +1,44 @@ -import { Endpoint, Prettify } from "better-call"; import { BetterAuth } from "../auth"; -import { HasRequiredKeys, UnionToIntersection } from "type-fest"; -import { - BetterFetchOption, - BetterFetchPlugin, - BetterFetchResponse, - createFetch, -} from "@better-fetch/fetch"; -import { BetterAuthError } from "../error/better-auth-error"; +import { createDynamicPathProxy } from "./proxy"; +import { createClient } from "better-call/client"; +import { BetterFetch, createFetch } from "@better-fetch/fetch"; +import { getSessionAtom } from "./session-atom"; +import { getOrganizationAtoms } from "./org-atoms"; +import { getPasskeyActions } from "./passkey-actions"; +import { addCurrentURL, csrfPlugin, redirectPlugin } from "./client-plugins"; +import { InferRoutes } from "./path-to-object"; +import { ClientOptions } from "./type"; +import { getBaseURL } from "./client-utils"; -type InferContext = T extends (ctx: infer Ctx) => any - ? Ctx extends - | { - body: infer Body; - } - | { - params: infer Param; - } - ? (Body extends undefined - ? {} - : { - body: Body; - }) & - (Param extends undefined - ? {} - : { - params: Param; - }) - : never - : never; - -export interface ClientOptions extends BetterFetchOption {} - -const redirectPlugin = { - id: "redirect", - name: "Redirect", - hooks: { - onSuccess(context) { - if (context.data.url && context.data.redirect) { - console.log("redirecting to", context.data.url); - } - }, - }, -} satisfies BetterFetchPlugin; - -function inferBaeURL() { - const url = - process.env.AUTH_URL || - process.env.NEXT_PUBLIC_AUTH_URL || - process.env.BETTER_AUTH_URL || - process.env.NEXT_PUBLIC_BETTER_AUTH_URL || - process.env.VERCEL_URL || - process.env.NEXT_PUBLIC_VERCEL_URL; - if (url) { - return url; - } - if ( - !url && - (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") - ) { - return "http://localhost:3000"; - } - throw new BetterAuthError( - "Could not infer baseURL from environment variables. Please pass it as an option to the createClient function.", - ); -} - -export const createBaseClient = ( +export const createVanillaClient = ( options?: ClientOptions, ) => { - const fetch = createFetch({ - baseURL: options?.baseURL || inferBaeURL(), + type API = Auth extends never ? BetterAuth["api"] : Auth["api"]; + const $fetch = createFetch({ ...options, - plugins: [...(options?.plugins || []), redirectPlugin], + baseURL: getBaseURL(options?.baseURL), + plugins: [redirectPlugin, addCurrentURL, csrfPlugin], }); - - type API = Auth["api"]; - type Options = API extends { - [key: string]: infer T; - } - ? T extends Endpoint - ? { - [key in T["path"]]: T; - } - : {} - : {}; - - type O = Prettify>; - return async ( - path: K, - ...options: HasRequiredKeys> extends true - ? [ - BetterFetchOption< - InferContext["body"], - any, - InferContext["params"] - >, - ] - : [ - BetterFetchOption< - InferContext["body"], - InferContext["params"] - >?, - ] - ): Promise< - BetterFetchResponse< - Awaited> - > - > => { - const opts = options[0] as { - params?: Record; - body?: Record; - }; - return (await fetch(path as string, { - ...options[0], - body: opts.body, - params: opts.params, - method: opts.body ? "POST" : "GET", - onRequest(context) { - console.log("request", context.url); - }, - })) as any; + const { $session, $sessionSignal } = getSessionAtom($fetch); + const { signInPasskey, signUpPasskey } = getPasskeyActions($fetch); + const { $activeOrganization, $listOrganizations, activeOrgId, $listOrg } = + getOrganizationAtoms($fetch, $session); + const actions = { + setActiveOrg: (orgId: string | null) => { + activeOrgId.set(orgId); + }, + signInPasskey, + signUpPasskey, + $atoms: { + $session, + $activeOrganization, + $listOrganizations, + }, }; + const proxy = createDynamicPathProxy(actions, $fetch, { + "/create/organization": $listOrg, + "/two-factor/enable": $sessionSignal, + "/two-factor/disable": $sessionSignal, + }) as unknown as InferRoutes & typeof actions; + return proxy; }; diff --git a/packages/better-auth/src/client/client-utils.ts b/packages/better-auth/src/client/client-utils.ts index b45856d5ea..d439bd8173 100644 --- a/packages/better-auth/src/client/client-utils.ts +++ b/packages/better-auth/src/client/client-utils.ts @@ -1,23 +1,44 @@ -import { BetterAuthError } from "../error/better-auth-error"; +export const HIDE_ON_CLIENT_METADATA = { + onClient: "hide" as const, +}; -export function inferBaeURL() { - const url = +function checkHasPath(url: string): boolean { + try { + const parsedUrl = new URL(url); + return parsedUrl.pathname !== "/"; + } catch (error) { + console.error("Invalid URL:", error); + return false; + } +} + +function withPath(url: string) { + const hasPath = checkHasPath(url); + if (hasPath) { + return url; + } + return `${url}/api/auth`; +} + +export function getBaseURL(url?: string) { + if (url) { + return withPath(url); + } + const fromEnv = process.env.AUTH_URL || process.env.NEXT_PUBLIC_AUTH_URL || process.env.BETTER_AUTH_URL || - process.env.NEXT_PUBLIC_BETTER_AUTH_URL || - process.env.VERCEL_URL || - process.env.NEXT_PUBLIC_VERCEL_URL; - if (url) { - return url; + process.env.NEXT_PUBLIC_BETTER_AUTH_URL; + if (fromEnv) { + return withPath(fromEnv); } if ( - !url && + !fromEnv && (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") ) { - return "http://localhost:3000"; + return "http://localhost:3000/api/auth"; } - throw new BetterAuthError( + throw new Error( "Could not infer baseURL from environment variables. Please pass it as an option to the createClient function.", ); } diff --git a/packages/better-auth/src/client/client.test.ts b/packages/better-auth/src/client/client.test.ts new file mode 100644 index 0000000000..5b346b7b47 --- /dev/null +++ b/packages/better-auth/src/client/client.test.ts @@ -0,0 +1,27 @@ +import { describe, it } from "vitest"; +import { getTestInstance } from "../test-utils/test-instance"; +import { createAuthClient } from "./react"; +import { passkey } from "../providers"; +import { createClient } from "better-call/client"; + +describe("client path to object", async () => { + const auth = await getTestInstance({ + providers: [ + passkey({ + rpID: "test", + rpName: "test", + }), + ], + }); + + it("should return a path to object", async () => { + const client = createAuthClient({ + baseURL: "http://localhost:3000/api/auth", + customFetchImpl: async (url, options) => { + console.log({ url, options }); + return new Response(); + }, + }); + console.log(client.$atoms.$session.get()); + }); +}); diff --git a/packages/better-auth/src/client/index.ts b/packages/better-auth/src/client/index.ts index 056a3b9afd..955fdd1439 100644 --- a/packages/better-auth/src/client/index.ts +++ b/packages/better-auth/src/client/index.ts @@ -1,81 +1 @@ -import { ClientOptions } from "./base"; -import { BetterAuth } from "../auth"; -import { - InferredActions, - PickDefaultPaths, - PickOrganizationPaths, - PickProvidePaths, -} from "./type"; -import { getProxy } from "./proxy"; -import { createClient } from "better-call/client"; -import { BetterFetch, createFetch } from "@better-fetch/fetch"; -import { OAuthProvider, OAuthProviderList } from "../types/provider"; -import { getSessionAtom } from "./session-atom"; -import { getOrganizationAtoms } from "./org-atoms"; -import { getPasskeyActions } from "./passkey-actions"; -import { inferBaeURL } from "./client-utils"; -import { addCurrentURL, csrfPlugin, redirectPlugin } from "./client-plugins"; - -export const createAuthClient = ( - options?: ClientOptions, -) => { - type API = BetterAuth["api"]; - - const client = createClient({ - ...options, - baseURL: options?.baseURL || inferBaeURL(), - plugins: [redirectPlugin, addCurrentURL, csrfPlugin], - }); - - const $fetch = createFetch({ - ...options, - baseURL: options?.baseURL || inferBaeURL(), - plugins: [redirectPlugin, addCurrentURL, csrfPlugin], - }); - - const signInOAuth = async (data: { - provider: Auth["options"]["providers"] extends Array - ? T extends OAuthProvider - ? T["id"] - : never - : OAuthProviderList[number]; - callbackURL: string; - }) => { - const res = await client("@post/signin/oauth", { - body: data, - }); - if (res.data?.redirect) { - window.location.href = res.data.url; - } - return res; - }; - - const { $session } = getSessionAtom($fetch); - const { signInPasskey, signUpPasskey } = getPasskeyActions($fetch); - const { $activeOrganization, $listOrganizations, activeOrgId, $listOrg } = - getOrganizationAtoms($fetch, $session); - - const actions = { - signInOAuth, - $session, - $activeOrganization, - $listOrganizations, - setActiveOrg: (orgId: string | null) => { - activeOrgId.set(orgId); - }, - signInPasskey, - signUpPasskey, - }; - - type PickedActions = Pick< - typeof actions, - | PickOrganizationPaths - | PickDefaultPaths - | PickProvidePaths<"passkey", "signInPasskey" | "signUpPasskey", Auth> - >; - - const proxy = getProxy(actions, client as BetterFetch, { - "create/organization": $listOrg, - }) as InferredActions & PickedActions; - return proxy; -}; +export * from "./base"; diff --git a/packages/better-auth/src/client/path-to-object.ts b/packages/better-auth/src/client/path-to-object.ts new file mode 100644 index 0000000000..2f311dc680 --- /dev/null +++ b/packages/better-auth/src/client/path-to-object.ts @@ -0,0 +1,46 @@ +import { BetterFetchResponse } from "@better-fetch/fetch"; +import { Context, Endpoint } from "better-call"; +import { + HasRequiredKeys, + Prettify, + UnionToIntersection, +} from "../types/helper"; + +type CamelCase = + S extends `${infer P1}-${infer P2}${infer P3}` + ? `${Lowercase}${Uppercase}${CamelCase}` + : Lowercase; + +export type PathToObject< + T extends string, + Fn extends (...args: any[]) => any, +> = T extends `/${infer Segment}/${infer Rest}` + ? { [K in CamelCase]: PathToObject<`/${Rest}`, Fn> } + : T extends `/${infer Segment}` + ? { [K in CamelCase]: Fn } + : never; + +type MergeRoutes = UnionToIntersection; +type InferRoute = API extends { + [key: string]: infer T; +} + ? T extends Endpoint + ? T["options"]["metadata"] extends { + onClient: "hide"; + } + ? {} + : PathToObject< + T["path"], + T extends (ctx: infer C) => infer R + ? C extends Context + ? ( + ...data: HasRequiredKeys extends true + ? [Prettify] + : [Prettify?] + ) => Promise>> + : never + : never + > + : never + : never; +export type InferRoutes = MergeRoutes>; diff --git a/packages/better-auth/src/client/preact.ts b/packages/better-auth/src/client/preact.ts index 00ce895ff7..8f992098e6 100644 --- a/packages/better-auth/src/client/preact.ts +++ b/packages/better-auth/src/client/preact.ts @@ -1,2 +1,23 @@ import { useStore } from "@nanostores/react"; export const useAuthStore = useStore; + +import { createVanillaClient } from "./base"; +import { BetterFetchOption } from "@better-fetch/fetch"; + +export const createAuthClient = (options?: BetterFetchOption) => { + const client = createVanillaClient(options); + function useSession() { + return useStore(client.$atoms.$session); + } + function useActiveOrganization() { + return useStore(client.$atoms.$activeOrganization); + } + function useListOrganization() { + return useStore(client.$atoms.$listOrganizations); + } + return Object.assign(client, { + useSession, + useActiveOrganization, + useListOrganization, + }); +}; diff --git a/packages/better-auth/src/client/proxy.ts b/packages/better-auth/src/client/proxy.ts index 9a5720b911..5f853dbe07 100644 --- a/packages/better-auth/src/client/proxy.ts +++ b/packages/better-auth/src/client/proxy.ts @@ -1,53 +1,12 @@ import { BetterFetch, BetterFetchOption } from "@better-fetch/fetch"; -import { createBaseClient } from "./base"; -import { Atom, PreinitializedWritableAtom } from "nanostores"; - -function fromCamelCase(str: string) { - const path = str - .split(/(?=[A-Z])/) - .join("/") - .toLowerCase(); - return `/${path}`; -} - -const knownCases = [ - ["sign", "in"], - ["sign", "up"], - ["sign", "out"], - ["invite", "member"], - ["update", "member"], - ["delete", "member"], - ["accept", "invitation"], - ["reject", "invitation"], - ["cancel", "invitation"], - ["has", "permission"], -]; - -/** - * Handles edge cases like signInCredential and - * signUpCredential - */ -function handleEdgeCases(str: string) { - const splits = str.split("/").filter((s) => s); - let index = 0; - for (const path of splits) { - const secondPath = splits[index + 1]?.trim(); - if (secondPath) { - const isKnownCase = knownCases.some( - ([a, b]) => a === path && b === secondPath, - ); - if (isKnownCase) { - splits[index] = `${path}-${secondPath}`; - splits.splice(1, index + 1); - } - } - } - return splits.join("/"); -} +import { PreinitializedWritableAtom } from "nanostores"; const knownPathMethods: Record = { "/sign-out": "POST", "enable/totp": "POST", + "/two-factor/disable": "POST", + "/two-factor/enable": "POST", + "/two-factor/send-otp": "POST", }; function getMethod(path: string, args?: BetterFetchOption) { @@ -61,40 +20,48 @@ function getMethod(path: string, args?: BetterFetchOption) { return "GET"; } -export function getProxy( - actions: Record, +export function createDynamicPathProxy>( + routes: T, client: BetterFetch, $signal?: { [key: string]: PreinitializedWritableAtom; }, -) { - return new Proxy(actions, { - get(target, key) { - if (key in target) { - return target[key as keyof typeof actions]; +): T { + const handler: ProxyHandler = { + get(target, prop: string) { + // If the property exists in the initial object, return it directly + if (prop in routes) { + return routes[prop as string]; } - return (args?: BetterFetchOption) => { - key = fromCamelCase(key as string); - key = handleEdgeCases(key); - if (args?.params) { - const paramPlaceholder = Object.keys(args?.params) - .map((key) => `:${key}`) + return new Proxy(() => {}, { + get: (_, nestedProp: string) => { + //@ts-expect-error + return handler.get(target, `${prop}.${nestedProp}`); + }, + apply: async (_, __, args) => { + if (prop in target) { + return target[prop](...args); + } + const path = prop + .split(".") + .map((segment) => + segment.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`), + ) .join("/"); - key = paramPlaceholder.length - ? `${key as string}/${paramPlaceholder}` - : key; - } - return client(key as "/signin/oauth", { - ...(args || {}), - method: getMethod(key, args), - onSuccess() { - const signal = $signal?.[key as string]; - if (signal) { - signal.set(!signal.get()); - } - }, - }); - }; + const routePath = `/${path}`; + return await client(routePath, { + ...args[0], + method: getMethod(routePath, args[0]), + onSuccess() { + const signal = $signal?.[routePath as string]; + if (signal) { + signal.set(!signal.get()); + } + }, + }); + }, + }); }, - }); + }; + return new Proxy(routes, handler); } diff --git a/packages/better-auth/src/client/react.ts b/packages/better-auth/src/client/react.ts index 00ce895ff7..18a96f9001 100644 --- a/packages/better-auth/src/client/react.ts +++ b/packages/better-auth/src/client/react.ts @@ -1,2 +1,26 @@ import { useStore } from "@nanostores/react"; +import { createVanillaClient } from "./base"; +import { BetterFetchOption } from "@better-fetch/fetch"; +import { BetterAuth } from "../auth"; + +export const createAuthClient = ( + options?: BetterFetchOption, +) => { + const client = createVanillaClient(options); + function useSession() { + return useStore(client.$atoms.$session); + } + function useActiveOrganization() { + return useStore(client.$atoms.$activeOrganization); + } + function useListOrganization() { + return useStore(client.$atoms.$listOrganizations); + } + return Object.assign(client, { + useSession, + useActiveOrganization, + useListOrganization, + }); +}; + export const useAuthStore = useStore; diff --git a/packages/better-auth/src/client/session-atom.ts b/packages/better-auth/src/client/session-atom.ts index ceb99cb9cf..3a9a27e5bb 100644 --- a/packages/better-auth/src/client/session-atom.ts +++ b/packages/better-auth/src/client/session-atom.ts @@ -1,6 +1,6 @@ import { atom, computed, task } from "nanostores"; import { Session, User } from "../adapters/schema"; -import { Prettify } from "../types/helper"; +import { Prettify, UnionToIntersection } from "../types/helper"; import { BetterAuth } from "../auth"; import { FieldAttribute, InferFieldOutput } from "../db"; import { BetterFetch } from "@better-fetch/fetch"; @@ -54,8 +54,10 @@ export function getSessionAtom(client: BetterFetch) { : {} : {}; - type UserWithAdditionalFields = User & AdditionalUserFields; - type SessionWithAdditionalFields = Session & AdditionalSessionFields; + type UserWithAdditionalFields = User & + UnionToIntersection; + type SessionWithAdditionalFields = Session & + UnionToIntersection; const $signal = atom(false); const $session = computed($signal, () => @@ -70,5 +72,5 @@ export function getSessionAtom(client: BetterFetch) { } | null; }), ); - return { $session }; + return { $session, $sessionSignal: $signal }; } diff --git a/packages/better-auth/src/client/solid.ts b/packages/better-auth/src/client/solid.ts deleted file mode 100644 index 9b1b9055c9..0000000000 --- a/packages/better-auth/src/client/solid.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { useStore } from "@nanostores/solid"; -export const useAuthStore = useStore; diff --git a/packages/better-auth/src/client/type.ts b/packages/better-auth/src/client/type.ts index 4f45a65232..001dccdba0 100644 --- a/packages/better-auth/src/client/type.ts +++ b/packages/better-auth/src/client/type.ts @@ -1,67 +1,11 @@ -import { Context, Endpoint } from "better-call"; -import { CamelCase } from "type-fest"; -import { - Prettify, - HasRequiredKeys, - UnionToIntersection, -} from "../types/helper"; -import { BetterFetchResponse } from "@better-fetch/fetch"; +import { UnionToIntersection } from "../types/helper"; import { BetterAuth } from "../auth"; import { CustomProvider } from "../providers"; +import { BetterFetchOption } from "@better-fetch/fetch"; +import type { useAuthStore as reactStore } from "./react"; +import type { useAuthStore as vueStore } from "./vue"; -export type InferKeys = T extends `/${infer A}/${infer B}` - ? CamelCase<`${A}-${InferKeys}`> - : T extends `${infer I}/:${infer _}` - ? I - : T extends `${infer I}:${infer _}` - ? I - : T extends `/${infer I}` - ? CamelCase - : CamelCase; - -export type InferActions = Actions extends { - [key: string]: infer T; -} - ? UnionToIntersection< - T extends Endpoint - ? { - [key in InferKeys]: T extends (ctx: infer C) => infer R - ? C extends Context - ? ( - ...data: HasRequiredKeys extends true - ? [Prettify] - : [Prettify?] - ) => Promise>> - : never - : never; - } - : never - > - : never; - -export type ExcludeCredentialPaths = - Auth["options"]["emailAndPassword"] extends { - enabled: true; - } - ? "" - : "signUpCredential" | "signInCredential"; - -export type ExcludedPasskeyPaths = - | "passkeyGenerateAuthenticateOptions" - | "passkeyGenerateRegisterOptions" - | "verifyPasskey"; - -export type ExcludedPaths = - | "signinOauth" - | "signUpOauth" - | "callback" - | "session" - | ExcludeCredentialPaths - | ExcludedPasskeyPaths; - -export type OrganizationPaths = "$activeOrganization" | "setActiveOrg"; - -type ProviderEndpoint = UnionToIntersection< +export type ProviderEndpoint = UnionToIntersection< Auth["options"]["providers"] extends Array ? T extends CustomProvider ? T["endpoints"] @@ -69,32 +13,5 @@ type ProviderEndpoint = UnionToIntersection< : {} >; -export type Actions = ProviderEndpoint & - Auth["api"]; - -export type InferredActions = Prettify< - Omit>, ExcludedPaths> ->; - -export type PickOrganizationPaths = - Auth["options"]["plugins"] extends Array - ? T extends { - id: "organization"; - } - ? OrganizationPaths - : never - : never; - -export type PickProvidePaths< - ID extends string, - PickedPath extends string, - Auth extends BetterAuth, -> = Auth["options"]["providers"] extends Array - ? P extends { - id: ID; - } - ? PickedPath - : never - : never; - -export type PickDefaultPaths = "$session"; +export type AuthStore = typeof reactStore | typeof vueStore; +export interface ClientOptions extends BetterFetchOption {} diff --git a/packages/better-auth/src/client/vue.ts b/packages/better-auth/src/client/vue.ts index 57c248f949..738caf4f5a 100644 --- a/packages/better-auth/src/client/vue.ts +++ b/packages/better-auth/src/client/vue.ts @@ -1,2 +1,23 @@ import { useStore } from "@nanostores/vue"; +import { BetterFetchOption } from "@better-fetch/fetch"; +import { createVanillaClient } from "./base"; + +export const createAuthClient = (options?: BetterFetchOption) => { + const client = createVanillaClient(options); + function useSession() { + return useStore(client.$atoms.$session); + } + function useActiveOrganization() { + return useStore(client.$atoms.$activeOrganization); + } + function useListOrganization() { + return useStore(client.$atoms.$listOrganizations); + } + return Object.assign(client, { + useSession, + useActiveOrganization, + useListOrganization, + }); +}; + export const useAuthStore = useStore; diff --git a/packages/better-auth/src/plugins/two-factor/backup-codes/index.ts b/packages/better-auth/src/plugins/two-factor/backup-codes/index.ts index 1c2488d856..a2dbcccb64 100644 --- a/packages/better-auth/src/plugins/two-factor/backup-codes/index.ts +++ b/packages/better-auth/src/plugins/two-factor/backup-codes/index.ts @@ -3,7 +3,7 @@ import { TwoFactorProvider, UserWithTwoFactor } from "../types"; import { symmetricDecrypt, symmetricEncrypt } from "../../../crypto"; import { z } from "zod"; import { createAuthEndpoint } from "../../../api/call"; -import { verifyTwoFactorMiddleware } from "../verify-middleware"; +import { verifyTwoFactorMiddleware } from "../two-fa-middleware"; import { sessionMiddleware } from "../../../api/middlewares/session"; export interface BackupCodeOptions { @@ -78,37 +78,37 @@ export function getBackupCodes(user: UserWithTwoFactor, key: string) { export const backupCode2fa = (options?: BackupCodeOptions) => { return { id: "backup_code", - verify: createAuthEndpoint( - "/verify/backup-code", - { - method: "POST", - body: z.object({ - code: z.string(), - }), - use: [verifyTwoFactorMiddleware], - }, - async (ctx) => { - const validate = verifyBackupCode( - { - user: ctx.context.session.user, - code: ctx.body.code, - }, - ctx.context.secret, - ); - if (!validate) { - return ctx.json( - { status: false }, + endpoints: { + verifyBackupCode: createAuthEndpoint( + "/two-factor/verify-backup-code", + { + method: "POST", + body: z.object({ + code: z.string(), + }), + use: [verifyTwoFactorMiddleware], + }, + async (ctx) => { + const validate = verifyBackupCode( { - status: 401, + user: ctx.context.session.user, + code: ctx.body.code, }, + ctx.context.secret, ); - } - return ctx.json({ status: true }); - }, - ), - customActions: { + if (!validate) { + return ctx.json( + { status: false }, + { + status: 401, + }, + ); + } + return ctx.json({ status: true }); + }, + ), generateBackupCodes: createAuthEndpoint( - "/generate/backup-codes", + "/two-factor/generate-backup-codes", { method: "POST", use: [sessionMiddleware], diff --git a/packages/better-auth/src/plugins/two-factor/index.ts b/packages/better-auth/src/plugins/two-factor/index.ts index 29b88c1f26..3ac7000d1b 100644 --- a/packages/better-auth/src/plugins/two-factor/index.ts +++ b/packages/better-auth/src/plugins/two-factor/index.ts @@ -6,7 +6,7 @@ import { TwoFactorOptions, UserWithTwoFactor } from "./types"; import { twoFactorMiddleware, verifyTwoFactorMiddleware, -} from "./verify-middleware"; +} from "./two-fa-middleware"; import { sessionMiddleware } from "../../api/middlewares/session"; import { alphabet, generateRandomString } from "oslo/crypto"; import { backupCode2fa, generateBackupCodes } from "./backup-codes"; @@ -24,11 +24,11 @@ export const twoFactor = (options: O) => { return { id: "two-factor", endpoints: { - ...totp.customActions, - ...otp.customActions, - ...backupCode.customActions, + ...totp.endpoints, + ...otp.endpoints, + ...backupCode.endpoints, enableTwoFactor: createAuthEndpoint( - "/enable/two-factor", + "/two-factor/enable", { method: "POST", use: [sessionMiddleware], @@ -40,6 +40,7 @@ export const twoFactor = (options: O) => { key: ctx.context.secret, data: secret, }); + console.log({ encryptedSecret }); const backupCodes = await generateBackupCodes( ctx.context.secret, options.backupCodeOptions, @@ -62,7 +63,7 @@ export const twoFactor = (options: O) => { }, ), disableTwoFactor: createAuthEndpoint( - "/disable/two-factor", + "/two-factor/disable", { method: "POST", use: [sessionMiddleware], @@ -84,51 +85,6 @@ export const twoFactor = (options: O) => { return ctx.json({ status: true }); }, ), - verifyTwoFactor: createAuthEndpoint( - "/verify/two-factor", - { - method: "POST", - body: z.object({ - /** - * The code to validate - */ - code: z.string(), - with: z.enum(["totp", "otp", "backup_code"]), - callbackURL: z.string().optional(), - }), - use: [verifyTwoFactorMiddleware], - }, - async (ctx) => { - const providerId = ctx.body.with; - const provider = providers.find((p) => p.id === providerId); - if (!provider) { - return ctx.json( - { status: false }, - { - status: 401, - }, - ); - } - const res = await provider.verify(ctx); - if (!res.status) { - return ctx.json( - { status: false }, - { - status: 401, - }, - ); - } - await ctx.context.createSession(); - if (ctx.body.callbackURL) { - return ctx.json({ - status: true, - callbackURL: ctx.body.callbackURL, - redirect: true, - }); - } - return ctx.json({ status: true }); - }, - ), }, options: options, middlewares: [ @@ -149,18 +105,11 @@ export const twoFactor = (options: O) => { type: "string", required: false, }, - backupCodes: { + twoFactorBackupCodes: { type: "string", required: false, returned: false, }, - /** - * list of two factor providers id separated by comma - */ - twoFactorProviders: { - type: "string", - required: false, - }, }, }, }, diff --git a/packages/better-auth/src/plugins/two-factor/otp/index.ts b/packages/better-auth/src/plugins/two-factor/otp/index.ts index e5f2a31d0f..5006153022 100644 --- a/packages/better-auth/src/plugins/two-factor/otp/index.ts +++ b/packages/better-auth/src/plugins/two-factor/otp/index.ts @@ -6,7 +6,7 @@ import { generateHOTP } from "oslo/otp"; import { generateRandomInteger } from "oslo/crypto"; import { OTP_RANDOM_NUMBER_COOKIE_NAME } from "../constant"; import { z } from "zod"; -import { verifyTwoFactorMiddleware } from "../verify-middleware"; +import { verifyTwoFactorMiddleware } from "../two-fa-middleware"; export interface OTPOptions { /** @@ -25,11 +25,11 @@ export const otp2fa = (options?: OTPOptions) => { /** * Generate OTP and send it to the user. */ - const generateOTP = createAuthEndpoint( - "/generate/otp", + const send2FaOTP = createAuthEndpoint( + "/two-factor/send-otp", { method: "POST", - use: [sessionMiddleware], + use: [verifyTwoFactorMiddleware], }, async (ctx) => { if (!options || !options.sendOTP) { @@ -63,7 +63,7 @@ export const otp2fa = (options?: OTPOptions) => { ); const verifyOTP = createAuthEndpoint( - "/verify/otp", + "/two-factor/verify-otp", { method: "POST", body: z.object({ @@ -86,34 +86,35 @@ export const otp2fa = (options?: OTPOptions) => { ctx.context.secret, ); if (!randomNumber) { - throw new APIError("BAD_REQUEST", { - message: "counter cookie not found", + throw new APIError("UNAUTHORIZED", { + message: "OTP is expired", }); } const toCheckOtp = await generateHOTP( Buffer.from(ctx.context.secret), parseInt(randomNumber), ); + console.log(toCheckOtp, ctx.body.code); - if (toCheckOtp !== ctx.body.code) { - await ctx.context.createSession(); - return ctx.json({ status: true }); + if (toCheckOtp === ctx.body.code) { + ctx.setCookie(cookie.name, "", { + path: "/", + sameSite: "lax", + httpOnly: true, + secure: false, + maxAge: 0, + }); + return ctx.context.valid(); } else { - return ctx.json( - { status: false }, - { - status: 401, - }, - ); + return ctx.context.invalid(); } }, ); - return { id: "otp", - verify: verifyOTP, - customActions: { - generateOTP: generateOTP, + endpoints: { + send2FaOTP, + verifyOTP, }, } satisfies TwoFactorProvider; }; diff --git a/packages/better-auth/src/plugins/two-factor/totp/index.ts b/packages/better-auth/src/plugins/two-factor/totp/index.ts index a5c830ed65..9ff8e561a8 100644 --- a/packages/better-auth/src/plugins/two-factor/totp/index.ts +++ b/packages/better-auth/src/plugins/two-factor/totp/index.ts @@ -1,13 +1,12 @@ import { TimeSpan } from "oslo"; -import { alphabet, generateRandomString } from "oslo/crypto"; import { TOTPController, createTOTPKeyURI } from "oslo/otp"; import { z } from "zod"; import { createAuthEndpoint } from "../../../api/call"; import { sessionMiddleware } from "../../../api/middlewares/session"; import { APIError } from "better-call"; import { TwoFactorProvider, UserWithTwoFactor } from "../types"; -import { verifyTwoFactorMiddleware } from "../verify-middleware"; -import { BackupCodeOptions, generateBackupCodes } from "../backup-codes"; +import { verifyTwoFactorMiddleware } from "../two-fa-middleware"; +import { BackupCodeOptions } from "../backup-codes"; import { symmetricDecrypt } from "../../../crypto"; export type TOTPOptions = { @@ -36,105 +35,10 @@ export const totp2fa = (options: TOTPOptions) => { const opts = { digits: 6, period: new TimeSpan(options?.period || 30, "s"), - secret: { - field: "twoFactorSecret", - }, }; - const enableTOTP = createAuthEndpoint( - "/enable/totp", - { - method: "POST", - use: [sessionMiddleware], - }, - async (ctx) => { - if (!options) { - ctx.context.logger.error( - "totp isn't configured. please pass totp option on two factor plugin to enable totp", - ); - throw new APIError("BAD_REQUEST", { - message: "totp isn't configured", - }); - } - const secret = generateRandomString(16, alphabet("a-z", "0-9", "-")); - const user = ctx.context.session.user as UserWithTwoFactor; - const uri = createTOTPKeyURI( - options.issuer || "BetterAuth", - user.name, - Buffer.from(secret), - opts, - ); - const backupCodes = await generateBackupCodes( - secret, - options.backupCodes, - ); - await ctx.context.adapter.update({ - model: "user", - update: { - twoFactorSecret: secret, - twoFactorEnabled: true, - backupCodes: backupCodes.encryptedBackupCodes, - }, - where: [ - { - field: "id", - value: user.id, - }, - ], - }); - return ctx.json({ uri, backupCodes: backupCodes.backupCodes }); - }, - ); - - async function enable(user: UserWithTwoFactor) { - const secret = generateRandomString(16, alphabet("a-z", "0-9", "-")); - const uri = createTOTPKeyURI( - options.issuer, - user.name, - Buffer.from(secret), - opts, - ); - const backupCodes = await generateBackupCodes(secret, options.backupCodes); - return { - uri, - backupCodes: backupCodes.backupCodes, - }; - } - - const disableTOTP = createAuthEndpoint( - "/disable/totp", - { - method: "POST", - use: [sessionMiddleware], - }, - async (ctx) => { - if (!options) { - ctx.context.logger.error( - "totp isn't configured. please pass totp option on two factor plugin to enable totp", - ); - throw new APIError("BAD_REQUEST", { - message: "totp isn't configured", - }); - } - const user = ctx.context.session.user as UserWithTwoFactor; - await ctx.context.adapter.update({ - model: "user", - update: { - twoFactorEnabled: false, - }, - where: [ - { - field: "id", - value: user.id, - }, - ], - }); - return ctx.json({ status: true }); - }, - ); - const generateTOTP = createAuthEndpoint( - "/generate/totp", + "/totp/generate", { method: "POST", use: [sessionMiddleware], @@ -148,16 +52,15 @@ export const totp2fa = (options: TOTPOptions) => { message: "totp isn't configured", }); } - const session = ctx.context.session; + const session = ctx.context.session.user as UserWithTwoFactor; const totp = new TOTPController(opts); - const secret = (session.user as any).secret; - const code = await totp.generate(secret); + const code = await totp.generate(Buffer.from(session.twoFactorSecret)); return { code }; }, ); const getTOTPURI = createAuthEndpoint( - "/get/totp/uri", + "/two-factor/get-totp-uri", { method: "GET", use: [sessionMiddleware], @@ -175,7 +78,7 @@ export const totp2fa = (options: TOTPOptions) => { return { totpURI: createTOTPKeyURI( options?.issuer || "BetterAuth", - user.name, + user.email, Buffer.from(user.twoFactorSecret), opts, ), @@ -184,7 +87,7 @@ export const totp2fa = (options: TOTPOptions) => { ); const verifyTOTP = createAuthEndpoint( - "/verify/totp", + "/two-factor/verify-totp", { method: "POST", body: z.object({ @@ -210,17 +113,18 @@ export const totp2fa = (options: TOTPOptions) => { }), ); const status = await totp.verify(ctx.body.code, secret); - return { - status, - }; + if (!status) { + return ctx.context.invalid(); + } + return ctx.context.valid(); }, ); return { id: "totp", - verify: verifyTOTP, - customActions: { + endpoints: { generateTOTP: generateTOTP, viewTOTPURI: getTOTPURI, + verifyTOTP, }, } satisfies TwoFactorProvider; }; diff --git a/packages/better-auth/src/plugins/two-factor/verify-middleware.ts b/packages/better-auth/src/plugins/two-factor/two-fa-middleware.ts similarity index 88% rename from packages/better-auth/src/plugins/two-factor/verify-middleware.ts rename to packages/better-auth/src/plugins/two-factor/two-fa-middleware.ts index 1b4692d405..9ade2de653 100644 --- a/packages/better-auth/src/plugins/two-factor/verify-middleware.ts +++ b/packages/better-auth/src/plugins/two-factor/two-fa-middleware.ts @@ -5,6 +5,7 @@ import { hs256 } from "../../crypto"; import { TWO_FACTOR_COOKIE_NAME } from "./constant"; import { TwoFactorOptions, UserWithTwoFactor } from "./types"; import { z } from "zod"; +import { signInCredential } from "../../api/routes"; export const verifyTwoFactorMiddleware = createAuthMiddleware(async (ctx) => { const cookie = await ctx.getSignedCookie( @@ -62,7 +63,7 @@ export const verifyTwoFactorMiddleware = createAuthMiddleware(async (ctx) => { } if (hashToMatch === hash) { return { - createSession: async () => { + valid: async () => { /** * Set the session cookie */ @@ -72,6 +73,26 @@ export const verifyTwoFactorMiddleware = createAuthMiddleware(async (ctx) => { ctx.context.secret, ctx.context.authCookies.sessionToken.options, ); + if (ctx.body.callbackURL) { + return ctx.json({ + status: true, + callbackURL: ctx.body.callbackURL, + redirect: true, + }); + } + + return ctx.json({ status: true }); + }, + invalid: async () => { + return ctx.json( + { status: false }, + { + status: 401, + body: { + message: "Invalid code", + }, + }, + ); }, session: { id: session.id, diff --git a/packages/better-auth/src/plugins/two-factor/types.ts b/packages/better-auth/src/plugins/two-factor/types.ts index f55aa04d2e..cea2247c9c 100644 --- a/packages/better-auth/src/plugins/two-factor/types.ts +++ b/packages/better-auth/src/plugins/two-factor/types.ts @@ -45,12 +45,5 @@ export interface UserWithTwoFactor extends User { export interface TwoFactorProvider { id: LiteralString; - enable?: (user: UserWithTwoFactor) => Promise; - disable?: () => Promise; - verify: Endpoint< - (ctx: any) => Promise<{ - status: boolean; - }> - >; - customActions?: Record; + endpoints?: Record; } diff --git a/packages/better-auth/src/providers/passkey.ts b/packages/better-auth/src/providers/passkey.ts index c81418f32a..5506fd72b4 100644 --- a/packages/better-auth/src/providers/passkey.ts +++ b/packages/better-auth/src/providers/passkey.ts @@ -82,6 +82,9 @@ export const passkey = (options: PasskeyOptions) => { { method: "GET", use: [sessionMiddleware], + metadata: { + client: false, + }, }, async (ctx) => { const session = ctx.context.session; diff --git a/packages/better-auth/src/test-utils/test-instance.ts b/packages/better-auth/src/test-utils/test-instance.ts index 623a42285e..a9d24554bc 100644 --- a/packages/better-auth/src/test-utils/test-instance.ts +++ b/packages/better-auth/src/test-utils/test-instance.ts @@ -4,9 +4,12 @@ import { beforeAll, afterAll } from "vitest"; import { type Listener, listen } from "listhen"; import { toNodeHandler } from "better-call"; import fs from "fs/promises"; +import { BetterAuthOptions } from "../types"; -export async function getTestInstance() { - const auth = betterAuth({ +export async function getTestInstance>( + options?: O, +) { + const opts = { providers: [ github({ clientId: "test", @@ -26,7 +29,12 @@ export async function getTestInstance() { emailAndPassword: { enabled: true, }, - }); + } satisfies BetterAuthOptions; + + const auth = betterAuth({ + ...opts, + ...options, + } as O extends undefined ? typeof opts : O & typeof opts); let server: Listener; diff --git a/packages/better-auth/src/types/options.ts b/packages/better-auth/src/types/options.ts index da2ee8fb07..be7b02f796 100644 --- a/packages/better-auth/src/types/options.ts +++ b/packages/better-auth/src/types/options.ts @@ -146,11 +146,5 @@ export interface BetterAuthOptions { * @default 32 */ minPasswordLength?: number; - /** - * Two factor configuration - */ - twoFactor?: { - enabled: boolean; - }; }; } diff --git a/packages/better-auth/src/utils/cookies.ts b/packages/better-auth/src/utils/cookies.ts index 8f45286741..232a66d128 100644 --- a/packages/better-auth/src/utils/cookies.ts +++ b/packages/better-auth/src/utils/cookies.ts @@ -74,7 +74,7 @@ export function createCookieGetter(options: BetterAuthOptions) { name: process.env.NODE_ENV === "production" ? `${secureCookiePrefix}${cookiePrefix}.${cookieName}` - : `${cookiePrefix}${cookieName}`, + : `${cookiePrefix}.${cookieName}`, options: { secure, sameSite: "lax", diff --git a/packages/better-auth/tsup.config.ts b/packages/better-auth/tsup.config.ts index f248c24c03..0aaaa52240 100644 --- a/packages/better-auth/tsup.config.ts +++ b/packages/better-auth/tsup.config.ts @@ -8,7 +8,6 @@ export default defineConfig({ cli: "./src/cli/index.ts", react: "./src/client/react.ts", preact: "./src/client/preact.ts", - solid: "./src/client/solid.ts", vue: "./src/client/vue.ts", plugins: "./src/plugins/index.ts", }, @@ -19,6 +18,7 @@ export default defineConfig({ external: [ "react", "svelte", + "solid-js", "$app/environment", "next", "pg", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c443be8e4b..a634bbd302 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,6 +127,9 @@ importers: react-hook-form: specifier: ^7.52.2 version: 7.52.2(react@18.3.1) + react-qr-code: + specifier: ^2.0.15 + version: 2.0.15(react@18.3.1) sonner: specifier: ^1.5.0 version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -198,8 +201,8 @@ importers: specifier: ^1.9.2 version: 1.9.2 better-call: - specifier: ^0.1.28 - version: 0.1.28(typescript@5.5.4) + specifier: ^0.1.33 + version: 0.1.33(typescript@5.5.4) chalk: specifier: ^5.3.0 version: 5.3.0 @@ -215,6 +218,9 @@ importers: jiti: specifier: ^1.21.6 version: 1.21.6 + jose: + specifier: ^5.7.0 + version: 5.7.0 kysely: specifier: ^0.27.4 version: 0.27.4 @@ -1733,8 +1739,8 @@ packages: peerDependencies: typescript: ^5.0.0 - better-call@0.1.28: - resolution: {integrity: sha512-9jl3q7Mb+3lRGeJUqkHAzAHph8BKcGHsj84h1gkfcMy2skyrCUbpFuhxMNAKBDtg2ppa66wTWDv0mGLOshxtEA==} + better-call@0.1.33: + resolution: {integrity: sha512-gzthE/AnimwMCNBNyy9LRqqAtjXkqO+dR4n1OjCiUhiBK4X+NZMKekQUKIzpfwDRC4k3hCshb1LsPhqwiSM7Bw==} peerDependencies: typescript: ^5.0.0 @@ -2233,6 +2239,9 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + jose@5.7.0: + resolution: {integrity: sha512-3P9qfTYDVnNn642LCAqIKbTGb9a1TBxZ9ti5zEVEr48aDdflgRjhspWFb6WM4PzAfFbGMJYC4+803v8riCRAKw==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -2717,6 +2726,9 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -2731,6 +2743,9 @@ packages: resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} engines: {node: '>=6.0.0'} + qr.js@0.0.0: + resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2755,9 +2770,17 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-qr-code@2.0.15: + resolution: {integrity: sha512-MkZcjEXqVKqXEIMVE0mbcGgDpkfSdd8zhuzXEl9QzYeNcw8Hq2oVIzDLWuZN2PQBwM5PWjc2S31K8Q1UbcFMfw==} + peerDependencies: + react: '*' + react-remove-scroll-bar@2.3.6: resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} @@ -4487,7 +4510,7 @@ snapshots: rou3: 0.5.1 typescript: 5.5.4 - better-call@0.1.28(typescript@5.5.4): + better-call@0.1.33(typescript@5.5.4): dependencies: '@better-fetch/fetch': 1.1.4 '@types/set-cookie-parser': 2.4.10 @@ -5009,6 +5032,8 @@ snapshots: jiti@1.21.6: {} + jose@5.7.0: {} + joycon@3.1.1: {} js-tokens@4.0.0: {} @@ -5469,6 +5494,12 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + pump@3.0.0: dependencies: end-of-stream: 1.4.4 @@ -5482,6 +5513,8 @@ snapshots: pvutils@1.1.3: {} + qr.js@0.0.0: {} + queue-microtask@1.2.3: {} radix3@1.1.2: {} @@ -5508,8 +5541,16 @@ snapshots: dependencies: react: 18.3.1 + react-is@16.13.1: {} + react-is@18.3.1: {} + react-qr-code@2.0.15(react@18.3.1): + dependencies: + prop-types: 15.8.1 + qr.js: 0.0.0 + react: 18.3.1 + react-remove-scroll-bar@2.3.6(@types/react@18.3.3)(react@18.3.1): dependencies: react: 18.3.1 diff --git a/todo.md b/todo.md new file mode 100644 index 0000000000..e354c3a6a8 --- /dev/null +++ b/todo.md @@ -0,0 +1,2 @@ +## TODO +[ ] handle migration when the config removes existing schema \ No newline at end of file