azify-logger 1.0.20 → 1.0.22
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 +2 -5
- package/server.js +54 -81
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azify-logger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.22",
|
|
4
4
|
"description": "Azify Logger Client - Centralized logging for OpenSearch",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -30,14 +30,11 @@
|
|
|
30
30
|
"axios": "^1.6.0",
|
|
31
31
|
"cors": "^2.8.5",
|
|
32
32
|
"express": "^4.18.2",
|
|
33
|
-
"express-session": "^1.17.3",
|
|
34
|
-
"passport": "^0.6.0",
|
|
35
|
-
"passport-azure-ad": "^4.3.5",
|
|
36
33
|
"require-in-the-middle": "^7.4.0",
|
|
37
34
|
"uuid": "^9.0.1"
|
|
38
35
|
},
|
|
39
36
|
"optionalDependencies": {
|
|
40
|
-
"@opentelemetry/api": "^1.
|
|
37
|
+
"@opentelemetry/api": "^1.9.0",
|
|
41
38
|
"@opentelemetry/auto-instrumentations-node": "^0.40.0",
|
|
42
39
|
"@opentelemetry/core": "^1.28.0",
|
|
43
40
|
"@opentelemetry/exporter-trace-otlp-http": "^0.45.0",
|
package/server.js
CHANGED
|
@@ -27,19 +27,57 @@ app.use(express.json({ limit: '10mb' }))
|
|
|
27
27
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }))
|
|
28
28
|
app.use(cors())
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
let ensureAuthenticated = (req, res, next) => next()
|
|
30
|
+
const IS_LOCAL = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev' || !process.env.NODE_ENV
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
function isPrivateOrLocalhost(ip) {
|
|
33
|
+
if (IS_LOCAL) {
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ip = ip.replace('::ffff:', '').replace('::1', '127.0.0.1')
|
|
38
|
+
|
|
39
|
+
if (ip === '127.0.0.1' || ip === 'localhost' || ip === '::1') {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parts = ip.split('.').map(Number)
|
|
44
|
+
|
|
45
|
+
if (parts.length !== 4) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (parts[0] === 10) return true
|
|
50
|
+
if (parts[0] === 127) return true
|
|
51
|
+
if (parts[0] === 192 && parts[1] === 168) return true
|
|
52
|
+
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true
|
|
53
|
+
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateNetworkAccess(req, res, next) {
|
|
58
|
+
if (req.path === '/health' || req.path === '/') {
|
|
59
|
+
return next()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const clientIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
|
63
|
+
req.headers['x-real-ip'] ||
|
|
64
|
+
req.ip ||
|
|
65
|
+
req.connection.remoteAddress ||
|
|
66
|
+
req.socket.remoteAddress
|
|
67
|
+
|
|
68
|
+
if (!IS_LOCAL && !isPrivateOrLocalhost(clientIP)) {
|
|
69
|
+
return res.status(403).json({
|
|
70
|
+
success: false,
|
|
71
|
+
message: 'Forbidden. Access denied - endpoint only accessible from same server (localhost/private IPs).',
|
|
72
|
+
clientIP: clientIP
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
next()
|
|
41
77
|
}
|
|
42
78
|
|
|
79
|
+
app.use(validateNetworkAccess)
|
|
80
|
+
|
|
43
81
|
const tracer = trace.getTracer('azify-logger', '1.0.0')
|
|
44
82
|
const propagator = new W3CTraceContextPropagator()
|
|
45
83
|
|
|
@@ -66,7 +104,7 @@ async function ensureIndexTemplate() {
|
|
|
66
104
|
},
|
|
67
105
|
mappings: {
|
|
68
106
|
properties: {
|
|
69
|
-
timestamp: { type: 'date' },
|
|
107
|
+
'@timestamp': { type: 'date' },
|
|
70
108
|
level: { type: 'keyword' },
|
|
71
109
|
message: { type: 'text' },
|
|
72
110
|
service: {
|
|
@@ -114,54 +152,6 @@ async function ensureIndexTemplate() {
|
|
|
114
152
|
|
|
115
153
|
ensureIndexTemplate()
|
|
116
154
|
|
|
117
|
-
/**
|
|
118
|
-
* Escapes HTML special characters to prevent XSS attacks
|
|
119
|
-
* This prevents script injection when data is rendered in OpenSearch Dashboards
|
|
120
|
-
* @param {string} str - String to escape
|
|
121
|
-
* @returns {string} Escaped string
|
|
122
|
-
*/
|
|
123
|
-
function escapeHtml(str) {
|
|
124
|
-
if (typeof str !== 'string') return str
|
|
125
|
-
const map = {
|
|
126
|
-
'&': '&',
|
|
127
|
-
'<': '<',
|
|
128
|
-
'>': '>',
|
|
129
|
-
'"': '"',
|
|
130
|
-
"'": ''',
|
|
131
|
-
'/': '/'
|
|
132
|
-
}
|
|
133
|
-
return str.replace(/[&<>"'/]/g, (m) => map[m])
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Sanitizes a value recursively to prevent XSS attacks
|
|
138
|
-
* Escapes HTML special characters in strings that could be rendered in Dashboards
|
|
139
|
-
* @param {any} value - Value to sanitize
|
|
140
|
-
* @returns {any} Sanitized value
|
|
141
|
-
*/
|
|
142
|
-
function sanitizeValue(value) {
|
|
143
|
-
if (typeof value === 'string') {
|
|
144
|
-
// Escape HTML special characters to prevent XSS when rendered in Dashboards
|
|
145
|
-
return escapeHtml(value)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (Array.isArray(value)) {
|
|
149
|
-
return value.map(item => sanitizeValue(item))
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (value !== null && typeof value === 'object') {
|
|
153
|
-
const sanitized = {}
|
|
154
|
-
for (const key in value) {
|
|
155
|
-
if (value.hasOwnProperty(key)) {
|
|
156
|
-
sanitized[key] = sanitizeValue(value[key])
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return sanitized
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return value
|
|
163
|
-
}
|
|
164
|
-
|
|
165
155
|
function generateTraceId() {
|
|
166
156
|
return Math.random().toString(36).substring(2, 15) +
|
|
167
157
|
Math.random().toString(36).substring(2, 15)
|
|
@@ -194,41 +184,24 @@ function getOrCreateTraceContext(requestId) {
|
|
|
194
184
|
app.get('/health', (req, res) => {
|
|
195
185
|
res.json({
|
|
196
186
|
status: 'ok',
|
|
197
|
-
service: 'azify-logger'
|
|
198
|
-
authEnabled: authEnabled
|
|
187
|
+
service: 'azify-logger'
|
|
199
188
|
})
|
|
200
189
|
})
|
|
201
190
|
|
|
202
|
-
app.get('/',
|
|
191
|
+
app.get('/', (req, res) => {
|
|
203
192
|
res.json({
|
|
204
193
|
service: 'azify-logger',
|
|
205
194
|
version: '1.0.0',
|
|
206
195
|
endpoints: {
|
|
207
196
|
health: '/health',
|
|
208
|
-
testLog: '/test-log'
|
|
209
|
-
|
|
210
|
-
login: '/auth/login',
|
|
211
|
-
logout: '/auth/logout',
|
|
212
|
-
me: '/auth/me'
|
|
213
|
-
} : null
|
|
214
|
-
},
|
|
215
|
-
user: req.user || null
|
|
197
|
+
testLog: '/test-log'
|
|
198
|
+
}
|
|
216
199
|
})
|
|
217
200
|
})
|
|
218
201
|
|
|
219
202
|
app.post('/log', async (req, res) => {
|
|
220
203
|
let { level, message, meta } = req.body
|
|
221
204
|
|
|
222
|
-
if (typeof message === 'string') {
|
|
223
|
-
message = sanitizeValue(message)
|
|
224
|
-
}
|
|
225
|
-
if (meta) {
|
|
226
|
-
meta = sanitizeValue(meta)
|
|
227
|
-
}
|
|
228
|
-
if (typeof level === 'string') {
|
|
229
|
-
level = sanitizeValue(level)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
205
|
if (!level || !message) {
|
|
233
206
|
return res.status(400).json({ success: false, message: 'Level and message are required.' })
|
|
234
207
|
}
|
|
@@ -297,7 +270,7 @@ app.post('/log', async (req, res) => {
|
|
|
297
270
|
}
|
|
298
271
|
|
|
299
272
|
const logEntry = {
|
|
300
|
-
timestamp: (meta && meta.timestamp) || new Date().toISOString(),
|
|
273
|
+
'@timestamp': (meta && meta.timestamp) || new Date().toISOString(),
|
|
301
274
|
level,
|
|
302
275
|
message,
|
|
303
276
|
service: {
|