prostgles-server 4.2.326 → 4.2.327

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.
Files changed (62) hide show
  1. package/dist/DboBuilder/TableHandler/DataValidator.d.ts +1 -9
  2. package/dist/DboBuilder/TableHandler/DataValidator.d.ts.map +1 -1
  3. package/dist/DboBuilder/TableHandler/DataValidator.js +35 -32
  4. package/dist/DboBuilder/TableHandler/DataValidator.js.map +1 -1
  5. package/dist/DboBuilder/TableHandler/insert/insert.d.ts.map +1 -1
  6. package/dist/DboBuilder/TableHandler/insert/insert.js +6 -7
  7. package/dist/DboBuilder/TableHandler/insert/insert.js.map +1 -1
  8. package/dist/DboBuilder/TableHandler/insert/insertNestedRecords.js +6 -6
  9. package/dist/DboBuilder/TableHandler/insert/insertNestedRecords.js.map +1 -1
  10. package/dist/FileManager/initFileManager.d.ts.map +1 -1
  11. package/dist/FileManager/initFileManager.js +1 -1
  12. package/dist/FileManager/initFileManager.js.map +1 -1
  13. package/dist/Prostgles.d.ts +1 -1
  14. package/dist/Prostgles.d.ts.map +1 -1
  15. package/dist/PublishParser/PublishParser.d.ts +3 -3
  16. package/dist/PublishParser/PublishParser.d.ts.map +1 -1
  17. package/dist/PublishParser/PublishParser.js +12 -6
  18. package/dist/PublishParser/PublishParser.js.map +1 -1
  19. package/dist/PublishParser/getSchemaFromPublish.d.ts +2 -2
  20. package/dist/PublishParser/getSchemaFromPublish.d.ts.map +1 -1
  21. package/dist/PublishParser/getSchemaFromPublish.js +3 -3
  22. package/dist/PublishParser/getSchemaFromPublish.js.map +1 -1
  23. package/dist/PublishParser/publishTypesAndUtils.d.ts +10 -1
  24. package/dist/PublishParser/publishTypesAndUtils.d.ts.map +1 -1
  25. package/dist/RestApi.d.ts.map +1 -1
  26. package/dist/RestApi.js +2 -2
  27. package/dist/RestApi.js.map +1 -1
  28. package/dist/WebsocketAPI/getClientHandlers.d.ts +2 -1
  29. package/dist/WebsocketAPI/getClientHandlers.d.ts.map +1 -1
  30. package/dist/WebsocketAPI/getClientHandlers.js +14 -8
  31. package/dist/WebsocketAPI/getClientHandlers.js.map +1 -1
  32. package/dist/WebsocketAPI/getClientSchema.d.ts +2 -1
  33. package/dist/WebsocketAPI/getClientSchema.d.ts.map +1 -1
  34. package/dist/WebsocketAPI/getClientSchema.js +4 -4
  35. package/dist/WebsocketAPI/getClientSchema.js.map +1 -1
  36. package/dist/WebsocketAPI/onSocketConnected.js +1 -1
  37. package/dist/WebsocketAPI/onSocketConnected.js.map +1 -1
  38. package/dist/WebsocketAPI/pushSocketSchema.js +1 -1
  39. package/dist/WebsocketAPI/pushSocketSchema.js.map +1 -1
  40. package/dist/initProstgles.d.ts +2 -2
  41. package/dist/initProstgles.d.ts.map +1 -1
  42. package/dist/initProstgles.js +1 -1
  43. package/dist/initProstgles.js.map +1 -1
  44. package/dist/runClientRequest.d.ts +2 -1
  45. package/dist/runClientRequest.d.ts.map +1 -1
  46. package/dist/runClientRequest.js +4 -4
  47. package/dist/runClientRequest.js.map +1 -1
  48. package/lib/DboBuilder/TableHandler/DataValidator.ts +37 -123
  49. package/lib/DboBuilder/TableHandler/insert/insert.ts +27 -33
  50. package/lib/DboBuilder/TableHandler/insert/insertNestedRecords.ts +7 -7
  51. package/lib/FileManager/initFileManager.ts +2 -1
  52. package/lib/PublishParser/PublishParser.ts +23 -9
  53. package/lib/PublishParser/getSchemaFromPublish.ts +7 -4
  54. package/lib/PublishParser/publishTypesAndUtils.ts +11 -1
  55. package/lib/RestApi.ts +3 -2
  56. package/lib/WebsocketAPI/getClientHandlers.ts +20 -9
  57. package/lib/WebsocketAPI/getClientSchema.ts +14 -7
  58. package/lib/WebsocketAPI/onSocketConnected.ts +1 -1
  59. package/lib/WebsocketAPI/pushSocketSchema.ts +1 -1
  60. package/lib/initProstgles.ts +9 -3
  61. package/lib/runClientRequest.ts +7 -5
  62. package/package.json +1 -1
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  AnyObject,
3
- ColumnInfo,
4
3
  FieldFilter,
