dexie-cloud-addon 4.2.4 → 4.3.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.
Files changed (35) hide show
  1. package/TODO-SOCIALAUTH.md +545 -0
  2. package/dexie-cloud-import.json +2 -1
  3. package/dist/modern/DexieCloudAPI.d.ts +4 -0
  4. package/dist/modern/DexieCloudOptions.d.ts +20 -0
  5. package/dist/modern/authentication/exchangeOAuthCode.d.ts +23 -0
  6. package/dist/modern/authentication/fetchAuthProviders.d.ts +14 -0
  7. package/dist/modern/authentication/handleOAuthCallback.d.ts +57 -0
  8. package/dist/modern/authentication/interactWithUser.d.ts +19 -0
  9. package/dist/modern/authentication/oauthLogin.d.ts +37 -0
  10. package/dist/modern/default-ui/AuthProviderButton.d.ts +21 -0
  11. package/dist/modern/default-ui/LoginDialog.d.ts +5 -2
  12. package/dist/modern/default-ui/ProviderSelectionDialog.d.ts +7 -0
  13. package/dist/modern/dexie-cloud-addon.js +719 -169
  14. package/dist/modern/dexie-cloud-addon.js.map +1 -1
  15. package/dist/modern/dexie-cloud-addon.min.js +1 -1
  16. package/dist/modern/dexie-cloud-addon.min.js.gz +0 -0
  17. package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
  18. package/dist/modern/errors/OAuthError.d.ts +10 -0
  19. package/dist/modern/service-worker.js +720 -170
  20. package/dist/modern/service-worker.js.map +1 -1
  21. package/dist/modern/service-worker.min.js +1 -1
  22. package/dist/modern/service-worker.min.js.map +1 -1
  23. package/dist/modern/types/DXCUserInteraction.d.ts +24 -1
  24. package/dist/umd/dexie-cloud-addon.js +2181 -2444
  25. package/dist/umd/dexie-cloud-addon.js.gz +0 -0
  26. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  27. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  28. package/dist/umd/dexie-cloud-addon.min.js.gz +0 -0
  29. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  30. package/dist/umd/service-worker.js +2029 -2292
  31. package/dist/umd/service-worker.js.map +1 -1
  32. package/dist/umd/service-worker.min.js +1 -1
  33. package/dist/umd/service-worker.min.js.map +1 -1
  34. package/oauth_flow.md +299 -0
  35. package/package.json +10 -7
