@nextsparkjs/core 0.1.0-beta.154 → 0.1.0-beta.156
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/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +8 -1
- package/dist/lib/services/user.service.js +5 -5
- package/dist/lib/teams/permissions.d.ts +18 -0
- package/dist/lib/teams/permissions.d.ts.map +1 -1
- package/dist/lib/teams/permissions.js +5 -0
- package/dist/styles/classes.json +1 -1
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +3 -14
- package/package.json +2 -2
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +3 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generic-handler.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/entity/generic-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAyhBvD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA+fnF;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"generic-handler.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/entity/generic-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAyhBvD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA+fnF;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAmZrF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAmMpJ;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAyXtJ;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAoJtJ;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAEtF"}
|
|
@@ -681,7 +681,14 @@ async function handleGenericCreate(request) {
|
|
|
681
681
|
let values;
|
|
682
682
|
let paramCount;
|
|
683
683
|
const isSharedEntity = ((_b = entityConfig.access) == null ? void 0 : _b.shared) === true;
|
|
684
|
-
|
|
684
|
+
let userIdToUse = authResult.user.id;
|
|
685
|
+
if (isSharedEntity && Object.prototype.hasOwnProperty.call(entityData, "userId")) {
|
|
686
|
+
if (rawUserId === null) {
|
|
687
|
+
userIdToUse = null;
|
|
688
|
+
} else if (typeof rawUserId === "string" && rawUserId.trim() !== "") {
|
|
689
|
+
userIdToUse = rawUserId;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
685
692
|
const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
|
|
686
693
|
if (!teamValidation.valid) {
|
|
687
694
|
return teamValidation.error;
|
|
@@ -35,7 +35,7 @@ class UserService {
|
|
|
35
35
|
"emailVerified",
|
|
36
36
|
"createdAt",
|
|
37
37
|
"updatedAt"
|
|
38
|
-
FROM "
|
|
38
|
+
FROM "users"
|
|
39
39
|
WHERE id = $1 OR email = $1`,
|
|
40
40
|
[identifier],
|
|
41
41
|
currentUserId
|
|
@@ -81,7 +81,7 @@ class UserService {
|
|
|
81
81
|
"emailVerified",
|
|
82
82
|
"createdAt",
|
|
83
83
|
"updatedAt"
|
|
84
|
-
FROM "
|
|
84
|
+
FROM "users"
|
|
85
85
|
WHERE email = $1`,
|
|
86
86
|
[email],
|
|
87
87
|
currentUserId
|
|
@@ -127,7 +127,7 @@ class UserService {
|
|
|
127
127
|
"emailVerified",
|
|
128
128
|
"createdAt",
|
|
129
129
|
"updatedAt"
|
|
130
|
-
FROM "
|
|
130
|
+
FROM "users"
|
|
131
131
|
WHERE id = $1`,
|
|
132
132
|
[userId],
|
|
133
133
|
currentUserId
|
|
@@ -174,7 +174,7 @@ class UserService {
|
|
|
174
174
|
"emailVerified",
|
|
175
175
|
"createdAt",
|
|
176
176
|
"updatedAt"
|
|
177
|
-
FROM "
|
|
177
|
+
FROM "users"
|
|
178
178
|
WHERE id IN (${placeholders})`,
|
|
179
179
|
userIds,
|
|
180
180
|
currentUserId
|
|
@@ -238,7 +238,7 @@ class UserService {
|
|
|
238
238
|
updateFields.push(`"updatedAt" = CURRENT_TIMESTAMP`);
|
|
239
239
|
values.push(userId);
|
|
240
240
|
const query = `
|
|
241
|
-
UPDATE "
|
|
241
|
+
UPDATE "users"
|
|
242
242
|
SET ${updateFields.join(", ")}
|
|
243
243
|
WHERE id = $${paramCount}
|
|
244
244
|
RETURNING
|
|
@@ -89,6 +89,24 @@ export declare function isSuperadmin(userRole: UserRole): boolean;
|
|
|
89
89
|
* @returns True if action is allowed, false otherwise
|
|
90
90
|
*/
|
|
91
91
|
export declare function canManageRole(actorRole: string, targetRole: string): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Check if a role can invite another role to the team
|
|
94
|
+
*
|
|
95
|
+
* Reads hierarchy from the merged permissions registry, so any roles added
|
|
96
|
+
* by consumers via additional config are supported automatically.
|
|
97
|
+
*
|
|
98
|
+
* Semantics: invitation is allowed when the actor's hierarchy level is
|
|
99
|
+
* greater than or equal to the target's hierarchy level. Peers can invite
|
|
100
|
+
* peers (e.g. admin → admin); a lower-ranked actor cannot invite a
|
|
101
|
+
* higher-ranked target.
|
|
102
|
+
*
|
|
103
|
+
* Missing hierarchy entries are treated as 0, mirroring canManageRole.
|
|
104
|
+
*
|
|
105
|
+
* @param actorRole - The role of the user issuing the invitation
|
|
106
|
+
* @param targetRole - The role the invitee would receive
|
|
107
|
+
* @returns True if the invitation is allowed, false otherwise
|
|
108
|
+
*/
|
|
109
|
+
export declare function canInviteToRole(actorRole: string, targetRole: string): boolean;
|
|
92
110
|
/**
|
|
93
111
|
* Get human-readable role description
|
|
94
112
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/lib/teams/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAUH;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAA;AAE9C;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,WAAW,GACX,aAAa,GACb,mBAAmB,GACnB,qBAAqB,GACrB,qBAAqB,GACrB,0BAA0B,GAC1B,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,GACnB,qBAAqB,CAAA;AAEzB;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,cAAc,EAYhD,CAAA;AAuCD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,CAAgC,CAAA;AAM7F;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,cAAc,EAC1B,aAAa,GAAE,OAAe,GAC7B,OAAO,CAWT;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,OAAe,GAC7B,cAAc,EAAE,CAQlB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,UAAU,EAAE,cAAc,GACzB,OAAO,CAcT;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAExD;AAMD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAKT;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGvD;AAMD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAI5C;AAMD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA0BvC"}
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/lib/teams/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAUH;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAA;AAE9C;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,WAAW,GACX,aAAa,GACb,mBAAmB,GACnB,qBAAqB,GACrB,qBAAqB,GACrB,0BAA0B,GAC1B,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,GACnB,qBAAqB,CAAA;AAEzB;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,cAAc,EAYhD,CAAA;AAuCD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,CAAgC,CAAA;AAM7F;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,cAAc,EAC1B,aAAa,GAAE,OAAe,GAC7B,OAAO,CAWT;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,OAAe,GAC7B,cAAc,EAAE,CAQlB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,UAAU,EAAE,cAAc,GACzB,OAAO,CAcT;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAExD;AAMD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAKT;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAIT;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGvD;AAMD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAI5C;AAMD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA0BvC"}
|
|
@@ -63,6 +63,10 @@ function canManageRole(actorRole, targetRole) {
|
|
|
63
63
|
const hierarchy = getHierarchyFromRegistry();
|
|
64
64
|
return (hierarchy[actorRole] ?? 0) > (hierarchy[targetRole] ?? 0);
|
|
65
65
|
}
|
|
66
|
+
function canInviteToRole(actorRole, targetRole) {
|
|
67
|
+
const hierarchy = getHierarchyFromRegistry();
|
|
68
|
+
return (hierarchy[actorRole] ?? 0) >= (hierarchy[targetRole] ?? 0);
|
|
69
|
+
}
|
|
66
70
|
function getRoleDescription(role) {
|
|
67
71
|
const descriptions = getDescriptionsFromRegistry();
|
|
68
72
|
return descriptions[role] ?? "No description available";
|
|
@@ -97,6 +101,7 @@ function validateRoleTransition(fromRole, toRole, actorRole) {
|
|
|
97
101
|
}
|
|
98
102
|
export {
|
|
99
103
|
ALL_TEAM_PERMISSIONS,
|
|
104
|
+
canInviteToRole,
|
|
100
105
|
canManageRole,
|
|
101
106
|
checkTeamPermission,
|
|
102
107
|
getInvitableRoles,
|
package/dist/styles/classes.json
CHANGED
|
@@ -14,24 +14,12 @@ import { checkRateLimit, withRateLimitTier } from '@nextsparkjs/core/lib/api/rat
|
|
|
14
14
|
import { RATE_LIMITS } from '@nextsparkjs/core/lib/api/keys'
|
|
15
15
|
import { inviteMemberSchema, memberListQuerySchema } from '@nextsparkjs/core/lib/teams/schema'
|
|
16
16
|
import { TeamMemberService, MembershipService } from '@nextsparkjs/core/lib/services'
|
|
17
|
+
import { canInviteToRole } from '@nextsparkjs/core/lib/teams/permissions'
|
|
17
18
|
import type { TeamMember, TeamInvitation, TeamRole, Team } from '@nextsparkjs/core/lib/teams/types'
|
|
18
19
|
import { EmailFactory } from '@nextsparkjs/core/lib/email/factory'
|
|
19
20
|
import { sendTeamInvitationEmail } from '@nextsparkjs/core/lib/email/send'
|
|
20
21
|
import { I18N_CONFIG } from '@nextsparkjs/core/lib/config'
|
|
21
22
|
|
|
22
|
-
// Role hierarchy for invite validation (higher number = more power)
|
|
23
|
-
const ROLE_HIERARCHY: Record<TeamRole, number> = {
|
|
24
|
-
owner: 4,
|
|
25
|
-
admin: 3,
|
|
26
|
-
member: 2,
|
|
27
|
-
viewer: 1,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check if a user can invite to a specific role (same level or below)
|
|
31
|
-
function canInviteToRole(actorRole: TeamRole, targetRole: TeamRole): boolean {
|
|
32
|
-
return ROLE_HIERARCHY[actorRole] >= ROLE_HIERARCHY[targetRole]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
23
|
// Handle CORS preflight
|
|
36
24
|
export async function OPTIONS() {
|
|
37
25
|
return handleCorsPreflightRequest()
|
|
@@ -226,7 +214,8 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
226
214
|
const body = await req.json()
|
|
227
215
|
const validatedData = inviteMemberSchema.parse(body)
|
|
228
216
|
|
|
229
|
-
// Check role hierarchy
|
|
217
|
+
// Check role hierarchy via the merged permissions registry — users can
|
|
218
|
+
// only invite to roles at the same level or below their own
|
|
230
219
|
if (!canInviteToRole(userRole, validatedData.role)) {
|
|
231
220
|
const response = createApiError(
|
|
232
221
|
`You cannot invite members to a role higher than your own. Your role: ${userRole}, requested role: ${validatedData.role}`,
|
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.156",
|
|
4
4
|
"description": "NextSpark - The complete SaaS framework for Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "NextSpark <hello@nextspark.dev>",
|
|
@@ -467,7 +467,7 @@
|
|
|
467
467
|
"tailwind-merge": "^3.3.1",
|
|
468
468
|
"uuid": "^13.0.0",
|
|
469
469
|
"zod": "^4.1.5",
|
|
470
|
-
"@nextsparkjs/testing": "0.1.0-beta.
|
|
470
|
+
"@nextsparkjs/testing": "0.1.0-beta.156"
|
|
471
471
|
},
|
|
472
472
|
"scripts": {
|
|
473
473
|
"postinstall": "node scripts/postinstall.mjs || true",
|
|
@@ -14,24 +14,12 @@ import { checkRateLimit, withRateLimitTier } from '@nextsparkjs/core/lib/api/rat
|
|
|
14
14
|
import { RATE_LIMITS } from '@nextsparkjs/core/lib/api/keys'
|
|
15
15
|
import { inviteMemberSchema, memberListQuerySchema } from '@nextsparkjs/core/lib/teams/schema'
|
|
16
16
|
import { TeamMemberService, MembershipService } from '@nextsparkjs/core/lib/services'
|
|
17
|
+
import { canInviteToRole } from '@nextsparkjs/core/lib/teams/permissions'
|
|
17
18
|
import type { TeamMember, TeamInvitation, TeamRole, Team } from '@nextsparkjs/core/lib/teams/types'
|
|
18
19
|
import { EmailFactory } from '@nextsparkjs/core/lib/email/factory'
|
|
19
20
|
import { sendTeamInvitationEmail } from '@nextsparkjs/core/lib/email/send'
|
|
20
21
|
import { I18N_CONFIG } from '@nextsparkjs/core/lib/config'
|
|
21
22
|
|
|
22
|
-
// Role hierarchy for invite validation (higher number = more power)
|
|
23
|
-
const ROLE_HIERARCHY: Record<TeamRole, number> = {
|
|
24
|
-
owner: 4,
|
|
25
|
-
admin: 3,
|
|
26
|
-
member: 2,
|
|
27
|
-
viewer: 1,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check if a user can invite to a specific role (same level or below)
|
|
31
|
-
function canInviteToRole(actorRole: TeamRole, targetRole: TeamRole): boolean {
|
|
32
|
-
return ROLE_HIERARCHY[actorRole] >= ROLE_HIERARCHY[targetRole]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
23
|
// Handle CORS preflight
|
|
36
24
|
export async function OPTIONS() {
|
|
37
25
|
return handleCorsPreflightRequest()
|
|
@@ -226,7 +214,8 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
226
214
|
const body = await req.json()
|
|
227
215
|
const validatedData = inviteMemberSchema.parse(body)
|
|
228
216
|
|
|
229
|
-
// Check role hierarchy
|
|
217
|
+
// Check role hierarchy via the merged permissions registry — users can
|
|
218
|
+
// only invite to roles at the same level or below their own
|
|
230
219
|
if (!canInviteToRole(userRole, validatedData.role)) {
|
|
231
220
|
const response = createApiError(
|
|
232
221
|
`You cannot invite members to a role higher than your own. Your role: ${userRole}, requested role: ${validatedData.role}`,
|