@nextsparkjs/core 0.1.0-beta.193 → 0.1.0-beta.194

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 (49) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/api/auth/permissions.d.ts +10 -0
  3. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  4. package/dist/lib/api/auth/permissions.js +7 -0
  5. package/dist/lib/api/rate-limit.d.ts.map +1 -1
  6. package/dist/lib/api/rate-limit.js +3 -0
  7. package/dist/lib/api/request-origin.d.ts +26 -0
  8. package/dist/lib/api/request-origin.d.ts.map +1 -0
  9. package/dist/lib/api/request-origin.js +46 -0
  10. package/dist/lib/auth.d.ts.map +1 -1
  11. package/dist/lib/auth.js +8 -12
  12. package/dist/lib/entities/external-api-generator.d.ts.map +1 -1
  13. package/dist/lib/entities/external-api-generator.js +3 -1
  14. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  15. package/dist/lib/services/subscription.service.js +3 -0
  16. package/dist/lib/utils/cors.d.ts +7 -0
  17. package/dist/lib/utils/cors.d.ts.map +1 -1
  18. package/dist/lib/utils/cors.js +8 -0
  19. package/dist/routes/api/v1/users/[id]/meta/[key]/route.d.ts.map +1 -1
  20. package/dist/routes/api/v1/users/[id]/meta/[key]/route.js +13 -0
  21. package/dist/routes/api/v1/users/[id]/route.d.ts.map +1 -1
  22. package/dist/routes/api/v1/users/[id]/route.js +17 -0
  23. package/dist/templates/projects/blog/package.json +3 -3
  24. package/dist/templates/projects/crm/package.json +3 -3
  25. package/dist/templates/projects/productivity/package.json +3 -3
  26. package/package.json +8 -5
  27. package/scripts/build/auth-readiness.mjs +4 -0
  28. package/scripts/build/registry/__tests__/build-output.test.mjs +1 -1
  29. package/scripts/build/registry/__tests__/project-mode-root-first.test.mjs +6 -6
  30. package/scripts/build/registry/__tests__/write-places.test.mjs +1 -1
  31. package/scripts/build/registry/contracts/__tests__/contracts-wire.test.mjs +1 -1
  32. package/scripts/build/registry/contracts/__tests__/contracts.test.mjs +1 -1
  33. package/scripts/build/registry/contracts/render.mjs +3 -3
  34. package/scripts/build/registry/host/README.md +2 -2
  35. package/scripts/build/registry/host/__tests__/facade-emitter.test.mjs +9 -9
  36. package/scripts/build/registry/host/__tests__/host-mode.test.mjs +1 -1
  37. package/scripts/build/registry/host/__tests__/host-project-composition.test.mjs +1 -1
  38. package/scripts/build/registry/host/__tests__/host-project.test.mjs +2 -2
  39. package/scripts/build/registry/host/__tests__/host-robustness.test.mjs +65 -2
  40. package/scripts/build/registry/host/facade-emitter.mjs +2 -2
  41. package/scripts/build/registry/host/generation.mjs +54 -0
  42. package/scripts/build/registry/host/next-route-exports.json +2 -2
  43. package/scripts/build/registry/host/prepare.mjs +3 -0
  44. package/scripts/db/migration-time-limit.mjs +216 -34
  45. package/scripts/db/run-migrations.mjs +11 -179
  46. package/scripts/db/verify-theme-migrations.mjs +1 -1
  47. package/templates/projects/blog/package.json +3 -3
  48. package/templates/projects/crm/package.json +3 -3
  49. package/templates/projects/productivity/package.json +3 -3
package/README.md CHANGED
@@ -5,8 +5,6 @@ The complete SaaS framework for Next.js. Build production-ready SaaS application
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @nextsparkjs/core
9
- # or
10
8
  pnpm add @nextsparkjs/core
11
9
  ```
12
10
 
@@ -16,6 +14,8 @@ pnpm add @nextsparkjs/core
16
14
  # Create a new NextSpark project
17
15
  npx create-nextspark-app my-app
18
16
  cd my-app
17
+ # set DATABASE_URL in .env (BETTER_AUTH_SECRET is already generated)
18
+ pnpm db:migrate
19
19
  pnpm dev
20
20
  ```
21
21
 
@@ -27,4 +27,14 @@ export declare function isSuperAdmin(authResult: DualAuthResult): boolean;
27
27
  * @returns true si tiene permisos de admin
28
28
  */
29
29
  export declare function hasAdminPermission(authResult: DualAuthResult, requiredScope?: string | string[]): boolean;
30
+ /**
31
+ * Whether the caller may read or edit the user `target` (an id or an email, as
32
+ * the /users/:id routes accept both): the user themselves, or a superadmin.
33
+ * An API key is its owner, so it gets the same answer, but a superadmin key
34
+ * must also carry `requiredScope`.
35
+ *
36
+ * Application-layer check: row-level security does not cover a deployment that
37
+ * has not cut over to the non-owner runtime role.
38
+ */
39
+ export declare function canAccessUser(authResult: DualAuthResult, target: string, requiredScope?: string | string[]): boolean;
30
40
  //# sourceMappingURL=permissions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/permissions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAOhE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,cAAc,EAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAChC,OAAO,CAYT"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/permissions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAOhE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,cAAc,EAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAChC,OAAO,CAYT;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,cAAc,EAC1B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAChC,OAAO,CAOT"}