@@ -0,0 +1,545 @@
1
+ # Social Authentication for Dexie Cloud
2
+
3
+ ## Overview
4
+
5
+ This feature adds support for OAuth 2.0 social login providers (Google, GitHub, Microsoft, Apple, and custom OAuth2) as an alternative to the existing OTP (One-Time Password) email authentication in Dexie Cloud.
6
+
7
+ **Key Design Principle**: The Dexie Cloud server acts as an OAuth broker, handling all provider interactions including the OAuth callback. The client library (dexie-cloud-addon) never receives provider tokens - only Dexie Cloud authorization codes which are exchanged for Dexie Cloud tokens.
8
+
9
+ ### Related Files
10
+
11
+ - **Detailed flow diagram**: [oauth_flow.md](oauth_flow.md) - Sequence diagrams and detailed protocol description
12
+ - **Server implementation**: `/Users/daw/repos/dexie-cloud/libs/dexie-cloud-server`
13
+ - `src/api/oauth/registerOAuthEndpoints.ts` - OAuth endpoints
14
+ - `src/api/oauth/oauth-helpers.ts` - Provider exchange logic
15
+ - `src/api/registerTokenEndpoint.ts` - Token endpoint (authorization_code grant)
16
+ - `web-templates/oauth-callback.handlebars` - Callback page template
17
+
18
+ ### Flow Summary
19
+
20
+ 1. **Client** fetches available auth providers from `GET /auth-providers`
21
+ 2. **Client** opens popup/redirect to `GET /oauth/login/:provider`
22
+ 3. **Dexie Cloud Server** redirects to OAuth provider and handles callback at `/oauth/callback/:provider`
23
+ 4. **Server** exchanges provider code for tokens, verifies email, generates single-use Dexie auth code
24
+ 5. **Server** delivers auth code to client via postMessage (popup), custom URL scheme (Capacitor), or redirect
25
+ 6. **Client** exchanges Dexie auth code for tokens via `POST /token` with `grant_type: "authorization_code"`
26
+
27
+ ### Supported Providers
28
+ - **Google** - OpenID Connect with PKCE
29
+ - **GitHub** - OAuth 2.0 (client secret only)
30
+ - **Microsoft** - OpenID Connect with PKCE
31
+ - **Apple** - Sign in with Apple (form_post response mode)
32
+ - **Custom OAuth2** - Configurable endpoints for self-hosted identity providers
33
+
34
+ ### Client Delivery Methods
35
+
36
+ The library must support multiple integration patterns:
37
+
38
+ | Method | Use Case | Delivery Mechanism |
39
+ |--------|----------|-------------------|
40
+ | **Popup** | Web SPAs (recommended) | `postMessage` with `type: 'dexie:oauthResult'` |
41
+ | **Custom URL Scheme** | Capacitor/Native apps | Deep link redirect (e.g., `myapp://oauth-callback`) |
42
+ | **Full Page Redirect** | Web without popup support | HTTP redirect with query params |
43
+
44
+ ---
45
+
46
+ ## Server-Side Tasks (dexie-cloud-server)
47
+
48
+ ### ✅ Completed
49
+
50
+ - [x] **OAuth provider configuration type** (`OAuthProviderConfig`)
51
+ - Supports `google`, `github`, `apple`, `microsoft`, `custom-oauth2`
52
+ - Configurable `clientId`, `clientSecret`, `scopes`, `enabled`
53
+ - Custom OAuth2 config for self-hosted providers (authorization/token/userinfo endpoints)
54
+ - `name` and `displayName` for custom providers
55
+ - `iconUrl` for custom provider icons
56
+
57
+ - [x] **`GET /auth-providers` endpoint**
58
+ - Returns list of enabled OAuth providers (without secrets) and OTP status
59
+ - Includes `type`, `name`, `displayName`, `iconUrl`, `scopes` per provider
60
+ - Default icons served from `/static/icons/` for built-in providers
61
+
62
+ - [x] **`GET /oauth/login/:provider` endpoint**
63
+ - Accepts `redirect_uri` parameter for postMessage target / deep link
64
+ - Validates `redirect_uri` against whitelisted origins (via `npx dexie-cloud whitelist`)
65
+ - Generates state and PKCE challenge
66
+ - Stores `state`, `codeVerifier`, `spaOrigin`, `clientRedirectUri` in challenges table
67
+ - Redirects to OAuth provider with server callback URL (`/oauth/callback/:provider`)
68
+
69
+ - [x] **`GET /oauth/callback/:provider` endpoint** (NEW)
70
+ - Receives callback from OAuth provider
71
+ - Verifies state against stored challenges
72
+ - Exchanges provider code for tokens (server-side)
73
+ - Fetches and verifies user info (email must be verified)
74
+ - Generates single-use Dexie Cloud authorization code (64 chars, 5 min TTL)
75
+ - Stores user claims with auth code in challenges table
76
+ - Returns HTML page that delivers auth code to client via:
77
+ - `postMessage` to `window.opener` (popup flow)
78
+ - Redirect to custom URL scheme (Capacitor/native)
79
+ - Redirect to HTTPS URL (full page redirect flow)
80
+
81
+ - [x] **`POST /token` with `grant_type: "authorization_code"`**
82
+ - Validates Dexie auth code (not provider code)
83
+ - Verifies code is `dexie_oauth` type and within 5 min TTL
84
+ - Extracts stored user claims (sub, email, name, picture, etc.)
85
+ - Generates Dexie Cloud access/refresh tokens
86
+ - `sub` format: `{provider}:{user_id}`
87
+
88
+ - [x] **OAuth helper functions** (`oauth-helpers.ts`)
89
+ - `getProviderEndpoints()` - Returns auth/token/userinfo URLs per provider
90
+ - `shouldUsePKCE()` - Determines if provider supports PKCE
91
+ - `exchangeCodeForTokens()` - Exchanges code for provider tokens
92
+ - `fetchUserInfo()` - Fetches and normalizes user info from provider
93
+
94
+ - [x] **Crypto utilities** (`crypto-utils.ts`)
95
+ - `generateRandomString()` - For state, PKCE code_verifier, and auth codes
96
+
97
+ - [x] **Handlebars template** (`oauth-callback.handlebars`)
98
+ - Renders callback page with postMessage/redirect logic
99
+ - Handles error display
100
+
101
+ - [x] **Configuration GUI in dexie-cloud-manager**
102
+ - `OAuthProviderDialog.tsx` - UI for configuring OAuth providers per database
103
+
104
+ ---
105
+
106
+ ## Client-Side Tasks (dexie-cloud-addon)
107
+
108
+ > **Note**: dexie-cloud-addon is a **library**, not an app. It must support various frameworks including Next.js, Vite, Svelte, SvelteKit, Capacitor, React Native, etc.
109
+
110
+ ### Backward Compatibility Requirements
111
+
112
+ The implementation must be backward compatible in several scenarios:
113
+
114
+ #### 1. Apps with Custom Login GUI (not using default-ui)
115
+
116
+ Apps that implement their own login UI by subscribing to `db.cloud.userInteraction` must continue to work without changes. The new `DXCProviderSelection` type should only be emitted when:
117
+ - The server has OAuth providers configured AND enabled
118
+ - The client has NOT opted out via `db.cloud.configure({ socialAuth: false })`
119
+
120
+ **Solution**: Fetch `/auth-providers` first. If it returns providers, emit `DXCProviderSelection`. If empty or fails, emit `DXCEmailPrompt` as before. Apps with custom GUIs that don't handle `DXCProviderSelection` can:
121
+ - Add `socialAuth: false` to their config to disable it
122
+ - Or update their UI to handle the new type
123
+
124
+ #### 2. requireAuth with Unknown Server Capabilities
125
+
126
+ When `requireAuth: true`, the client must authenticate before accessing the database. The `/auth-providers` endpoint must be accessible **without authentication** (public read-only endpoint) so the client can discover available auth methods.
127
+
128
+ **Note**: The server already implements `/auth-providers` as a public endpoint (uses `readRateLimiter`, no auth required).
129
+
130
+ #### 3. On-Prem Customers with Older Server Versions
131
+
132
+ Customers running older dexie-cloud-server versions won't have the `/auth-providers` endpoint. The client must handle this gracefully:
133
+
134
+ **Solution**:
135
+ - Catch 404/network errors from `/auth-providers`
136
+ - Fall back to OTP-only flow (existing `DXCEmailPrompt` behavior)
137
+ - Never emit `DXCProviderSelection` if endpoint unavailable
138
+ - Log a debug message for troubleshooting
139
+
140
+ #### 4. Client Opt-Out
141
+
142
+ Even if the server has OAuth configured, apps may want to disable it client-side (e.g., during migration, or for specific deployments).
143
+
144
+ **Solution**: Add `socialAuth` option to `DexieCloudOptions`:
145
+ ```typescript
146
+ interface DexieCloudOptions {
147
+ // ... existing options
148
+
149
+ /** Enable social/OAuth authentication.
150
+ * - true: Fetch providers from server, show if available (default)
151
+ * - false: Disable OAuth, always use OTP flow
152
+ */
153
+ socialAuth?: boolean; // Default: true
154
+ }
155
+ ```
156
+
157
+ ### ✅ Completed
158
+
159
+ #### Types in dexie-cloud-common
160
+
161
+ - [x] **Create `OAuthProviderInfo` type** (`libs/dexie-cloud-common/src/OAuthProviderInfo.ts`)
162
+ - [x] **Create `AuthProvidersResponse` type** (`libs/dexie-cloud-common/src/AuthProvidersResponse.ts`)
163
+ - [x] **Create `OAuthResultMessage` type** (`libs/dexie-cloud-common/src/OAuthResultMessage.ts`)
164
+ - [x] **Extend `TokenRequest` types** - Added `AuthorizationCodeTokenRequest` (`libs/dexie-cloud-common/src/AuthorizationCodeTokenRequest.ts`)
165
+
166
+ ### 🔲 TODO
167
+
168
+ #### Types in dexie-cloud-common
169
+
170
+ > Types that reflect server API data should be placed in `dexie-cloud-common` (shared between server and client).
171
+
172
+ - [x] **Create `OAuthProviderInfo` type** (`libs/dexie-cloud-common`)
173
+ ```typescript
174
+ interface OAuthProviderInfo {
175
+ type: 'google' | 'github' | 'microsoft' | 'apple' | 'custom-oauth2';
176
+ name: string; // Provider identifier (e.g., 'google' or custom name)
177
+ displayName: string; // Human-readable name
178
+ iconUrl?: string; // URL to provider icon
179
+ scopes?: string[];
180
+ }
181
+ ```
182
+
183
+ - [x] **Create `AuthProvidersResponse` type** (`libs/dexie-cloud-common`)
184
+ ```typescript
185
+ interface AuthProvidersResponse {
186
+ providers: OAuthProviderInfo[];
187
+ otpEnabled: boolean;
188
+ }
189
+ ```
190
+
191
+ - [x] **Create `OAuthResultMessage` type** (`libs/dexie-cloud-common`)
192
+ ```typescript
193
+ interface OAuthResultMessage {
194
+ type: 'dexie:oauthResult';
195
+ code?: string; // Dexie auth code
196
+ error?: string; // Error message
197
+ provider: string;
198
+ state: string;
199
+ }
200
+ ```
201
+
202
+ - [x] **Extend `TokenRequest` types** (`libs/dexie-cloud-common`)
203
+ - Add `AuthorizationCodeTokenRequest` type for `grant_type: "authorization_code"`
204
+
205
+ #### Types and Interfaces in dexie-cloud-addon
206
+
207
+ - [x] **Extend `LoginHints` interface** (`src/DexieCloudAPI.ts`)
208
+ - Add `provider?: string` - Provider name to initiate OAuth flow
209
+ - Add `oauthCode?: string` - Dexie auth code received from callback
210
+ - Keep existing `email`, `userId`, `grant_type`, `otpId`, `otp`
211
+
212
+ - [x] **Add `DXCProviderSelection` to `DXCUserInteraction`** (`src/types/DXCUserInteraction.ts`)
213
+
214
+ The current `DXCUserInteraction` only supports text input fields. For OAuth, we need clickable provider buttons. Add a new interaction type:
215
+
216
+ ```typescript
217
+ /** When the system needs user to select a login method (OAuth provider or OTP) */
218
+ export interface DXCProviderSelection {
219
+ type: 'provider-selection';
220
+ title: string;
221
+ alerts: DXCAlert[];
222
+ providers: OAuthProviderInfo[]; // Available OAuth providers
223
+ otpEnabled: boolean; // Whether email/OTP option is available
224
+ fields: {}; // Empty - no text fields
225
+ submitLabel?: undefined; // No submit button - provider buttons instead
226
+ cancelLabel: string;
227
+ onSelectProvider: (providerName: string) => void;
228
+ onSelectOtp: () => void; // User chose email/OTP instead
229
+ onCancel: () => void;
230
+ }
231
+ ```
232
+
233
+ Update the union type:
234
+ ```typescript
235
+ export type DXCUserInteraction =
236
+ | DXCGenericUserInteraction
237
+ | DXCEmailPrompt
238
+ | DXCOTPPrompt
239
+ | DXCMessageAlert
240
+ | DXCLogoutConfirmation
241
+ | DXCProviderSelection; // NEW
242
+ ```
243
+
244
+ #### Core Authentication Flow
245
+
246
+ - [x] **Create `fetchAuthProviders()` function** (`src/authentication/fetchAuthProviders.ts`)
247
+ - Fetches `GET /auth-providers` from database URL
248
+ - Returns `AuthProvidersResponse`
249
+ - **Handles failures gracefully**:
250
+ - 404 → Return `{ providers: [], otpEnabled: true }` (old server)
251
+ - Network error → Return `{ providers: [], otpEnabled: true }`
252
+ - Log debug message for troubleshooting
253
+ - Cache result with TTL to avoid repeated requests
254
+ - Respects `socialAuth: false` option (returns empty providers without fetching)
255
+
256
+ - [x] **Update authentication flow to check providers first**
257
+ - Before prompting for email, call `fetchAuthProviders()`
258
+ - If `providers.length > 0`:
259
+ - Emit `DXCProviderSelection` interaction
260
+ - Wait for user to select provider or OTP
261
+ - If `providers.length === 0` or `otpEnabled` only:
262
+ - Emit `DXCEmailPrompt` as before (existing behavior)
263
+
264
+ - [x] **Create `oauthLogin()` function** (`src/authentication/oauthLogin.ts`)
265
+ - Opens popup window to `/oauth/login/:provider`
266
+ - Listens for `postMessage` with `type: 'dexie:oauthResult'`
267
+ - Validates message origin
268
+ - Returns auth code or throws on error/cancel
269
+ - Handles popup blocked scenario (fallback to redirect?)
270
+
271
+ - [x] **Create `exchangeOAuthCode()` function** (`src/authentication/exchangeOAuthCode.ts`)
272
+ - Sends `POST /token` with:
273
+ ```json
274
+ {
275
+ "grant_type": "authorization_code",
276
+ "code": "<DEXIE_AUTH_CODE>",
277
+ "public_key": "<SPA_PUBLIC_KEY>",
278
+ "scopes": ["ACCESS_DB"]
279
+ }
280
+ ```
281
+ - Returns `TokenFinalResponse`
282
+
283
+ - [x] **Update `login()` function** (`src/authentication/login.ts`)
284
+ - If `hints.provider` is set, use OAuth flow instead of OTP
285
+ - If `hints.oauthCode` is set, exchange code directly (for redirect/deep link flows)
286
+ - Integrate with existing `authenticate()` flow
287
+
288
+ - [x] **Create `handleOAuthCallback()` function** (`src/authentication/handleOAuthCallback.ts`)
289
+ - For redirect/deep link flows where app needs to handle the callback
290
+ - Parses `code`, `provider`, `state`, `error` from URL
291
+ - Completes login flow by calling `login({ oauthCode, provider })`
292
+ - Can be called by apps on their callback route
293
+
294
+ #### Default UI Components
295
+
296
+ - [x] **Create `ProviderSelectionDialog` component** (`src/default-ui/ProviderSelectionDialog.tsx`)
297
+ - Handles the new `DXCProviderSelection` interaction type
298
+ - Renders OAuth provider buttons
299
+ - Renders "Continue with email" button if `otpEnabled`
300
+ - Visual divider ("or") between options
301
+
302
+ - [x] **Create `AuthProviderButton` component** (`src/default-ui/AuthProviderButton.tsx`)
303
+ - Renders button for a single OAuth provider
304
+ - Displays provider icon and name
305
+ - Follows provider branding guidelines (Google, Apple, Microsoft have strict rules)
306
+ - Supports light/dark mode
307
+
308
+ - [x] **Update `LoginDialog.tsx`**
309
+ - Handle OAuth popup flow when provider button clicked (via `onSelectProvider`)
310
+ - Handle loading/error states
311
+
312
+ - [x] **Update main dialog renderer** (`src/default-ui/index.tsx`)
313
+ - Render `ProviderSelectionDialog` when `type === 'provider-selection'`
314
+
315
+ - [x] **Update `Styles.ts`**
316
+ - Add styles for OAuth provider buttons
317
+ - Provider-specific button colors (Google blue, GitHub black, etc.)
318
+ - Dark mode variants
319
+
320
+ #### Capacitor / Mobile Support
321
+
322
+ - [x] **Document deep link handling** (not library code, but patterns)
323
+ - Show how apps should register custom URL scheme
324
+ - Provide example of handling `appUrlOpen` event
325
+ - Call `db.cloud.login({ oauthCode, provider })` from handler
326
+
327
+ - [x] **Support `redirect_uri` configuration**
328
+ - Allow apps to configure their redirect URI in `db.cloud.configure()`
329
+ - Use for Capacitor apps with custom URL schemes
330
+ - Use for full-page redirect flows
331
+
332
+ #### DexieCloudOptions Extension
333
+
334
+ - [x] **Add OAuth-related options** (`src/DexieCloudOptions.ts`)
335
+ ```typescript
336
+ interface DexieCloudOptions {
337
+ // ... existing options
338
+
339
+ /** Enable social/OAuth authentication.
340
+ * - true (default): Fetch providers from server, show if available
341
+ * - false: Disable OAuth, always use OTP flow
342
+ *
343
+ * Use `false` for backward compatibility if your custom login UI
344
+ * doesn't handle the `DXCProviderSelection` interaction type yet.
345
+ */
346
+ socialAuth?: boolean;
347
+
348
+ /** Redirect URI for OAuth callback (Capacitor/redirect flows).
349
+ * For web popups, this is auto-detected from window.location.origin.
350
+ * Required for:
351
+ * - Capacitor apps: 'myapp://oauth-callback'
352
+ * - Full-page redirect flows: 'https://myapp.com/oauth-callback'
353
+ */
354
+ oauthRedirectUri?: string;
355
+
356
+ /** Use popup window for OAuth flow.
357
+ * - true (default for web): Opens OAuth in popup, uses postMessage
358
+ * - false: Opens OAuth in same window or system browser (Capacitor)
359
+ */
360
+ oauthPopup?: boolean;
361
+ }
362
+ ```
363
+
364
+ #### Error Handling
365
+
366
+ - [x] **Create `OAuthError` class** (`src/errors/OAuthError.ts`)
367
+ - Extends existing error handling
368
+ - Error codes: `popup_blocked`, `popup_closed`, `access_denied`, `invalid_state`, `email_not_verified`, `expired_code`
369
+ - User-friendly messages
370
+
371
+ - [x] **Handle common OAuth errors in UI**
372
+ - Popup blocked → show message with manual retry button
373
+ - User cancelled → silent failure or subtle message
374
+ - Provider error → show error alert
375
+
376
+ #### Testing
377
+
378
+ - [ ] **Unit tests for OAuth flow**
379
+ - Mock postMessage events
380
+ - Test popup lifecycle handling
381
+ - Test error scenarios
382
+
383
+ - [ ] **Integration tests**
384
+ - Test with mock OAuth callback page
385
+ - Test token exchange
386
+
387
+ **Testing tip**: The dexie-cloud-todo-app sample (`samples/dexie-cloud-todo-app`) can be used for manual testing. Configure OAuth providers in dexie-cloud-manager for your test database.
388
+
389
+ #### Documentation
390
+
391
+ - [ ] **Update README.md**
392
+ - Document OAuth login: `db.cloud.login({ provider: 'google' })`
393
+ - Show Capacitor integration pattern
394
+ - Explain redirect vs popup flows
395
+
396
+ ---
397
+
398
+ ## Client Integration Patterns
399
+
400
+ ### Web SPA (Popup Flow - Default)
401
+
402
+ ```typescript
403
+ // User clicks "Login with Google"
404
+ await db.cloud.login({ provider: 'google' });
405
+ // Popup opens, user authenticates, popup closes, user is logged in
406
+ ```
407
+
408
+ ### Capacitor / Native App
409
+
410
+ ```typescript
411
+ // In db.cloud.configure()
412
+ db.cloud.configure({
413
+ databaseUrl: 'https://mydb.dexie.cloud',
414
+ oauthRedirectUri: 'myapp://oauth-callback',
415
+ oauthPopup: false
416
+ });
417
+
418
+ // Handle deep link in app
419
+ App.addListener('appUrlOpen', async ({ url }) => {
420
+ const params = new URL(url).searchParams;
421
+ const code = params.get('code');
422
+ const provider = params.get('provider');
423
+ if (code && provider) {
424
+ await db.cloud.login({ oauthCode: code, provider });
425
+ }
426
+ });
427
+
428
+ // Initiate login (opens system browser)
429
+ await db.cloud.login({ provider: 'google' });
430
+ ```
431
+
432
+ ### Full Page Redirect (No Popup)
433
+
434
+ ```typescript
435
+ db.cloud.configure({
436
+ databaseUrl: 'https://mydb.dexie.cloud',
437
+ oauthRedirectUri: 'https://myapp.com/oauth-callback',
438
+ oauthPopup: false
439
+ });
440
+
441
+ // On /oauth-callback page:
442
+ const params = new URLSearchParams(window.location.search);
443
+ const code = params.get('code');
444
+ const provider = params.get('provider');
445
+ if (code && provider) {
446
+ await db.cloud.login({ oauthCode: code, provider });
447
+ window.history.replaceState({}, '', '/'); // Clean URL
448
+ }
449
+ ```
450
+
451
+ ---
452
+
453
+ ## Architecture Diagram
454
+
455
+ ```
456
+ ┌──────────────────────────────────────────────────────────────────────────────┐
457
+ │ CLIENT (dexie-cloud-addon) │
458
+ │ │
459
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────────────┐ │
460
+ │ │ LoginDialog │───▶│ oauthLogin()│───▶│ Opens popup to │ │
461
+ │ │ (default UI)│ │ │ │ /oauth/login/:provider │ │
462
+ │ └─────────────┘ └─────────────┘ └─────────────────────────────────┘ │
463
+ │ │ │
464
+ │ │ Listens for postMessage │
465
+ │ ▼ │
466
+ │ ┌─────────────┐ │
467
+ │ │ Receives │ │
468
+ │ │ auth code │ │
469
+ │ └──────┬──────┘ │
470
+ │ │ │
471
+ │ ▼ │
472
+ │ ┌─────────────────┐ ┌─────────────────────────────┐ │
473
+ │ │exchangeOAuthCode│───▶│ POST /token │ │
474
+ │ │ │ │ grant_type: authorization_ │ │
475
+ │ │ │◀───│ code │ │
476
+ │ └─────────────────┘ └─────────────────────────────┘ │
477
+ │ │ │
478
+ │ ▼ │
479
+ │ ┌─────────────┐ │
480
+ │ │ User logged │ │
481
+ │ │ in! │ │
482
+ │ └─────────────┘ │
483
+ └──────────────────────────────────────────────────────────────────────────────┘
484
+
485
+ ┌──────────────────────────────────────────────────────────────────────────────┐
486
+ │ DEXIE CLOUD SERVER │
487
+ │ │
488
+ │ /oauth/login/:provider │
489
+ │ ├── Generate state, PKCE │
490
+ │ ├── Store in challenges table │
491
+ │ └── Redirect to OAuth provider │
492
+ │ │
493
+ │ /oauth/callback/:provider ◀── OAuth provider redirects here │
494
+ │ ├── Verify state │
495
+ │ ├── Exchange code for provider tokens (server-side!) │
496
+ │ ├── Fetch user info, verify email │
497
+ │ ├── Generate Dexie auth code (single-use, 5 min TTL) │
498
+ │ └── Return HTML page with postMessage/redirect │
499
+ │ │
500
+ │ POST /token (grant_type: authorization_code) │
501
+ │ ├── Validate Dexie auth code │
502
+ │ ├── Extract stored user claims │
503
+ │ └── Return Dexie Cloud access + refresh tokens │
504
+ └──────────────────────────────────────────────────────────────────────────────┘
505
+ ```
506
+
507
+ ---
508
+
509
+ ## Security Properties
510
+
511
+ - 🛡 **No provider tokens reach client** - All provider exchange happens server-side
512
+ - 🛡 **Single-use Dexie auth codes** - 5 minute TTL, deleted after use
513
+ - 🛡 **PKCE protection** - Prevents code interception (where supported)
514
+ - 🛡 **State parameter** - CSRF protection, stored server-side
515
+ - 🛡 **Origin validation** - postMessage uses captured origin, redirect_uri whitelisted
516
+ - 🛡 **Email verification enforced** - Server rejects unverified emails
517
+
518
+ ---
519
+
520
+ ## Key Files to Create/Modify
521
+
522
+ **New files in dexie-cloud-common:**
523
+ - `src/OAuthProviderInfo.ts`
524
+ - `src/AuthProvidersResponse.ts`
525
+ - `src/OAuthResultMessage.ts`
526
+ - `src/AuthorizationCodeTokenRequest.ts`
527
+
528
+ **New files in dexie-cloud-addon:**
529
+ - `src/types/DXCUserInteraction.ts` - Add `DXCProviderSelection`
530
+ - `src/authentication/fetchAuthProviders.ts`
531
+ - `src/authentication/oauthLogin.ts`
532
+ - `src/authentication/exchangeOAuthCode.ts`
533
+ - `src/authentication/handleOAuthCallback.ts`
534
+ - `src/errors/OAuthError.ts`
535
+ - `src/default-ui/ProviderSelectionDialog.tsx`
536
+ - `src/default-ui/AuthProviderButton.tsx`
537
+
538
+ **Files to modify:**
539
+ - `src/DexieCloudAPI.ts` - Extend `LoginHints`
540
+ - `src/DexieCloudOptions.ts` - Add OAuth options
541
+ - `src/authentication/login.ts` - Integrate OAuth flow
542
+ - `src/authentication/interactWithUser.ts` - Add `promptForProvider()` helper
543
+ - `src/default-ui/index.tsx` - Render `ProviderSelectionDialog`
544
+ - `src/default-ui/LoginDialog.tsx` - Handle provider selection callbacks
545
+ - `src/default-ui/Styles.ts` - Provider button styles
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "demoUsers": {
3
3
  "foo@demo.local": {},
4
- "bar@demo.local": {}
4
+ "bar@demo.local": {},
5
+ "issue2228@demo.local": {}
5
6
  }
