@nextsparkjs/core 0.1.0-beta.174 → 0.1.0-beta.176

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-06-25T14:35:28.210Z",
2
+ "generated": "2026-06-28T23:29:37.388Z",
3
3
  "totalClasses": 1081,
4
4
  "classes": [
5
5
  "!text-2xl",
@@ -62,11 +62,16 @@ export const POST = withRateLimitTier(withApiLogging(
62
62
  return addCorsHeaders(response)
63
63
  }
64
64
 
65
- // Step 1: Validate invitation token (without RLS since user doesn't exist yet)
65
+ // Step 1: Validate the invitation token via the service pool (RLS bypass).
66
+ // The invitee has no session yet, so the unguessable token IS the
67
+ // credential. Passing a 'system' string as the userId does NOT bypass RLS
68
+ // — it sets a bogus RLS context on the gated pool, so under enforced RLS
69
+ // the row is invisible and the lookup returns null (false "not found").
66
70
  const invitation = await queryOneWithRLS<TeamInvitation>(
67
71
  'SELECT * FROM "team_invitations" WHERE token = $1',
68
72
  [inviteToken],
69
- 'system' // Use system context for initial validation
73
+ undefined,
74
+ { service: true }
70
75
  )
71
76
 
72
77
  if (!invitation) {
@@ -165,15 +170,24 @@ export const POST = withRateLimitTier(withApiLogging(
165
170
  return addCorsHeaders(response)
166
171
  }
167
172
 
173
+ // Steps 3-4 run via the service pool (RLS bypass). This is a trusted
174
+ // server flow: the invite token was validated above and the account was
175
+ // just created, so there is no invitee session to drive RLS — and a
176
+ // `SET LOCAL app.user_id` context does not reliably propagate to the
177
+ // membership INSERT over a pooled (PgBouncer) connection, which makes the
178
+ // team_members self-join policy intermittently reject it. Writing as the
179
+ // service role sidesteps that; every row carries explicit ids.
180
+
168
181
  // Step 3: Mark email as verified (skip email verification since invitation proves email ownership)
169
182
  await mutateWithRLS(
170
183
  'UPDATE "users" SET "emailVerified" = true, "updatedAt" = CURRENT_TIMESTAMP WHERE id = $1',
171
184
  [userId],
172
- userId
185
+ undefined,
186
+ { service: true }
173
187
  )
174
188
 
175
189
  // Step 4: Accept the invitation (add user to team)
176
- const tx = await getTransactionClient(userId)
190
+ const tx = await getTransactionClient(userId, { service: true })
177
191
 
178
192
  try {
179
193
  // Add user as team member
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.174",
3
+ "version": "0.1.0-beta.176",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -469,7 +469,7 @@
469
469
  "tailwind-merge": "^3.3.1",
470
470
  "uuid": "^13.0.0",
471
471
  "zod": "^4.1.5",
472
- "@nextsparkjs/testing": "0.1.0-beta.174"
472
+ "@nextsparkjs/testing": "0.1.0-beta.176"
473
473
  },
474
474
  "scripts": {
475
475
  "postinstall": "node scripts/postinstall.mjs || true",
@@ -62,11 +62,16 @@ export const POST = withRateLimitTier(withApiLogging(
62
62
  return addCorsHeaders(response)
63
63
  }
64
64
 
65
- // Step 1: Validate invitation token (without RLS since user doesn't exist yet)
65
+ // Step 1: Validate the invitation token via the service pool (RLS bypass).
66
+ // The invitee has no session yet, so the unguessable token IS the
67
+ // credential. Passing a 'system' string as the userId does NOT bypass RLS
68
+ // — it sets a bogus RLS context on the gated pool, so under enforced RLS
69
+ // the row is invisible and the lookup returns null (false "not found").
66
70
  const invitation = await queryOneWithRLS<TeamInvitation>(
67
71
  'SELECT * FROM "team_invitations" WHERE token = $1',
68
72
  [inviteToken],
69
- 'system' // Use system context for initial validation
73
+ undefined,
74
+ { service: true }
70
75
  )
71
76
 
72
77
  if (!invitation) {
@@ -165,15 +170,24 @@ export const POST = withRateLimitTier(withApiLogging(
165
170
  return addCorsHeaders(response)
166
171
  }
167
172
 
173
+ // Steps 3-4 run via the service pool (RLS bypass). This is a trusted
174
+ // server flow: the invite token was validated above and the account was
175
+ // just created, so there is no invitee session to drive RLS — and a
176
+ // `SET LOCAL app.user_id` context does not reliably propagate to the
177
+ // membership INSERT over a pooled (PgBouncer) connection, which makes the
178
+ // team_members self-join policy intermittently reject it. Writing as the
179
+ // service role sidesteps that; every row carries explicit ids.
180
+
168
181
  // Step 3: Mark email as verified (skip email verification since invitation proves email ownership)
169
182
  await mutateWithRLS(
170
183
  'UPDATE "users" SET "emailVerified" = true, "updatedAt" = CURRENT_TIMESTAMP WHERE id = $1',
171
184
  [userId],
172
- userId
185
+ undefined,
186
+ { service: true }
173
187
  )
174
188
 
175
189
  // Step 4: Accept the invitation (add user to team)
176
- const tx = await getTransactionClient(userId)
190
+ const tx = await getTransactionClient(userId, { service: true })
177
191
 
178
192
  try {
179
193
  // Add user as team member