@sveltebase/auth 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -168,26 +168,49 @@ Initialize the state once using a Svelte 5 reactive getter:
168
168
 
169
169
  ---
170
170
 
171
- ## 5. WebSocket Sync Verification (Post-Load Security)
171
+ ## 5. Sync Worker Verification
172
172
 
173
- To run background cryptographic and database checks, register the `createAuthSync` handler on your sync server:
173
+ Sync authentication is verified inside the standalone sync Worker, not in the SvelteKit app Worker. The app Worker should proxy `/api/sync` to the sync Worker and should not forward resolved auth objects.
174
+
175
+ Create the sync Worker with `jwtCookieAuth()`:
176
+
177
+ ```typescript
178
+ // src/worker/sync.ts
179
+ import { jwtCookieAuth } from "@sveltebase/auth/sync";
180
+ import { defineSyncWorker, SyncEngine } from "@sveltebase/sync/cloudflare";
181
+ import { handlers } from "$lib/server/sync-handlers";
182
+
183
+ export default defineSyncWorker({
184
+ handlers,
185
+ auth: jwtCookieAuth()
186
+ });
187
+
188
+ export { SyncEngine };
189
+ ```
190
+
191
+ `jwtCookieAuth()` reads the `sf_session` cookie by default, verifies it with `platform.env.JWT_SECRET`, resolves identity from `user.id`, and marks unauthenticated websocket connections as rejected by default.
192
+
193
+ Both Workers need the same secret:
194
+
195
+ ```bash
196
+ wrangler secret put JWT_SECRET --config wrangler.jsonc
197
+ wrangler secret put JWT_SECRET --config wrangler.sync.jsonc
198
+ ```
199
+
200
+ Register `createAuthSync()` on the sync Worker handlers to protect the `"users"` channel and optionally check the database:
174
201
 
175
202
  ```typescript
176
203
  // src/lib/server/sync-handlers.ts
177
204
  import { createAuthSync } from "@sveltebase/auth/server";
178
- import { JWT_SECRET } from "$env/static/private";
179
205
  import { getDB } from "./db.js";
180
206
 
181
207
  export const handlers = [
182
208
  createAuthSync({
183
- jwtSecret: JWT_SECRET,
184
- // Database check to verify if the user account is active/valid
185
209
  verifyUser: async (user, ctx) => {
186
210
  const db = getDB(ctx.platform);
187
211
  const activeUser = await db.select().from(users).where(eq(users.id, user.id)).get();
188
212
  return !!activeUser && !activeUser.isSuspended;
189
213
  },
190
- // Optional persistence hook for user mutations (e.g. auth.update)
191
214
  onUpdate: async (userId, changes, ctx) => {
192
215
  const db = getDB(ctx.platform);
193
216
  return await db.update(users).set(changes).where(eq(users.id, userId)).returning();
@@ -197,37 +220,7 @@ export const handlers = [
197
220
  ];
198
221
  ```
199
222
 
200
- `createAuthSync` registers and protects the `"users"` sync channel. It verifies the session token when the client subscribes to that channel and can call your `verifyUser` hook to check the database.
201
-
202
- To make the verified user object available to every other sync handler, resolve connection auth in your `/api/sync` WebSocket upgrade route.
203
-
204
- Recommended SvelteKit setup:
205
-
206
- ```typescript
207
- // src/routes/api/sync/+server.ts
208
- import { JWT_SECRET } from "$env/static/private";
209
- import { getVerifiedUserFromRequest } from "@sveltebase/auth";
210
- import { handleUpgrade } from "@sveltebase/sync";
211
- import type { User } from "$lib/server/db/schema";
212
- import type { RequestEvent, RequestHandler } from "@sveltejs/kit";
213
-
214
- export const GET: RequestHandler = async (event: RequestEvent) => {
215
- return handleUpgrade(event.request, event.platform, {
216
- auth: async (request) => {
217
- const user = await getVerifiedUserFromRequest<User>(
218
- request,
219
- JWT_SECRET
220
- );
221
-
222
- return user ? { user } : null;
223
- },
224
- identity: (auth) => auth.user.id,
225
- allowUnauthenticated: false
226
- });
227
- };
228
- ```
229
-
230
- After this, other sync handlers can use `ctx.auth.user` for row ownership checks. The `identity` option gives `@sveltebase/sync` a stable user ID for `scope` filtering:
223
+ Other sync handlers can use `ctx.auth?.user` and `ctx.identity` for row ownership checks:
231
224
 
