Skip to content

Commit 4e38645

Browse files
Kinfe123Bekacruping-maxwell
authored
feat: sveltekit cookie helper plugin (#3049)
* fix(email-verification): improve email verification logic to check session and user email consistency (#3042) * docs(passkey): Fixed signIn passkey props (#3014) callbackURL doesn't exist. * feat: svelte kit cookie helper * lint * docs and clean up * clean up * lint * clean up * clean up * sync cookies * lint * pruning * pruning and lint * update * update * update --------- Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com> Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com>
1 parent be3face commit 4e38645

File tree

5 files changed

+90
-7
lines changed

5 files changed

+90
-7
lines changed

docs/components/builder/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { useAtom } from "jotai";
2828
import { optionsAtom } from "./store";
2929
import { useTheme } from "next-themes";
3030
import { ScrollArea } from "../ui/scroll-area";
31-
import styles from "./builder.module.css";
3231
const frameworks = [
3332
{
3433
title: "Next.js",

docs/content/docs/integrations/svelte-kit.mdx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ export async function handle({ event, resolve }) {
1818
}
1919
```
2020

21+
### Server Action Cookies
22+
23+
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.
24+
25+
You need to add it as a plugin to your Better Auth instance.
26+
27+
<Callout>
28+
The `getRequestEvent` function is available in SvelteKit `2.2.0` and later. Make sure you are using a compatible version.
29+
</Callout>
30+
31+
```ts title="lib/auth.ts"
32+
import { BetterAuth } from "better-auth";
33+
import { sveltekitCookies } from "better-auth/svelte-kit";
34+
35+
export const auth = betterAuth({
36+
// ... your config
37+
plugins: [sveltekitCookies()]
38+
})
39+
```
40+
41+
2142
## Create a client
2243

2344
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.

docs/content/docs/plugins/passkey.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ Signin method accepts:
117117

118118
`autoFill`: Browser autofill, a.k.a. Conditional UI. [read more](https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui)
119119

120-
`callbackURL`: The URL to redirect to after the user has signed in. (optional)
120+
`email`: The email of the user to sign in.
121+
122+
`fetchOptions`: Fetch options to pass to the fetch request.
121123

122124
```ts
123125
const data = await authClient.signIn.passkey();

packages/better-auth/src/api/routes/email-verification.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { APIError } from "better-call";
44
import { getSessionFromCtx } from "./session";
55
import { setSessionCookie } from "../../cookies";
66
import type { GenericEndpointContext, User } from "../../types";
7-
import { BASE_ERROR_CODES } from "../../error/codes";
87
import { jwtVerify, type JWTPayload, type JWTVerifyResult } from "jose";
98
import { signJWT } from "../../crypto/jwt";
109
import { originCheck } from "../middlewares";
@@ -155,13 +154,32 @@ export const sendVerificationEmail = createAuthEndpoint(
155154
});
156155
}
157156
const { email } = ctx.body;
158-
const user = await ctx.context.internalAdapter.findUserByEmail(email);
159-
if (!user) {
157+
const session = await getSessionFromCtx(ctx);
158+
if (!session) {
159+
const user = await ctx.context.internalAdapter.findUserByEmail(email);
160+
if (!user) {
161+
//we're returning true to avoid leaking information about the user
162+
return ctx.json({
163+
status: true,
164+
});
165+
}
166+
await sendVerificationEmailFn(ctx, user.user);
167+
return ctx.json({
168+
status: true,
169+
});
170+
}
171+
if (session?.user.emailVerified) {
172+
throw new APIError("BAD_REQUEST", {
173+
message:
174+
"You can only send a verification email to an unverified email",
175+
});
176+
}
177+
if (session?.user.email !== email) {
160178
throw new APIError("BAD_REQUEST", {
161-
message: BASE_ERROR_CODES.USER_NOT_FOUND,
179+
message: "You can only send a verification email to your own email",
162180
});
163181
}
164-
await sendVerificationEmailFn(ctx, user.user);
182+
await sendVerificationEmailFn(ctx, session.user);
165183
return ctx.json({
166184
status: true,
167185
});

packages/better-auth/src/integrations/svelte-kit.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import type { BetterAuthOptions } from "../types";
2+
import type { BetterAuthPlugin } from "../types";
3+
import { createAuthMiddleware } from "../api";
4+
import { parseSetCookieHeader } from "../cookies";
25

36
export const toSvelteKitHandler = (auth: {
47
handler: (request: Request) => any;
@@ -49,3 +52,43 @@ export function isAuthPath(url: string, options: BetterAuthOptions) {
4952
return false;
5053
return true;
5154
}
55+
export const sveltekitCookies = () => {
56+
return {
57+
id: "sveltekit-cookies",
58+
hooks: {
59+
after: [
60+
{
61+
matcher() {
62+
return true;
63+
},
64+
handler: createAuthMiddleware(async (ctx) => {
65+
const returned = ctx.context.responseHeaders;
66+
if ("_flag" in ctx && ctx._flag === "router") {
67+
return;
68+
}
69+
if (returned instanceof Headers) {
70+
const setCookies = returned?.get("set-cookie");
71+
if (!setCookies) return;
72+
// @ts-expect-error
73+
const { getRequestEvent } = await import("$app/server");
74+
const event = await getRequestEvent();
75+
if (!event) return;
76+
const parsed = parseSetCookieHeader(setCookies);
77+
for (const [name, { value, ...ops }] of parsed) {
78+
event.cookies.set(name, decodeURIComponent(value), {
79+
sameSite: ops.samesite,
80+
path: ops.path || "/",
81+
expires: ops.expires,
82+
secure: ops.secure,
83+
httpOnly: ops.httponly,
84+
domain: ops.domain,
85+
maxAge: ops["max-age"],
86+
});
87+
}
88+
}
89+
}),
90+
},
91+
],
92+
},
93+
} satisfies BetterAuthPlugin;
94+
};

0 commit comments

Comments
 (0)