@cap-js/audit-logging 0.8.1 → 0.8.3
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/CHANGELOG.md +14 -2
- package/lib/_relation.js +208 -0
- package/lib/modification.js +6 -6
- package/lib/utils.js +55 -25
- package/package.json +1 -1
- package/srv/log2restv2.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,23 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## Version 0.8.3 - 2025-04-09
|
|
8
|
+
|
|
9
|
+
- Preperation for `@sap/cds^9`
|
|
10
|
+
|
|
11
|
+
## Version 0.8.2 - 2024-11-27
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Erroneous modification log for non-updated key properties
|
|
16
|
+
- Error during non-modifying queries on database level
|
|
17
|
+
- Specify charset UTF-8 for requests to SAP Audit Log Service
|
|
18
|
+
|
|
7
19
|
## Version 0.8.1 - 2024-09-13
|
|
8
20
|
|
|
9
21
|
### Fixed
|
|
10
22
|
|
|
11
|
-
- Support for
|
|
23
|
+
- Support for `@sap/cds^8.2`
|
|
12
24
|
- Reduce clutter in error raised for outbound requests
|
|
13
25
|
|
|
14
26
|
## Version 0.8.0 - 2024-05-24
|
|
@@ -31,7 +43,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
31
43
|
|
|
32
44
|
### Added
|
|
33
45
|
|
|
34
|
-
- Support for
|
|
46
|
+
- Support for `@sap/cds^7.5`
|
|
35
47
|
|
|
36
48
|
### Fixed
|
|
37
49
|
|
package/lib/_relation.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
let initializing = false
|
|
2
|
+
|
|
3
|
+
class Relation {
|
|
4
|
+
constructor(csn, path = []) {
|
|
5
|
+
if (!initializing) throw new Error(`Do not new a relation, use 'Relation.to()' instead`)
|
|
6
|
+
Object.defineProperty(this, 'csn', { get: () => csn })
|
|
7
|
+
Object.defineProperty(this, 'path', {
|
|
8
|
+
get: () => path,
|
|
9
|
+
set: _ => {
|
|
10
|
+
path = _
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
if (csn.target) Object.defineProperty(this, 'target', { get: () => csn.target })
|
|
14
|
+
initializing = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static to(from, name) {
|
|
18
|
+
initializing = true
|
|
19
|
+
if (!name) return new Relation(from)
|
|
20
|
+
return from._elements[name] && new Relation(from._elements[name], [...from.path, name])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_has(prop) {
|
|
24
|
+
return Reflect.has(this, prop)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get _elements() {
|
|
28
|
+
if (this.csn.elements) return this.csn.elements
|
|
29
|
+
if (this.csn._target && this.csn._target.elements) return this.csn._target.elements
|
|
30
|
+
// if (csn.targetAspect) relation.elements = model.definitions[csn.targetAspect].elements
|
|
31
|
+
// if (csn.kind = 'type') relation.elements = model.definitions[csn.type].element
|
|
32
|
+
return {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
join(fromAlias = '', toAlias = '') {
|
|
36
|
+
return _getOnCond(this.csn, this.path, { select: fromAlias, join: toAlias })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
|
|
41
|
+
|
|
42
|
+
const relationHandler = relation => ({
|
|
43
|
+
get: (target, name) => {
|
|
44
|
+
const path = name.split(',')
|
|
45
|
+
const prop = path.join('_')
|
|
46
|
+
if (!target[prop]) {
|
|
47
|
+
if (path.length === 1) {
|
|
48
|
+
// REVISIT: property 'join' must not be used in CSN to make this working
|
|
49
|
+
if (relation._has(prop)) return relation[prop]
|
|
50
|
+
const newRelation = Relation.to(relation, prop)
|
|
51
|
+
if (newRelation) {
|
|
52
|
+
target[prop] = new Proxy(exposeRelation(newRelation), relationHandler(newRelation))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return target[prop]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
target[prop] = path.reduce((relation, value) => relation[value] || relation.csn._relations[value], relation)
|
|
59
|
+
target[prop].path = path
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return target[prop]
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
Relation,
|
|
68
|
+
exposeRelation,
|
|
69
|
+
relationHandler
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//
|
|
73
|
+
// ----- utils
|
|
74
|
+
//
|
|
75
|
+
|
|
76
|
+
const _prefixForStruct = element => {
|
|
77
|
+
const prefixes = []
|
|
78
|
+
let parent = element.parent
|
|
79
|
+
while (parent && parent.kind !== 'entity') {
|
|
80
|
+
prefixes.push(parent.name)
|
|
81
|
+
parent = parent.parent
|
|
82
|
+
}
|
|
83
|
+
return prefixes.length ? prefixes.reverse().join('_') + '_' : ''
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const _toRef = (alias, column) => {
|
|
87
|
+
if (Array.isArray(column)) column = column.join('_')
|
|
88
|
+
return { ref: alias ? [alias, column] : [column] }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const _adaptRefs = (onCond, path, { select, join }) => {
|
|
92
|
+
const _adaptEl = el => {
|
|
93
|
+
const ref = el.ref
|
|
94
|
+
|
|
95
|
+
if (ref) {
|
|
96
|
+
if (ref[0] === path.join('_') && ref[1]) {
|
|
97
|
+
return _toRef(select, ref.slice(1))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// no alias for special $user of canonical localized association
|
|
101
|
+
if (ref[0] === '$user' && path[0] === 'localized') {
|
|
102
|
+
return _toRef(undefined, ref.slice(0))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return _toRef(join, ref.slice(0))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (el.xpr) return { xpr: el.xpr.map(_adaptEl) }
|
|
109
|
+
return el
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return onCond.map(_adaptEl)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const _replace$selfAndAliasOnCond = (xpr, csnElement, aliases, path) => {
|
|
116
|
+
const selfIndex = xpr.findIndex(({ ref }) => ref?.[0] === '$self')
|
|
117
|
+
if (selfIndex != -1) {
|
|
118
|
+
let backLinkIndex
|
|
119
|
+
if (xpr[selfIndex + 1] && xpr[selfIndex + 1] === '=') backLinkIndex = selfIndex + 2
|
|
120
|
+
if (xpr[selfIndex - 1] && xpr[selfIndex - 1] === '=') backLinkIndex = selfIndex - 2
|
|
121
|
+
if (backLinkIndex != null) {
|
|
122
|
+
const ref = xpr[backLinkIndex].ref
|
|
123
|
+
const backlinkName = ref[ref.length - 1]
|
|
124
|
+
const mutOnCond = _newOnConditions(csnElement._backlink, [backlinkName], {
|
|
125
|
+
select: aliases.join,
|
|
126
|
+
join: aliases.select
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
xpr.splice(Math.min(backLinkIndex, selfIndex), 3, ...mutOnCond)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < xpr.length; i++) {
|
|
134
|
+
const element = xpr[i]
|
|
135
|
+
if (element.xpr) {
|
|
136
|
+
_replace$selfAndAliasOnCond(element.xpr, csnElement, aliases, path)
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (element.ref) {
|
|
141
|
+
if (element.ref[0] === path.join('_') && element.ref[1]) {
|
|
142
|
+
element.ref = _toRef(aliases.select, element.ref.slice(1)).ref
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// no alias for special $user of canonical localized association
|
|
147
|
+
if (element.ref[0] === '$user' && path[0] === 'localized') {
|
|
148
|
+
element.ref = _toRef(undefined, element.ref.slice(0)).ref
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
//no alias for special $now variable
|
|
152
|
+
if (element.ref[0] === '$now') {
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (element.ref[0] === aliases.join || element.ref[0] === aliases.select) {
|
|
157
|
+
// nothing todo here, as already right alias
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
element.ref = _toRef(aliases.join, element.ref.slice(0)).ref
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const _args = (csnElement, path, aliases) => {
|
|
167
|
+
const onCond = csnElement.on
|
|
168
|
+
if (!onCond || onCond.length === 0) return []
|
|
169
|
+
if (onCond.length < 3 && !onCond[0]?.xpr) return onCond
|
|
170
|
+
if (!csnElement._isSelfManaged) return _adaptRefs(onCond, path, aliases)
|
|
171
|
+
|
|
172
|
+
const onCondCopy = JSON.parse(JSON.stringify(onCond))
|
|
173
|
+
_replace$selfAndAliasOnCond(onCondCopy, csnElement, aliases, path)
|
|
174
|
+
|
|
175
|
+
return onCondCopy
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// this is only for 2one managed w/o on-conditions, i.e. no static values are possible
|
|
179
|
+
const _foreignToOn = (csnElement, path, { select, join }) => {
|
|
180
|
+
const on = []
|
|
181
|
+
|
|
182
|
+
for (const key of csnElement._foreignKeys) {
|
|
183
|
+
if (on.length !== 0) {
|
|
184
|
+
on.push('and')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const prefixChild = _prefixForStruct(key.childElement)
|
|
188
|
+
const ref1 = _toRef(select, prefixChild + key.childElement.name)
|
|
189
|
+
const structPrefix = path.length > 1 ? path.slice(0, -1) : []
|
|
190
|
+
const ref2 = _toRef(join, [...structPrefix, key.parentElement.name])
|
|
191
|
+
on.push(ref1, '=', ref2)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return on
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const _newOnConditions = (csnElement, path, aliases) => {
|
|
198
|
+
if (csnElement.keys) {
|
|
199
|
+
return _foreignToOn(csnElement, path, aliases)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return _args(csnElement, path, aliases)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const _getOnCond = (csnElement, path = [], aliases = { select: '', join: '' }) => {
|
|
206
|
+
const onCond = _newOnConditions(csnElement, path, aliases)
|
|
207
|
+
return [{ xpr: onCond }]
|
|
208
|
+
}
|
package/lib/modification.js
CHANGED
|
@@ -76,7 +76,7 @@ const addDiffToCtx = async function (req) {
|
|
|
76
76
|
if (!_audit.diffs) _audit.diffs = new Map()
|
|
77
77
|
|
|
78
78
|
// get diff
|
|
79
|
-
let diff = await req.diff()
|
|
79
|
+
let diff = (await req.diff()) || {}
|
|
80
80
|
diff = _getDataWithAppliedTransitions(diff, req)
|
|
81
81
|
|
|
82
82
|
// add keys, if necessary
|
|
@@ -88,9 +88,9 @@ const addDiffToCtx = async function (req) {
|
|
|
88
88
|
}
|
|
89
89
|
addDiffToCtx._initial = true
|
|
90
90
|
|
|
91
|
-
const _getOldAndNew = (action, row, key) => {
|
|
91
|
+
const _getOldAndNew = (action, row, key, entity) => {
|
|
92
92
|
let oldValue = action === 'Create' ? null : row._old && row._old[key]
|
|
93
|
-
if (oldValue === undefined) oldValue = null
|
|
93
|
+
if (oldValue === undefined) oldValue = action === 'Update' && key in entity.keys ? row[key] : null
|
|
94
94
|
else if (Array.isArray(oldValue)) oldValue = JSON.stringify(oldValue)
|
|
95
95
|
let newValue = action === 'Delete' ? null : row[key]
|
|
96
96
|
if (newValue === undefined) newValue = null
|
|
@@ -98,9 +98,9 @@ const _getOldAndNew = (action, row, key) => {
|
|
|
98
98
|
return { oldValue, newValue }
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const _addAttribute = (log, action, row, key) => {
|
|
101
|
+
const _addAttribute = (log, action, row, key, entity) => {
|
|
102
102
|
if (!log.attributes.find(ele => ele.name === key)) {
|
|
103
|
-
const { oldValue, newValue } = _getOldAndNew(action, row, key)
|
|
103
|
+
const { oldValue, newValue } = _getOldAndNew(action, row, key, entity)
|
|
104
104
|
if (oldValue !== newValue) {
|
|
105
105
|
const attr = { name: key }
|
|
106
106
|
if (action !== 'Create') attr.old = oldValue
|
|
@@ -139,7 +139,7 @@ const _processorFnModification = (modificationLogs, model, req, beforeWrite) =>
|
|
|
139
139
|
} else if (category === 'DataSubjectID') {
|
|
140
140
|
addDataSubject(modificationLog, row, key, entity)
|
|
141
141
|
} else if (category === 'IsPotentiallyPersonal' || category === 'IsPotentiallySensitive') {
|
|
142
|
-
_addAttribute(modificationLog, action, row, key)
|
|
142
|
+
_addAttribute(modificationLog, action, row, key, entity)
|
|
143
143
|
// do not log the value of a sensitive attribute
|
|
144
144
|
if (element['@PersonalData.IsPotentiallySensitive']) _maskAttribute(modificationLog.attributes, key)
|
|
145
145
|
}
|
package/lib/utils.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
2
|
|
|
3
|
+
const { Relation, exposeRelation, relationHandler } = require('./_relation')
|
|
4
|
+
|
|
3
5
|
const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
|
|
4
6
|
|
|
5
7
|
const $hasPersonalData = Symbol('@cap-js/audit-logging:hasPersonalData')
|
|
@@ -9,7 +11,7 @@ const $visitedUp = Symbol('@cap-js/audit-logging:visitedUp')
|
|
|
9
11
|
const $visitedDown = Symbol('@cap-js/audit-logging:visitedDown')
|
|
10
12
|
|
|
11
13
|
const hasPersonalData = entity => {
|
|
12
|
-
if (
|
|
14
|
+
if (entity.own($hasPersonalData) == null) {
|
|
13
15
|
if (!entity['@PersonalData.EntitySemantics']) entity.set($hasPersonalData, false)
|
|
14
16
|
else {
|
|
15
17
|
// default role to entity name
|
|
@@ -121,7 +123,22 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo
|
|
|
121
123
|
const targetAlias = _alias(element._target)
|
|
122
124
|
const relativeAlias = _alias(relative)
|
|
123
125
|
|
|
124
|
-
|
|
126
|
+
if (!('_relations' in relative)) {
|
|
127
|
+
const newRelation = Relation.to(relative)
|
|
128
|
+
relative._relations = new Proxy(exposeRelation(newRelation), relationHandler(newRelation))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let w = relative._relations[element.name].join(targetAlias, relativeAlias)
|
|
132
|
+
|
|
133
|
+
// REVISIT: rewrite to path expression, if alias for relative is already used in subselect to avoid sql error
|
|
134
|
+
if (previousCqn?._aliases.has(relativeAlias)) {
|
|
135
|
+
let t
|
|
136
|
+
for (const a in entity.associations) if (entity.associations[a].target === relative.name) t = entity.associations[a]
|
|
137
|
+
if (t && w[0]?.xpr) for (const ele of w[0].xpr) if (ele.ref?.[0] === relativeAlias) ele.ref.splice(0, 1, as, t.name)
|
|
138
|
+
}
|
|
139
|
+
childCqn._aliases = new Set(previousCqn ? [...previousCqn._aliases.values(), as] : [as])
|
|
140
|
+
|
|
141
|
+
childCqn.where(w)
|
|
125
142
|
|
|
126
143
|
if (previousCqn) childCqn.where('exists', previousCqn)
|
|
127
144
|
else childCqn.where(_addKeysToWhere(keys, row, as))
|
|
@@ -147,22 +164,24 @@ const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => {
|
|
|
147
164
|
}
|
|
148
165
|
|
|
149
166
|
const _getUps = (entity, model) => {
|
|
150
|
-
if (entity.own($parents))
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
if (entity.own($parents) == null) {
|
|
168
|
+
const ups = []
|
|
169
|
+
for (const def of Object.values(model.definitions)) {
|
|
170
|
+
if (def.kind !== 'entity' || !def.associations) continue
|
|
171
|
+
for (const element of Object.values(def.associations)) {
|
|
172
|
+
if (element.target !== entity.name || element._isBacklink || element.name === 'SiblingEntity') continue
|
|
173
|
+
ups.push(element)
|
|
174
|
+
}
|
|
157
175
|
}
|
|
176
|
+
entity.set($parents, ups)
|
|
158
177
|
}
|
|
159
|
-
return entity.
|
|
178
|
+
return entity.own($parents)
|
|
160
179
|
}
|
|
161
180
|
|
|
162
181
|
const _getDataSubjectUp = (root, model, entity, prev, next, result) => {
|
|
163
182
|
for (const element of _getUps(entity, model)) {
|
|
164
183
|
// cycle detection
|
|
165
|
-
if (
|
|
184
|
+
if (element.own($visitedUp) == null) element.set($visitedUp, new Set())
|
|
166
185
|
if (element.own($visitedUp).has(root)) continue
|
|
167
186
|
element.own($visitedUp).add(root)
|
|
168
187
|
|
|
@@ -192,7 +211,7 @@ const _getDataSubjectDown = (root, entity, prev, next) => {
|
|
|
192
211
|
}
|
|
193
212
|
for (const element of associations) {
|
|
194
213
|
// cycle detection
|
|
195
|
-
if (
|
|
214
|
+
if (element.own($visitedDown) == null) element.set($visitedDown, new Set())
|
|
196
215
|
if (element.own($visitedDown).has(root)) continue
|
|
197
216
|
element.own($visitedDown).add(root)
|
|
198
217
|
|
|
@@ -204,12 +223,14 @@ const _getDataSubjectDown = (root, entity, prev, next) => {
|
|
|
204
223
|
}
|
|
205
224
|
|
|
206
225
|
const getDataSubject = (entity, model) => {
|
|
207
|
-
if (entity.own($dataSubject))
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
226
|
+
if (entity.own($dataSubject) == null) {
|
|
227
|
+
// entities with EntitySemantics 'DataSubjectDetails' or 'Other' must not necessarily
|
|
228
|
+
// be always below or always above 'DataSubject' entity in CSN tree
|
|
229
|
+
let dataSubjectInfo = _getDataSubjectUp(entity.name, model, entity)
|
|
230
|
+
if (!dataSubjectInfo) dataSubjectInfo = _getDataSubjectDown(entity.name, entity)
|
|
231
|
+
entity.set($dataSubject, dataSubjectInfo)
|
|
232
|
+
}
|
|
233
|
+
return entity.own($dataSubject)
|
|
213
234
|
}
|
|
214
235
|
|
|
215
236
|
const _getDataSubjectsMap = req => {
|
|
@@ -235,20 +256,29 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
|
|
|
235
256
|
else map.set(role, _getDataSubjectIdQuery(dataSubjectInfo, row, model))
|
|
236
257
|
}
|
|
237
258
|
|
|
238
|
-
const resolveDataSubjects =
|
|
259
|
+
const resolveDataSubjects = (logs, req) => {
|
|
260
|
+
const ps = []
|
|
261
|
+
|
|
239
262
|
const map = _getDataSubjectsMap(req)
|
|
263
|
+
|
|
240
264
|
for (const each of Object.values(logs)) {
|
|
241
265
|
if (each.data_subject.id instanceof cds.ql.Query) {
|
|
242
266
|
const q = each.data_subject.id
|
|
243
|
-
if (map.has(q)) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
map.set(q, res)
|
|
248
|
-
each.data_subject.id = res
|
|
267
|
+
if (!map.has(q)) {
|
|
268
|
+
const p = cds.run(q).then(res => map.set(q, res))
|
|
269
|
+
map.set(q, p)
|
|
270
|
+
ps.push(p)
|
|
249
271
|
}
|
|
250
272
|
}
|
|
251
273
|
}
|
|
274
|
+
|
|
275
|
+
return Promise.all(ps).then(() => {
|
|
276
|
+
for (const each of Object.values(logs)) {
|
|
277
|
+
if (each.data_subject.id instanceof cds.ql.Query) {
|
|
278
|
+
each.data_subject.id = map.get(each.data_subject.id)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
})
|
|
252
282
|
}
|
|
253
283
|
|
|
254
284
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/audit-logging",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "CDS plugin providing integration to the SAP Audit Log service as well as out-of-the-box personal data-related audit logging based on annotations.",
|
|
5
5
|
"repository": "cap-js/audit-logging",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
package/srv/log2restv2.js
CHANGED
|
@@ -80,7 +80,7 @@ module.exports = class AuditLog2RESTv2 extends AuditLogService {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
async _send(data, path) {
|
|
83
|
-
const headers = { 'content-type': 'application/json' }
|
|
83
|
+
const headers = { 'content-type': 'application/json;charset=utf-8' }
|
|
84
84
|
if (this._vcap) {
|
|
85
85
|
headers.XS_AUDIT_ORG = this._vcap.organization_name
|
|
86
86
|
headers.XS_AUDIT_SPACE = this._vcap.space_name
|