@nextsparkjs/core 0.1.0-beta.153 → 0.1.0-beta.155
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 +6 -2
- 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,CAyXrF;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"}
|
|
@@ -739,7 +739,9 @@ async function handleGenericCreate(request) {
|
|
|
739
739
|
values.push(relationId);
|
|
740
740
|
} else if (field.type === "relation-multi") {
|
|
741
741
|
placeholders.push(`$${paramCount++}::jsonb`);
|
|
742
|
-
values.push(
|
|
742
|
+
values.push(
|
|
743
|
+
typeof value === "string" ? value : JSON.stringify(Array.isArray(value) ? value : [])
|
|
744
|
+
);
|
|
743
745
|
} else if (field.type === "tags") {
|
|
744
746
|
placeholders.push(`$${paramCount++}::jsonb`);
|
|
745
747
|
values.push(JSON.stringify(Array.isArray(value) ? value : []));
|
|
@@ -1144,7 +1146,9 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1144
1146
|
values.push(relationId);
|
|
1145
1147
|
} else if (field.type === "relation-multi") {
|
|
1146
1148
|
updates.push(`${columnName} = $${paramCount++}::jsonb`);
|
|
1147
|
-
values.push(
|
|
1149
|
+
values.push(
|
|
1150
|
+
typeof value === "string" ? value : JSON.stringify(Array.isArray(value) ? value : [])
|
|
1151
|
+
);
|
|
1148
1152
|
} else if (field.type === "tags") {
|
|
1149
1153
|
updates.push(`${columnName} = $${paramCount++}::jsonb`);
|
|
1150
1154
|
values.push(JSON.stringify(Array.isArray(value) ? value : []));
|
|
@@ -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.155",
|
|
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.155"
|
|
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}`,
|