@teamkeel/functions-runtime 0.365.18 → 0.366.0-audit-logs0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamkeel/functions-runtime",
3
- "version": "0.365.18",
3
+ "version": "0.366.0-audit-logs0",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -28,6 +28,7 @@
28
28
  "json-rpc-2.0": "^1.4.1",
29
29
  "ksuid": "^3.0.0",
30
30
  "kysely": "^0.23.4",
31
- "pg": "^8.8.0"
31
+ "pg": "^8.8.0",
32
+ "traceparent": "^1.0.0"
32
33
  }
33
34
  }
package/src/ModelAPI.js CHANGED
@@ -3,6 +3,7 @@ const { QueryBuilder } = require("./QueryBuilder");
3
3
  const { QueryContext } = require("./QueryContext");
4
4
  const { applyWhereConditions } = require("./applyWhereConditions");
5
5
  const { applyJoins } = require("./applyJoins");
6
+
6
7
  const {
7
8
  applyLimit,
8
9
  applyOffset,
@@ -0,0 +1,119 @@
1
+ const { AsyncLocalStorage } = require("async_hooks");
2
+ const TraceParent = require("traceparent");
3
+ const { sql, SelectionNode } = require("kysely");
4
+
5
+ const auditContextStorage = new AsyncLocalStorage();
6
+
7
+ // withAuditContext creates the audit context from the runtime request body
8
+ // and sets it to in AsyncLocalStorage so that this data is available to the
9
+ // ModelAPI during the execution of actions, jobs and subscribers.
10
+ async function withAuditContext(request, cb) {
11
+ let audit = {};
12
+
13
+ if (request.meta?.identity) {
14
+ audit.identityId = request.meta.identity.id;
15
+ }
16
+ if (request.meta?.tracing?.traceparent) {
17
+ audit.traceId = TraceParent.fromString(
18
+ request.meta.tracing.traceparent
19
+ )?.traceId;
20
+ }
21
+
22
+ return await auditContextStorage.run(audit, () => {
23
+ return cb();
24
+ });
25
+ }
26
+
27
+ // getAuditContext retrieves the audit context from AsyncLocalStorage.
28
+ function getAuditContext() {
29
+ let auditStore = auditContextStorage.getStore();
30
+ return {
31
+ identityId: auditStore?.identityId,
32
+ traceId: auditStore?.traceId,
33
+ };
34
+ }
35
+
36
+ // AuditContextPlugin is a Kysely plugin which ensures that the audit context data
37
+ // is written to Postgres configuration parameters in the same execution as a query.
38
+ // It does this by calling the set_identity_id() and set_trace_id() functions as a
39
+ // clause in the returning statement. It then subsequently drops these from the actual result.
40
+ // This ensures that these parameters are set when the tables' AFTER trigger function executes.
41
+ class AuditContextPlugin {
42
+ constructor() {
43
+ this.identityIdAlias = "__keel_identity_id";
44
+ this.traceIdAlias = "__keel_trace_id";
45
+ }
46
+
47
+ #setIdentityClause(value) {
48
+ return `set_identity_id('${value}')`;
49
+ }
50
+
51
+ #setTraceIdClause(value) {
52
+ return `set_trace_id('${value}')`;
53
+ }
54
+
55
+ // Appends set_identity_id() and set_trace_id() function calls to the returning statement
56
+ // of INSERT, UPDATE and DELETE operations.
57
+ transformQuery(args) {
58
+ switch (args.node.kind) {
59
+ case "InsertQueryNode":
60
+ case "UpdateQueryNode":
61
+ case "DeleteQueryNode":
62
+ const returning = {
63
+ kind: "ReturningNode",
64
+ selections: [],
65
+ };
66
+ if (args.node.returning) {
67
+ returning.selections.push(...args.node.returning.selections);
68
+ }
69
+
70
+ // Retrieve the audit context from async storage.
71
+ const audit = getAuditContext();
72
+
73
+ if (audit.identityId) {
74
+ const rawNode = sql
75
+ .raw(
76
+ this.#setIdentityClause(audit.identityId, this.identityIdAlias)
77
+ )
78
+ .as(this.identityIdAlias)
79
+ .toOperationNode();
80
+
81
+ returning.selections.push(SelectionNode.create(rawNode));
82
+ }
83
+
84
+ if (audit.traceId) {
85
+ const rawNode = sql
86
+ .raw(this.#setTraceIdClause(audit.traceId))
87
+ .as(this.traceIdAlias)
88
+ .toOperationNode();
89
+
90
+ returning.selections.push(SelectionNode.create(rawNode));
91
+ }
92
+
93
+ return {
94
+ ...args.node,
95
+ returning: returning,
96
+ };
97
+ }
98
+
99
+ return {
100
+ ...args.node,
101
+ };
102
+ }
103
+
104
+ // Drops the set_identity_id() and set_trace_id() fields from the result.
105
+ transformResult(args) {
106
+ if (args.result?.rows) {
107
+ for (let i = 0; i < args.result.rows.length; i++) {
108
+ delete args.result.rows[i][this.identityIdAlias];
109
+ delete args.result.rows[i][this.traceIdAlias];
110
+ }
111
+ }
112
+
113
+ return args.result;
114
+ }
115
+ }
116
+
117
+ module.exports.withAuditContext = withAuditContext;
118
+ module.exports.getAuditContext = getAuditContext;
119
+ module.exports.AuditContextPlugin = AuditContextPlugin;
@@ -0,0 +1,362 @@
1
+ import { test, expect, beforeEach } from "vitest";
2
+ const { ModelAPI } = require("./ModelAPI");
3
+ const { PROTO_ACTION_TYPES } = require("./consts");
4
+ const { sql } = require("kysely");
5
+ const { useDatabase, withDatabase } = require("./database");
6
+ const KSUID = require("ksuid");
7
+ const TraceParent = require("traceparent");
8
+ const { withAuditContext } = require("./auditing");
9
+
10
+ let personAPI;
11
+ const db = useDatabase();
12
+
13
+ beforeEach(async () => {
14
+ await sql`
15
+
16
+ DROP TABLE IF EXISTS post;
17
+ DROP TABLE IF EXISTS person;
18
+ DROP TABLE IF EXISTS author;
19
+
20
+ CREATE TABLE person(
21
+ id text PRIMARY KEY,
22
+ name text UNIQUE
23
+ );
24
+
25
+ CREATE OR REPLACE FUNCTION set_identity_id(id VARCHAR)
26
+ RETURNS TEXT AS $$
27
+ BEGIN
28
+ RETURN set_config('audit.identity_id', id, true);
29
+ END
30
+ $$ LANGUAGE plpgsql;
31
+
32
+ CREATE OR REPLACE FUNCTION set_trace_id(id VARCHAR)
33
+ RETURNS TEXT AS $$
34
+ BEGIN
35
+ RETURN set_config('audit.trace_id', id, true);
36
+ END
37
+ $$ LANGUAGE plpgsql;
38
+ `.execute(db);
39
+
40
+ personAPI = new ModelAPI("person", undefined, {});
41
+ });
42
+
43
+ async function identityIdFromConfigParam(database, nonLocal = true) {
44
+ const result =
45
+ await sql`SELECT NULLIF(current_setting('audit.identity_id', ${sql.literal(
46
+ nonLocal
47
+ )}), '') AS id`.execute(database);
48
+ return result.rows[0].id;
49
+ }
50
+
51
+ async function traceIdFromConfigParam(database, nonLocal = true) {
52
+ const result =
53
+ await sql`SELECT NULLIF(current_setting('audit.trace_id', ${sql.literal(
54
+ nonLocal
55
+ )}), '') AS id`.execute(database);
56
+ return result.rows[0].id;
57
+ }
58
+
59
+ test("auditing - capturing identity id in transaction", async () => {
60
+ const request = {
61
+ meta: {
62
+ identity: { id: KSUID.randomSync().string },
63
+ },
64
+ };
65
+
66
+ const identityId = request.meta.identity.id;
67
+
68
+ const row = await withDatabase(
69
+ db,
70
+ PROTO_ACTION_TYPES.CREATE, // CREATE will ensure a transaction is opened
71
+ async ({ transaction }) => {
72
+ const row = withAuditContext(request, async () => {
73
+ return await personAPI.create({
74
+ id: KSUID.randomSync().string,
75
+ name: "James",
76
+ });
77
+ });
78
+
79
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
80
+ expect(await identityIdFromConfigParam(db)).toBeNull();
81
+
82
+ return row;
83
+ }
84
+ );
85
+
86
+ expect(row.name).toEqual("James");
87
+ expect(await identityIdFromConfigParam(db)).toBeNull();
88
+ expect(await identityIdFromConfigParam(db, false)).toBeNull();
89
+ });
90
+
91
+ test("auditing - capturing tracing in transaction", async () => {
92
+ const request = {
93
+ meta: {
94
+ tracing: {
95
+ traceparent: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
96
+ },
97
+ },
98
+ };
99
+
100
+ const traceId = TraceParent.fromString(
101
+ request.meta.tracing.traceparent
102
+ ).traceId;
103
+
104
+ const row = await withDatabase(
105
+ db,
106
+ PROTO_ACTION_TYPES.CREATE, // CREATE will ensure a transaction is opened
107
+ async ({ transaction }) => {
108
+ const row = withAuditContext(request, async () => {
109
+ return await personAPI.create({
110
+ id: KSUID.randomSync().string,
111
+ name: "Jim",
112
+ });
113
+ });
114
+
115
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
116
+ expect(await traceIdFromConfigParam(db)).toBeNull();
117
+
118
+ return row;
119
+ }
120
+ );
121
+
122
+ expect(row.name).toEqual("Jim");
123
+ expect(await traceIdFromConfigParam(db)).toBeNull();
124
+ expect(await traceIdFromConfigParam(db, false)).toBeNull();
125
+ });
126
+
127
+ test("auditing - capturing identity id without transaction", async () => {
128
+ const request = {
129
+ meta: {
130
+ identity: { id: KSUID.randomSync().string },
131
+ },
132
+ };
133
+
134
+ const row = await withDatabase(
135
+ db,
136
+ PROTO_ACTION_TYPES.GET, // GET will _not_ open a transaction
137
+ async ({ sDb }) => {
138
+ const row = withAuditContext(request, async () => {
139
+ return await personAPI.create({
140
+ id: KSUID.randomSync().string,
141
+ name: "James",
142
+ });
143
+ });
144
+
145
+ expect(await identityIdFromConfigParam(sDb)).toBeNull();
146
+ expect(await identityIdFromConfigParam(db)).toBeNull();
147
+
148
+ return row;
149
+ }
150
+ );
151
+
152
+ expect(row.name).toEqual("James");
153
+ expect(await identityIdFromConfigParam(db)).toBeNull();
154
+ expect(await identityIdFromConfigParam(db, false)).toBeNull();
155
+ });
156
+
157
+ test("auditing - capturing tracing without transaction", async () => {
158
+ const request = {
159
+ meta: {
160
+ tracing: {
161
+ traceparent: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
162
+ },
163
+ },
164
+ };
165
+
166
+ const row = await withDatabase(
167
+ db,
168
+ PROTO_ACTION_TYPES.GET, // GET will _not_ open a transaction
169
+ async ({ sDb }) => {
170
+ const row = withAuditContext(request, async () => {
171
+ return await personAPI.create({
172
+ id: KSUID.randomSync().string,
173
+ name: "Jim",
174
+ });
175
+ });
176
+
177
+ expect(await traceIdFromConfigParam(sDb)).toBeNull();
178
+ expect(await traceIdFromConfigParam(db)).toBeNull();
179
+
180
+ return row;
181
+ }
182
+ );
183
+
184
+ expect(row.name).toEqual("Jim");
185
+ expect(await traceIdFromConfigParam(db)).toBeNull();
186
+ expect(await traceIdFromConfigParam(db, false)).toBeNull();
187
+ });
188
+
189
+ test("auditing - no audit context", async () => {
190
+ const row = await withDatabase(
191
+ db,
192
+ PROTO_ACTION_TYPES.CREATE,
193
+ async ({ transaction }) => {
194
+ const row = withAuditContext({}, async () => {
195
+ return await personAPI.create({
196
+ id: KSUID.randomSync().string,
197
+ name: "Jake",
198
+ });
199
+ });
200
+
201
+ expect(await identityIdFromConfigParam(transaction)).toBeNull();
202
+ expect(await identityIdFromConfigParam(db)).toBeNull();
203
+ expect(await traceIdFromConfigParam(transaction)).toBeNull();
204
+ expect(await traceIdFromConfigParam(db)).toBeNull();
205
+ return row;
206
+ }
207
+ );
208
+
209
+ expect(KSUID.parse(row.id).string).toEqual(row.id);
210
+ expect(row.name).toEqual("Jake");
211
+ });
212
+
213
+ test("auditing - ModelAPI.create", async () => {
214
+ const request = {
215
+ meta: {
216
+ identity: { id: KSUID.randomSync().string },
217
+ tracing: {
218
+ traceparent: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
219
+ },
220
+ },
221
+ };
222
+
223
+ const identityId = request.meta.identity.id;
224
+ const traceId = TraceParent.fromString(
225
+ request.meta.tracing.traceparent
226
+ ).traceId;
227
+
228
+ const row = await withDatabase(
229
+ db,
230
+ PROTO_ACTION_TYPES.CREATE,
231
+ async ({ transaction }) => {
232
+ const row = withAuditContext(request, async () => {
233
+ return await personAPI.create({
234
+ id: KSUID.randomSync().string,
235
+ name: "Jake",
236
+ });
237
+ });
238
+
239
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
240
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
241
+
242
+ return row;
243
+ }
244
+ );
245
+
246
+ expect(row.name).toEqual("Jake");
247
+ });
248
+
249
+ test("auditing - ModelAPI.update", async () => {
250
+ const request = {
251
+ meta: {
252
+ identity: { id: KSUID.randomSync().string },
253
+ tracing: {
254
+ traceparent: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
255
+ },
256
+ },
257
+ };
258
+
259
+ const identityId = request.meta.identity.id;
260
+ const traceId = TraceParent.fromString(
261
+ request.meta.tracing.traceparent
262
+ ).traceId;
263
+
264
+ const created = await personAPI.create({
265
+ id: KSUID.randomSync().string,
266
+ name: "Jake",
267
+ });
268
+
269
+ const row = await withDatabase(
270
+ db,
271
+ PROTO_ACTION_TYPES.CREATE,
272
+ async ({ transaction }) => {
273
+ const row = withAuditContext(request, async () => {
274
+ return await personAPI.update({ id: created.id }, { name: "Jim" });
275
+ });
276
+
277
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
278
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
279
+
280
+ return row;
281
+ }
282
+ );
283
+
284
+ expect(row.name).toEqual("Jim");
285
+ });
286
+
287
+ test("auditing - ModelAPI.delete", async () => {
288
+ const request = {
289
+ meta: {
290
+ identity: { id: KSUID.randomSync().string },
291
+ tracing: {
292
+ traceparent: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
293
+ },
294
+ },
295
+ };
296
+
297
+ const identityId = request.meta.identity.id;
298
+ const traceId = TraceParent.fromString(
299
+ request.meta.tracing.traceparent
300
+ ).traceId;
301
+
302
+ const created = await personAPI.create({
303
+ id: KSUID.randomSync().string,
304
+ name: "Jake",
305
+ });
306
+
307
+ const row = await withDatabase(
308
+ db,
309
+ PROTO_ACTION_TYPES.CREATE,
310
+ async ({ transaction }) => {
311
+ const row = withAuditContext(request, async () => {
312
+ return await personAPI.delete({ id: created.id });
313
+ });
314
+
315
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
316
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
317
+
318
+ return row;
319
+ }
320
+ );
321
+
322
+ expect(row).toEqual(created.id);
323
+ });
324
+
325
+ test("auditing - identity id and trace id fields dropped from result", async () => {
326
+ const request = {
327
+ meta: {
328
+ identity: { id: KSUID.randomSync().string },
329
+ tracing: {
330
+ traceparent: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
331
+ },
332
+ },
333
+ };
334
+
335
+ const identityId = request.meta.identity.id;
336
+ const traceId = TraceParent.fromString(
337
+ request.meta.tracing.traceparent
338
+ ).traceId;
339
+
340
+ const row = await withDatabase(
341
+ db,
342
+ PROTO_ACTION_TYPES.CREATE,
343
+ async ({ transaction }) => {
344
+ const row = withAuditContext(request, async () => {
345
+ return await personAPI.create({
346
+ id: KSUID.randomSync().string,
347
+ name: "Jake",
348
+ });
349
+ });
350
+
351
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
352
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
353
+
354
+ return row;
355
+ }
356
+ );
357
+
358
+ expect(row.name).toEqual("Jake");
359
+ expect(row.keelIdentityId).toBeUndefined();
360
+ expect(row.keelTraceId).toBeUndefined();
361
+ expect(Object.keys(row).length).toEqual(2);
362
+ });
package/src/database.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const { Kysely, PostgresDialect, CamelCasePlugin } = require("kysely");
2
2
  const { AsyncLocalStorage } = require("async_hooks");
