@cap-js/audit-logging 1.1.1 → 1.2.1

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/srv/log2alsng.js CHANGED
@@ -1,135 +1,135 @@
1
- const cds = require('@sap/cds')
2
- const LOG = cds.log('audit-log')
1
+ const cds = require("@sap/cds");
2
+ const LOG = cds.log("audit-log");
3
+ const { appMetadata } = require("../lib/utils");
4
+ const https = require("https");
3
5
 
4
- const https = require('https')
5
-
6
- const AuditLogService = require('./service')
6
+ const AuditLogService = require("./service");
7
7
 
8
8
  module.exports = class AuditLog2ALSNG extends AuditLogService {
9
9
  constructor() {
10
- super()
11
- this._vcap = JSON.parse(process.env.VCAP_SERVICES || '{}')
12
- this._userProvided = this._vcap['user-provided']?.find(obj => obj.tags.includes('auditlog-ng')) || {}
13
- if (!this._userProvided.credentials) throw new Error('No credentials found for SAP Audit Log Service NG')
14
- this._vcapApplication = JSON.parse(process.env.VCAP_APPLICATION || '{}')
10
+ super();
11
+ if (!cds.env.requires["audit-log"]?.credentials)
12
+ throw new Error("No credentials found for SAP Audit Log Service NG");
15
13
  }
16
14
 
17
15
  async init() {
18
- this.on('*', function (req) {
19
- const { event, data } = req
20
- return this.eventMapper(event, data)
21
- })
22
- await super.init()
16
+ this.on("*", function (req) {
17
+ const { event, data } = req;
18
+ return this.eventMapper(event, data);
19
+ });
20
+ await super.init();
23
21
  }
24
22
 
25
23
  eventMapper(event, data) {
26
24
  const known = {
27
- PersonalDataModified: () => this.logEvent('dppDataModification', data),
28
- SensitiveDataRead: () => this.logEvent('dppDataAccess', data),
29
- ConfigurationModified: () => this.logEvent('configurationChange', data),
30
- SecurityEvent: () => this.logEvent('legacySecurityWrapper', data)
31
- }
32
- const dfault = () => this.logEvent(event, data)
33
- return (known[event] ?? dfault)()
25
+ PersonalDataModified: () => this.logEvent("dppDataModification", data),
26
+ SensitiveDataRead: () => this.logEvent("dppDataAccess", data),
27
+ ConfigurationModified: () => this.logEvent("configurationChange", data),
28
+ SecurityEvent: () => this.logEvent("legacySecurityWrapper", data)
29
+ };
30
+ const dfault = () => this.logEvent(event, data);
31
+ return (known[event] ?? dfault)();
34
32
  }
35
33
 
36
34
  flattenAndSortIdObject(id) {
37
- if (!id || !Object.keys(id).length) return 'not provided'
35
+ if (!id || !Object.keys(id).length) return "not provided";
38
36
 
39
- let s = ''
40
- for (const k of Object.keys(id).sort()) s += `${k}:${id[k]} `
41
- return s.trim()
37
+ let s = "";
38
+ for (const k of Object.keys(id).sort()) s += `${k}:${id[k]} `;
39
+ return s.trim();
42
40
  }
43
41
 
44
42
  eventDataPayload(event, data) {
45
- const object = data['object'] || { type: 'not provided', id: { ID: 'not provided' } }
46
- const channel = data['channel'] || { type: 'not specified', id: 'not specified' }
47
- const subject = data['data_subject'] || { type: 'not provided', id: { ID: 'not provided' } }
48
- const attributes = data['attributes'] || [{ name: 'not provided', old: 'not provided', new: 'not provided' }]
49
- const objectId = this.flattenAndSortIdObject(object['id'])
50
- const oldValue = attributes[0]['old'] ?? ''
51
- const newValue = attributes[0]['new'] ?? ''
52
- const dataSubjectId = this.flattenAndSortIdObject(subject['id'])
43
+ const object = data["object"] || { type: "not provided", id: { ID: "not provided" } };
44
+ const channel = data["channel"] || { type: "not specified", id: "not specified" };
45
+ const subject = data["data_subject"] || { type: "not provided", id: { ID: "not provided" } };
46
+ const attributes = data["attributes"] || [
47
+ { name: "not provided", old: "not provided", new: "not provided" }
48
+ ];
49
+ const objectId = this.flattenAndSortIdObject(object["id"]);
50
+ const oldValue = attributes[0]["old"] ?? "";
51
+ const newValue = attributes[0]["new"] ?? "";
52
+ const dataSubjectId = this.flattenAndSortIdObject(subject["id"]);
53
53
 
54
54
  const known = {
55
55
  dppDataModification: {
56
- objectType: object['type'],
56
+ objectType: object["type"],
57
57
  objectId: objectId,
58
- attribute: attributes[0]['name'],
58
+ attribute: attributes[0]["name"],
59
59
  oldValue: oldValue,
60
60
  newValue: newValue,
61
- dataSubjectType: subject['type'],
61
+ dataSubjectType: subject["type"],
62
62
  dataSubjectId: dataSubjectId
63
63
  },
64
64
  dppDataAccess: {
65
- channelType: channel['type'],
66
- channelId: channel['id'],
67
- dataSubjectType: subject['type'],
65
+ channelType: channel["type"],
66
+ channelId: channel["id"],
67
+ dataSubjectType: subject["type"],
68
68
  dataSubjectId: dataSubjectId,
69
- objectType: object['type'],
69
+ objectType: object["type"],
70
70
  objectId: objectId,
71
- attribute: attributes[0]['name']
71
+ attribute: attributes[0]["name"]
72
72
  },
73
73
  configurationChange: {
74
- propertyName: attributes[0]['name'],
74
+ propertyName: attributes[0]["name"],
75
75
  oldValue: oldValue,
76
76
  newValue: newValue,
77
- objectType: object['type'],
77
+ objectType: object["type"],
78
78
  objectId: objectId
79
79
  },
80
80
  legacySecurityWrapper: {
81
81
  origEvent: JSON.stringify({
82
82
  ...data,
83
83
  data:
84
- typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)
84
+ typeof data.data === "object" && data.data !== null && !Array.isArray(data.data)
85
85
  ? JSON.stringify(data.data)
86
86
  : data.data
87
87
  })
88
88
  }
89
- }
90
- if (event in known) return known[event]
89
+ };
90
+ if (event in known) return known[event];
91
91
 