@@ -14,7 +14,14 @@ function hasAdminPermission(authResult, requiredScope) {
14
14
  }
15
15
  return true;
16
16
  }
17
+ function canAccessUser(authResult, target, requiredScope) {
18
+ if (!authResult.success || !authResult.user) return false;
19
+ if (hasAdminPermission(authResult, requiredScope)) return true;
20
+ const { id, email } = authResult.user;
21
+ return target === id || !!email && target === email;
22
+ }
17
23
  export {
24
+ canAccessUser,
18
25
  hasAdminPermission,
19
26
  isSuperAdmin
20
27
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/lib/api/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,KAAK,GAAE,MAAa,EACpB,QAAQ,GAAE,MAAc,GACvB,eAAe,CAyCjB;AAED;;;GAGG;AACH,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,aAAqB,GAC1B,OAAO,CAAC,eAAe,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6CpD;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,eAAe,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAwB5G;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,IAAI,OAAO,CAEzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,GACf,YAAY,GAAG,IAAI,CAkCrB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,GACf,YAAY,CASd;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,EAC/C,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,IAEtD,SAAS,WAAW,EAAE,GAAG,MAAM,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CAwCvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB,GAAG,IAAI,CAaP;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAGlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,KAAK,CAAC;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,CASD;AAED;;GAEG;AACH,wBAAgB,sBAAsB;;;;;EAErC;AAoGD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,EACnD,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,EACpE,IAAI,GAAE,aAAqB,IAEb,SAAS,WAAW,EAAE,GAAG,MAAM,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CAkCvE"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/lib/api/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,KAAK,GAAE,MAAa,EACpB,QAAQ,GAAE,MAAc,GACvB,eAAe,CAyCjB;AAED;;;GAGG;AACH,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,aAAqB,GAC1B,OAAO,CAAC,eAAe,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6CpD;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,eAAe,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAwB5G;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,IAAI,OAAO,CAEzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,GACf,YAAY,GAAG,IAAI,CAkCrB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,GACf,YAAY,CASd;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,EAC/C,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,IAEtD,SAAS,WAAW,EAAE,GAAG,MAAM,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CAwCvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB,GAAG,IAAI,CAaP;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAGlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,KAAK,CAAC;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,CASD;AAED;;GAEG;AACH,wBAAgB,sBAAsB;;;;;EAErC;AAsGD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,EACnD,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,EACpE,IAAI,GAAE,aAAqB,IAEb,SAAS,WAAW,EAAE,GAAG,MAAM,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CAsCvE"}
@@ -6,6 +6,7 @@ import {
6
6
  checkRateLimit as checkRedisRateLimit,
7
7
  maybeRedisConfigured
8
8
  } from "../rate-limit-redis.js";
