dexie-cloud-addon 4.3.0 → 4.3.3

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.
@@ -13,16 +13,15 @@ This feature adds support for OAuth 2.0 social login providers (Google, GitHub,
13
13
  - `src/api/oauth/registerOAuthEndpoints.ts` - OAuth endpoints
14
14
  - `src/api/oauth/oauth-helpers.ts` - Provider exchange logic
15
15
  - `src/api/registerTokenEndpoint.ts` - Token endpoint (authorization_code grant)
16
- - `web-templates/oauth-callback.handlebars` - Callback page template
17
16
 
18
17
  ### Flow Summary
19
18
 
20
19
  1. **Client** fetches available auth providers from `GET /auth-providers`
21
- 2. **Client** opens popup/redirect to `GET /oauth/login/:provider`
20
+ 2. **Client** redirects to `GET /oauth/login/:provider` (full page redirect)
22
21
  3. **Dexie Cloud Server** redirects to OAuth provider and handles callback at `/oauth/callback/:provider`
23
22
  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"`
23
+ 5. **Server** redirects back to client with `dxc-auth` query parameter (base64url-encoded JSON)
24
+ 6. **Client** detects `dxc-auth` in `db.cloud.configure()`, exchanges code for tokens via `POST /token`
26
25
 
27
26
  ### Supported Providers
28
27
  - **Google** - OpenID Connect with PKCE
@@ -33,395 +32,132 @@ This feature adds support for OAuth 2.0 social login providers (Google, GitHub,
33
32
 
34
33
  ### Client Delivery Methods
35
34
 
36
- The library must support multiple integration patterns:
37
-
38
35
  | Method | Use Case | Delivery Mechanism |
39
36
  |--------|----------|-------------------|
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 |
37
+ | **Full Page Redirect** | Web SPAs (recommended) | HTTP redirect with `dxc-auth` query param |
38
+ | **Custom URL Scheme** | Capacitor/Native apps | Deep link redirect (e.g., `myapp://`) |
43
39
 
44
40
  ---
45
41
 
46
- ## Server-Side Tasks (dexie-cloud-server)
42
+ ## Implementation Status
47
43
 
48
- ### ✅ Completed
44
+ ### ✅ Server-Side (dexie-cloud-server) - COMPLETE
49
45
 
50
46
  - [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
-
47
+ - [x] **`GET /auth-providers` endpoint** - Returns enabled providers and OTP status
48
+ - [x] **`GET /oauth/login/:provider` endpoint** - Initiates OAuth flow with PKCE
49
+ - [x] **`GET /oauth/callback/:provider` endpoint** - Handles provider callback, redirects with `dxc-auth`
50
+ - [x] **`POST /token` with `grant_type: "authorization_code"`** - Exchanges Dexie auth code for tokens
88
51
  - [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
52
  - [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
53
 
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
54
+ ### Client-Side (dexie-cloud-addon) - COMPLETE
158
55
 
159
56
  #### Types in dexie-cloud-common
160
57
 
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"`
58
+ - [x] **`OAuthProviderInfo` type** - Provider metadata
59
+ - [x] **`AuthProvidersResponse` type** - Response from `/auth-providers`
60
+ - [x] **`AuthorizationCodeTokenRequest` type** - Token request for OAuth codes
204
61
 
205
62
  #### Types and Interfaces in dexie-cloud-addon
206
63
 
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
- ```
64
+ - [x] **Extended `LoginHints` interface** - Added `provider`, `oauthCode`
65
+ - [x] **`DXCProviderSelection` interaction type** - For provider selection UI
66
+ - [x] **`DexieCloudOptions` extension** - Added `socialAuth`, `oauthRedirectUri`
243
67
 
244
68
  #### Core Authentication Flow
245
69
 
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
70
+ - [x] **`fetchAuthProviders()`** - Fetches available providers from server
71
+ - [x] **`startOAuthRedirect()`** - Initiates OAuth via full page redirect
72
+ - [x] **`parseOAuthCallback()`** - Parses `dxc-auth` query parameter
73
+ - [x] **`cleanupOAuthUrl()`** - Removes `dxc-auth` from URL via `history.replaceState()`
74
+ - [x] **`exchangeOAuthCode()`** - Exchanges Dexie auth code for tokens
75
+ - [x] **OAuth detection in `configure()`** - Auto-detects `dxc-auth` on page load
76
+ - [x] **OAuth processing in `onDbReady`** - Completes login when database is ready
77
+ - [x] **Updated `login()` function** - Supports `provider` and `oauthCode` hints
293
78
 
294
79
  #### Default UI Components
295
80
 
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
- ```
81
+ - [x] **`ProviderSelectionDialog`** - Renders provider selection screen
82
+ - [x] **`AuthProviderButton`** - Renders individual provider buttons with icons
83
+ - [x] **`OtpButton`** - "Continue with email" option
84
+ - [x] **Updated `LoginDialog.tsx`** - Handles OAuth redirect flow
85
+ - [x] **Updated `Styles.ts`** - Provider button styles
363
86
 
364
87
  #### Error Handling
365
88
 
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
89
+ - [x] **`OAuthError` class** - Error codes: `access_denied`, `invalid_state`, `email_not_verified`, `expired_code`, `provider_error`, `network_error`
370
90
 
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
91
+ ---
92
+
93
+ ## 🔲 Remaining TODO
375
94
 
376
- #### Testing
95
+ ### Testing
377
96
 
378
97
  - [ ] **Unit tests for OAuth flow**
379
- - Mock postMessage events
380
- - Test popup lifecycle handling
98
+ - Test `parseOAuthCallback()` with various `dxc-auth` payloads
381
99
  - Test error scenarios
100
+ - Test URL cleanup
382
101
 
383
102
  - [ ] **Integration tests**
384
- - Test with mock OAuth callback page
103
+ - Test full redirect flow with mock server
385
104
  - Test token exchange
386
105
 
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.
106
+ - [ ] **Manual testing**
107
+ - Test with `samples/dexie-cloud-todo-app`
108
+ - Test with Capacitor app (deep links)
388
109
 
389
- #### Documentation
110
+ ### Documentation
390
111
 
391
112
  - [ ] **Update README.md**
392
113
  - Document OAuth login: `db.cloud.login({ provider: 'google' })`
393
114
  - Show Capacitor integration pattern
394
- - Explain redirect vs popup flows
115
+ - Explain redirect flow
116
+
117
+ - [ ] **Update dexie.org docs**
118
+ - Add OAuth configuration guide
119
+ - Document `socialAuth` and `oauthRedirectUri` options
395
120
 
396
121
  ---
397
122
 
398
123
  ## Client Integration Patterns
399
124
 
400
- ### Web SPA (Popup Flow - Default)
125
+ ### Web SPA (Redirect Flow)
401
126
 
402
127
  ```typescript
403
- // User clicks "Login with Google"
128
+ // Configure database
129
+ db.cloud.configure({
130
+ databaseUrl: 'https://mydb.dexie.cloud'
131
+ });
132
+
133
+ // OAuth callback is handled automatically!
134
+ // When page loads with ?dxc-auth=..., the addon:
135
+ // 1. Detects the parameter in configure()
136
+ // 2. Cleans up the URL immediately
137
+ // 3. Completes login in db.on('ready')
138
+
139
+ // To manually initiate OAuth (e.g., from custom UI):
404
140
  await db.cloud.login({ provider: 'google' });
405
- // Popup opens, user authenticates, popup closes, user is logged in
141
+ // Page redirects to OAuth provider, then back with auth code
406
142
  ```
407
143
 
408
144
  ### Capacitor / Native App
409
145
 
410
146
  ```typescript
411
- // In db.cloud.configure()
147
+ // Configure with custom URL scheme
412
148
  db.cloud.configure({
413
149
  databaseUrl: 'https://mydb.dexie.cloud',
414
- oauthRedirectUri: 'myapp://oauth-callback',
415
- oauthPopup: false
150
+ oauthRedirectUri: 'myapp://'
416
151
  });
417
152
 
418
153
  // Handle deep link in app
419
154
  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 });
155
+ const callback = handleOAuthCallback(url);
156
+ if (callback) {
157
+ await db.cloud.login({
158
+ oauthCode: callback.code,
159
+ provider: callback.provider
160
+ });
425
161
  }
426
162
  });
427
163
 
@@ -429,25 +165,6 @@ App.addListener('appUrlOpen', async ({ url }) => {
429
165
  await db.cloud.login({ provider: 'google' });
430
166
  ```
431
167
 
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
168
  ---
452
169
 
453
170
  ## Architecture Diagram
@@ -456,30 +173,29 @@ if (code && provider) {
456
173
  ┌──────────────────────────────────────────────────────────────────────────────┐
457
174
  │ CLIENT (dexie-cloud-addon) │
458
175
  │ │
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
- │ └─────────────┘ │
176
+ ┌─────────────────┐ ┌───────────────────┐
177
+ │ │ LoginDialog │───▶│ startOAuthRedirect│──▶ window.location.href =
178
+ │ │ (default UI) │ │ () │ /oauth/login/:provider
179
+ └─────────────────┘ └───────────────────┘
180
+
181
+ ... page navigates away, user authenticates ...
182
+
183
+ ┌─────────────────┐ ┌───────────────────┐
184
+ Page loads with │───▶│ db.cloud. │──▶ Detects dxc-auth param
185
+ ?dxc-auth=... configure() │ Cleans URL immediately │
186
+ └─────────────────┘ └───────────────────┘ Stores pending code
187
+
188
+
189
+ ┌─────────────────┐ ┌─────────────────────────┐
190
+ db.on('ready') │───▶│ POST /token │ │
191
+ │ │ │ grant_type: │ │
192
+ │ │◀───│ authorization_code │ │
193
+ └─────────────────┘ └─────────────────────────┘
194
+
195
+
196
+ ┌─────────────────┐
197
+ │ User logged in!
198
+ └─────────────────┘
483
199
  └──────────────────────────────────────────────────────────────────────────────┘
484
200
 
485
201
  ┌──────────────────────────────────────────────────────────────────────────────┐
@@ -495,7 +211,7 @@ if (code && provider) {
495
211
  │ ├── Exchange code for provider tokens (server-side!) │
496
212
  │ ├── Fetch user info, verify email │
497
213
  │ ├── Generate Dexie auth code (single-use, 5 min TTL) │
498
- │ └── Return HTML page with postMessage/redirect
214
+ │ └── HTTP 302 redirect with ?dxc-auth=<base64url-json>
499
215
  │ │
500
216
  │ POST /token (grant_type: authorization_code) │
501
217
  │ ├── Validate Dexie auth code │
@@ -506,40 +222,53 @@ if (code && provider) {
506
222
 
507
223
  ---
508
224
 
225
+ ## The `dxc-auth` Query Parameter
226
+
227
+ The OAuth callback uses a single `dxc-auth` query parameter containing base64url-encoded JSON to avoid collisions with app query parameters:
228
+
229
+ **Success:**
230
+ ```json
231
+ { "code": "DEXIE_AUTH_CODE", "provider": "google", "state": "..." }
232
+ ```
233
+
234
+ **Error:**
235
+ ```json
236
+ { "error": "Error message", "provider": "google", "state": "..." }
237
+ ```
238
+
239
+ Example URL:
240
+ ```
241
+ https://myapp.com/?dxc-auth=eyJjb2RlIjoiLi4uIiwicHJvdmlkZXIiOiJnb29nbGUiLCJzdGF0ZSI6Ii4uLiJ9
242
+ ```
243
+
244
+ ---
245
+
509
246
  ## Security Properties
510
247
 
511
248
  - 🛡 **No provider tokens reach client** - All provider exchange happens server-side
512
249
  - 🛡 **Single-use Dexie auth codes** - 5 minute TTL, deleted after use
513
250
  - 🛡 **PKCE protection** - Prevents code interception (where supported)
514
251
  - 🛡 **State parameter** - CSRF protection, stored server-side
515
- - 🛡 **Origin validation** - postMessage uses captured origin, redirect_uri whitelisted
252
+ - 🛡 **Origin validation** - redirect_uri validated and whitelisted
516
253
  - 🛡 **Email verification enforced** - Server rejects unverified emails
254
+ - 🛡 **No tokens in URL fragments** - Auth code in query param, not fragment
517
255
 
518
256
  ---
519
257
 
520
- ## Key Files to Create/Modify
258
+ ## Key Files
521
259
 
522
- **New files in dexie-cloud-common:**
260
+ **dexie-cloud-common:**
523
261
  - `src/OAuthProviderInfo.ts`
524
262
  - `src/AuthProvidersResponse.ts`
525
- - `src/OAuthResultMessage.ts`
526
263
  - `src/AuthorizationCodeTokenRequest.ts`
527
264
 
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
265
+ **dexie-cloud-addon:**
266
+ - `src/authentication/oauthLogin.ts` - `startOAuthRedirect()`, `mapOAuthError()`
267
+ - `src/authentication/handleOAuthCallback.ts` - `parseOAuthCallback()`, `cleanupOAuthUrl()`
268
+ - `src/authentication/exchangeOAuthCode.ts` - Token exchange
269
+ - `src/authentication/fetchAuthProviders.ts` - Fetch available providers
270
+ - `src/errors/OAuthError.ts` - OAuth-specific errors
271
+ - `src/default-ui/ProviderSelectionDialog.tsx` - Provider selection UI
272
+ - `src/default-ui/AuthProviderButton.tsx` - Provider button component
273
+ - `src/dexie-cloud-client.ts` - OAuth detection in `configure()`, processing in `onDbReady`
274
+ - `src/DexieCloudOptions.ts` - `socialAuth`, `oauthRedirectUri` options