92
92
  // For unknown events, remove common audit log entry fields from the event payload
93
- if (typeof data === 'object' && data !== null) {
94
- const rest = this.removeCommonAuditLogFields(data)
95
- return rest
93
+ if (typeof data === "object" && data !== null) {
94
+ const rest = this.removeCommonAuditLogFields(data);
95
+ return rest;
96
96
  }
97
97
 
98
- return data
98
+ return data;
99
99
  }
100
100
 
101
101
  removeCommonAuditLogFields(obj) {
102
- if (typeof obj !== 'object' || obj === null) return obj
103
- const { ...rest } = obj
104
- delete rest.uuid
105
- delete rest.user
106
- delete rest.time
107
- delete rest.tenant
108
- return rest
102
+ if (typeof obj !== "object" || obj === null) return obj;
103
+ const { ...rest } = obj;
104
+ delete rest.uuid;
105
+ delete rest.user;
106
+ delete rest.time;
107
+ delete rest.tenant;
108
+ return rest;
109
109
  }
110
110
 
111
111
  eventPayload(event, data) {
112
- const tenant = cds.context?.tenant || null
113
- const timestamp = new Date().toISOString()
112
+ const tenant = cds.context?.tenant || null;
113
+ const timestamp = new Date().toISOString();
114
114
 
115
115
  const eventData = {
116
116
  id: cds.utils.uuid(),
117
117
  specversion: 1,
118
- source: `/${this._userProvided.credentials?.region}/${this._userProvided.credentials?.namespace}/${tenant}`,
118
+ source: `/${cds.env.requires["audit-log"].credentials?.region}/${cds.env.requires["audit-log"].credentials?.namespace}/${tenant}`,
119
119
  type: event,
120
120
  time: timestamp,
121
121
  data: {
122
122
  metadata: {
123
123
  ts: timestamp,
124
- appId: this._vcapApplication.application_id || 'default app',
124
+ appId: appMetadata.appID || "default app",
125
125
  infrastructure: {
126
126
  other: {
127
- runtimeType: 'Node.js'
127
+ runtimeType: "Node.js"
128
128
  }
129
129
  },
130
130
  platform: {
131
131
  other: {
132
- platformName: 'CAP'
132
+ platformName: "CAP"
133
133
  }
134
134
  }
135
135
  },
@@ -137,78 +137,84 @@ module.exports = class AuditLog2ALSNG extends AuditLogService {
137
137
  [event]: this.eventDataPayload(event, data)
138
138
  }
139
139
  }
140
- }
140
+ };
141
141
 
142
- return eventData
142
+ return eventData;
143
143
  }
144
144
 
145
145
  formatEventData(event, data) {
146
- if (event === 'legacySecurityWrapper') {
147
- return JSON.stringify([this.eventPayload(event, data)])
146
+ if (event === "legacySecurityWrapper") {
147
+ return JSON.stringify([this.eventPayload(event, data)]);
148
148
  }
149
149
 
150
150
  if (event in { dppDataModification: 1, dppDataAccess: 1, configurationChange: 1 }) {
151
- const eventData = data['attributes']?.map(attr => {
151
+ const eventData = data["attributes"]?.map((attr) => {
152
152
  return this.eventPayload(event, {
153
153
  ...data,
154
154
  attributes: [attr]
155
- })
156
- })
157
- return JSON.stringify(eventData || [])
155
+ });
156
+ });
157
+ return JSON.stringify(eventData || []);
158
158
  }