9
+ import { checkRequestOrigin } from "./request-origin.js";
9
10
  function checkRateLimit(identifier, limit = 1e3, windowMs = 6e4) {
10
11
  const now = Date.now();
11
12
  const cacheKey = getCacheKey("rate_limit", identifier);
@@ -239,6 +240,8 @@ function isRateLimitingDisabled() {
239
240
  }
240
241
  function withRateLimitTier(handler, tier = "api") {
241
242
  return async (request, ...args) => {
243
+ const originRefused = checkRequestOrigin(request);
244
+ if (originRefused) return originRefused;
242
245
  if (isRateLimitingDisabled()) {
243
246
  return handler(request, ...args);
244
247
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Request origin check for cookie-authenticated writes.
3
+ *
4
+ * A POST/PUT/PATCH/DELETE that authenticates with the session cookie must come
5
+ * from an origin the app trusts: its own (NEXT_PUBLIC_APP_URL / BETTER_AUTH_URL)
6
+ * or one in the list Better Auth already trusts for sign-in (see
7
+ * getTrustedOrigins). withRateLimitTier runs it before every handler it wraps.
8
+ *
9
+ * What passes without a trusted origin, and why:
10
+ * - Reads (GET/HEAD/OPTIONS).
11
+ * - Requests without the session cookie: there is no session for them to use.
12
+ * - Requests with an `Authorization: Bearer` token or an x-api-key header (API
13
+ * keys, the mobile client's bearer token). A browser adds those only when the page's script
14
+ * sets them, and a script on another origin needs CORS approval first.
15
+ * - Requests with neither Origin nor Referer, unless their body is one an HTML
16
+ * form can send. Browsers send Origin on every write, so these come from
17
+ * native or server-side clients; requiring a non-form content type keeps
18
+ * a body without origin information from ever being read as a form post.
19
+ */
20
+ import { NextResponse } from 'next/server';
21
+ /**
22
+ * Returns a 403 response when a cookie-authenticated write does not come from a
23
+ * trusted origin, or null when the request may proceed.
24
+ */
25
+ export declare function checkRequestOrigin(request: Request): NextResponse | null;
26
+ //# sourceMappingURL=request-origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-origin.d.ts","sourceRoot":"","sources":["../../../src/lib/api/request-origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AA6B1C;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAsBxE"}
@@ -0,0 +1,46 @@
1
+ import { NextResponse } from "next/server";
2
+ import { APP_CONFIG_MERGED } from "../config/index.js";
3
+ import { hasSessionCookie } from "../auth/session-hint.js";
4
+ import { getTrustedOrigins, isOriginAllowed, normalizeOrigin } from "../utils/cors.js";
5
+ const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
6
+ const FORM_CONTENT_TYPES = /* @__PURE__ */ new Set(["text/plain", "application/x-www-form-urlencoded", "multipart/form-data"]);
7
+ function requestOrigin(headers) {
8
+ const origin = headers.get("origin");
9
+ if (origin) return origin;
10
+ const referer = headers.get("referer");
11
+ if (!referer) return null;
12
+ try {
13
+ return new URL(referer).origin;
14
+ } catch {
15
+ return "null";
16
+ }
17
+ }
18
+ function hasHeaderCredential(headers) {
19
+ var _a;
20
+ if ((_a = headers.get("x-api-key")) == null ? void 0 : _a.trim()) return true;
21
+ return /^bearer\s+\S/i.test(headers.get("authorization") ?? "");
22
+ }
23
+ function checkRequestOrigin(request) {
24
+ if (!WRITE_METHODS.has(request.method.toUpperCase())) return null;
25
+ const headers = request.headers;
26
+ if (!hasSessionCookie(headers.get("cookie"))) return null;
27
+ if (hasHeaderCredential(headers)) return null;
28
+ const origin = requestOrigin(headers);
29
+ if (origin === null) {
30
+ const contentType = (headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
31
+ if (!FORM_CONTENT_TYPES.has(contentType)) return null;
32
+ return NextResponse.json(
33
+ { success: false, error: "This request needs an Origin header", code: "ORIGIN_REQUIRED" },
34
+ { status: 403 }
35
+ );
36
+ }
37
+ const normalized = normalizeOrigin(origin);
38
+ if (isOriginAllowed(normalized, getTrustedOrigins(APP_CONFIG_MERGED, normalized))) return null;
39
+ return NextResponse.json(
40
+ { success: false, error: "Request origin not allowed", code: "ORIGIN_NOT_ALLOWED" },
41
+ { status: 403 }
42
+ );
43
+ }
44
+ export {
45
+ checkRequestOrigin
46
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgF,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAkBvH;;;;;;;;;;;;;GAaG;AACH,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQlF;AAED;;;;;;;;;GASG;AACH,wBAAsB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnF;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsHD,eAAO,MAAM,IAAI,qEAkXf,CAAC;AAEH,MAAM,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;AACjD,MAAM,MAAM,WAAW,GAAG,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,kBAAkB,EAAE,QAAQ,EAAE,CAAC;CAC/C,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5B,IAAI,EAAE,WAAW,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAO9B"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgF,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAkBvH;;;;;;;;;;;;;GAaG;AACH,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQlF;AAED;;;;;;;;;GASG;AACH,wBAAsB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnF;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsHD,eAAO,MAAM,IAAI,qEA8Wf,CAAC;AAEH,MAAM,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;AACjD,MAAM,MAAM,WAAW,GAAG,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,kBAAkB,EAAE,QAAQ,EAAE,CAAC;CAC/C,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5B,IAAI,EAAE,WAAW,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAO9B"}
package/dist/lib/auth.js CHANGED
@@ -19,7 +19,7 @@ import { resolveSessionConfig } from "./auth/session-config.js";
19
19
  import { resolveOtpConfig } from "./auth/otp-config.js";
20
20
  import { isPasswordLoginEnabled } from "./auth/auth-methods.js";
21
21
  import { isRuntimeEmailAvailable, isRuntimeGoogleAvailable } from "./auth/runtime-readiness.js";
22
- import { getCorsOrigins, isPrivateLanOrigin, normalizeCorsEnvironment } from "./utils/cors.js";
22
+ import { getTrustedOrigins } from "./utils/cors.js";
23
23
  import { withBasePath } from "./base-path.js";
24
24
  async function hasPendingInvitationForEmail(email) {
25
25
  const row = await queryOne(
@@ -206,17 +206,7 @@ const auth = betterAuth({
206
206
  // adding a wildcard entry (e.g. "https://*.vercel.app") to
207
207
  // api.cors.additionalOrigins — better-auth's own trustedOrigins matching
208
208
  // already supports the same wildcard syntax as isOriginAllowed() above.
209
- trustedOrigins: async (request) => {
210
- const origins = getCorsOrigins(APP_CONFIG_MERGED);
211
- if (normalizeCorsEnvironment(process.env.NODE_ENV || "development") === "production") {
212
- return origins;
213
- }
214
- const requestOrigin = request == null ? void 0 : request.headers.get("origin");
215
- if (requestOrigin && isPrivateLanOrigin(requestOrigin)) {
216
- return [...origins, requestOrigin];
217
- }
218
- return origins;
219
- },
209
+ trustedOrigins: async (request) => getTrustedOrigins(APP_CONFIG_MERGED, request == null ? void 0 : request.headers.get("origin")),
220
210
  // Redirect auth errors to our custom error page instead of Better Auth's default.
221
211
  // Better Auth redirects to this path as-is, outside the Next.js router.
222
212
  onAPIError: {
@@ -249,6 +239,12 @@ const auth = betterAuth({
249
239
  nextCookies()
250
240
  // MUST be the last plugin for Next.js cookie handling
251
241
  ],
242
+ // The proxy reads the session on every request; that lookup only reads the
243
+ // caller's own signed cookie, so it is kept out of the shared auth rate limit.
244
+ // The default limits (sign-in, sign-up, OTP, ...) stay.
245
+ rateLimit: {
246
+ customRules: { "/get-session": false }
247
+ },
252
248
  session: {
253
249
  // Configurable per theme via `auth.session` in app.config.ts (defaults:
254
250
  // 7 days / renewed daily / 5-minute cookie cache).
@@ -1 +1 @@
1
- {"version":3,"file":"external-api-generator.d.ts","sourceRoot":"","sources":["../../../src/lib/entities/external-api-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAqB,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAK3C,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,eAAe,CAAC,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,WAAW,CAAC,EAAE;QACZ,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,OAAO,EAAE,MAAM,EAAE,CAAA;KAClB,CAAA;CACF;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,YAAY,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,kBAAuB;mBAqFT,WAAW,YAAY;QAAE,MAAM,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;oBAAnD,WAAW,YAAY;QAAE,MAAM,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;qBAAnD,WAAW,YAAY;QAAE,MAAM,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;sBAAnD,WAAW,YAAY;QAAE,MAAM,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;uBAkGnD,WAAW;EAnHnC;AAyKD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoMrD"}
1
+ {"version":3,"file":"external-api-generator.d.ts","sourceRoot":"","sources":["../../../src/lib/entities/external-api-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAqB,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAK3C,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,eAAe,CAAC,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,WAAW,CAAC,EAAE;QACZ,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,OAAO,EAAE,MAAM,EAAE,CAAA;KAClB,CAAA;CACF;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,YAAY,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,kBAAuB;mBAqFT,WAAW,YAAY;QAAE,MAAM,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;oBAAnD,WAAW,YAAY;QAAE,MAAM,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;qBAAnD,WAAW,YAAY;QAAE,MAAM,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;sBAAnD,WAAW,YAAY;QAAE,MAAM,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;uBAkGnD,WAAW;EAnHnC;AA4KD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoMrD"}
@@ -153,7 +153,9 @@ function addCORSHeaders(response, corsOptions, request) {
153
153
  response.headers.set("Access-Control-Allow-Methods", corsOptions.methods.join(", "));
154
154
  response.headers.set("Access-Control-Allow-Headers", corsOptions.headers.join(", "));
155
155
  response.headers.set("Access-Control-Max-Age", "86400");
156
- response.headers.set("Access-Control-Allow-Credentials", "true");
156
+ if (origin && corsOptions.origins.includes(origin)) {
157
+ response.headers.set("Access-Control-Allow-Credentials", "true");
158
+ }
157
159
  }
158
160
  function createErrorResponse(status, message, details) {
159
161
  return NextResponse.json({
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/subscription.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subscription.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/subscription.service.ts"],"names":[],"mappings":"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"}
@@ -810,6 +810,9 @@ class SubscriptionService {
810
810
  console.warn("[SubscriptionService] Team permissions module not available");
811
811
  }
812
812
  }
813
+ if (BILLING_REGISTRY.plans.length === 0) {
814
+ return { allowed: true };
815
+ }
813
816
  const requiredFeature = BILLING_REGISTRY.actionMappings.features[action];
814
817
  if (requiredFeature) {
815
818
  const hasFeatureAccess = await this.hasFeature(teamId, requiredFeature, userId);
@@ -81,6 +81,13 @@ export declare function getCorsOrigins(config: ApplicationConfig, env?: string):
81
81
  * production trust — pair with an environment check.
82
82
  */
83
83
  export declare function isPrivateLanOrigin(origin: string): boolean;
84
+ /**
85
+ * The origins trusted for cookie-authenticated requests: Better Auth's
86
+ * trustedOrigins and the write-origin check (lib/api/request-origin) share it.
87
+ * getCorsOrigins() plus, outside production, the request's own origin when it is
88
+ * a private-network address (#170).
89
+ */
90
+ export declare function getTrustedOrigins(config: ApplicationConfig, requestOrigin?: string | null, env?: string): string[];
84
91
  /**
85
92
  * Check whether a request origin is allowed, supporting wildcard-pattern entries
86
93
  * in the allow-list. A `*` in an entry matches exactly ONE host label (no dots),
@@ -1 +1 @@
1
- {"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,GAAG,IAAI,CAAA;AAc7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,YAAY,CAkBlF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,iBAAiB,EACzB,GAAG,GAAE,MAA8C,GAClD,MAAM,EAAE,CA+CV;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAqB1D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CA6Bf"}
1
+ {"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,GAAG,IAAI,CAAA;AAc7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,YAAY,CAkBlF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,iBAAiB,EACzB,GAAG,GAAE,MAA8C,GAClD,MAAM,EAAE,CA+CV;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAqB1D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,iBAAiB,EACzB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,EAC7B,GAAG,GAAE,MAA8C,GAClD,MAAM,EAAE,CAMV;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CA6Bf"}
@@ -79,6 +79,13 @@ function isPrivateLanOrigin(origin) {
79
79
  if (a === 169 && b === 254) return true;
80
80
  return false;
81
81
  }
82
+ function getTrustedOrigins(config, requestOrigin, env = process.env.NODE_ENV || "development") {
83
+ const origins = getCorsOrigins(config, env);
84
+ if (normalizeCorsEnvironment(env) !== "production" && requestOrigin && isPrivateLanOrigin(requestOrigin)) {
85
+ return [...origins, requestOrigin];
86
+ }
87
+ return origins;
88
+ }
82
89
  function isOriginAllowed(requestOrigin, allowedOrigins) {
83
90
  if (!requestOrigin) return null;
84
91
  for (const entry of allowedOrigins) {
@@ -98,6 +105,7 @@ function isOriginAllowed(requestOrigin, allowedOrigins) {
98
105
  }
99
106
  export {
100
107
  getCorsOrigins,
108
+ getTrustedOrigins,
101
109
  isOriginAllowed,
102
110
  isPrivateLanOrigin,
103
111
  normalizeCorsEnvironment,
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/routes/api/v1/users/[id]/meta/[key]/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAsBvD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,GAAG;YAEQ,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BA6DjD,CAAC;AAEZ;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,GAAG;YAEQ,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BA8FhD,CAAC;AAEb;;;;GAIG;AACH,eAAO,MAAM,KAAK;YArGM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BAqGpC,CAAA;AAExB;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;YAEK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BAwDhD,CAAC"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/routes/api/v1/users/[id]/meta/[key]/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAuBvD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,GAAG;YAEQ,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BAmEjD,CAAC;AAEZ;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,GAAG;YAEQ,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BAoGhD,CAAC;AAEb;;;;GAIG;AACH,eAAO,MAAM,KAAK;YA3GM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BA2GpC,CAAA;AAExB;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;YAEK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BA8DhD,CAAC"}
@@ -7,6 +7,7 @@ import {
7
7
  addCorsHeaders
8
8
  } from "@nextsparkjs/core/lib/api/helpers";
9
9
  import { authenticateRequest, createAuthFailureResponse } from "@nextsparkjs/core/lib/api/auth/dual-auth";
10
+ import { canAccessUser } from "@nextsparkjs/core/lib/api/auth/permissions";
10
11
  import * as z from "zod";
11
12
  import { withRateLimitTier } from "@nextsparkjs/core/lib/api/rate-limit";
12
13
  const metadataValueSchema = z.object({
@@ -33,6 +34,10 @@ export const GET = withRateLimitTier(withApiLogging(async (req, { params }) => {
33
34
  const response2 = createApiError("User ID is required", 400, null, "MISSING_USER_ID");
34
35
  return addCorsHeaders(response2, req);
35
36
  }
37
+ if (!canAccessUser(authResult, id, "users:read")) {
38
+ const response2 = createApiError("Insufficient permissions", 403, null, "FORBIDDEN");
39
+ return addCorsHeaders(response2, req);
40
+ }
36
41
  if (!key || key.trim() === "") {
37
42
  const response2 = createApiError("Metadata key is required", 400, null, "MISSING_META_KEY");
38
43
  return addCorsHeaders(response2, req);
@@ -75,6 +80,10 @@ export const PUT = withRateLimitTier(withApiLogging(async (req, { params }) => {
75
80
  const response2 = createApiError("User ID is required", 400, null, "MISSING_USER_ID");
76
81
  return addCorsHeaders(response2, req);
77
82
  }
83
+ if (!canAccessUser(authResult, id, "users:write")) {
84
+ const response2 = createApiError("Insufficient permissions", 403, null, "FORBIDDEN");
85
+ return addCorsHeaders(response2, req);
86
+ }
78
87
  if (!key || key.trim() === "") {
79
88
  const response2 = createApiError("Metadata key is required", 400, null, "MISSING_META_KEY");
80
89
  return addCorsHeaders(response2, req);
@@ -150,6 +159,10 @@ export const DELETE = withRateLimitTier(withApiLogging(async (req, { params }) =
150
159
  const response2 = createApiError("User ID is required", 400, null, "MISSING_USER_ID");
151
160
  return addCorsHeaders(response2, req);
152
161
  }
162
+ if (!canAccessUser(authResult, id, "users:write")) {
163
+ const response2 = createApiError("Insufficient permissions", 403, null, "FORBIDDEN");
164
+ return addCorsHeaders(response2, req);
165
+ }
153
166
  if (!key || key.trim() === "") {
154
167
  const response2 = createApiError("Metadata key is required", 400, null, "MISSING_META_KEY");
155
168
  return addCorsHeaders(response2, req);
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/routes/api/v1/users/[id]/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA0BxD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAGD,eAAO,MAAM,GAAG;YAEQ,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;2BA8CpC,CAAC;AAGZ,eAAO,MAAM,KAAK;YAEM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;2BAyHnC,CAAC;AAGb,eAAO,MAAM,MAAM;YAEK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;2BA2DnC,CAAC"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/routes/api/v1/users/[id]/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2BxD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAGD,eAAO,MAAM,GAAG;YAEQ,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;2BAoDpC,CAAC;AAGZ,eAAO,MAAM,KAAK;YAEM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;2BAqInC,CAAC;AAGb,eAAO,MAAM,MAAM;YAEK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;2BAiEnC,CAAC"}
@@ -11,6 +11,7 @@ import {
11
11
  processEntityMetadata
12
12
  } from "@nextsparkjs/core/lib/api/helpers";
13
13
  import { authenticateRequest, createAuthFailureResponse } from "@nextsparkjs/core/lib/api/auth/dual-auth";
14
+ import { canAccessUser, hasAdminPermission } from "@nextsparkjs/core/lib/api/auth/permissions";
14
15
  import * as z from "zod";
15
16
  import { withRateLimitTier } from "@nextsparkjs/core/lib/api/rate-limit";
16
17
  const updateUserSchema = z.object({
@@ -37,6 +38,10 @@ export const GET = withRateLimitTier(withApiLogging(async (req, { params }) => {
37
38
  const response2 = createApiError("User ID or email is required", 400, null, "MISSING_IDENTIFIER");
38
39
  return addCorsHeaders(response2, req);
39
40
  }
41
+ if (!canAccessUser(authResult, id, "users:read")) {
42
+ const response2 = createApiError("Insufficient permissions", 403, null, "FORBIDDEN");
43
+ return addCorsHeaders(response2, req);
44
+ }
40
45
  const user = await queryOneWithRLS(
41
46
  'SELECT id, email, name, "firstName", "lastName", image, country, timezone, language, role, "emailVerified", "createdAt", "updatedAt" FROM "users" WHERE id = $1 OR email = $1',
42
47
  [id],
@@ -70,9 +75,17 @@ export const PATCH = withRateLimitTier(withApiLogging(async (req, { params }) =>
70
75
  const response2 = createApiError("User ID or email is required", 400, null, "MISSING_IDENTIFIER");
71
76
  return addCorsHeaders(response2, req);
72
77
  }
78
+ if (!canAccessUser(authResult, id, "users:write")) {
79
+ const response2 = createApiError("Insufficient permissions", 403, null, "FORBIDDEN");
80
+ return addCorsHeaders(response2, req);
81
+ }
73
82
  const body = await req.json();
74
83
  const { metas, ...userData } = body;
75
84
  const validatedData = updateUserSchema.parse(userData);
85
+ if (validatedData.role !== void 0 && !hasAdminPermission(authResult, "users:write")) {
86
+ const response2 = createApiError("Only a superadmin can change a role", 403, null, "FORBIDDEN");
87
+ return addCorsHeaders(response2, req);
88
+ }
76
89
  const updates = [];
77
90
  const values = [];
78
91
  let paramCount = 1;
@@ -157,6 +170,10 @@ export const DELETE = withRateLimitTier(withApiLogging(async (req, { params }) =
157
170
  const response2 = createApiError("User ID or email is required", 400, null, "MISSING_IDENTIFIER");
158
171
  return addCorsHeaders(response2, req);
159
172
  }
173
+ if (!hasAdminPermission(authResult, "users:delete")) {
174
+ const response2 = createApiError("Insufficient permissions. Superadmin access required.", 403, null, "FORBIDDEN");
175
+ return addCorsHeaders(response2, req);
176
+ }
160
177
  const targetUser = await queryOneWithRLS(
161
178
  'SELECT id, email FROM "users" WHERE id = $1 OR email = $1',
162
179
  [id],
@@ -14,10 +14,10 @@
14
14
  "@nextsparkjs/core": ">=0.1.0-0",
15
15
  "@nextsparkjs/testing": ">=0.1.0-0",
16
16
  "lucide-react": "^0.539.0",
17
- "next": "^15.0.0 || ^16.0.0",
17
+ "next": "~16.3.6",
18
18
  "next-intl": "^4.0.0",
19
- "react": "^19.0.0",
20
- "react-dom": "^19.0.0",
19
+ "react": "^19.2.0",
20
+ "react-dom": "^19.2.0",
21
21
  "zod": "^4.0.0"
22
22
  },
23
23
  "nextspark": {
@@ -13,10 +13,10 @@
13
13
  "@nextsparkjs/core": ">=0.1.0-0",
14
14
  "@nextsparkjs/testing": ">=0.1.0-0",
15
15
  "lucide-react": "^0.539.0",
16
- "next": "^15.0.0 || ^16.0.0",
16
+ "next": "~16.3.6",
17
17
  "next-intl": "^4.0.0",
18
- "react": "^19.0.0",
19
- "react-dom": "^19.0.0",
18
+ "react": "^19.2.0",
19
+ "react-dom": "^19.2.0",
20
20
  "zod": "^4.0.0"
21
21
  },
22
22
  "nextspark": {
@@ -17,9 +17,9 @@
17
17
  "@nextsparkjs/core": ">=0.1.0-0",
18
18
  "@nextsparkjs/testing": ">=0.1.0-0",
19
19
  "lucide-react": "^0.539.0",
20
- "next": "^15.0.0 || ^16.0.0",
21
- "react": "^19.0.0",
22
- "react-dom": "^19.0.0",
20
+ "next": "~16.3.6",
21
+ "react": "^19.2.0",
22
+ "react-dom": "^19.2.0",
23
23
  "zod": "^4.0.0"
24
24
  },
25
25
  "nextspark": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.193",
3
+ "version": "0.1.0-beta.194",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -390,9 +390,9 @@
390
390
  "scripts"
391
391
  ],
392
392
  "peerDependencies": {
393
- "next": "~16.3.5",
394
- "react": ">=18.0.0",
395
- "react-dom": ">=18.0.0"
393
+ "next": "~16.3.6",
394
+ "react": "^19.2.0",
395
+ "react-dom": "^19.2.0"
396
396
  },
397
397
  "devDependencies": {
398
398
  "@better-fetch/fetch": "1.3.1",
@@ -496,7 +496,10 @@
496
496
  "use-intl": "^4.9.1",
497
497
  "uuid": "^13.0.0",
498
498
  "zod": "^4.3.6",
499
- "@nextsparkjs/ui": "0.1.0-beta.193"
499
+ "@nextsparkjs/ui": "0.1.0-beta.194"
500
+ },
501
+ "engines": {
502
+ "node": ">=22.14.0"
500
503
  },
501
504
  "scripts": {
502
505
  "postinstall": "node scripts/postinstall.mjs || true",
@@ -192,6 +192,10 @@ async function main() {
192
192
  console.log(' RESEND_API_KEY, RESEND_FROM_EMAIL; Google: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET), or change auth.methods.')
193
193
  console.log(' Credentials injected only at runtime: declare them with NEXTSPARK_AUTH_RUNTIME_ONLY=email,google;')
194
194
  console.log(' they are validated again when the server starts and on every login request.')
195
+ if (result.diagnostics.some(({ code }) => code.endsWith('_PLACEHOLDER'))) {
196
+ console.log(' A placeholder value (re_..., your-google-client-id) is a value, not a missing one: NEXTSPARK_AUTH_RUNTIME_ONLY does not defer it.')
197
+ console.log(' Remove the placeholder lines from .env (and the build environment), or replace them with the real values.')
198
+ }
195
199
  process.exit(1)
196
200
  }
197
201
 
@@ -29,7 +29,7 @@ const RAW_CONTROL = /[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u061c\u200e\u200f\
29
29
  async function createProject(parent = tmpdir(), name = 'nextspark-build-output-test-') {
30
30
  const root = await mkdtemp(join(parent, name))
31
31
  await writeFile(join(root, 'nextspark.config.ts'), 'export default {}\n')
32
- await writeFile(join(root, 'package.json'), '{"dependencies":{"next":"16.3.5"}}')
32
+ await writeFile(join(root, 'package.json'), '{"dependencies":{"next":"16.3.6"}}')
33
33
  await mkdir(join(root, 'src', 'app'), { recursive: true })
34
34
  return root
35
35
  }
@@ -17,7 +17,7 @@ async function project(files = {}) {
17
17
 
18
18
  test('the nearest nextspark.config.ts owns every project source path', async () => {
19
19
  const root = await project({
20
- 'package.json': JSON.stringify({ dependencies: { next: '16.3.5' } }),
20
+ 'package.json': JSON.stringify({ dependencies: { next: '16.3.6' } }),
21
21
  'nextspark.config.ts': 'export default {}\n',
22
22
  })
23
23
  const nested = join(root, 'entities', 'tasks')
@@ -40,7 +40,7 @@ test('the nearest nextspark.config.ts owns every project source path', async ()
40
40
 
41
41
  test('a next.config file cannot select a project without nextspark.config.ts', async () => {
42
42
  const root = await project({
43
- 'package.json': JSON.stringify({ dependencies: { next: '16.3.5' } }),
43
+ 'package.json': JSON.stringify({ dependencies: { next: '16.3.6' } }),
44
44
  'next.config.mjs': 'export default {}\n',
45
45
  })
46
46
  try {
@@ -66,7 +66,7 @@ test('an enabled packaged plugin resolves through the project dependency tree',
66
66
  const root = await project({
67
67
  'package.json': JSON.stringify({
68
68
  dependencies: {
69
- next: '16.3.5',
69
+ next: '16.3.6',
70
70
  '@example/plugin-search': '1.0.0',
71
71
  },
72
72
  }),
@@ -98,7 +98,7 @@ test('an enabled packaged plugin that is not installed fails with an actionable
98
98
  const root = await project({
99
99
  'package.json': JSON.stringify({
100
100
  dependencies: {
101
- next: '16.3.5',
101
+ next: '16.3.6',
102
102
  '@example/plugin-search': '1.0.0',
103
103
  },
104
104
  }),
@@ -119,7 +119,7 @@ test('a packaged plugin cannot use a path segment as its logical name', async ()
119
119
  const root = await project({
120
120
  'package.json': JSON.stringify({
121
121
  dependencies: {
122
- next: '16.3.5',
122
+ next: '16.3.6',
123
123
  '@example/plugin-search': '1.0.0',
124
124
  },
125
125
  }),
@@ -146,7 +146,7 @@ test('a local plugin shadows an installed packaged plugin with the same NextSpar
146
146
  const root = await project({
147
147
  'package.json': JSON.stringify({
148
148
  dependencies: {
149
- next: '16.3.5',
149
+ next: '16.3.6',
150
150
  '@example/plugin-search': '1.0.0',
151
151
  },
152
152
  }),
@@ -269,7 +269,7 @@ async function snapshot(root) {
269
269
  async function buildableProject() {
270
270
  const project = await directory()
271
271
  await writeIn(project.root, 'nextspark.config.ts', 'export default {}\n')
272
- await writeIn(project.root, 'package.json', '{"dependencies":{"next":"16.3.5"}}')
272
+ await writeIn(project.root, 'package.json', '{"dependencies":{"next":"16.3.6"}}')
273
273
  await writeIn(project.root, 'templates/pricing/page.tsx', 'export default function Pricing() { return null }\n')
274
274
  return project
275
275
  }
@@ -22,7 +22,7 @@ async function planFor(files) {
22
22
  const root = temp()
23
23
  try {
24
24
  write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
25
- write(root, 'package.json', JSON.stringify({ name: 'wire', dependencies: { next: '16.3.5' } }))
25
+ write(root, 'package.json', JSON.stringify({ name: 'wire', dependencies: { next: '16.3.6' } }))
26
26
  write(root, 'config/theme.config.ts', "export const wireThemeConfig = { name: 'wire' }\n")
27
27
  for (const [path, content] of Object.entries(files)) write(root, path, content)
28
28
  const entities = await discoverAllEntities(getConfig(root), { includeCore: false })
@@ -73,7 +73,7 @@ const NOTES_CONFIG = `export const noteEntityConfig = {
73
73
  function project(extra = {}) {
74
74
  const root = temp()
75
75
  write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
76
- write(root, 'package.json', JSON.stringify({ name: 'contracts-project', dependencies: { next: '16.3.5' } }))
76
+ write(root, 'package.json', JSON.stringify({ name: 'contracts-project', dependencies: { next: '16.3.6' } }))
77
77
  write(root, 'config/theme.config.ts', "export const contractsThemeConfig = { name: 'contracts' }\n")
78
78
  write(root, 'entities/tasks/tasks.config.ts', TASKS_CONFIG)
79
79
  write(root, 'entities/tasks/tasks.fields.ts', FIELDS)
@@ -201,7 +201,7 @@ export function renderEntity(contract, { source, shape } = {}) {
201
201
  const lines = [CONTRACTS_HEADER]
202
202
  if (source) lines.push(`// Source: ${source}`)
203
203
  for (const warning of contract.warnings) lines.push(`// Warning: ${warning}`)
204
- lines.push('', "import { z } from 'zod'", "import type { EntityConfig } from '../schema/types'", "import { generateEntitySchemas } from '../schema/schema-generator'", `import { ${usedFromShape.join(', ')} } from '../schema/response-shape'`)
204
+ lines.push('', "import * as z from 'zod'", "import type { EntityConfig } from '../schema/types'", "import { generateEntitySchemas } from '../schema/schema-generator'", `import { ${usedFromShape.join(', ')} } from '../schema/response-shape'`)
205
205
  if (shared.length > 0) lines.push(`import { ${shared.join(', ')} } from '../fields'`)
206
206
  lines.push('', `export const ${names.apiPath} = ${quote(`/api/v1/${contract.slug}`)}`, '')
207
207
 
@@ -246,7 +246,7 @@ export function renderEntity(contract, { source, shape } = {}) {
246
246
  export function renderEnvelope() {
247
247
  return `${CONTRACTS_HEADER}
248
248
 
249
- import { z } from 'zod'
249
+ import * as z from 'zod'
250
250
 
251
251
  /** \`info\` of every response: the timestamp, plus whatever the endpoint adds (pagination, \`created\`). */
252
252
  export const apiInfoSchema = z.looseObject({ timestamp: z.string() })
@@ -307,7 +307,7 @@ export const apiErrorResponseSchema = z.object({
307
307
  export function renderFields() {
308
308
  return `${CONTRACTS_HEADER}
309
309
 
310
- import { z } from 'zod'
310
+ import * as z from 'zod'
311
311
  import { addressSchema, fileObjectSchema } from './schema/schema-generator'
312
312
  import { mediaRefSchema } from './schema/media-ref'
313
313