better-auth-studio 1.1.3-beta.48 → 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
@@ -352,6 +352,8 @@ const config: StudioConfig = {
352
352
  access: {
353
353
  roles: ["admin"],
354
354
  allowEmails: ["admin@example.com"],
355
+ allowIpAddresses: ["127.0.0.1", "::1", "192.168.*"],
356
+ blockIpAddresses: ["203.0.113.45"],
355
357
  },
356
358
  };
357
359
 
@@ -397,15 +399,17 @@ Access at `http://localhost:3000/api/studio`
397
399
 
398
400
  ### Configuration Options
399
401
 
400
- | Option | Required | Description |
401
- | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
402
- | `auth` | Yes | Your Better Auth instance |
403
- | `basePath` | Yes | URL path where studio is mounted |
404
- | `access.allowEmails` | No | Array of admin email addresses |
405
- | `access.roles` | No | Array of allowed user roles |
406
- | `ipAddress` | No | IP geolocation for Events/Sessions: `provider` ("ipinfo" \| "ipapi"), `apiToken`, `baseUrl`, optional `endpoint` (ipinfo: "lite" \| "lookup") |
407
- | `lastSeenAt` | No | Enable last-seen tracking: `{ enabled: true, columnName?: string }` |
408
- | `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) |
409
413
 
410
414
  ## 📝 Development
411
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",