@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.
package/dist/styles/classes.json
CHANGED
|
@@ -62,11 +62,16 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
62
62
|
return addCorsHeaders(response)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// Step 1: Validate invitation token
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|