better-auth-studio 1.1.3-beta.47 → 1.1.3-beta.49

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
@@ -187,12 +187,14 @@ pnpx better-auth-studio start [options]
187
187
 
188
188
  **Options:**
189
189
 
190
- - `--port <number>` - Specify port (default: 3000)
190
+ - `--port <number>` - Specify port (default: 3002)
191
191
  - `--host <string>` - Specify host (default: localhost)
192
192
  - `--no-open` - Don't automatically open browser
193
193
  - `--config <path>` - Path to auth config file (default: auto-detect)
194
194
  - `--watch` - Watch for changes in auth config file and reload server automatically
195
195
 
196
+ `3002` is just the standalone Studio default so it does not clash with apps that already use `3000`. You can run Studio on `3000`, `4000`, or any other free port.
197
+
196
198
  **Examples:**
197
199
 
198
200
  ```bash
@@ -270,6 +272,62 @@ pnpm add better-auth-studio
270
272
 
271
273
  > **Note:** The CLI usage (standalone studio) can be installed as a dev dependency, but self-hosting requires it in `dependencies` for production deployments.
272
274
 
275
+ ### Docker (Standalone Self-Host)
276
+
277
+ If you want to run Better Auth Studio in its own container, this repository includes a Docker setup for the standalone CLI server.
278
+
279
+ This mode is useful when:
280
+
281
+ - you want a separate admin container
282
+ - your Better Auth app already lives in its own repository
283
+ - you want Docker-managed `node_modules` and package-manager cache volumes
284
+
285
+ > **Important:** The standalone container still needs your Better Auth project mounted into it, because Studio loads your real `auth.ts` and `studio.config.*` files at runtime.
286
+
287
+ Build the image from this repository:
288
+
289
+ ```bash
290
+ docker build -t better-auth-studio:self-host .
291
+ ```
292
+
293
+ Run it with Docker Compose:
294
+
295
+ ```bash
296
+ HOST_PROJECT_PATH=/absolute/path/to/your-better-auth-project \
297
+ CONFIG_PATH=./auth.ts \
298
+ docker compose -f docker/compose.yml up --build
299
+ ```
300
+
301
+ If your auth config is not at the project root, pass it explicitly:
302
+
303
+ ```bash
304
+ HOST_PROJECT_PATH=/absolute/path/to/your-better-auth-project \
305
+ CONFIG_PATH=./src/lib/auth.ts \
306
+ docker compose -f docker/compose.yml up --build
307
+ ```
308
+
309
+ If you already used the older filename, `docker/docker-compose.self-host.yml` still works too.
310
+
311
+ What the container does:
312
+
313
+ - mounts your Better Auth project into `/workspace`
314
+ - installs project dependencies into Docker volumes on first boot
315
+ - starts `better-auth-studio start --host 0.0.0.0 --port 3002 --no-open`
316
+
317
+ Useful environment variables:
318
+
319
+ - `HOST_PROJECT_PATH` - absolute path to your Better Auth project on the host machine
320
+ - `CONFIG_PATH` - optional auth config path inside the mounted project, for example `./src/lib/auth.ts`
321
+ - `PORT` - studio port, defaults to `3002`
322
+ - `WATCH` - set to `true` to enable watch mode
323
+ - `AUTO_INSTALL` - set to `false` if your container/project already has dependencies installed
324
+ - `PROJECT_INSTALL_CMD` - optional custom install command for non-standard setups
325
+ - `GEO_DB_PATH` - optional GeoLite database path inside the mounted project
326
+
327
+ If you change the Studio port, make sure your Better Auth config also trusts that origin when needed. The examples usually include both `http://localhost:3000` and `http://localhost:3002` for that reason.
328
+
329
+ If you already embed Studio in your server with the framework adapters below, that remains the recommended production setup. The Docker flow is mainly for running the standalone Studio server in a containerized environment.
330
+
273
331
  ### Setup
274
332
 
275
333
  **Step 1: Initialize configuration**
@@ -294,6 +352,8 @@ const config: StudioConfig = {
294
352
  access: {
295
353
  roles: ["admin"],
296
354
  allowEmails: ["admin@example.com"],
355
+ allowIpAddresses: ["127.0.0.1", "::1", "192.168.*"],
356
+ blockIpAddresses: ["203.0.113.45"],
297
357
  },
298
358
  };
