@dotbots-boutique/auth-sdk 0.1.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,51 +1,51 @@
1
1
  # @dotbots-boutique/auth-sdk
2
-
2
+
3
3
  The official authentication and authorisation SDK for apps running on the [DotBots Boutique](https://dotbots.boutique) platform.
4
-
4
+
5
5
  ## What is DotBots Boutique?
6
-
6
+
7
7
  [DotBots Boutique](https://dotbots.boutique) is a marketplace platform where vibe coders build and publish apps. The platform handles user management, authentication, authorisation, payments, and infrastructure, and offers a growing set of additional services — so you can focus on building your app.
8
-
8
+
9
9
  ---
10
-
10
+
11
11
  ## Installation
12
-
12
+
13
13
  ```bash
14
14
  npm install @dotbots-boutique/auth-sdk
15
15
  ```
16
-
16
+
17
17
  > **Note:** This package is for **browser use only**. Do not install it in a Deno or Node backend. A separate `@dotbots-boutique/server-sdk` for backend use is coming soon.
18
-
18
+
19
19
  ---
20
-
20
+
21
21
  ## Quick start
22
-
22
+
23
23
  ### React
24
-
24
+
25
25
  ```tsx
26
26
  // main.tsx
27
27
  import { DotBotsAuth } from '@dotbots-boutique/auth-sdk';
28
28
  import { DotBotsAuthProvider } from '@dotbots-boutique/auth-sdk/react';
29
-
29
+
30
30
  const auth = new DotBotsAuth({
31
31
  appId: import.meta.env.VITE_DOTBOTS_APP_ID,
32
32
  apiUrl: import.meta.env.VITE_DOTBOTS_API_URL
33
33
  });
34
-
34
+
35
35
  ReactDOM.createRoot(document.getElementById('root')!).render(
36
36
  <DotBotsAuthProvider auth={auth}>
37
37
  <App />
38
38
  </DotBotsAuthProvider>
39
39
  );
40
40
  ```
41
-
41
+
42
42
  ```tsx
43
43
  // In any component
44
44
  import { useDotBotsAuth } from '@dotbots-boutique/auth-sdk/react';
45
-
45
+
46
46
  const MyComponent = () => {
47
47
  const { user, can, hasRole, fetch, logout } = useDotBotsAuth();
48
-
48
+
49
49
  return (
50
50
  <div>
51
51
  <p>Welcome, {user?.name}</p>
@@ -56,94 +56,140 @@ const MyComponent = () => {
56
56
  );
57
57
  };
58
58
  ```
59
-
59
+
60
+ Custom loading and error screens:
61
+
62
+ ```tsx
63
+ <DotBotsAuthProvider
64
+ auth={auth}
65
+ loadingComponent={<MySpinner />}
66
+ errorComponent={(error) => <MyError error={error} />}
67
+ >
68
+ <App />
69
+ </DotBotsAuthProvider>
70
+ ```
71
+
60
72
  ### Without React
61
-
73
+
62
74
  ```typescript
63
75
  import { DotBotsAuth } from '@dotbots-boutique/auth-sdk';
64
-
76
+
65
77
  const auth = new DotBotsAuth({
66
78
  appId: import.meta.env.VITE_DOTBOTS_APP_ID,
67
79
  apiUrl: import.meta.env.VITE_DOTBOTS_API_URL
68
80
  });
69
-
81
+
70
82
  await auth.initialize();
71
-
83
+
72
84
  const user = await auth.getUser();
73
85
  console.log(user.name, user.roles);
74
86
  ```
75
-
87
+
76
88
  ---
77
-
89
+
78
90
  ## How authentication works
79
-
91
+
80
92
  Your app can run in two modes — the SDK handles both automatically.
81
-
93
+
82
94
  **Inside an iframe** (embedded in the DotBots Boutique marketplace):
83
95
  1. The SDK sends a `DOTBOTS_READY` message to the marketplace
84
96
  2. The marketplace responds with a short-lived auth code
85
97
  3. The SDK exchanges the code for an access token and refresh token
86
-
98
+
87
99
  **Standalone** (opened directly on your app's domain):
88
100
  1. The SDK checks for a `?code=` parameter in the URL
89
101
  2. If absent, it redirects the user to the DotBots login page
90
102
  3. After login, the user is redirected back with a code that the SDK exchanges for tokens
91
-
103
+
92
104
  All tokens are stored in memory only — never in `localStorage`, `sessionStorage`, or cookies.
93
-
105
+
106
+ ---
107
+
108
+ ## Proxy architecture
109
+
110
+ The SDK does not communicate directly with `api.dotbots.ai` for app-related calls. Each app server runs a shared proxy that handles caching, local database access, and request forwarding:
111
+
112
+ ```
113
+ App (frontend) → Local proxy (per server) → api.dotbots.ai
114
+ ```
115
+
116
+ During `initialize()`, the SDK fetches the proxy config from `GET {apiUrl}/api/proxy/config`. After that, all `auth.fetch()` calls are routed through the proxy automatically. If the proxy config cannot be fetched, the SDK falls back to direct communication with `apiUrl`.
117
+
118
+ Auth endpoints (`/api/auth/*`) and the proxy config endpoint always go directly to `apiUrl` — never through the proxy.
119
+
120
+ ---
121
+
122
+ ## X-Environment header
123
+
124
+ The SDK automatically detects the environment based on `window.location.hostname`:
125
+
126
+ - Hostnames containing `test-apps` → `test`
127
+ - All other hostnames → `prod`
128
+
129
+ The `X-Environment` header is included on **every** request the SDK makes — auth endpoints, proxy config, and all `auth.fetch()` calls. No configuration is needed.
130
+
94
131
  ---
95
-
132
+
96
133
  ## Making API calls
97
-
134
+
98
135
  Always use `auth.fetch()` instead of the native `fetch()`. The SDK automatically:
99
136
  - Adds the `Authorization`, `X-App-Id` and `X-Environment` headers
100
137
  - Routes calls through the local proxy when available
101
138
  - Refreshes the access token when it is about to expire
102
139
  - Retries once on a `401` response
103
-
140
+
104
141
  ```typescript
105
142
  // GET
106
143
  const response = await auth.fetch('/api/customers');
107
144
  const customers = await response.json();
108
-
145
+
109
146
  // POST
110
147
  const response = await auth.fetch('/api/customers', {
111
148
  method: 'POST',
112
149
  body: JSON.stringify({ name: 'Acme' })
113
150
  });
114
151
  ```
115
-
152
+
153
+ This is the **only** method that routes via `proxyUrl`. All auth calls and the proxy config call always go directly to `apiUrl`.
154
+
116
155
  ---
117
-
156
+
118
157
  ## Checking permissions
119
-
158
+
120
159
  ```typescript
121
160
  auth.can('customers.read') // true/false
122
161
  auth.canAll(['customers.read', 'customers.write']) // true/false — all required
123
162
  auth.canAny(['customers.read', 'invoices.read']) // true/false — at least one
124
163
  auth.hasRole('admin') // true/false
125
164
  ```
126
-
165
+
127
166
  ---
128
-
167
+
129
168
  ## Events
130
-
169
+
131
170
  ```typescript
132
171
  auth.on('tokenRefreshed', () => { });
133
172
  auth.on('sessionExpired', () => {
134
173
  // Show a message to the user
135
174
  });
136
175
  auth.on('loggedOut', () => { });
137
- auth.on('userLoaded', (user) => { });
176
+ auth.on('userLoaded', () => { });
138
177
  ```
139
-
178
+
179
+ | Event | Description |
180
+ |-------|-------------|
181
+ | `tokenRefreshed` | Access token was refreshed |
182
+ | `loggedOut` | User logged out |
183
+ | `sessionExpired` | Refresh token expired, user must re-authenticate |
184
+ | `userLoaded` | User data was fetched |
185
+
140
186
  ---
141
-
187
+
142
188
  ## Error handling
143
-
189
+
144
190
  ```typescript
145
191
  import { DotBotsAuthError } from '@dotbots-boutique/auth-sdk';
146
-
192
+
147
193
  try {
148
194
  await auth.initialize();
149
195
  } catch (error) {
@@ -165,9 +211,9 @@ try {
165
211
  }
166
212
  }
167
213
  ```
168
-
214
+
169
215
  ### Error codes
170
-
216
+
171
217
  | Code | Description | Fatal |
172
218
  |------|-------------|-------|
173
219
  | `IFRAME_TIMEOUT` | Marketplace did not respond within the timeout | Yes |
@@ -177,11 +223,11 @@ try {
177
223
  | `NETWORK_ERROR` | API unreachable | Yes |
178
224
  | `NOT_INITIALIZED` | `initialize()` has not been called yet | Yes |
179
225
  | `PROXY_UNAVAILABLE` | Proxy config could not be fetched | No — falls back to direct API |
180
-
226
+
181
227
  ---
182
-
228
+
183
229
  ## Configuration
184
-
230
+
185
231
  ```typescript
186
232
  interface DotBotsConfig {
187
233
  appId: string; // Required — app ID assigned by the platform
@@ -192,11 +238,11 @@ interface DotBotsConfig {
192
238
  onTokenRefreshFailed?: () => void; // Called when token refresh fails
193
239
  }
194
240
  ```
195
-
241
+
196
242
  ---
197
-
243
+
198
244
  ## DotBotsUser
199
-
245
+
200
246
  ```typescript
201
247
  interface DotBotsUser {
202
248
  id: string;
@@ -204,25 +250,108 @@ interface DotBotsUser {
204
250
  email: string | null; // null if 'email' scope not granted
205
251
  orgId: string;
206
252
  orgName: string | null; // null if 'org' scope not granted
253
+ teams: { teamId: string; teamName: string }[];
207
254
  roles: string[];
208
255
  permissions: string[];
209
256
  avatarUrl: string | null; // null if 'avatar' scope not granted
210
257
  }
211
258
  ```
212
-
213
- Fields marked as `null` are not provided by the platform because the user has not granted the corresponding scope. Scopes are declared in your app's `dotbots.boutique.json` file.
214
-
259
+
260
+ Fields marked as `null` are not provided by the platform because the user has not granted the corresponding scope.
261
+
215
262
  ---
216
-
263
+
264
+ ## API Reference
265
+
266
+ ### `DotBotsAuth`
267
+
268
+ #### `constructor(config: DotBotsConfig)`
269
+
270
+ Creates a new SDK instance.
271
+
272
+ #### `initialize(): Promise<void>`
273
+
274
+ Starts the authentication flow. Must be called before any other method.
275
+
276
+ 1. Fetches proxy config from `apiUrl`
277
+ 2. Detects the auth scenario (iframe or standalone)
278
+ 3. Authenticates the user
279
+
280
+ #### `getUser(): Promise<DotBotsUser>`
281
+
282
+ Returns the current user. Cached until the access token expires.
283
+
284
+ #### `can(permission: string): boolean`
285
+
286
+ Check if the user has a specific permission. Throws if `getUser()` hasn't been called.
287
+
288
+ #### `canAll(permissions: string[]): boolean`
289
+
290
+ Check if the user has **all** specified permissions.
291
+
292
+ #### `canAny(permissions: string[]): boolean`
293
+
294
+ Check if the user has **at least one** of the specified permissions.
295
+
296
+ #### `hasRole(role: string): boolean`
297
+
298
+ Check if the user has a specific role.
299
+
300
+ #### `fetch(url: string, options?: RequestInit): Promise<Response>`
301
+
302
+ Authenticated fetch wrapper. Routes through the proxy when available, falls back to `apiUrl` when not. Retries once on 401 after refreshing the token.
303
+
304
+ #### `logout(): Promise<void>`
305
+
306
+ Logs out the user. Revokes tokens on `apiUrl`, clears state, and redirects (standalone) or notifies the parent (iframe).
307
+
308
+ #### `on(event: DotBotsAuthEvent, handler: Function): void`
309
+
310
+ Register an event listener.
311
+
312
+ #### `off(event: DotBotsAuthEvent, handler: Function): void`
313
+
314
+ Remove an event listener.
315
+
316
+ ### `useDotBotsAuth()` (React)
317
+
318
+ ```typescript
319
+ const {
320
+ user, // DotBotsUser | null
321
+ isLoading, // boolean
322
+ error, // DotBotsAuthError | null
323
+ can, // (permission: string) => boolean
324
+ canAll, // (permissions: string[]) => boolean
325
+ canAny, // (permissions: string[]) => boolean
326
+ hasRole, // (role: string) => boolean
327
+ fetch, // auth.fetch proxied
328
+ logout, // auth.logout proxied
329
+ } = useDotBotsAuth();
330
+ ```
331
+
332
+ ---
333
+
217
334
  ## Backend integration
218
-
335
+
219
336
  If your app has a backend, forward the three headers from the frontend request to your backend, and from your backend to the proxy.
220
-
337
+
338
+ ### Frontend → Proxy (direct)
339
+
340
+ Use this when your app has no backend or when the data is public within the organisation. The SDK handles everything automatically.
341
+
342
+ ```typescript
343
+ const response = await auth.fetch('/api/customers');
344
+ ```
345
+
346
+ ### Frontend → Backend → Proxy
347
+
348
+ Use this when your backend performs extra logic before sending data to the frontend, such as validation, transformation, or aggregation of multiple proxy calls.
349
+
221
350
  ```typescript
222
351
  // Deno backend example
223
352
  app.get('/api/customers', async (req) => {
224
353
  const proxyUrl = Deno.env.get('DOTBOTS_PROXY_URL');
225
-
354
+
226
355
  const response = await fetch(`${proxyUrl}/api/customers`, {
227
356
  headers: {
228
357
  'Authorization': req.headers.get('authorization') ?? '',
@@ -230,19 +359,21 @@ app.get('/api/customers', async (req) => {
230
359
  'X-Environment': req.headers.get('x-environment') ?? '',
231
360
  }
232
361
  });
233
-
362
+
234
363
  return response;
235
364
  });
236
365
  ```
237
-
366
+
367
+ Forward the headers exactly as received from the frontend. Do not add your own logic to the token.
368
+
238
369
  **Never** store tokens in a database or log file, and never forward them to services outside the DotBots platform. The access token is scoped to your specific app — it cannot be used to access any other app's data.
239
-
370
+
240
371
  ---
241
-
372
+
242
373
  ## dotbots.boutique.json
243
-
374
+
244
375
  Your repository must contain a `dotbots.boutique.json` file. Use the `env` field to declare any environment variables your app needs beyond the platform defaults. The platform will ask the installer to fill these in before deployment.
245
-
376
+
246
377
  ```json
247
378
  {
248
379
  "dotbotsPromptVersion": "2.1.0",
@@ -267,33 +398,34 @@ Your repository must contain a `dotbots.boutique.json` file. Use the `env` field
267
398
  ]
268
399
  }
269
400
  ```
270
-
401
+
271
402
  Platform variables (`DATABASE_URL`, `PORT`, `DOTBOTS_APP_ID`, `DOTBOTS_PROXY_URL`, `DOTBOTS_API_URL`) are injected automatically and do not need to be declared here.
272
-
403
+
273
404
  ---
274
-
405
+
275
406
  ## Security
276
-
407
+
277
408
  - Tokens are stored in memory only — never persisted
278
409
  - `postMessage` origin is always validated — only messages from `marketplaceOrigin` are accepted
279
410
  - Auth codes are single-use and removed from the URL immediately after exchange
411
+ - Tokens are never logged, not even in development mode
280
412
  - No runtime dependencies — eliminates supply chain risk entirely
281
413
  - Open source — [view the source on GitHub](https://github.com/dotbots-boutique/auth-sdk)
282
-
414
+
283
415
  To report a security vulnerability, please email [security@dotbots.ai](mailto:security@dotbots.ai) instead of opening a public issue.
284
-
416
+
285
417
  ---
286
-
418
+
287
419
  ## Exports
288
-
420
+
289
421
  ```typescript
290
422
  import { DotBotsAuth, DotBotsAuthError } from '@dotbots-boutique/auth-sdk';
291
423
  import type { DotBotsUser, DotBotsConfig, DotBotsAuthEvent, DotBotsProxyConfig } from '@dotbots-boutique/auth-sdk';
292
424
  import { DotBotsAuthProvider, useDotBotsAuth } from '@dotbots-boutique/auth-sdk/react';
293
425
  ```
294
-
426
+
295
427
  ---
296
-
428
+
297
429
  ## License
298
-
430
+
299
431
  [MIT](./LICENSE) — © DotBots Boutique
package/dist/cjs/index.js CHANGED
@@ -213,17 +213,17 @@ class ProxyConfigManager {
213
213
  }
214
214
  }
215
215
  getProxyUrl() {
216
- return this.config?.proxyUrl ?? null;
216
+ return this.config !== null ? this.config.proxyUrl : null;
217
217
  }
218
218
  getConfig() {
219
219
  return this.config;
220
220
  }
221
221
  /**
222
- * Returns the base URL to use for API calls.
222
+ * Returns the base URL to use for auth.fetch() calls.
223
223
  * Uses the proxy if available, otherwise falls back to apiUrl.
224
224
  */
225
225
  getBaseUrl() {
226
- return this.config?.proxyUrl ?? this.apiUrl;
226
+ return this.config !== null ? this.config.proxyUrl : this.apiUrl;
227
227
  }
228
228
  }
229
229
 
package/dist/esm/index.js CHANGED
@@ -211,17 +211,17 @@ class ProxyConfigManager {
211
211
  }
212
212
  }
213
213
  getProxyUrl() {
214
- return this.config?.proxyUrl ?? null;
214
+ return this.config !== null ? this.config.proxyUrl : null;
215
215
  }
216
216
  getConfig() {
217
217
  return this.config;
218
218
  }
219
219
  /**
220
- * Returns the base URL to use for API calls.
220
+ * Returns the base URL to use for auth.fetch() calls.
221
221
  * Uses the proxy if available, otherwise falls back to apiUrl.
222
222
  */
223
223
  getBaseUrl() {
224
- return this.config?.proxyUrl ?? this.apiUrl;
224
+ return this.config !== null ? this.config.proxyUrl : this.apiUrl;
225
225
  }
226
226
  }
227
227
 
@@ -9,7 +9,7 @@ export declare class ProxyConfigManager {
9
9
  getProxyUrl(): string | null;
10
10
  getConfig(): DotBotsProxyConfig | null;
11
11
  /**
12
- * Returns the base URL to use for API calls.
12
+ * Returns the base URL to use for auth.fetch() calls.
13
13
  * Uses the proxy if available, otherwise falls back to apiUrl.
14
14
  */
15
15
  getBaseUrl(): string;
@@ -211,17 +211,17 @@ class ProxyConfigManager {
211
211
  }
212
212
  }
213
213
  getProxyUrl() {
214
- return this.config?.proxyUrl ?? null;
214
+ return this.config !== null ? this.config.proxyUrl : null;
215
215
  }
216
216
  getConfig() {
217
217
  return this.config;
218
218
  }
219
219
  /**
220
- * Returns the base URL to use for API calls.
220
+ * Returns the base URL to use for auth.fetch() calls.
221
221
  * Uses the proxy if available, otherwise falls back to apiUrl.
222
222
  */
223
223
  getBaseUrl() {
224
- return this.config?.proxyUrl ?? this.apiUrl;
224
+ return this.config !== null ? this.config.proxyUrl : this.apiUrl;
225
225
  }
226
226
  }
227
227
 
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Shared types for @dotbots/auth-sdk.
2
+ * Shared types for @dotbots-boutique/auth-sdk.
3
3
  * This file contains no browser dependencies so it can be reused
4
- * in a future @dotbots/server-sdk package.
4
+ * in a future @dotbots-boutique/server-sdk package.
5
5
  */
6
6
  export interface DotBotsConfig {
7
7
  /** Unique app identifier */
@@ -23,6 +23,10 @@ export interface DotBotsUser {
23
23
  email: string | null;
24
24
  orgId: string;
25
25
  orgName: string | null;
26
+ teams: {
27
+ teamId: string;
28
+ teamName: string;
29
+ }[];
26
30
  roles: string[];
27
31
  permissions: string[];
28
32
  avatarUrl: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotbots-boutique/auth-sdk",
3
- "version": "0.1.0",
3
+ "version": "1.0.2",
4
4
  "description": "Authentication SDK for DotBots marketplace apps",
5
5
  "license": "MIT",
6
6
  "type": "module",