5
4
  asName,
6
5
  getKeys,
@@ -288,43 +287,43 @@ const getValidatedRowFieldData = async (
288
287
  return rowFieldData;
289
288
  };
290
289
 
291
- const getTextPatch = (c: TableSchemaColumn, fieldValue: any) => {
292
- if (
293
- c.data_type === "text" &&
294
- fieldValue &&
295
- isObject(fieldValue) &&
296
- !["from", "to"].find((key) => typeof fieldValue[key] !== "number")
297
- ) {
298
- const unrecProps = Object.keys(fieldValue).filter(
299
- (k) => !["from", "to", "text", "md5"].includes(k)
300
- );
301
- if (unrecProps.length) {
302
- throw "Unrecognised params in textPatch field: " + unrecProps.join(", ");
303
- }
304
- const patchedTextData: {
305
- fieldName: string;
306
- from: number;
307
- to: number;
308
- text: string;
309
- md5: string;
310
- } = {
311
- ...fieldValue,
312
- fieldName: c.name,
313
- } as any;
314
-
315
- // if (tableRules && !tableRules.select) throw "Select needs to be permitted to patch data";
316
- // const rows = await this.find(filter, { select: patchedTextData.reduce((a, v) => ({ ...a, [v.fieldName]: 1 }), {}) }, undefined, tableRules);
317
-
318
- // if (rows.length !== 1) {
319
- // throw "Cannot patch data within a filter that affects more/less than 1 row";
320
- // }
321
- // return unpatchText(rows[0][p.fieldName], patchedTextData);
322
- const rawValue = `OVERLAY(${asName(c.name)} PLACING ${asValue(patchedTextData.text)} FROM ${asValue(patchedTextData.from)} FOR ${asValue(patchedTextData.to - patchedTextData.from + 1)})`;
323
- return rawValue;
324
- }
325
-
326
- return undefined;
327
- };
290
+ // const getTextPatch = (c: TableSchemaColumn, fieldValue: any) => {
291
+ // if (
292
+ // c.data_type === "text" &&
293
+ // fieldValue &&
294
+ // isObject(fieldValue) &&
295
+ // !["from", "to"].find((key) => typeof fieldValue[key] !== "number")
296
+ // ) {
297
+ // const unrecProps = Object.keys(fieldValue).filter(
298
+ // (k) => !["from", "to", "text", "md5"].includes(k)
299
+ // );
300
+ // if (unrecProps.length) {
301
+ // throw "Unrecognised params in textPatch field: " + unrecProps.join(", ");
302
+ // }
303
+ // const patchedTextData: {
304
+ // fieldName: string;
305
+ // from: number;
306
+ // to: number;
307
+ // text: string;
308
+ // md5: string;
309
+ // } = {
310
+ // ...fieldValue,
311
+ // fieldName: c.name,
312
+ // } as any;
313
+
314
+ // // if (tableRules && !tableRules.select) throw "Select needs to be permitted to patch data";
315
+ // // const rows = await this.find(filter, { select: patchedTextData.reduce((a, v) => ({ ...a, [v.fieldName]: 1 }), {}) }, undefined, tableRules);
316
+
317
+ // // if (rows.length !== 1) {
318
+ // // throw "Cannot patch data within a filter that affects more/less than 1 row";
319
+ // // }
320
+ // // return unpatchText(rows[0][p.fieldName], patchedTextData);
321
+ // const rawValue = `OVERLAY(${asName(c.name)} PLACING ${asValue(patchedTextData.text)} FROM ${asValue(patchedTextData.from)} FOR ${asValue(patchedTextData.to - patchedTextData.from + 1)})`;
322
+ // return rawValue;
323
+ // }
324
+
325
+ // return undefined;
326
+ // };
328
327
 