299
359
 
@@ -339,15 +399,17 @@ Access at `http://localhost:3000/api/studio`
339
399
 
340
400
  ### Configuration Options
341
401
 
342
- | Option | Required | Description |
343
- | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
344
- | `auth` | Yes | Your Better Auth instance |
345
- | `basePath` | Yes | URL path where studio is mounted |
346
- | `access.allowEmails` | No | Array of admin email addresses |
347
- | `access.roles` | No | Array of allowed user roles |
348
- | `ipAddress` | No | IP geolocation for Events/Sessions: `provider` ("ipinfo" \| "ipapi"), `apiToken`, `baseUrl`, optional `endpoint` (ipinfo: "lite" \| "lookup") |
349
- | `lastSeenAt` | No | Enable last-seen tracking: `{ enabled: true, columnName?: string }` |
350
- | `metadata` | No | Custom branding (title, theme) |
402
+ | Option | Required | Description |
403
+ | ------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
404
+ | `auth` | Yes | Your Better Auth instance |
405
+ | `basePath` | Yes | URL path where studio is mounted |
406
+ | `access.allowEmails` | No | Array of admin email addresses |
407
+ | `access.roles` | No | Array of allowed user roles |
408
+ | `access.allowIpAddresses` | No | IP allowlist for Studio requests. Supports exact IPs and wildcard patterns (example: `192.168.*`) |
409
+ | `access.blockIpAddresses` | No | IP blocklist for Studio requests. Supports exact IPs and wildcard patterns |
410
+ | `ipAddress` | No | IP geolocation for Events/Sessions: `provider` ("ipinfo" \| "ipapi"), `apiToken`, `baseUrl`, optional `endpoint` (ipinfo: "lite" \| "lookup") |
411
+ | `lastSeenAt` | No | Enable last-seen tracking: `{ enabled: true, columnName?: string }` |
412
+ | `metadata` | No | Custom branding (title, theme) |
351
413
 
352
414
  ## 📝 Development
353
415
 
@@ -28,6 +28,7 @@ function convertExpressToUniversal(req) {
28
28
  url: req.originalUrl,
29
29
  method: req.method,
30
30
  headers: req.headers,
31
+ ip: req.ip,
31
32
  body: req.body,
32
33
  };
33
34
  }
@@ -5,6 +5,7 @@ import { createClickHouseProvider, createHttpProvider, createNodeSqliteProvider,
5
5
  import { initializeEventIngestion, isEventIngestionInitialized } from "../utils/event-ingestion.js";
6
6
  import { injectEventHooks, injectLastSeenAtHooks } from "../utils/hook-injector.js";
7
7
  import { serveIndexHtml as getIndexHtml } from "../utils/html-injector.js";
8
+ import { evaluateRequestAccess } from "../utils/access-rules.js";
8
9
  import { decryptSession, isSessionValid, STUDIO_COOKIE_NAME } from "../utils/session.js";
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = dirname(__filename);
@@ -132,6 +133,23 @@ export async function handleStudioRequest(request, config) {
132
133
  if (path === "" || path === "/") {
133
134
  path = "/";
134
135
  }
136
+ if (isSelfHosted) {
137
+ const accessDecision = evaluateRequestAccess({
138
+ accessConfig: config.access,
139
+ path,
140
+ method: request.method,
141
+ headers: request.headers,
142
+ ip: request.ip,
143
+ });
144
+ if (!accessDecision.allowed) {
145
+ return jsonResponse(403, {
146
+ success: false,
147
+ message: accessDecision.message,
148
+ reason: accessDecision.reason,
149
+ ...(accessDecision.ipAddress ? { ipAddress: accessDecision.ipAddress } : {}),
150
+ });
151
+ }
152
+ }
135
153
  if (path.startsWith("/assets/") ||
136
154
  path === "/vite.svg" ||
137
155
  path === "/favicon.svg" ||
@@ -262,6 +280,7 @@ async function handleApiRoute(request, path, config) {
262
280
  path: path,
263
281
  method: request.method,
264
282
  headers: request.headers,
283
+ ip: request.ip,
265
284
  body: request.body,
266
285
  auth: config.auth,
267
286
  basePath: config.basePath || "/api/studio",