159
159
 
160
160
  // Always wrap event in an envelope for custom events
161
- return JSON.stringify([this.eventPayload(event, data)])
161
+ return JSON.stringify([this.eventPayload(event, data)]);
162
162
  }
163
163
 
164
164
  logEvent(event, data) {
165
- const passphrase = this._userProvided.credentials?.keyPassphrase
166
- const url = new URL(`${this._userProvided.credentials?.url}/ingestion/v1/events`)
167
- const eventData = this.formatEventData(event, data)
165
+ const credentials = cds.env.requires["audit-log"].credentials;
166
+ if (!credentials) {
167
+ throw new Error("No credentials found for SAP Audit Log Service NG");
168
+ }
169
+
170
+ const passphrase = credentials.keyPassphrase;
171
+ const url = new URL(`${credentials.url}/ingestion/v1/events`);
172
+ const eventData = this.formatEventData(event, data);
168
173
 
169
174
  const options = {
170
- method: 'POST',
175
+ method: "POST",
171
176
  headers: {
172
- 'Content-Type': 'application/json',
173
- 'Content-Length': Buffer.byteLength(eventData)
177
+ "Content-Type": "application/json",
178
+ "Content-Length": Buffer.byteLength(eventData)
174
179
  },
175
- key: this._userProvided.credentials?.key,
176
- cert: this._userProvided.credentials?.cert,
180
+ key: credentials.key,
181
+ cert: credentials.cert,
177
182
  ...(passphrase !== undefined && { passphrase })
178
- }
183
+ };
179
184
 
180
185
  return new Promise((resolve, reject) => {
181
- const req = https.request(url, options, res => {
182
- LOG.trace('🛰️ Status Code:', res.statusCode)
186
+ const req = https.request(url, options, (res) => {
187
+ LOG.trace("🛰️ Status Code:", res.statusCode);
183
188
 
184
- const chunks = []
185
- res.on('data', chunk => chunks.push(chunk))
189
+ const chunks = [];
190
+ res.on("data", (chunk) => chunks.push(chunk));
186
191
 
187
- res.on('end', () => {
188
- const { statusCode, statusMessage } = res
189
- let body = Buffer.concat(chunks).toString()
190
- if (res.headers['content-type']?.match(/json/)) body = JSON.parse(body)
192
+ res.on("end", () => {
193
+ const { statusCode, statusMessage } = res;
194
+ let body = Buffer.concat(chunks).toString();
195
+ if (res.headers["content-type"]?.match(/json/)) body = JSON.parse(body);
191
196
  if (res.statusCode >= 400) {
192
197
  // prettier-ignore
193
198
  const err = new Error(`Request failed with${statusMessage ? `: ${statusCode} - ${statusMessage}` : ` status ${statusCode}`}`)
194
- err.request = { method: options.method, url, headers: options.headers, body: data }
199
+ err.request = { method: options.method, url, headers: options.headers, body: data };
195
200
  if (err.request.headers.authorization)
196
- err.request.headers.authorization = err.request.headers.authorization.split(' ')[0] + ' ***'
197
- err.response = { statusCode, statusMessage, headers: res.headers, body }
198
- reject(err)
201
+ err.request.headers.authorization =
202
+ err.request.headers.authorization.split(" ")[0] + " ***";
203
+ err.response = { statusCode, statusMessage, headers: res.headers, body };
204
+ reject(err);
199
205
  } else {
200
- resolve(body)
206
+ resolve(body);
201
207
  }
202
- })
203
- })
208
+ });
209
+ });
204
210
 
205
- req.on('error', e => {
206
- reject(e.message)
207
- LOG.trace(`Problem with request: ${e.message}`)
208
- })
211
+ req.on("error", (e) => {
212
+ reject(e.message);
213
+ LOG.trace(`Problem with request: ${e.message}`);
214
+ });
209
215
 
210
- req.write(eventData)
211
- req.end()
212
- })
216
+ req.write(eventData);
217
+ req.end();
218
+ });
213
219
  }
214
- }
220
+ };
@@ -1,15 +1,15 @@
1
- const AuditLogService = require('./service')
1
+ const AuditLogService = require("./service");
2
2
 
3
3
  module.exports = class AuditLog2Console extends AuditLogService {
4
4
  async init() {
5
- this.on('*', function (req) {
6
- const { event, data } = req
5
+ this.on("*", function (req) {
6
+ const { event, data } = req;
7
7
 
8
8
  // eslint-disable-next-line no-console
9
- console.log(`[audit-log] - ${event}:`, data)
10
- })
9
+ console.log(`[audit-log] - ${event}:`, data);
10
+ });
11
11
 
12
12
  // call AuditLogService's init
13
- await super.init()
13
+ await super.init();
14
14
  }
15
- }
15
+ };