329
328
  const getParsedRowFieldDataFunction = (rowPart: RowFieldDataFunction, args: ParseDataArgs) => {
330
329
  const func = convertionFuncs.find((f) => `$${f.name}` === rowPart.funcName);
@@ -407,88 +406,3 @@ const convertionFuncs: ConvertionFunc[] = [
407
406
  },
408
407
  },
409
408
  ];
410
-
411
- export class ColSet {
412
- opts: {
413
- columns: ColumnInfo[];
414
- tableName: string;
415
- colNames: string[];
416
- };
417
-
418
- constructor(columns: ColumnInfo[], tableName: string) {
419
- this.opts = { columns, tableName, colNames: columns.map((c) => c.name) };
420
- }
421
-
422
- // private async getRow(data: any, allowedCols: string[], dbTx: DBHandlerServer, validate: ValidateRow | undefined, command: "update" | "insert", localParams: LocalParams | undefined): Promise<ParsedRowFieldData[]> {
423
- // const badCol = allowedCols.find(c => !this.opts.colNames.includes(c))
424
- // if (!allowedCols || badCol) {
425
- // throw "Missing or unexpected columns: " + badCol;
426
- // }
427
-
428
- // if (command === "update" && isEmpty(data)) {
429
- // throw "No data provided for update";
430
- // }
431
-
432
- // let row = pickKeys(data, allowedCols);
433
- // if (validate) {
434
- // if (!localParams) throw "localParams missing"
435
- // row = await validate({ row, dbx: dbTx, localParams });
436
- // }
437
-
438
- // return Object.entries(row).map(([fieldName, fieldValue]) => {
439
- // const col = this.opts.columns.find(c => c.name === fieldName);
440
- // if (!col) throw "Unexpected missing col name";
441
-
442
- // /**
443
- // * Add conversion functions for PostGIS data
444
- // */
445
- // let escapedVal = "";
446
- // if ((col.udt_name === "geometry" || col.udt_name === "geography") && isObject(fieldValue)) {
447
-
448
- // const dataKeys = Object.keys(fieldValue);
449
- // const funcName = dataKeys[0]!;
450
- // const func = convertionFuncs.find(f => f.name === funcName);
451
- // const funcArgs = fieldValue?.[funcName]
452
- // if (dataKeys.length !== 1 || !func || !Array.isArray(funcArgs)) {
453
- // throw `Expecting only one function key (${convertionFuncs.join(", ")}) \nwith an array of arguments \n within column (${fieldName}) data but got: ${JSON.stringify(fieldValue)} \nExample: { geo_col: { ST_GeomFromText: ["POINT(-71.064544 42.28787)", 4326] } }`;
454
- // }
455
- // escapedVal = func.getQuery(funcArgs);
456
- // } else if (col.udt_name === "text") {
457
-
458
- // } else {
459
- // /** Prevent pg-promise formatting jsonb */
460
- // const colIsJSON = ["json", "jsonb"].includes(col.data_type);
461
- // escapedVal = pgp.as.format(colIsJSON ? "$1:json" : "$1", [fieldValue])
462
- // }
463
-
464
- // /**
465
- // * Cast to type to avoid array errors (they do not cast automatically)
466
- // */
467
- // escapedVal += `::${col.udt_name}`
468
-
469
- // return {
470
- // escapedCol: asName(fieldName),
471
- // escapedVal,
472
- // }
473
- // });
474
-
475
- // }
476
-
477
- // async getInsertQuery(data: AnyObject[], allowedCols: string[], dbTx: DBHandlerServer, validate: ValidateRowBasic | undefined, localParams: LocalParams | undefined) {
478
- // const inserts = (await Promise.all(data.map(async d => {
479
- // const rowParts = await this.getRow(d, allowedCols, dbTx, validate, "insert", localParams);
480
- // return Object.fromEntries(rowParts.map(rp => [rp.escapedCol, rp.escapedVal]));
481
- // })));
482
- // const uniqueColumns = Array.from(new Set(inserts.flatMap(row => Object.keys(row))))
483
- // const values = inserts.map(row => `(${uniqueColumns.map(colName => row[colName] ?? 'DEFAULT')})`).join(",\n");
484
- // const whatToInsert = !uniqueColumns.length ? "DEFAULT VALUES" : `(${uniqueColumns}) VALUES ${values}`
485
- // return `INSERT INTO ${this.opts.tableName} ${whatToInsert} `;
486
- // }
487
- // async getUpdateQuery(data: AnyObject | AnyObject[], allowedCols: string[], dbTx: DBHandlerServer, validate: ValidateRowBasic | undefined, localParams: LocalParams | undefined): Promise<string> {
488
- // const res = (await Promise.all((Array.isArray(data) ? data : [data]).map(async d => {
489
- // const rowParts = await this.getRow(d, allowedCols, dbTx, validate, "update", localParams);
490
- // return `UPDATE ${this.opts.tableName} SET ` + rowParts.map(r => `${r.escapedCol} = ${r.escapedVal} `).join(",\n")
491
- // }))).join(";\n") + " ";
492
- // return res;
493
- // }
494
- }
@@ -1,16 +1,11 @@
1
- import { AnyObject, InsertParams, asName, isObject } from "prostgles-types";
1
+ import { AnyObject, InsertParams, asName, getSerialisableError, isObject } from "prostgles-types";
2
2
  import { ParsedTableRule, ValidateRowBasic } from "../../../PublishParser/PublishParser";