6
7
  }
@@ -17,6 +17,10 @@ export interface LoginHints {
17
17
  grant_type?: 'demo' | 'otp';
18
18
  otpId?: string;
19
19
  otp?: string;
20
+ /** OAuth provider name to initiate OAuth flow (e.g., 'google', 'github') */
21
+ provider?: string;
22
+ /** Dexie Cloud authorization code received from OAuth callback */
23
+ oauthCode?: string;
20
24
  }
21
25
  export interface DexieCloudAPI {
22
26
  version: string;
@@ -24,4 +24,24 @@ export interface DexieCloudOptions {
24
24
  };
25
25
  }) => Promise<TokenFinalResponse>;
26
26
  awarenessProtocol?: typeof import('y-protocols/awareness');
27
+ /** Enable social/OAuth authentication.
28
+ * - true (default): Fetch providers from server, show if available
29
+ * - false: Disable OAuth, always use OTP flow
30
+ *
31
+ * Use `false` for backward compatibility if your custom login UI
32
+ * doesn't handle the `DXCProviderSelection` interaction type yet.
33
+ */
34
+ socialAuth?: boolean;
35
+ /** Redirect URI for OAuth callback (Capacitor/redirect flows).
36
+ * For web popups, this is auto-detected from window.location.origin.
37
+ * Required for:
38
+ * - Capacitor apps: 'myapp://oauth-callback'
39
+ * - Full-page redirect flows: 'https://myapp.com/oauth-callback'
40
+ */
41
+ oauthRedirectUri?: string;
42
+ /** Use popup window for OAuth flow.
43
+ * - true (default for web): Opens OAuth in popup, uses postMessage
44
+ * - false: Opens OAuth in same window or system browser (Capacitor)
45
+ */
46
+ oauthPopup?: boolean;
27
47
  }
