@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 +44 -39
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +20 -1
- package/dist/sync/index.d.ts +14 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +21 -0
- package/package.json +5 -1
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.
|
|
171
|
+
## 5. Sync Worker Verification
|
|
172
172
|
|
|
173
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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"}
|
package/dist/server/index.js
CHANGED
|
@@ -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 =
|
|
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
|
+
"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",
|