3
- import {
4
- LocalParams,
5
- getErrorAsObject,
6
- getSerializedClientErrorFromPGError,
7
- withUserRLS,
8
- } from "../../DboBuilder";
9
- import { insertNestedRecords } from "./insertNestedRecords";
3
+ import { LocalParams, getSerializedClientErrorFromPGError, withUserRLS } from "../../DboBuilder";
10
4
  import { prepareNewData } from "../DataValidator";
11
5
  import { TableHandler } from "../TableHandler";
12
6
  import { insertTest } from "../insertTest";
13
7
  import { runInsertUpdateQuery } from "../runInsertUpdateQuery";
8
+ import { insertNestedRecords } from "./insertNestedRecords";
14
9
 
15
10
  export async function insert(
16
11
  this: TableHandler,
@@ -66,7 +61,7 @@ export async function insert(
66
61
  }
67
62
  }
68
63
  const isMultiInsert = Array.isArray(rowOrRows);
69
- const rows = isMultiInsert ? rowOrRows : [rowOrRows];
64
+ const rows = isMultiInsert ? (rowOrRows as AnyObject[]) : [rowOrRows];
70
65
 
71
66
  requiredNestedInserts?.forEach(({ ftable, maxRows, minRows }) => {
72
67
  if (this.column_names.includes(ftable))
@@ -106,7 +101,7 @@ export async function insert(
106
101
  if (!localParams) throw "localParams missing for insert preValidate";
107
102
  row = await preValidate({
108
103
  row,
109
- dbx: (this.tx?.dbTX || this.dboBuilder.dbo) as any,
104
+ dbx: this.tx?.dbTX || this.dboBuilder.dbo,
110
105
  localParams,
111
106
  });
112
107
  }
@@ -133,29 +128,28 @@ export async function insert(
133
128
 
134
129
  const pkeyNames = this.columns.filter((c) => c.is_pkey).map((c) => c.name);
135
130
  const getInsertQuery = async (_rows: AnyObject[]) => {
136
- const validatedData = await Promise.all(
137
- _rows.map(async (_row) => {
138
- const row = { ..._row };
131
+ const validatedData = _rows.map((_row) => {
132
+ const row = { ..._row };
139
133
 
140
- if (!isObject(row)) {
141
- throw (
142
- "\nInvalid insert data provided. Expected an object but received: " +
143
- JSON.stringify(row)
144
- );
145
- }
134
+ if (!isObject(row)) {
135
+ throw (
136
+ "\nInvalid insert data provided. Expected an object but received: " +
137
+ JSON.stringify(row)
138
+ );
139
+ }
140
+
141
+ const { data: validatedRow, allowedCols } = prepareNewData({
142
+ row,
143
+ forcedData,
144
+ allowedFields: fields,
145
+ tableRules,
146
+ removeDisallowedFields,
147
+ tableConfigurator: this.dboBuilder.prostgles.tableConfigurator,
148
+ tableHandler: this,
149
+ });
150
+ return { validatedRow, allowedCols };
151
+ });
146
152
 
147
- const { data: validatedRow, allowedCols } = await prepareNewData({
148
- row,
149
- forcedData,
150
- allowedFields: fields,
151
- tableRules,
152
- removeDisallowedFields,
153
- tableConfigurator: this.dboBuilder.prostgles.tableConfigurator,
154
- tableHandler: this,
155
- });
156
- return { validatedRow, allowedCols };
157
- })
158
- );
159
153
  const validatedRows = validatedData.map((d) => d.validatedRow);
160
154
  const allowedCols = Array.from(new Set(validatedData.flatMap((d) => d.allowedCols)));
161
155
  const dbTx = finalDBtx || this.dboBuilder.dbo;
@@ -163,7 +157,7 @@ export async function insert(
163
157
  validate: validate as ValidateRowBasic,
164
158
  localParams,
165
159
  };
166
- // const query = await this.colSet.getInsertQuery(validatedRows, allowedCols, dbTx, validate, localParams);
160
+
167
161
  const query = (
168
162
  await this.dataValidator.parse({
169
163
  command: "insert",
@@ -234,7 +228,7 @@ export async function insert(
234
228
  localParams,
235
229
  data: { rowOrRows, param2: insertParams },
236
230
  duration: Date.now() - start,
237
- error: getErrorAsObject(e),
231
+ error: getSerialisableError(e),
238
232
  });
239
233
  throw getSerializedClientErrorFromPGError(e, {
240
234
  type: "tableMethod",
@@ -99,7 +99,7 @@ export async function insertNestedRecords(
99
99
  const colInserts = getReferenceColumnInserts(this, row);
100
100
 
101
101
  /* Ensure we're using the same transaction */
102
- const _this = this.tx ? this : (dbTX![this.name] as TableHandler);
102
+ const tableHandler = this.tx ? this : (dbTX![this.name] as TableHandler);
103
103
 
104
104
  const omitedKeys = extraKeys.concat(colInserts.map((c) => c.insertedFieldName));
105
105
 
@@ -126,7 +126,7 @@ export async function insertNestedRecords(
126
126
  },
127
127
  };
128
128
  const colRows = await referencedInsert(
129
- _this,
129
+ tableHandler,
130
130
  dbTX,
131
131
  newLocalParams,
132
132
  colInsert.tableName,
@@ -157,7 +157,7 @@ export async function insertNestedRecords(
157
157
  }
158
158
  }
159
159
 
160
- const fullRootResult = (await _this.insert(
160
+ const fullRootResult = (await tableHandler.insert(
161
161
  rootData,
162
162
  { returning: "*" },
163
163
  undefined,
@@ -235,7 +235,7 @@ export async function insertNestedRecords(
235
235
  }
236
236
 
237
237
  insertedChildren = await childInsert(
238
- childDataItems.map((d: AnyObject) => {
238
+ childDataItems.map((d) => {
239
239
  const result = { ...d };
240
240
  colsRefT1.map((col) => {
241
241
  result[col.references![0]!.cols[0]!] =
@@ -262,14 +262,14 @@ export async function insertNestedRecords(
262
262
  if (
263
263
  !(
264
264
  cols2.filter((c) => c.references?.[0]?.ftable === fileTable).length === 1 &&
265
- cols2.filter((c) => c.references?.[0]?.ftable === _this.name).length === 1
265
+ cols2.filter((c) => c.references?.[0]?.ftable === tableHandler.name).length === 1
266
266
  )
267
267
  ) {
268
268
  console.log({
269
269
  tbl1,
270
270
  tbl2,
271
271
  tbl3,
272
- name: _this.name,
272
+ name: tableHandler.name,
273
273
  tthisName: this.name,
274
274
  });
275
275
  throw (
@@ -293,7 +293,7 @@ export async function insertNestedRecords(
293
293
  tbl2Row[col.name] = fullRootResult[col.references![0]!.fcols[0]!];
294
294
  });
295
295
 
296
- await childInsert(tbl2Row, tbl2!); //.then(() => {});
296
+ await childInsert(tbl2Row, tbl2!);
297
297
  })
298
298
  );
299
299
  } else {
@@ -207,7 +207,8 @@ export async function initFileManager(this: FileManager, prg: Prostgles) {
207
207
  {
208
208
  res,
209
209
  httpReq: req,
210
- }
210
+ },
211
+ undefined
211
212
  )) as Required<Media> | undefined;
212
213
 
213
214
  if (!media) {
@@ -1,4 +1,4 @@
1
- import { Method, getObjectEntries, isObject, type ClientSchema } from "prostgles-types";
1
+ import { Method, getObjectEntries, isObject } from "prostgles-types";
2
2
  import { AuthClientRequest, AuthResultWithSID, SessionUser } from "../Auth/AuthTypes";
3
3
  import { DBOFullyTyped } from "../DBSchemaBuilder";
4
4
  import { DB, DBHandlerServer, Prostgles } from "../Prostgles";
@@ -13,11 +13,12 @@ import {
13
13
  DboTableCommand,
14
14
  ParsedTableRule,
15
15
  PublishMethods,
16
- type PublishMethodsV2,
17
- type PublishObject,
18
16
  PublishParams,
19
17
  RULE_TO_METHODS,
20
18
  parsePublishTableRule,
19
+ type PublishMethodsV2,
20
+ type PublishObject,
21
+ type PermissionScope,
21
22
  } from "./publishTypesAndUtils";
22
23
 
23
24
  export class PublishParser {
@@ -59,7 +60,8 @@ export class PublishParser {
59
60
  db: this.db,
60
61
  clientReq,
61
62
  tables: this.prostgles.dboBuilder.tables,
62
- getClientDBHandlers: () => getClientHandlers(this.prostgles, clientReq),
63
+ getClientDBHandlers: (scope: PermissionScope | undefined) =>
64
+ getClientHandlers(this.prostgles, clientReq, scope),
63
65
  };
64
66
  }
65
67
 
@@ -135,21 +137,33 @@ export class PublishParser {
135
137
  if (clientInfo === "new-session-redirect") {
136
138
  throw "new-session-redirect";
137
139
  }
138
- const rules = await this.getValidatedRequestRule({ tableName, command, clientReq }, clientInfo);
140
+ const rules = await this.getValidatedRequestRule(
141
+ { tableName, command, clientReq },
142
+ clientInfo,
143
+ undefined
144
+ );
139
145
  return rules;
140
146
  }
141
147
 
142
148
  async getValidatedRequestRule(
143
149
  { tableName, command, clientReq }: DboTableCommand,
144
- clientInfo: AuthResultWithSID | undefined
150
+ clientInfo: AuthResultWithSID | undefined,
151
+ scope: PermissionScope | undefined
145
152
  ): Promise<ParsedTableRule> {
146
153
  if (!command || !tableName) throw "command OR tableName are missing";
147
154
 
148
- const rtm = RULE_TO_METHODS.find((rtms) => rtms.methods.some((v) => v === command));
149
- if (!rtm) {
155
+ const rule = RULE_TO_METHODS.find((rtms) => rtms.methods.some((v) => v === command));
156
+ if (!rule) {
150
157
  throw "Invalid command: " + command;
151
158
  }
152
159
 
160
+ if (scope) {
161
+ const tableScope = scope.tables;
162
+ if (!tableScope?.[tableName] || !tableScope[tableName]?.[rule.sqlRule]) {
163
+ throw `Invalid or disallowed command: ${tableName}.${command}. The PermissionsScope does not allow this command.`;
164
+ }
165
+ }
166
+
153
167
  /* Must be local request -> allow everything */
154
168
  if (!clientReq) {
155
169
  return RULE_TO_METHODS.reduce(
@@ -185,7 +199,7 @@ export class PublishParser {
185
199
  }
186
200
  }
187
201
 
188
- if (!tableRule[rtm.rule]) {
202
+ if (!tableRule[rule.rule]) {
189
203
  throw {
190
204
  stack: ["getValidatedRequestRule()"],
191
205
  message: `Invalid or disallowed command: ${tableName}.${command}`,
@@ -14,7 +14,7 @@ import { AuthClientRequest, AuthResultWithSID } from "../Auth/AuthTypes";
14
14
  import { getErrorAsObject } from "../DboBuilder/DboBuilder";
15
15
  import type { TableHandler } from "../DboBuilder/TableHandler/TableHandler";
16
16
  import { TABLE_METHODS } from "../Prostgles";
17
- import { type PublishObject, PublishParser } from "./PublishParser";
17
+ import { type PermissionScope, type PublishObject, PublishParser } from "./PublishParser";
18
18
 
19
19
  type Args = AuthClientRequest & {
20
20
  userData: AuthResultWithSID | undefined;
@@ -23,7 +23,8 @@ const SUBSCRIBE_METHODS = ["subscribe", "subscribeOne", "sync", "unsubscribe", "
23
23
 
24
24
  export async function getSchemaFromPublish(
25
25
  this: PublishParser,
26
- { userData, ...clientReq }: Args
26
+ { userData, ...clientReq }: Args,
27
+ scope: PermissionScope | undefined
27
28
  ): Promise<{
28
29
  schema: TableSchemaForClient;
29
30
  tables: DBSchemaTable[];
@@ -118,7 +119,8 @@ export async function getSchemaFromPublish(
118
119
  command: method,
119
120
  clientReq,
120
121
  },
121
- clientInfo
122
+ clientInfo,
123
+ scope
122
124
  );
123
125
  if (this.prostgles.opts.testRulesOnConnect) {
124
126
  await (this.dbo[tableName] as TableHandler)[method](
@@ -150,7 +152,8 @@ export async function getSchemaFromPublish(
150
152
  if (method === "getInfo" || method === "getColumns") {
151
153
  const tableRules = await this.getValidatedRequestRule(
152
154
  { tableName, command: method, clientReq },
153
- clientInfo
155
+ clientInfo,
156
+ scope
154
157
  );
155
158
  const res = await (this.dbo[tableName] as TableHandler)[method](
156
159
  undefined,
@@ -506,6 +506,11 @@ export type DbTableInfo = {
506
506
  info: TableOrViewInfo;
507
507
  columns: TableSchemaColumn[];
508
508
  };
509
+ export type PermissionScope = {
510
+ sql?: true;
511
+ tables?: Record<string, Partial<Record<"select" | "update" | "delete" | "insert", true>>>;
512
+ methods?: Record<string, true>;
513
+ };
509
514
  export type PublishParams<S = void, SUser extends SessionUser = SessionUser> = {
510
515
  sid: string | undefined;
511
516
  dbo: DBOFullyTyped<S>;
@@ -513,7 +518,12 @@ export type PublishParams<S = void, SUser extends SessionUser = SessionUser> = {
513
518
  user?: SUser["user"];
514
519
  clientReq: AuthClientRequest;
515
520
  tables: DbTableInfo[];
516
- getClientDBHandlers: () => Promise<ClientHandlers<S>>;
521
+ getClientDBHandlers: (
522
+ /**
523
+ * Used to filter permissions
524
+ */
525
+ scope: PermissionScope | undefined
526
+ ) => Promise<ClientHandlers<S>>;
517
527
  };
518
528
  export type RequestParams = { dbo?: DBHandlerServer; socket?: any };
519
529
  export type PublishAllOrNothing = boolean | "*" | null;
package/lib/RestApi.ts CHANGED
@@ -102,7 +102,7 @@ export class RestApi {
102
102
  };
103
103
  onPostSchema = async (req: ExpressReq, res: ExpressRes) => {
104
104
  try {
105
- const data = await this.prostgles.getClientSchema({ httpReq: req, res });
105
+ const data = await this.prostgles.getClientSchema({ httpReq: req, res }, undefined);
106
106
  res.json(data);
107
107
  } catch (rawError) {
108
108
  const error = getSerializedClientErrorFromPGError(rawError, {
@@ -161,7 +161,8 @@ export class RestApi {
161
161
  {
162
162
  httpReq: req,
163
163
  res,
164
- }
164
+ },
165
+ undefined
165
166
  );
166
167
  res.json(data);
167
168
  } catch (rawError) {
@@ -11,16 +11,18 @@ import type { DBOFullyTyped } from "../DBSchemaBuilder";
11
11
  import type { Prostgles } from "../Prostgles";
12
12
  import { runClientMethod, runClientRequest, runClientSqlRequest } from "../runClientRequest";
13
13
  import { getClientSchema } from "./getClientSchema";
14
+ import type { PermissionScope } from "../PublishParser/publishTypesAndUtils";
14
15
 
15
16
  export const getClientHandlers = async <S = void>(
16
17
  prostgles: Prostgles,
17
- clientReq: AuthClientRequest
18
+ clientReq: AuthClientRequest,
19
+ scope: PermissionScope | undefined
18
20
  ): Promise<{
19
21
  clientDb: DBOFullyTyped<S, false>;
20
22
  clientMethods: Record<string, Method>;
21
23
  }> => {
22
24
  const clientSchema =
23
- clientReq.socket?.prostgles ?? (await getClientSchema.bind(prostgles)(clientReq));
25
+ clientReq.socket?.prostgles ?? (await getClientSchema.bind(prostgles)(clientReq, scope));
24
26
  const sql: SQLHandler | undefined = ((query: string, params?: unknown, options?: SQLOptions) =>
25
27
  runClientSqlRequest.bind(prostgles)({ query, params, options }, clientReq)) as SQLHandler;
26
28
  const tableHandlers = Object.fromEntries(
@@ -31,7 +33,8 @@ export const getClientHandlers = async <S = void>(
31
33
  const method = (param1: unknown, param2: unknown, param3: unknown) =>
32
34
  runClientRequest.bind(prostgles)(
33
35
  { command, tableName: table.name, param1, param2, param3 },
34
- clientReq
36
+ clientReq,
37
+ scope
35
38
  );
36
39
  return [command, method];
37
40
  })
@@ -48,17 +51,25 @@ export const getClientHandlers = async <S = void>(
48
51
  const clientDb = {
49
52
  ...tableHandlers,
50
53
  ...txNotAllowed,
51
- sql,
54
+ sql:
55
+ scope && !scope.sql ?
56
+ () => {
57
+ throw new Error("SQL is dissallowed by PermissionScope");
58
+ }
59
+ : sql,
52
60
  } as DBOFullyTyped<S, false>;
53
61
 
54
62
  const clientMethods: Record<string, Method> = Object.fromEntries(
55
63
  clientSchema.methods.map((method) => {
56
64
  const methodName = typeof method === "string" ? method : method.name;
57
- return [
58
- methodName,
59
- (...params: any[]) =>
60
- runClientMethod.bind(prostgles)({ method: methodName, params }, clientReq),
61
- ];
65
+ const methodHandler =
66
+ scope && !scope.methods?.[methodName] ?
67
+ () => {
68
+ throw new Error(`Method ${methodName} is not allowed by PermissionScope`);
69
+ }
70
+ : (...params: any[]) =>
71
+ runClientMethod.bind(prostgles)({ method: methodName, params }, clientReq);
72
+ return [methodName, methodHandler];
62
73
  })
63
74
  );
64
75
 
@@ -1,11 +1,15 @@
1
1
  import { isObject, omitKeys, tryCatchV2, type ClientSchema } from "prostgles-types";
2
2
  import type { AuthClientRequest } from "../Auth/AuthTypes";
3
3
  import { Prostgles } from "../Prostgles";
4
- import type { PublishParser } from "../PublishParser/PublishParser";
4
+ import type { PermissionScope, PublishParser } from "../PublishParser/PublishParser";
5
5
  import { clientCanRunSqlRequest } from "../runClientRequest";
6
- const version = (require("../../package.json") as { version: string }).version;
6
+ import { version } from "../../package.json";
7
7
 
8
- export async function getClientSchema(this: Prostgles, clientReq: AuthClientRequest) {
8
+ export async function getClientSchema(
9
+ this: Prostgles,
10
+ clientReq: AuthClientRequest,
11
+ scope: PermissionScope | undefined
12
+ ) {
9
13
  const result = await tryCatchV2(async () => {
10
14
  const clientInfo =
11
15
  clientReq.socket ?
@@ -22,10 +26,13 @@ export async function getClientSchema(this: Prostgles, clientReq: AuthClientRequ
22
26
 
23
27
  try {
24
28
  if (!publishParser) throw "publishParser undefined";
25
- fullSchema = await publishParser.getSchemaFromPublish({
26
- ...clientInfo,
27
- userData,
28
- });
29
+ fullSchema = await publishParser.getSchemaFromPublish(
30
+ {
31
+ ...clientInfo,
32
+ userData,
33
+ },
34
+ scope
35
+ );
29
36
  } catch (e) {
30
37
  publishValidationError = e;
31
38
  console.error(`\nProstgles Publish validation failed (after socket connected):\n ->`, e);
@@ -105,7 +105,7 @@ export async function onSocketConnected(this: Prostgles, socket: PRGLIOSocket) {
105
105
  }
106
106
  ) => {
107
107
  runClientRequest
108
- .bind(this)(args, { socket })
108
+ .bind(this)(args, { socket }, undefined)
109
109
  .then((res) => {
110
110
  cb(null, res);
111
111
  })
@@ -5,7 +5,7 @@ import { runClientSqlRequest } from "../runClientRequest";
5
5
  import { makeSocketError } from "./onSocketConnected";
6
6
  export async function pushSocketSchema(this: Prostgles, socket: PRGLIOSocket) {
7
7
  try {
8
- const clientSchema = await this.getClientSchema({ socket });
8
+ const clientSchema = await this.getClientSchema({ socket }, undefined);
9
9
  socket.prostgles = clientSchema;
10
10
  if (clientSchema.rawSQL) {
11
11
  socket.removeAllListeners(CHANNELS.SQL);