diff --git a/docs/components/builder/index.tsx b/docs/components/builder/index.tsx index 09f191ac..59e66bb3 100644 --- a/docs/components/builder/index.tsx +++ b/docs/components/builder/index.tsx @@ -28,7 +28,6 @@ import { useAtom } from "jotai"; import { optionsAtom } from "./store"; import { useTheme } from "next-themes"; import { ScrollArea } from "../ui/scroll-area"; -import styles from "./builder.module.css"; const frameworks = [ { title: "Next.js", diff --git a/docs/content/docs/integrations/svelte-kit.mdx b/docs/content/docs/integrations/svelte-kit.mdx index a88f3be9..903a62d4 100644 --- a/docs/content/docs/integrations/svelte-kit.mdx +++ b/docs/content/docs/integrations/svelte-kit.mdx @@ -18,6 +18,27 @@ export async function handle({ event, resolve }) { } ``` +### Server Action Cookies + +To ensure cookies are properly set when you call functions like `signInEmail` or `signUpEmail` in a server action, you should use the `sveltekitCookies` plugin. This plugin will automatically handle setting cookies for you in SvelteKit. + +You need to add it as a plugin to your Better Auth instance. + + +The `getRequestEvent` function is available in SvelteKit `2.2.0` and later. Make sure you are using a compatible version. + + +```ts title="lib/auth.ts" +import { BetterAuth } from "better-auth"; +import { sveltekitCookies } from "better-auth/svelte-kit"; + +export const auth = betterAuth({ + // ... your config + plugins: [sveltekitCookies()] +}) +``` + + ## Create a client Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory. diff --git a/docs/content/docs/plugins/passkey.mdx b/docs/content/docs/plugins/passkey.mdx index b69c9ad8..388c84c7 100644 --- a/docs/content/docs/plugins/passkey.mdx +++ b/docs/content/docs/plugins/passkey.mdx @@ -117,7 +117,9 @@ Signin method accepts: `autoFill`: Browser autofill, a.k.a. Conditional UI. [read more](https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui) -`callbackURL`: The URL to redirect to after the user has signed in. (optional) +`email`: The email of the user to sign in. + +`fetchOptions`: Fetch options to pass to the fetch request. ```ts const data = await authClient.signIn.passkey(); diff --git a/packages/better-auth/src/api/routes/email-verification.ts b/packages/better-auth/src/api/routes/email-verification.ts index 944dc733..83b8fa0b 100644 --- a/packages/better-auth/src/api/routes/email-verification.ts +++ b/packages/better-auth/src/api/routes/email-verification.ts @@ -4,7 +4,6 @@ import { APIError } from "better-call"; import { getSessionFromCtx } from "./session"; import { setSessionCookie } from "../../cookies"; import type { GenericEndpointContext, User } from "../../types"; -import { BASE_ERROR_CODES } from "../../error/codes"; import { jwtVerify, type JWTPayload, type JWTVerifyResult } from "jose"; import { signJWT } from "../../crypto/jwt"; import { originCheck } from "../middlewares"; @@ -155,13 +154,32 @@ export const sendVerificationEmail = createAuthEndpoint( }); } const { email } = ctx.body; - const user = await ctx.context.internalAdapter.findUserByEmail(email); - if (!user) { - throw new APIError("BAD_REQUEST", { - message: BASE_ERROR_CODES.USER_NOT_FOUND, + const session = await getSessionFromCtx(ctx); + if (!session) { + const user = await ctx.context.internalAdapter.findUserByEmail(email); + if (!user) { + //we're returning true to avoid leaking information about the user + return ctx.json({ + status: true, + }); + } + await sendVerificationEmailFn(ctx, user.user); + return ctx.json({ + status: true, }); } - await sendVerificationEmailFn(ctx, user.user); + if (session?.user.emailVerified) { + throw new APIError("BAD_REQUEST", { + message: + "You can only send a verification email to an unverified email", + }); + } + if (session?.user.email !== email) { + throw new APIError("BAD_REQUEST", { + message: "You can only send a verification email to your own email", + }); + } + await sendVerificationEmailFn(ctx, session.user); return ctx.json({ status: true, }); diff --git a/packages/better-auth/src/integrations/svelte-kit.ts b/packages/better-auth/src/integrations/svelte-kit.ts index aecd3aa1..84023296 100644 --- a/packages/better-auth/src/integrations/svelte-kit.ts +++ b/packages/better-auth/src/integrations/svelte-kit.ts @@ -1,4 +1,7 @@ import type { BetterAuthOptions } from "../types"; +import type { BetterAuthPlugin } from "../types"; +import { createAuthMiddleware } from "../api"; +import { parseSetCookieHeader } from "../cookies"; export const toSvelteKitHandler = (auth: { handler: (request: Request) => any; @@ -49,3 +52,43 @@ export function isAuthPath(url: string, options: BetterAuthOptions) { return false; return true; } +export const sveltekitCookies = () => { + return { + id: "sveltekit-cookies", + hooks: { + after: [ + { + matcher() { + return true; + }, + handler: createAuthMiddleware(async (ctx) => { + const returned = ctx.context.responseHeaders; + if ("_flag" in ctx && ctx._flag === "router") { + return; + } + if (returned instanceof Headers) { + const setCookies = returned?.get("set-cookie"); + if (!setCookies) return; + // @ts-expect-error + const { getRequestEvent } = await import("$app/server"); + const event = await getRequestEvent(); + if (!event) return; + const parsed = parseSetCookieHeader(setCookies); + for (const [name, { value, ...ops }] of parsed) { + event.cookies.set(name, decodeURIComponent(value), { + sameSite: ops.samesite, + path: ops.path || "/", + expires: ops.expires, + secure: ops.secure, + httpOnly: ops.httponly, + domain: ops.domain, + maxAge: ops["max-age"], + }); + } + } + }), + }, + ], + }, + } satisfies BetterAuthPlugin; +};