@sveltebase/auth 1.0.0
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 +395 -0
- package/dist/client/auth.svelte.d.ts +47 -0
- package/dist/client/auth.svelte.d.ts.map +1 -0
- package/dist/client/auth.svelte.js +130 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +1 -0
- package/dist/google/GoogleLogin.svelte +83 -0
- package/dist/google/GoogleLogin.svelte.d.ts +22 -0
- package/dist/google/GoogleLogin.svelte.d.ts.map +1 -0
- package/dist/google/GoogleOAuthProvider.svelte +39 -0
- package/dist/google/GoogleOAuthProvider.svelte.d.ts +10 -0
- package/dist/google/GoogleOAuthProvider.svelte.d.ts.map +1 -0
- package/dist/google/GoogleOneTapLogin.svelte +52 -0
- package/dist/google/GoogleOneTapLogin.svelte.d.ts +14 -0
- package/dist/google/GoogleOneTapLogin.svelte.d.ts.map +1 -0
- package/dist/google/context.svelte.d.ts +11 -0
- package/dist/google/context.svelte.d.ts.map +1 -0
- package/dist/google/context.svelte.js +23 -0
- package/dist/google/google.svelte.d.ts +20 -0
- package/dist/google/google.svelte.d.ts.map +1 -0
- package/dist/google/google.svelte.js +109 -0
- package/dist/google/index.d.ts +9 -0
- package/dist/google/index.d.ts.map +1 -0
- package/dist/google/index.js +8 -0
- package/dist/google/loader.d.ts +2 -0
- package/dist/google/loader.d.ts.map +1 -0
- package/dist/google/loader.js +27 -0
- package/dist/google/types.d.ts +117 -0
- package/dist/google/types.d.ts.map +1 -0
- package/dist/google/types.js +1 -0
- package/dist/google/verifier.d.ts +17 -0
- package/dist/google/verifier.d.ts.map +1 -0
- package/dist/google/verifier.js +64 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +194 -0
- package/dist/server/index.d.ts +28 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +70 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# @sveltebase/auth
|
|
2
|
+
|
|
3
|
+
A lightweight, Edge-native, DB-agnostic authentication library built for **Svelte 5** and **SvelteKit** that integrates seamlessly with **@sveltebase/sync** for real-time WebSocket session verification.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Stateless JWT Sessions**: Session data is signed using HMAC-SHA256 and stored directly in secure, server-side `HttpOnly` cookies.
|
|
8
|
+
- **DB-Agnostic Design**: Works out-of-the-box with D1 SQLite, Drizzle, Prisma, Postgres, or any other database of your choice.
|
|
9
|
+
- **Fast, Database-Free SSR**: Page renders decode the signed cookie instantly without database queries or heavy cryptographic signature validation on initial loads.
|
|
10
|
+
- **Svelte 5 Reactive Getters**: Hydrate client state with a reactive getter function (`() => data.user`) so that the UI reactively updates without writing `$effect` sync boilerplate in layouts.
|
|
11
|
+
- **WebSocket Channel Verification**: Offloads token verification and database checkups to Sveltebase Sync's WebSocket connection, verifying credentials in the background.
|
|
12
|
+
- **Automatic Offline Cache Wiping**: Automatically cleans up local IndexedDB database tables and terminates WebSocket connections on session expiry, account deletion, or user logout.
|
|
13
|
+
- **Web Crypto API Native**: Built on top of `globalThis.crypto.subtle` (zero NPM dependencies) to run at maximum speed in Edge runtimes like Cloudflare Workers.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Architecture & Flows
|
|
18
|
+
|
|
19
|
+
### 1. Fast Page Load (SSR)
|
|
20
|
+
```
|
|
21
|
+
Browser (Client) ------------[ GET /dashboard ]------------> SvelteKit Server (SSR)
|
|
22
|
+
│
|
|
23
|
+
getUserFromCookie()
|
|
24
|
+
(Fast JSON base64-decode)
|
|
25
|
+
│
|
|
26
|
+
Browser (Client) <-----------[ HTML + page.data ]───────────────────┘
|
|
27
|
+
```
|
|
28
|
+
SSR is instant because no database queries or cryptographic signature verifications are performed during the page load.
|
|
29
|
+
|
|
30
|
+
### 2. WebSocket Background Verification
|
|
31
|
+
```
|
|
32
|
+
Browser (Client) ────────────[ Connect WebSocket ]───────────> Sync Engine (DO)
|
|
33
|
+
│ │
|
|
34
|
+
├───────────────[ Subscribe: "users" channel ]───────────────>┤
|
|
35
|
+
│ │
|
|
36
|
+
│ verifyJWT()
|
|
37
|
+
│ (Crypto verification)
|
|
38
|
+
│ │
|
|
39
|
+
│ verifyUser()
|
|
40
|
+
│ (Database check)
|
|
41
|
+
│ │
|
|
42
|
+
│<──────────────[ Subscription Authorized ]───────────────────┤ (Success)
|
|
43
|
+
│ │
|
|
44
|
+
│<──────────────[ Subscription Error ]────────────────────────┘ (Fail)
|
|
45
|
+
│
|
|
46
|
+
▼ (If failed)
|
|
47
|
+
auth.logout()
|
|
48
|
+
(Wipes IndexedDB & redirects)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 1. Installation
|
|
54
|
+
|
|
55
|
+
Add the package to your SvelteKit project workspace:
|
|
56
|
+
```bash
|
|
57
|
+
bun add @sveltebase/auth
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 2. Server Setup
|
|
63
|
+
|
|
64
|
+
Configure the server-side authentication manager:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// src/lib/server/auth.ts
|
|
68
|
+
import { createServerAuth } from "@sveltebase/auth";
|
|
69
|
+
import { JWT_SECRET } from "$env/static/private";
|
|
70
|
+
|
|
71
|
+
export interface AppUser {
|
|
72
|
+
id: string;
|
|
73
|
+
email: string;
|
|
74
|
+
name: string;
|
|
75
|
+
token: string; // Embedded JWT signature
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const auth = createServerAuth<AppUser>({
|
|
79
|
+
jwtSecret: JWT_SECRET,
|
|
80
|
+
cookieName: "sf_session",
|
|
81
|
+
cookieOptions: {
|
|
82
|
+
maxAge: 60 * 60 * 24 * 7, // 1 week
|
|
83
|
+
path: "/",
|
|
84
|
+
httpOnly: true,
|
|
85
|
+
secure: true,
|
|
86
|
+
sameSite: "lax"
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 3. SvelteKit Route Integration
|
|
94
|
+
|
|
95
|
+
### Server load (`src/routes/+layout.server.ts`)
|
|
96
|
+
Retrieve the user payload from the cookies during page render:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { getUserFromCookie } from "@sveltebase/auth";
|
|
100
|
+
import type { AppUser } from "$lib/auth-client.js";
|
|
101
|
+
import type { LayoutServerLoad } from "./$types";
|
|
102
|
+
|
|
103
|
+
export const load: LayoutServerLoad = async ({ cookies }) => {
|
|
104
|
+
// Sub-millisecond unverified JSON decode
|
|
105
|
+
const user = getUserFromCookie<AppUser>(cookies);
|
|
106
|
+
return { user };
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Server endpoints (`src/routes/api/auth/login/+server.ts`)
|
|
111
|
+
Set the session cookie upon credentials verification:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { auth } from "$lib/server/auth.js";
|
|
115
|
+
import { json } from "@sveltejs/kit";
|
|
116
|
+
|
|
117
|
+
export const POST = async ({ cookies, request }) => {
|
|
118
|
+
const credentials = await request.json();
|
|
119
|
+
|
|
120
|
+
// 1. Verify credentials with your database
|
|
121
|
+
const dbUser = await verifyCredentials(credentials);
|
|
122
|
+
if (!dbUser) {
|
|
123
|
+
return new Response("Invalid credentials", { status: 401 });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2. setSession creates the JWT, embeds it in user.token, and writes the cookie
|
|
127
|
+
const sessionUser = await auth.login(cookies, {
|
|
128
|
+
id: dbUser.id,
|
|
129
|
+
email: dbUser.email,
|
|
130
|
+
name: dbUser.name
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return json({ success: true, user: sessionUser });
|
|
134
|
+
};
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 4. Client Setup (Svelte 5)
|
|
140
|
+
|
|
141
|
+
Initialize the client auth state, linking it to your `@sveltebase/sync` client:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// src/lib/auth-client.ts
|
|
145
|
+
import { createAuth } from "@sveltebase/auth/client";
|
|
146
|
+
import { sync } from "./sync-client.js"; // your Sveltebase Sync instance
|
|
147
|
+
import type { AppUser } from "./server/auth.js";
|
|
148
|
+
|
|
149
|
+
export const auth = createAuth<AppUser>({
|
|
150
|
+
syncClient: sync // Handles automated channel monitoring & auto-logouts
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Layout Binding (`src/routes/+layout.svelte`)
|
|
155
|
+
Initialize the state once using a Svelte 5 reactive getter:
|
|
156
|
+
|
|
157
|
+
```svelte
|
|
158
|
+
<script lang="ts">
|
|
159
|
+
import { auth } from "$lib/auth-client";
|
|
160
|
+
let { data, children } = $props();
|
|
161
|
+
|
|
162
|
+
// Initialize once. It automatically tracks changes to data.user
|
|
163
|
+
auth.init(() => data.user);
|
|
164
|
+
</script>
|
|
165
|
+
|
|
166
|
+
{@render children()}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 5. WebSocket Sync Verification (Post-Load Security)
|
|
172
|
+
|
|
173
|
+
To run background cryptographic and database checks, register the `createAuthSync` handler on your sync server:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// src/lib/server/sync-handlers.ts
|
|
177
|
+
import { createAuthSync } from "@sveltebase/auth/server";
|
|
178
|
+
import { JWT_SECRET } from "$env/static/private";
|
|
179
|
+
import { getDB } from "./db.js";
|
|
180
|
+
|
|
181
|
+
export const handlers = [
|
|
182
|
+
createAuthSync({
|
|
183
|
+
jwtSecret: JWT_SECRET,
|
|
184
|
+
// Database check to verify if the user account is active/valid
|
|
185
|
+
verifyUser: async (user, ctx) => {
|
|
186
|
+
const db = getDB(ctx.platform);
|
|
187
|
+
const activeUser = await db.select().from(users).where(eq(users.id, user.id)).get();
|
|
188
|
+
return !!activeUser && !activeUser.isSuspended;
|
|
189
|
+
},
|
|
190
|
+
// Optional persistence hook for user mutations (e.g. auth.update)
|
|
191
|
+
onUpdate: async (userId, changes, ctx) => {
|
|
192
|
+
const db = getDB(ctx.platform);
|
|
193
|
+
return await db.update(users).set(changes).where(eq(users.id, userId)).returning();
|
|
194
|
+
}
|
|
195
|
+
}),
|
|
196
|
+
todoSync
|
|
197
|
+
];
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 6. API Reference
|
|
203
|
+
|
|
204
|
+
### Server APIs (`@sveltebase/auth`)
|
|
205
|
+
|
|
206
|
+
* **`createServerAuth<User>(config: AuthConfig)`**
|
|
207
|
+
Creates cookie management helpers.
|
|
208
|
+
* `login(cookies: Cookies, userPayload: Omit<User, 'token'>, options?): Promise<User>`: Encodes and writes session to cookie.
|
|
209
|
+
* `logout(cookies: Cookies, options?): void`: Deletes the session cookie.
|
|
210
|
+
* **`getUserFromCookie<User>(cookies: Cookies, cookieName?): User | null`**
|
|
211
|
+
Decodes the cookie payload without signature verification (fast SSR).
|
|
212
|
+
* **`getUserFromRequest<User>(request: Request, cookieName?): User | null`**
|
|
213
|
+
Parses raw cookie header on standard web requests (e.g. Workers, Durable Objects).
|
|
214
|
+
* **`getVerifiedUserFromRequest<User>(request: Request, jwtSecret: string, cookieName?): Promise<User | null>`**
|
|
215
|
+
Parses request cookies and cryptographically verifies the token signature.
|
|
216
|
+
|
|
217
|
+
### Client APIs (`@sveltebase/auth/client`)
|
|
218
|
+
|
|
219
|
+
* **`createAuth<User>(config: AuthClientConfig)`**
|
|
220
|
+
Creates the client reactive state.
|
|
221
|
+
* `get user(): User | null`: Returns the active user. Supports Svelte 5 reactive lookups.
|
|
222
|
+
* `set user(value: User | null)`: Overrides local user state (optimistic mutations).
|
|
223
|
+
* `isAuthenticated: boolean`: Helper check.
|
|
224
|
+
* `init(user: MaybeGetter<User | null>)`: Binds layout page data.
|
|
225
|
+
* `update(changes: Partial<User>): Promise<void>`: Optimistically updates user, syncs over WS, and rewrites the cookie.
|
|
226
|
+
* `logout(): Promise<void>`: Deletes session and clears IndexedDB cache.
|
|
227
|
+
|
|
228
|
+
### Sync Server Handler (`@sveltebase/auth/server`)
|
|
229
|
+
|
|
230
|
+
* **`createAuthSync(config: SyncAuthConfig)`**
|
|
231
|
+
Establishes the read-only `"users"` sync channel verifying WebSocket subscriptions.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 7. Google OAuth Integration
|
|
236
|
+
|
|
237
|
+
`@sveltebase/auth/google` provides a fully reactive Svelte 5 wrapper for the **Google Identity Services (GIS)** SDK. It allows you to embed official Google Sign-In buttons, use One Tap login, or trigger custom-styled login buttons.
|
|
238
|
+
|
|
239
|
+
### Wrapper Setup (`GoogleOAuthProvider`)
|
|
240
|
+
|
|
241
|
+
Wrap your page or layout where you intend to use Google Sign-In with the `<GoogleOAuthProvider>` and pass your Google Client ID:
|
|
242
|
+
|
|
243
|
+
```svelte
|
|
244
|
+
<script lang="ts">
|
|
245
|
+
import { GoogleOAuthProvider } from "@sveltebase/auth/google";
|
|
246
|
+
import MyLoginComponent from "./MyLoginComponent.svelte";
|
|
247
|
+
</script>
|
|
248
|
+
|
|
249
|
+
<GoogleOAuthProvider clientId="YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com">
|
|
250
|
+
<MyLoginComponent />
|
|
251
|
+
</GoogleOAuthProvider>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Standard Sign-In Button (`GoogleLogin`)
|
|
255
|
+
|
|
256
|
+
The `<GoogleLogin>` component renders the official iframe-based customizable button:
|
|
257
|
+
|
|
258
|
+
```svelte
|
|
259
|
+
<!-- MyLoginComponent.svelte -->
|
|
260
|
+
<script lang="ts">
|
|
261
|
+
import { GoogleLogin } from "@sveltebase/auth/google";
|
|
262
|
+
import type { CredentialResponse } from "@sveltebase/auth/google";
|
|
263
|
+
|
|
264
|
+
function handleSuccess(response: CredentialResponse) {
|
|
265
|
+
console.log("JWT credential token:", response.credential);
|
|
266
|
+
// Send this credential to your backend to log in or create an account!
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function handleError() {
|
|
270
|
+
console.error("Sign-In failed");
|
|
271
|
+
}
|
|
272
|
+
</script>
|
|
273
|
+
|
|
274
|
+
<GoogleLogin onSuccess={handleSuccess} onError={handleError} />
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Custom Styled Buttons (`createGoogleLogin`)
|
|
278
|
+
|
|
279
|
+
For custom-styled login buttons, use the `createGoogleLogin` helper. It must be called in a child component of `<GoogleOAuthProvider>`. It returns a trigger `login` function alongside reactive `loading` and `error` states:
|
|
280
|
+
|
|
281
|
+
```svelte
|
|
282
|
+
<!-- MyLoginComponent.svelte -->
|
|
283
|
+
<script lang="ts">
|
|
284
|
+
import { createGoogleLogin } from "@sveltebase/auth/google";
|
|
285
|
+
|
|
286
|
+
const googleAuth = createGoogleLogin({
|
|
287
|
+
flow: "implicit", // or 'auth-code'
|
|
288
|
+
scope: "email profile", // additional scopes if needed
|
|
289
|
+
onSuccess: (response) => {
|
|
290
|
+
console.log("OAuth response:", response); // contains access_token or code
|
|
291
|
+
},
|
|
292
|
+
onError: (err) => {
|
|
293
|
+
console.error("Auth error:", err);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
</script>
|
|
297
|
+
|
|
298
|
+
<button
|
|
299
|
+
onclick={() => googleAuth.login()}
|
|
300
|
+
disabled={googleAuth.loading}
|
|
301
|
+
class="custom-btn"
|
|
302
|
+
>
|
|
303
|
+
{#if googleAuth.loading}
|
|
304
|
+
Logging in...
|
|
305
|
+
{:else}
|
|
306
|
+
Login with Google (Custom UI)
|
|
307
|
+
{/if}
|
|
308
|
+
</button>
|
|
309
|
+
|
|
310
|
+
{#if googleAuth.error}
|
|
311
|
+
<p class="error">Error: {googleAuth.error.message}</p>
|
|
312
|
+
{/if}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Google One Tap (`GoogleOneTapLogin`)
|
|
316
|
+
|
|
317
|
+
Mount the `<GoogleOneTapLogin>` component to display Google's native One Tap prompt when the page loads:
|
|
318
|
+
|
|
319
|
+
```svelte
|
|
320
|
+
<script lang="ts">
|
|
321
|
+
import { GoogleOneTapLogin } from "@sveltebase/auth/google";
|
|
322
|
+
import type { CredentialResponse } from "@sveltebase/auth/google";
|
|
323
|
+
</script>
|
|
324
|
+
|
|
325
|
+
<GoogleOneTapLogin
|
|
326
|
+
onSuccess={(response: CredentialResponse) => console.log("One Tap JWT:", response.credential)}
|
|
327
|
+
onError={() => console.error("One Tap dismissed/failed")}
|
|
328
|
+
/>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Revoking/Logging Out (`googleLogout`)
|
|
332
|
+
|
|
333
|
+
To sign out the user from the current Google session and disable automatic sign-in on future visits:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { googleLogout } from "@sveltebase/auth/google";
|
|
337
|
+
|
|
338
|
+
function logout() {
|
|
339
|
+
googleLogout();
|
|
340
|
+
// ... clear local user session state
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Decoding Credentials (`decodeCredentials`)
|
|
345
|
+
|
|
346
|
+
When using the `<GoogleLogin>` or `<GoogleOneTapLogin>` components, Google returns a credential string which is a signed JWT token containing the user's profile information. You can use the `decodeCredentials` function to decode this token on either the client or server into a typed `GoogleData` object:
|
|
347
|
+
|
|
348
|
+
```svelte
|
|
349
|
+
<script lang="ts">
|
|
350
|
+
import { GoogleLogin, decodeCredentials } from "@sveltebase/auth/google";
|
|
351
|
+
import type { CredentialResponse, GoogleData } from "@sveltebase/auth/google";
|
|
352
|
+
|
|
353
|
+
function handleSuccess(response: CredentialResponse) {
|
|
354
|
+
if (response.credential) {
|
|
355
|
+
const decoded: GoogleData = decodeCredentials(response.credential);
|
|
356
|
+
console.log("Decoded user profile:", decoded);
|
|
357
|
+
console.log("User email:", decoded.email);
|
|
358
|
+
console.log("User name:", decoded.name);
|
|
359
|
+
console.log("Profile picture:", decoded.picture);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
</script>
|
|
363
|
+
|
|
364
|
+
<GoogleLogin onSuccess={handleSuccess} />
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Cryptographically Verifying ID Tokens (`verifyIdToken`)
|
|
368
|
+
|
|
369
|
+
To securely verify the authenticity of a Google Identity credential (JWT) on your server (e.g. SvelteKit `+server.ts` or form actions), use `verifyIdToken`. It fetches Google's public JWK keys and cryptographically verifies the token's RS256 signature and standard claims using the Web Crypto API.
|
|
370
|
+
|
|
371
|
+
> [!IMPORTANT]
|
|
372
|
+
> This function **does not** require a Google Client Secret. It only requires your **Google Client ID** to verify the token's audience.
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { verifyIdToken } from "@sveltebase/auth/google";
|
|
376
|
+
|
|
377
|
+
export const POST = async ({ request }) => {
|
|
378
|
+
const { credential } = await request.json();
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const payload = await verifyIdToken({
|
|
382
|
+
credential,
|
|
383
|
+
clientId: "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
console.log("Verified user email:", payload.email);
|
|
387
|
+
console.log("Verified user name:", payload.name);
|
|
388
|
+
|
|
389
|
+
// Create session, log in the user, etc.
|
|
390
|
+
return new Response(JSON.stringify({ success: true, user: payload }));
|
|
391
|
+
} catch (err: any) {
|
|
392
|
+
return new Response(err.message, { status: 400 });
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { SyncClient } from "@sveltebase/sync/client";
|
|
2
|
+
export type MaybeGetter<T> = T | (() => T);
|
|
3
|
+
export interface AuthClientConfig {
|
|
4
|
+
/**
|
|
5
|
+
* The Svelteflare Sync client instance.
|
|
6
|
+
* If provided, session verification will run automatically over the WebSocket connection.
|
|
7
|
+
*/
|
|
8
|
+
syncClient?: SyncClient<any>;
|
|
9
|
+
}
|
|
10
|
+
export declare class AuthClientState<User extends {
|
|
11
|
+
id: any;
|
|
12
|
+
}> {
|
|
13
|
+
#private;
|
|
14
|
+
constructor(config?: AuthClientConfig);
|
|
15
|
+
/**
|
|
16
|
+
* Gets the current reactive user object (evaluates getter if provided).
|
|
17
|
+
*/
|
|
18
|
+
get user(): User | null;
|
|
19
|
+
/**
|
|
20
|
+
* Overrides the current reactive user object.
|
|
21
|
+
*/
|
|
22
|
+
set user(value: User | null);
|
|
23
|
+
/**
|
|
24
|
+
* Helper check to verify if user is authenticated.
|
|
25
|
+
*/
|
|
26
|
+
get isAuthenticated(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Initializes the client-side user state.
|
|
29
|
+
* Accepts a static user object or a getter function (e.g. `() => data.user`).
|
|
30
|
+
*/
|
|
31
|
+
init(user: MaybeGetter<User | null>): void;
|
|
32
|
+
/**
|
|
33
|
+
* Updates the user profile reactively, syncs it over the WebSocket, and updates the server cookie.
|
|
34
|
+
*/
|
|
35
|
+
update(changes: Partial<User>): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Clears client state, calls the server logout endpoint to delete cookies, and wipes local IndexedDB caches.
|
|
38
|
+
*/
|
|
39
|
+
logout(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates client-side reactive auth state.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createAuth<User extends {
|
|
45
|
+
id: any;
|
|
46
|
+
}>(config?: AuthClientConfig): AuthClientState<User>;
|
|
47
|
+
//# sourceMappingURL=auth.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.svelte.d.ts","sourceRoot":"","sources":["../../src/client/auth.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;CAC9B;AAYD,qBAAa,eAAe,CAAC,IAAI,SAAS;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE;;gBAMvC,MAAM,CAAC,EAAE,gBAAgB;IAIrC;;OAEG;IACH,IAAI,IAAI,IAAI,IAAI,GAAG,IAAI,CAMtB;IAED;;OAEG;IACH,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAE1B;IAED;;OAEG;IACH,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,GAAG,IAAI,CAAC;IAyCnC;;OAEG;IACG,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC;IA4BnC;;OAEG;IACG,MAAM;CAWb;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,SAAS;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAAC,IAAI,CAAC,CAErG"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
async function clearSyncData(sync) {
|
|
2
|
+
if (sync.db && typeof sync.db.tables !== "undefined") {
|
|
3
|
+
try {
|
|
4
|
+
await Promise.all(sync.db.tables.map((table) => table.clear()));
|
|
5
|
+
}
|
|
6
|
+
catch (err) {
|
|
7
|
+
console.error("Failed to clear local database tables", err);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class AuthClientState {
|
|
12
|
+
#userGetter = $state(null);
|
|
13
|
+
#localOverride = $state(undefined);
|
|
14
|
+
#syncClient;
|
|
15
|
+
#usersQuery = $state(null);
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.#syncClient = config?.syncClient;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Gets the current reactive user object (evaluates getter if provided).
|
|
21
|
+
*/
|
|
22
|
+
get user() {
|
|
23
|
+
if (this.#localOverride !== undefined) {
|
|
24
|
+
return this.#localOverride;
|
|
25
|
+
}
|
|
26
|
+
const val = this.#userGetter;
|
|
27
|
+
return typeof val === "function" ? val() : val;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Overrides the current reactive user object.
|
|
31
|
+
*/
|
|
32
|
+
set user(value) {
|
|
33
|
+
this.#localOverride = value;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Helper check to verify if user is authenticated.
|
|
37
|
+
*/
|
|
38
|
+
get isAuthenticated() {
|
|
39
|
+
return this.user !== null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Initializes the client-side user state.
|
|
43
|
+
* Accepts a static user object or a getter function (e.g. `() => data.user`).
|
|
44
|
+
*/
|
|
45
|
+
init(user) {
|
|
46
|
+
this.#userGetter = user;
|
|
47
|
+
this.#localOverride = undefined; // Reset local override on new init
|
|
48
|
+
// Reactive effect to reset local override whenever the server's user getter updates
|
|
49
|
+
$effect(() => {
|
|
50
|
+
// Access the getter reactively so Svelte tracks this dependency
|
|
51
|
+
const serverUser = typeof this.#userGetter === "function"
|
|
52
|
+
? this.#userGetter()
|
|
53
|
+
: this.#userGetter;
|
|
54
|
+
// Clear any local override (like query error overrides) whenever the server session changes
|
|
55
|
+
this.#localOverride = undefined;
|
|
56
|
+
});
|
|
57
|
+
// Set up the internal sync channel subscription if syncClient is provided and not already initialized.
|
|
58
|
+
// Calling this here is safe because init() is executed during layout component initialization.
|
|
59
|
+
if (this.#syncClient && !this.#usersQuery) {
|
|
60
|
+
const sync = this.#syncClient;
|
|
61
|
+
// Create a live query monitoring the "users" channel
|
|
62
|
+
this.#usersQuery = sync.table("users").liveQuery((t) => t.toArray());
|
|
63
|
+
// Svelte 5 reactive effect to auto-logout on authentication failure
|
|
64
|
+
$effect(() => {
|
|
65
|
+
// Read the user reactively so Svelte tracks this effect dependency
|
|
66
|
+
const activeUser = this.user;
|
|
67
|
+
const query = this.#usersQuery;
|
|
68
|
+
if (query && query.status === "error") {
|
|
69
|
+
console.warn("WebSocket session verification failed: logging out.");
|
|
70
|
+
this.#localOverride = null;
|
|
71
|
+
fetch("/api/auth/logout", { method: "POST" }).catch(() => {
|
|
72
|
+
// Ignore fetch errors if offline or route not mounted
|
|
73
|
+
});
|
|
74
|
+
clearSyncData(sync);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Updates the user profile reactively, syncs it over the WebSocket, and updates the server cookie.
|
|
81
|
+
*/
|
|
82
|
+
async update(changes) {
|
|
83
|
+
const activeUser = this.user;
|
|
84
|
+
if (!activeUser)
|
|
85
|
+
return;
|
|
86
|
+
const updatedUser = { ...activeUser, ...changes };
|
|
87
|
+
this.#localOverride = updatedUser;
|
|
88
|
+
// 1. Update over Sync WebSocket
|
|
89
|
+
if (this.#syncClient) {
|
|
90
|
+
try {
|
|
91
|
+
await this.#syncClient.table("users").put(activeUser.id, changes);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error("Failed to update user profile over Sync WebSocket:", err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 2. Update the server cookie
|
|
98
|
+
try {
|
|
99
|
+
await fetch("/api/auth/update", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
body: JSON.stringify(changes),
|
|
102
|
+
headers: { "Content-Type": "application/json" }
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
console.error("Failed to update server session cookie:", err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Clears client state, calls the server logout endpoint to delete cookies, and wipes local IndexedDB caches.
|
|
111
|
+
*/
|
|
112
|
+
async logout() {
|
|
113
|
+
this.#localOverride = null;
|
|
114
|
+
try {
|
|
115
|
+
await fetch("/api/auth/logout", { method: "POST" });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Ignore network failure on logout fetch
|
|
119
|
+
}
|
|
120
|
+
if (this.#syncClient) {
|
|
121
|
+
clearSyncData(this.#syncClient);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Creates client-side reactive auth state.
|
|
127
|
+
*/
|
|
128
|
+
export function createAuth(config) {
|
|
129
|
+
return new AuthClientState(config);
|
|
130
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./auth.svelte.js";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getGoogleOAuthContext } from './context.svelte.js';
|
|
3
|
+
import type { CredentialResponse, MomentNotification } from './types';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
onSuccess: (credentialResponse: CredentialResponse) => void;
|
|
7
|
+
onError?: () => void;
|
|
8
|
+
promptMomentNotification?: (notification: MomentNotification) => void;
|
|
9
|
+
useOneTap?: boolean;
|
|
10
|
+
theme?: 'outline' | 'filled_blue' | 'filled_black';
|
|
11
|
+
size?: 'small' | 'medium' | 'large';
|
|
12
|
+
text?: 'signin_with' | 'signup_with' | 'signin' | 'signup' | 'continue_with' | 'signin_with_google';
|
|
13
|
+
shape?: 'rectangular' | 'pill' | 'circle' | 'square';
|
|
14
|
+
logo_alignment?: 'left' | 'center';
|
|
15
|
+
width?: string;
|
|
16
|
+
locale?: string;
|
|
17
|
+
auto_select?: boolean;
|
|
18
|
+
cancel_on_tap_outside?: boolean;
|
|
19
|
+
nonce?: string;
|
|
20
|
+
hosted_domain?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
onSuccess,
|
|
25
|
+
onError,
|
|
26
|
+
promptMomentNotification,
|
|
27
|
+
useOneTap = false,
|
|
28
|
+
theme = 'outline',
|
|
29
|
+
size = 'large',
|
|
30
|
+
text = 'signin_with',
|
|
31
|
+
shape = 'rectangular',
|
|
32
|
+
logo_alignment = 'left',
|
|
33
|
+
width,
|
|
34
|
+
locale,
|
|
35
|
+
auto_select = false,
|
|
36
|
+
cancel_on_tap_outside = true,
|
|
37
|
+
nonce,
|
|
38
|
+
hosted_domain
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const ctx = getGoogleOAuthContext();
|
|
42
|
+
let container = $state<HTMLDivElement | null>(null);
|
|
43
|
+
|
|
44
|
+
$effect(() => {
|
|
45
|
+
if (!ctx.isLoaded || !container) return;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
window.google.accounts.id.initialize({
|
|
49
|
+
client_id: ctx.clientId,
|
|
50
|
+
callback: (response) => {
|
|
51
|
+
if (response.credential) {
|
|
52
|
+
onSuccess(response);
|
|
53
|
+
} else {
|
|
54
|
+
onError?.();
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
auto_select,
|
|
58
|
+
cancel_on_tap_outside,
|
|
59
|
+
nonce,
|
|
60
|
+
hosted_domain
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
window.google.accounts.id.renderButton(container, {
|
|
64
|
+
theme,
|
|
65
|
+
size,
|
|
66
|
+
text,
|
|
67
|
+
shape,
|
|
68
|
+
logo_alignment,
|
|
69
|
+
width,
|
|
70
|
+
locale
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (useOneTap) {
|
|
74
|
+
window.google.accounts.id.prompt(promptMomentNotification);
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('Error initializing Google Login button:', err);
|
|
78
|
+
onError?.();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<div bind:this={container}></div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CredentialResponse, MomentNotification } from './types';
|
|
2
|
+
interface Props {
|
|
3
|
+
onSuccess: (credentialResponse: CredentialResponse) => void;
|
|
4
|
+
onError?: () => void;
|
|
5
|
+
promptMomentNotification?: (notification: MomentNotification) => void;
|
|
6
|
+
useOneTap?: boolean;
|
|
7
|
+
theme?: 'outline' | 'filled_blue' | 'filled_black';
|
|
8
|
+
size?: 'small' | 'medium' | 'large';
|
|
9
|
+
text?: 'signin_with' | 'signup_with' | 'signin' | 'signup' | 'continue_with' | 'signin_with_google';
|
|
10
|
+
shape?: 'rectangular' | 'pill' | 'circle' | 'square';
|
|
11
|
+
logo_alignment?: 'left' | 'center';
|
|
12
|
+
width?: string;
|
|
13
|
+
locale?: string;
|
|
14
|
+
auto_select?: boolean;
|
|
15
|
+
cancel_on_tap_outside?: boolean;
|
|
16
|
+
nonce?: string;
|
|
17
|
+
hosted_domain?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const GoogleLogin: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type GoogleLogin = ReturnType<typeof GoogleLogin>;
|
|
21
|
+
export default GoogleLogin;
|
|
22
|
+
//# sourceMappingURL=GoogleLogin.svelte.d.ts.map
|