@@ -0,0 +1,23 @@
1
+ import type { TokenFinalResponse } from 'dexie-cloud-common';
2
+ /** Options for exchanging an OAuth code */
3
+ export interface ExchangeOAuthCodeOptions {
4
+ /** The Dexie Cloud database URL */
5
+ databaseUrl: string;
6
+ /** The Dexie Cloud authorization code from OAuth callback */
7
+ code: string;
8
+ /** The client's public key in PEM format */
9
+ publicKey: string;
10
+ /** Requested scopes (defaults to ['ACCESS_DB']) */
11
+ scopes?: string[];
12
+ }
13
+ /**
14
+ * Exchanges a Dexie Cloud authorization code for access and refresh tokens.
15
+ *
16
+ * This is called after the OAuth callback delivers the authorization code
17
+ * via postMessage (popup flow) or redirect.
18
+ *
19
+ * @param options - Exchange options
20
+ * @returns Promise resolving to TokenFinalResponse
21
+ * @throws OAuthError or TokenErrorResponseError on failure
22
+ */
23
+ export declare function exchangeOAuthCode(options: ExchangeOAuthCodeOptions): Promise<TokenFinalResponse>;
@@ -0,0 +1,14 @@
1
+ import type { AuthProvidersResponse } from 'dexie-cloud-common';
2
+ /**
3
+ * Fetches available authentication providers from the Dexie Cloud server.
4
+ *
5
+ * @param databaseUrl - The Dexie Cloud database URL
6
+ * @param socialAuthEnabled - Whether social auth is enabled in client config (default: true)
7
+ * @returns Promise resolving to AuthProvidersResponse
8
+ *
9
+ * Handles failures gracefully:
10
+ * - 404 → Returns OTP-only (old server version)
11
+ * - Network error → Returns OTP-only
12
+ * - socialAuthEnabled: false → Returns OTP-only without fetching
13
+ */
14
+ export declare function fetchAuthProviders(databaseUrl: string, socialAuthEnabled?: boolean): Promise<AuthProvidersResponse>;