3
+ const { AuditContextPlugin } = require("./auditing");
3
4
  const pg = require("pg");
4
5
  const { PROTO_ACTION_TYPES } = require("./consts");
5
6
  const { withSpan } = require("./tracing");
@@ -15,6 +16,7 @@ async function withDatabase(db, actionType, cb) {
15
16
  let requiresTransaction = true;
16
17
 
17
18
  switch (actionType) {
19
+ case PROTO_ACTION_TYPES.SUBSCRIBER:
18
20
  case PROTO_ACTION_TYPES.JOB:
19
21
  case PROTO_ACTION_TYPES.GET:
20
22
  case PROTO_ACTION_TYPES.LIST:
@@ -78,6 +80,8 @@ function getDatabaseClient() {
78
80
  db = new Kysely({
79
81
  dialect: getDialect(),
80
82
  plugins: [
83
+ // ensures that the audit context data is written to Postgres configuration parameters
84
+ new AuditContextPlugin(),
81
85
  // allows users to query using camelCased versions of the database column names, which
82
86
  // should match the names we use in our schema.
83
87
  // https://kysely-org.github.io/kysely/classes/CamelCasePlugin.html
@@ -119,14 +123,13 @@ class InstrumentedClient extends pg.Client {
119
123
  const sql = args[0];
120
124
 
121
125
  let sqlAttribute = false;
122
-
123
126
  let spanName = txStatements[sql.toLowerCase()];
124
127
  if (!spanName) {
125
128
  spanName = "Database Query";
126
129
  sqlAttribute = true;
127
130
  }
128
131
 
129
- return withSpan(spanName, function (span) {
132
+ return await withSpan(spanName, function (span) {
130
133
  if (sqlAttribute) {
131
134
  span.setAttribute("sql", args[0]);
132
135
  }
@@ -282,79 +282,4 @@ describe("ModelAPI error handling", () => {
282
282
  },
283
283
  });
284
284
  });
285
-
286
- test("when there is a uniqueness constraint error", async () => {
287
- await sql`
288
-
289
- INSERT INTO post (id, title, author_id) values(${
290
- KSUID.randomSync().string
291
- }, 'hello', 'adam')
292
- `.execute(db);
293
-
294
- const rpcReq = createJSONRPCRequest("123", "createPost", {
295
- title: "hello",
296
- author_id: "something",
297
- });
298
-
299
- expect(await handleRequest(rpcReq, functionConfig)).toEqual({
300
- id: "123",
301
- jsonrpc: "2.0",
302
- error: {
303
- code: RuntimeErrors.UniqueConstraintError,
304
- message:
305
- 'duplicate key value violates unique constraint "post_title_key"',
306
- data: {
307
- code: "23505",
308
- column: "title",
309
- detail: "Key (title)=(hello) already exists.",
310
- table: "post",
311
- value: "hello",
312
- },
313
- },
314
- });
315
- });
316
-
317
- test("when there is a null value in a foreign key column", async () => {
318
- const rpcReq = createJSONRPCRequest("123", "createPost", { title: "123" });
319
-
320
- expect(await handleRequest(rpcReq, functionConfig)).toEqual({
321
- id: "123",
322
- jsonrpc: "2.0",
323
- error: {
324
- code: RuntimeErrors.NotNullConstraintError,
325
- message:
326
- 'null value in column "author_id" violates not-null constraint',
327
- data: {
328
- code: "23502",
329
- column: "author_id",
330
- detail: expect.stringContaining("Failing row contains"),
331
- table: "post",
332
- },
333
- },
334
- });
335
- });
336
-
337
- test("when there is a foreign key constraint violation", async () => {
338
- const rpcReq2 = createJSONRPCRequest("123", "createPost", {
339
- title: "123",
340
- author_id: "fake",
341
- });
342
-
343
- expect(await handleRequest(rpcReq2, functionConfig)).toEqual({
344
- id: "123",
345
- jsonrpc: "2.0",
346
- error: {
347
- code: RuntimeErrors.ForeignKeyConstraintError,
348
- message:
349
- 'insert or update on table "post" violates foreign key constraint "post_author_id_fkey"',
350
- data: {
351
- code: "23503",
352
- column: "author_id",
353
- detail: 'Key (author_id)=(fake) is not present in table "author".',
354
- table: "post",
355
- value: "fake",
356
- },
357
- },
358
- });
359
- });
360
285
  });
@@ -49,7 +49,7 @@ async function handleSubscriber(request, config) {
49
49
  const subscriberFunction = subscribers[request.method];
50
50
  const actionType = PROTO_ACTION_TYPES.SUBSCRIBER;
51
51
 
52
- await tryExecuteSubscriber({ db, actionType }, async () => {
52
+ await tryExecuteSubscriber({ request, db, actionType }, async () => {
53
53
  // Return the subscriber function to the containing tryExecuteSubscriber block
54
54
  return subscriberFunction(ctx, request.params);
55
55
  });
@@ -1,4 +1,5 @@
1
1
  const { withDatabase } = require("./database");
2
+ const { withAuditContext } = require("./auditing");
2
3
  const {
3
4
  withPermissions,
4
5
  PERMISSION_STATE,
@@ -10,12 +11,15 @@ const { PROTO_ACTION_TYPES } = require("./consts");
10
11
  // tryExecuteFunction will create a new database transaction around a function call
11
12
  // and handle any permissions checks. If a permission check fails, then an Error will be thrown and the catch block will be hit.
12
13
  function tryExecuteFunction(
13
- { db, permitted, permissionFns, actionType, request, ctx },
14
+ { request, db, permitted, permissionFns, actionType, ctx },
14
15
  cb
15
16
  ) {
16
17
  return withPermissions(permitted, async ({ getPermissionState }) => {
17
18
  return withDatabase(db, actionType, async ({ transaction }) => {
18
- const fnResult = await cb();
19
+ const fnResult = await withAuditContext(request, async () => {
20
+ return cb();
21
+ });
22
+
19
23
  // api.permissions maintains an internal state of whether the current function has been *explicitly* permitted/denied by the user in the course of their custom function, or if execution has already been permitted by a role based permission (evaluated in the main runtime).
20
24
  // we need to check that the final state is permitted or unpermitted. if it's not, then it means that the user has taken no explicit action to permit/deny
21
25
  // and therefore we default to checking the permissions defined in the schema automatically.
@@ -1,14 +1,17 @@
1
1
  const { withDatabase } = require("./database");
2
+ const { withAuditContext } = require("./auditing");
2
3
  const { withPermissions, PERMISSION_STATE } = require("./permissions");
3
-
4
4
  const { PermissionError } = require("./errors");
5
5
 
6
6
  // tryExecuteJob will create a new database transaction around a function call
7
7
  // and handle any permissions checks. If a permission check fails, then an Error will be thrown and the catch block will be hit.
8
8
  function tryExecuteJob({ db, permitted, actionType, request }, cb) {
9
9
  return withPermissions(permitted, async ({ getPermissionState }) => {
10
- return withDatabase(db, actionType, async ({ transaction }) => {
11
- await cb();
10
+ return withDatabase(db, actionType, async () => {
11
+ await withAuditContext(request, async () => {
12
+ return cb();
13
+ });
14
+
12
15
  // api.permissions maintains an internal state of whether the current operation has been *explicitly* permitted/denied by the user in the course of their custom function, or if execution has already been permitted by a role based permission (evaluated in the main runtime).
13
16
  // we need to check that the final state is permitted or unpermitted. if it's not, then it means that the user has taken no explicit action to permit/deny
14
17
  // and therefore we default to checking the permissions defined in the schema automatically.
@@ -1,9 +1,12 @@
1
1
  const { withDatabase } = require("./database");
2
+ const { withAuditContext } = require("./auditing");
2
3
 
3
4
  // tryExecuteSubscriber will create a new database connection and execute the function call.
4
- function tryExecuteSubscriber({ db, actionType }, cb) {
5
+ function tryExecuteSubscriber({ request, db, actionType }, cb) {
5
6
  return withDatabase(db, actionType, async () => {
6
- await cb();
7
+ await withAuditContext(request, async () => {
8
+ return cb();
9
+ });
7
10
  });
8
11
  }
9
12