@stoker-platform/cli 0.5.138 → 0.5.139

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/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/cli",
3
- "version": "0.5.137",
3
+ "version": "0.5.138",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./lib/src/main.js",
@@ -24,9 +24,9 @@
24
24
  "@google-cloud/secret-manager": "^6.1.2",
25
25
  "@google-cloud/storage": "^7.19.0",
26
26
  "@inquirer/prompts": "^8.5.2",
27
- "@stoker-platform/node-client": "0.5.86",
28
- "@stoker-platform/types": "0.5.64",
29
- "@stoker-platform/utils": "0.5.77",
27
+ "@stoker-platform/node-client": "0.5.87",
28
+ "@stoker-platform/types": "0.5.65",
29
+ "@stoker-platform/utils": "0.5.78",
30
30
  "algoliasearch": "^5.53.0",
31
31
  "commander": "^15.0.0",
32
32
  "cross-spawn": "^7.0.6",
@@ -204,11 +204,8 @@ export const lintSchema = async (noLog = false) => {
204
204
  if (!groupKeyRegex.test(groupKey)) {
205
205
  errors.push(`Collection ${collectionName} has a field access group key ${groupKey} that is invalid. Keys must start with a letter and contain only letters, digits and hyphens.`);
206
206
  }
207
- if (fieldNames.includes(groupKey)) {
208
- errors.push(`Collection ${collectionName} has a field access group key ${groupKey} that collides with a field name`);
209
- }
210
- if (roles.includes(groupKey)) {
211
- errors.push(`Collection ${collectionName} has a field access group key ${groupKey} that collides with a role name`);
207
+ if (/-\d+$/.test(groupKey)) {
208
+ errors.push(`Collection ${collectionName} has a field access group key ${groupKey} that ends with a hyphen followed by digits.`);
212
209
  }
213
210
  // eslint-disable-next-line security/detect-object-injection
214
211
  if (!groupFields[groupKey]?.length) {
@@ -1,5 +1,5 @@
1
1
  import { generateSchema } from "../deploy/schema/generateSchema.js";
2
- import { getAccessFields, getField, getDependencyIndexFields, getFieldAccessGroupFields, getFieldAccessGroupIndexFields, getRoleFields, isDependencyField, isRelationField, getRoleGroups, getFieldCustomization, roleHasOperationAccess, } from "@stoker-platform/utils";
2
+ import { getAccessFields, getField, getDependencyIndexFields, getFieldAccessGroupFields, getFieldAccessGroupIndexFields, isDependencyField, isRelationField, getRoleGroups, getFieldCustomization, roleHasOperationAccess, } from "@stoker-platform/utils";
3
3
  import { join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  /* eslint-disable security/detect-object-injection */
@@ -27,15 +27,20 @@ export const securityReport = async () => {
27
27
  writeRuleReads[collectionName].batch.add("User Document Lookup");
28
28
  writeRuleReads[collectionName].batch.add("Latest Deploy Document");
29
29
  writeRuleReads[collectionName].batch.add("Maintenance Mode Document");
30
- const roleGroups = getRoleGroups(collectionSchema, schema);
30
+ const roleGroups = Array.from(getRoleGroups(collectionSchema, schema));
31
31
  roleGroups.forEach(() => {
32
32
  writeRuleReads[collectionName].batch.add("Main Document");
33
33
  });
34
34
  const fieldAccessGroups = getFieldAccessGroupFields(collectionSchema);
35
- Object.values(fieldAccessGroups).forEach((groupFields) => {
35
+ Object.entries(fieldAccessGroups).forEach(([groupKey, groupFields]) => {
36
36
  if (groupFields.length === 0)
37
37
  return;
38
- writeRuleReads[collectionName].batch.add(`Main Document`);
38
+ for (const roleGroup of roleGroups) {
39
+ const overlayFields = getFieldAccessGroupIndexFields(groupKey, collectionSchema, roleGroup);
40
+ if (overlayFields.length === 0)
41
+ continue;
42
+ writeRuleReads[collectionName].batch.add("Main Document");
43
+ }
39
44
  });
40
45
  if (auth) {
41
46
  writeRuleReads[collectionName].main.add("Document Lock Lookup");
@@ -154,34 +159,6 @@ export const securityReport = async () => {
154
159
  }
155
160
  }
156
161
  }
157
- if (roleHasOperationAccess(collectionSchema, role, "read")) {
158
- const fieldAccessGroups = getFieldAccessGroupFields(collectionSchema);
159
- const roleFields = getRoleFields(collectionSchema, role);
160
- const roleFieldNames = new Set(roleFields.map((field) => field.name));
161
- for (const [groupKey, groupFields] of Object.entries(fieldAccessGroups)) {
162
- if (groupFields.length === 0)
163
- continue;
164
- // eslint-disable-next-line security/detect-object-injection
165
- if (!collectionSchema.fieldAccessGroups?.[groupKey]?.applicableRoles.includes(role))
166
- continue;
167
- const groupFieldNames = new Set(groupFields.map((field) => field.name));
168
- const overlayFields = getFieldAccessGroupIndexFields(groupKey, collectionSchema);
169
- for (const overlayField of overlayFields) {
170
- if (overlayField.name === "id" || overlayField.name === "Collection_Path") {
171
- continue;
172
- }
173
- if (groupFieldNames.has(overlayField.name))
174
- continue;
175
- if (roleFieldNames.has(overlayField.name))
176
- continue;
177
- roles[role][collectionName] ||= {};
178
- if (!roles[role][collectionName][overlayField.name]) {
179
- roles[role][collectionName][overlayField.name] = new Set();
180
- }
181
- roles[role][collectionName][overlayField.name].add(`Field Access Group ${groupKey}- Index`);
182
- }
183
- }
184
- }
185
162
  }
186
163
  }
187
164
  console.log("Security Rule Write Reads:\n");
@@ -212,7 +189,6 @@ export const securityReport = async () => {
212
189
  console.log("\n");
213
190
  }
214
191
  console.log("\n\nPossible Excess Permissions:\n");
215
- console.log("(Includes fields on field access group overlays that are not on the role's mirror, after filtering overlay metadata to the roles declared on each field access group.)\n");
216
192
  for (const [role, collections] of Object.entries(roles)) {
217
193
  console.log(`${role.toUpperCase()}\n`);
218
194
  for (const [collection, fields] of Object.entries(collections)) {
@@ -1,5 +1,5 @@
1
1
  import { fetchCurrentSchema, initializeStoker, getStokerFirestore } from "@stoker-platform/node-client";
2
- import { getDependencyIndexFields, getFieldAccessGroupFields, getFieldAccessGroupIndexFields, getLowercaseFields, getRoleGroups, getSingleFieldRelations, isDependencyField, isRelationField, } from "@stoker-platform/utils";
2
+ import { getDependencyIndexFields, getFieldAccessGroupFields, getFieldAccessGroupIndexFields, getFieldAccessGroupKey, getLowercaseFields, getRoleGroups, getSingleFieldRelations, isDependencyField, isRelationField, } from "@stoker-platform/utils";
3
3
  import { join } from "node:path";
4
4
  import isEqual from "lodash/isEqual.js";
5
5
  import isEmpty from "lodash/isEmpty.js";
@@ -121,57 +121,62 @@ export const auditDenormalized = async (options) => {
121
121
  for (const [groupKey, groupFields] of Object.entries(fieldAccessGroups)) {
122
122
  if (groupFields.length === 0)
123
123
  continue;
124
- const overlayIndexFields = getFieldAccessGroupIndexFields(groupKey, collectionSchema);
125
- const overlaySnapshot = await db
126
- .collection("tenants")
127
- .doc(options.tenant)
128
- .collection("system_fields")
129
- .doc(collectionName)
130
- .collection(`${collectionName}-${groupKey}`)
131
- .get();
132
- const overlayIds = new Set();
133
- const lowercaseFields = getLowercaseFields(collectionSchema, overlayIndexFields);
134
- const lowercaseFieldNames = Array.from(lowercaseFields).map((field) => field.name);
135
- overlaySnapshot.forEach((overlay) => {
136
- overlayIds.add(overlay.id);
137
- const overlayData = overlay.data();
138
- for (const indexField of overlayIndexFields) {
139
- if (indexField.name === "Collection_Path_String")
140
- continue;
141
- if (isRelationField(indexField)) {
142
- const overlayValue = {
143
- [indexField.name]: overlayData[indexField.name],
144
- [`${indexField.name}_Array`]: overlayData[`${indexField.name}_Array`],
145
- };
146
- const collectionValue = {
147
- [indexField.name]: collectionData[overlay.id]?.[indexField.name],
148
- [`${indexField.name}_Array`]: collectionData[overlay.id]?.[`${indexField.name}_Array`],
149
- };
150
- if (singleFieldRelationNames.includes(indexField.name)) {
151
- overlayValue[`${indexField.name}_Single`] = overlayData[`${indexField.name}_Single`];
152
- collectionValue[`${indexField.name}_Single`] =
153
- collectionData[overlay.id]?.[`${indexField.name}_Single`];
154
- }
155
- if (!isEqual(overlayValue, collectionValue)) {
156
- console.log(`${collectionName} ${overlay.id} Field Access Group ${groupKey}: ${indexField.name} - ${JSON.stringify(overlayValue)} !== ${JSON.stringify(collectionValue)}`);
124
+ for (const roleGroup of roleGroups) {
125
+ const overlayIndexFields = getFieldAccessGroupIndexFields(groupKey, collectionSchema, roleGroup);
126
+ if (overlayIndexFields.length === 0)
127
+ continue;
128
+ const overlayKey = getFieldAccessGroupKey(groupKey, roleGroup.key);
129
+ const overlaySnapshot = await db
130
+ .collection("tenants")
131
+ .doc(options.tenant)
132
+ .collection("system_fields")
133
+ .doc(collectionName)
134
+ .collection(`${collectionName}-${overlayKey}`)
135
+ .get();
136
+ const overlayIds = new Set();
137
+ const lowercaseFields = getLowercaseFields(collectionSchema, overlayIndexFields);
138
+ const lowercaseFieldNames = Array.from(lowercaseFields).map((field) => field.name);
139
+ overlaySnapshot.forEach((overlay) => {
140
+ overlayIds.add(overlay.id);
141
+ const overlayData = overlay.data();
142
+ for (const indexField of overlayIndexFields) {
143
+ if (indexField.name === "Collection_Path_String")
144
+ continue;
145
+ if (isRelationField(indexField)) {
146
+ const overlayValue = {
147
+ [indexField.name]: overlayData[indexField.name],
148
+ [`${indexField.name}_Array`]: overlayData[`${indexField.name}_Array`],
149
+ };
150
+ const collectionValue = {
151
+ [indexField.name]: collectionData[overlay.id]?.[indexField.name],
152
+ [`${indexField.name}_Array`]: collectionData[overlay.id]?.[`${indexField.name}_Array`],
153
+ };
154
+ if (singleFieldRelationNames.includes(indexField.name)) {
155
+ overlayValue[`${indexField.name}_Single`] = overlayData[`${indexField.name}_Single`];
156
+ collectionValue[`${indexField.name}_Single`] =
157
+ collectionData[overlay.id]?.[`${indexField.name}_Single`];
158
+ }
159
+ if (!isEqual(overlayValue, collectionValue)) {
160
+ console.log(`${collectionName} ${overlay.id} Field Access Group ${overlayKey}: ${indexField.name} - ${JSON.stringify(overlayValue)} !== ${JSON.stringify(collectionValue)}`);
161
+ }
157
162
  }
158
- }
159
- else {
160
- const overlayValue = overlayData[indexField.name];
161
- const collectionValue = collectionData[overlay.id]?.[indexField.name];
162
- if (!isEqual(overlayValue, collectionValue)) {
163
- console.log(`${collectionName} ${overlay.id} Field Access Group ${groupKey}: ${indexField.name} - ${JSON.stringify(overlayValue)} !== ${JSON.stringify(collectionValue)}`);
163
+ else {
164
+ const overlayValue = overlayData[indexField.name];
165
+ const collectionValue = collectionData[overlay.id]?.[indexField.name];
166
+ if (!isEqual(overlayValue, collectionValue)) {
167
+ console.log(`${collectionName} ${overlay.id} Field Access Group ${overlayKey}: ${indexField.name} - ${JSON.stringify(overlayValue)} !== ${JSON.stringify(collectionValue)}`);
168
+ }
164
169
  }
165
- }
166
- if (lowercaseFieldNames.includes(indexField.name)) {
167
- const overlayValue = overlayData[`${indexField.name}_Lowercase`];
168
- const collectionValue = collectionData[overlay.id]?.[`${indexField.name}_Lowercase`];
169
- if (!isEqual(overlayValue, collectionValue)) {
170
- console.log(`${collectionName} ${overlay.id} Field Access Group ${groupKey}: ${indexField.name}_Lowercase - ${JSON.stringify(overlayValue)} !== ${JSON.stringify(collectionValue)}`);
170
+ if (lowercaseFieldNames.includes(indexField.name)) {
171
+ const overlayValue = overlayData[`${indexField.name}_Lowercase`];
172
+ const collectionValue = collectionData[overlay.id]?.[`${indexField.name}_Lowercase`];
173
+ if (!isEqual(overlayValue, collectionValue)) {
174
+ console.log(`${collectionName} ${overlay.id} Field Access Group ${overlayKey}: ${indexField.name}_Lowercase - ${JSON.stringify(overlayValue)} !== ${JSON.stringify(collectionValue)}`);
175
+ }
171
176
  }
172
177
  }
173
- }
174
- });
178
+ });
179
+ }
175
180
  }
176
181
  console.log(`${collectionName} audited.\n`);
177
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/cli",
3
- "version": "0.5.138",
3
+ "version": "0.5.139",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./lib/src/main.js",
@@ -24,9 +24,9 @@
24
24
  "@google-cloud/secret-manager": "^6.1.2",
25
25
  "@google-cloud/storage": "^7.19.0",
26
26
  "@inquirer/prompts": "^8.5.2",
27
- "@stoker-platform/node-client": "0.5.87",
27
+ "@stoker-platform/node-client": "0.5.88",
28
28
  "@stoker-platform/types": "0.5.65",
29
- "@stoker-platform/utils": "0.5.78",
29
+ "@stoker-platform/utils": "0.5.79",
30
30
  "algoliasearch": "^5.53.0",
31
31
  "commander": "^15.0.0",
32
32
  "cross-spawn": "^7.0.6",