studiocms 0.4.2 → 0.4.4

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.
@@ -90,15 +90,37 @@ export const CreateHandlers = HttpApiBuilder.group(
90
90
  return yield* new DashboardAPIError({ error: 'Unauthorized' });
91
91
  }
92
92
 
93
- const [token, user] = yield* Effect.all([
94
- sdk.resetTokenBucket.new(userId),
95
- sdk.GET.users.byId(userId),
96
- ]);
93
+ const user = yield* sdk.GET.users.byId(userId);
97
94
 
98
- if (!token || !user) {
95
+ if (!user) {
99
96
  return yield* new DashboardAPIError({ error: 'User not found' });
100
97
  }
101
98
 
99
+ const rank = user.permissionsData?.rank;
100
+
101
+ if (!rank) {
102
+ return yield* new DashboardAPIError({ error: 'User rank not found' });
103
+ }
104
+
105
+ if (!ValidRanks.has(rank) || rank === 'unknown') {
106
+ return yield* new DashboardAPIError({ error: 'Invalid rank' });
107
+ }
108
+
109
+ const callerPerm = availablePermissionRanks.indexOf(userData.permissionLevel);
110
+ const targetPerm = availablePermissionRanks.indexOf(rank);
111
+
112
+ if (targetPerm >= callerPerm) {
113
+ return yield* new DashboardAPIError({
114
+ error: 'Unauthorized: insufficient permissions to assign target rank',
115
+ });
116
+ }
117
+
118
+ const token = yield* sdk.resetTokenBucket.new(userId);
119
+
120
+ if (!token) {
121
+ return yield* new DashboardAPIError({ error: 'Failed to generate reset token' });
122
+ }
123
+
102
124
  yield* notifier
103
125
  .sendAdminNotification('user_updated', user.username)
104
126
  .pipe(
@@ -277,6 +277,12 @@ export const UsersHandlers = HttpApiBuilder.group(StudioCMSDashboardApiSpec, 'us
277
277
  });
278
278
  }
279
279
 
280
+ if (id !== userData.user?.id && !userData.userPermissionLevel.isAdmin) {
281
+ return yield* new DashboardAPIError({
282
+ error: "Unauthorized: cannot modify another user's notification preferences",
283
+ });
284
+ }
285
+
280
286
  const existingUser = yield* sdk.GET.users.byId(id);
281
287
 
282
288
  if (!existingUser) {
@@ -1362,9 +1362,12 @@ export const RestApiSecureHandler = HttpApiBuilder.group(
1362
1362
  });
1363
1363
  }
1364
1364
 
1365
- if (newUserRank === 'owner' && rank !== 'owner') {
1365
+ const callerPerm = availablePermissionRanks.indexOf(rank);
1366
+ const targetPerm = availablePermissionRanks.indexOf(newUserRank);
1367
+
1368
+ if (targetPerm >= callerPerm) {
1366
1369
  return yield* new RestAPIError({
1367
- error: 'Unauthorized to create user with owner rank',
1370
+ error: 'Unauthorized: insufficient permissions to assign target rank',
1368
1371
  });
1369
1372
  }
1370
1373
 
@@ -1372,12 +1375,6 @@ export const RestApiSecureHandler = HttpApiBuilder.group(
1372
1375
  password = yield* sdk.UTIL.Generators.generateRandomPassword(12);
1373
1376
  }
1374
1377
 
1375
- if (rank === 'admin' && newUserRank === 'owner') {
1376
- return yield* new RestAPIError({
1377
- error: 'Unauthorized to create user with owner rank',
1378
- });
1379
- }
1380
-
1381
1378
  const checkEmail = isValidEmail(email);
1382
1379
 
1383
1380
  if (!checkEmail.success) {
@@ -1639,19 +1636,24 @@ export const RestApiSecureHandler = HttpApiBuilder.group(
1639
1636
  })
1640
1637
  );
1641
1638
 
1642
- if (rank !== 'owner') {
1643
- data = data.filter((user) => user.rank !== 'owner');
1644
- }
1639
+ const loggedInUserRankIndex = availablePermissionRanks.indexOf(user.rank);
1640
+
1641
+ data = data.filter((candidate) => {
1642
+ const candidateRankIndex = availablePermissionRanks.indexOf(candidate.rank);
1643
+ return loggedInUserRankIndex > candidateRankIndex;
1644
+ });
1645
1645
 
1646
1646
  if (name) {
1647
- data = data.filter((user) => user.name.toLowerCase().includes(name.toLowerCase()));
1647
+ data = data.filter((candidate) =>
1648
+ candidate.name.toLowerCase().includes(name.toLowerCase())
1649
+ );
1648
1650
  }
1649
1651
  if (rank) {
1650
- data = data.filter((user) => user.rank === rank);
1652
+ data = data.filter((candidate) => candidate.rank === rank);
1651
1653
  }
1652
1654
  if (username) {
1653
- data = data.filter((user) =>
1654
- user.username.toLowerCase().includes(username.toLowerCase())
1655
+ data = data.filter((candidate) =>
1656
+ candidate.username.toLowerCase().includes(username.toLowerCase())
1655
1657
  );
1656
1658
  }
1657
1659
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "studiocms",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "A Community-Driven Astro native CMS. Built from the ground up by the Astro community.",
5
5
  "author": {
6
6
  "name": "withstudiocms",
@@ -253,17 +253,17 @@
253
253
  "tinyglobby": "^0.2.15",
254
254
  "ultrahtml": "^1.6.0",
255
255
  "web-vitals": "^5.1.0",
256
- "@withstudiocms/api-spec": "^0.3.1",
256
+ "@withstudiocms/api-spec": "^0.3.2",
257
257
  "@withstudiocms/internal_helpers": "^0.2.0",
258
258
  "@withstudiocms/auth-kit": "^0.1.4",
259
259
  "@withstudiocms/cli-kit": "^0.2.1",
260
260
  "@withstudiocms/component-registry": "^0.1.4",
261
261
  "@withstudiocms/config-utils": "^0.2.0",
262
- "@withstudiocms/effect": "^0.4.0",
262
+ "@withstudiocms/effect": "^0.4.1",
263
263
  "@withstudiocms/template-lang": "^0.1.0",
264
264
  "@withstudiocms/kysely": "^0.2.1",
265
265
  "@withstudiocms/sdk": "^0.3.0",
266
- "effectify": "^0.1.1"
266
+ "effectify": "^0.2.0"
267
267
  },
268
268
  "devDependencies": {
269
269
  "@types/mdast": "^4.0.4",
@@ -276,6 +276,7 @@
276
276
  },
277
277
  "peerDependencies": {
278
278
  "@effect/platform": "^0.94.5",
279
+ "@effect/platform-node": "^0.104.1",
279
280
  "@libsql/client": "^0.15.15",
280
281
  "astro": "^5.12.9",
281
282
  "effect": "^3.19.19",
@@ -283,9 +284,21 @@
283
284
  "mysql2": "^3.18.0"
284
285
  },
285
286
  "peerDependenciesMeta": {
287
+ "@effect/platform": {
288
+ "optional": false
289
+ },
290
+ "@effect/platform-node": {
291
+ "optional": false
292
+ },
286
293
  "@libsql/client": {
287
294
  "optional": true
288
295
  },
296
+ "astro": {
297
+ "optional": false
298
+ },
299
+ "effect": {
300
+ "optional": false
301
+ },
289
302
  "pg": {
290
303
  "optional": true
291
304
  },