232
225
  ```typescript
233
226
  fetch: async (ctx) => {
@@ -238,7 +231,17 @@ fetch: async (ctx) => {
238
231
  }
239
232
  ```
240
233
 
241
- ---
234
+ Keep the browser connecting to the app origin so the session cookie is sent:
235
+
236
+ ```typescript
237
+ // src/routes/api/sync/+server.ts
238
+ import { SYNC_WORKER_URL } from "$env/static/private";
239
+ import { syncProxy } from "@sveltebase/sync/sveltekit";
240
+
241
+ export const { GET, POST } = syncProxy({
242
+ fallbackUrl: SYNC_WORKER_URL
243
+ });
244
+ ```
242
245
 
243
246
  ## 6. API Reference
244
247
 
@@ -269,7 +272,9 @@ fetch: async (ctx) => {
269
272
  ### Sync Server Handler (`@sveltebase/auth/server`)
270
273
 
271
274
  * **`createAuthSync(config: SyncAuthConfig)`**
272
- Establishes the `"users"` sync channel and verifies WebSocket subscriptions for auth state. For unrelated sync channels, use `handleUpgrade(..., { auth, identity })` in your `/api/sync` route to populate `ctx.auth`.
275
+ Establishes the `"users"` sync channel. Use `jwtCookieAuth()` in the standalone sync Worker to populate `ctx.auth` and `ctx.identity` for all sync handlers.
276
+ * **`jwtCookieAuth(options?)`** from `@sveltebase/auth/sync`
277
+ Verifies the `sf_session` cookie using `platform.env.JWT_SECRET` during the sync Worker websocket handshake.
273
278
 
274
279
  ---
275
280
 
@@ -2,8 +2,10 @@ import { type SyncContext } from "@sveltebase/sync";
2
2
  export interface SyncAuthConfig<User = any> {
3
3
  /**
4
4
  * Secret key used to verify the JWT tokens.
5
+ * Optional when the sync Worker uses jwtCookieAuth(), because ctx.auth.user
6
+ * is already verified during the websocket handshake.
5
7
  */
6
- jwtSecret: string;
8
+ jwtSecret?: string;
7
9
  /**
8
10
  * Name of the session cookie.
9
11
  * @default "sf_session"
@@ -24,5 +26,5 @@ export interface SyncAuthConfig<User = any> {
24
26
  * Creates a Svelteflare Sync handler for WebSocket-based session validation.
25
27
  * Registers the read-only "users" channel.
26
28
  */
27
- export declare function createAuthSync<User = any>(config: SyncAuthConfig<User>): import("@sveltebase/sync").SyncHandler<User>;
29
+ export declare function createAuthSync<User = any>(config: SyncAuthConfig<User>): import("@sveltebase/sync").SyncHandler<User, any>;
28
30
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,MAAM,WAAW,cAAc,CAAC,IAAI,GAAG,GAAG;IACxC;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChE;;OAEG;IACH,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,IAAI,SAAS;QAAE,EAAE,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,EAAE,GAAG,MAAM,EACnD,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EACtB,GAAG,EAAE,WAAW,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAG,GAAG,EAAE,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,gDA0EtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,MAAM,WAAW,cAAc,CAAC,IAAI,GAAG,GAAG;IACxC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChE;;OAEG;IACH,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,IAAI,SAAS;QAAE,EAAE,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,EAAE,GAAG,MAAM,EACnD,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EACtB,GAAG,EAAE,WAAW,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAG,GAAG,EAAE,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,qDAuGtE"}
@@ -10,6 +10,19 @@ export function createAuthSync(config) {
10
10
  channel: "users",
11
11
  // Runs automatically when the client queries/subscribes to the "users" channel
12
12
  authorize: async (ctx) => {
13
+ const authenticatedUser = ctx.auth?.user;
14
+ if (authenticatedUser) {
15
+ if (config.verifyUser) {
16
+ const isValid = await config.verifyUser(authenticatedUser, ctx);
17
+ if (!isValid) {
18
+ throw new Error("Unauthorized: User no longer exists");
19
+ }
20
+ }
21
+ return;
22
+ }
23
+ if (!config.jwtSecret) {
24
+ throw new Error("Unauthorized: No verified sync auth found");
25
+ }
13
26
  const user = getUserFromRequest(ctx.request, cookieName);
14
27
  if (!user) {
15
28
  throw new Error("Unauthorized: No session cookie found");
@@ -32,6 +45,9 @@ export function createAuthSync(config) {
32
45
  },
33
46
  // Returns the current session user so the client knows it is successfully authorized
34
47
  fetch: async (ctx) => {
48
+ const authenticatedUser = ctx.auth?.user;
49
+ if (authenticatedUser)
50
+ return [authenticatedUser];
35
51
  const user = getUserFromRequest(ctx.request, cookieName);
36
52
  return user ? [user] : [];
37
53
  },
@@ -40,7 +56,10 @@ export function createAuthSync(config) {
40
56
  },
41
57
  update: async (ctx, key, changes) => {
42
58
  // Verify token authenticity before allowing writes
43
- const user = await getVerifiedUserFromRequest(ctx.request, config.jwtSecret, cookieName);
59
+ const user = ctx.auth?.user ??
60
+ (config.jwtSecret
61
+ ? await getVerifiedUserFromRequest(ctx.request, config.jwtSecret, cookieName)
62
+ : null);
44
63
  if (!user) {
45
64
  throw new Error("Unauthorized: Invalid session");
46
65
  }
@@ -0,0 +1,14 @@
1
+ import type { SyncPlatform } from "@sveltebase/sync";
2
+ export declare function jwtCookieAuth<User extends {
3
+ id: any;
4
+ }>(options?: {
5
+ secretBinding?: string;
6
+ cookieName?: string;
7
+ identity?: (user: User) => string | number | bigint | null | undefined;
8
+ }): ((request: Request, platform: SyncPlatform) => Promise<{
9
+ user: import("../index.js").SessionUser<User>;
10
+ identity: string | null;
11
+ } | null>) & {
12
+ allowUnauthenticated: boolean;
13
+ };
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,wBAAgB,aAAa,CAAC,IAAI,SAAS;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,EAAE,OAAO,CAAC,EAAE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACxE,cACkC,OAAO,YAAY,YAAY;;;;;EA0BjE"}
@@ -0,0 +1,21 @@
1
+ import { getVerifiedUserFromRequest } from "../index.js";
2
+ export function jwtCookieAuth(options) {
3
+ const resolver = async (request, platform) => {
4
+ const secretBinding = options?.secretBinding ?? "JWT_SECRET";
5
+ const secret = platform.env[secretBinding];
6
+ if (!secret) {
7
+ throw new Error(`Missing ${secretBinding} binding for sync auth`);
8
+ }
9
+ const user = await getVerifiedUserFromRequest(request, String(secret), options?.cookieName);
10
+ if (!user)
11
+ return null;
12
+ const identityValue = options?.identity
13
+ ? options.identity(user)
14
+ : user.id;
15
+ const identity = identityValue == null ? null : String(identityValue);
16
+ return { user, identity };
17
+ };
18
+ return Object.assign(resolver, {
19
+ allowUnauthenticated: false,
20
+ });
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltebase/auth",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,6 +25,10 @@
25
25
  "types": "./dist/server/index.d.ts",
26
26
  "default": "./dist/server/index.js"
27
27
  },
28
+ "./sync": {
29
+ "types": "./dist/sync/index.d.ts",
30
+ "default": "./dist/sync/index.js"
31
+ },
28
32
  "./google": {
29
33
  "types": "./dist/google/index.d.ts",
30
34
  "svelte": "./dist/google/index.js",