@naturalcycles/backend-lib 9.73.1 → 9.75.0

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.
@@ -0,0 +1,303 @@
1
+ import { _assert, AppError } from '@naturalcycles/js-lib/error'
2
+ import type { ErrorData } from '@naturalcycles/js-lib/error'
3
+ import type { AnyObject, Promisable } from '@naturalcycles/js-lib/types'
4
+ import { timingSafeStringEqual } from '@naturalcycles/nodejs-lib'
5
+ import type { BackendRequest, BackendRequestHandler } from '../server/server.model.js'
6
+
7
+ /**
8
+ * Authenticates requests and checks permissions.
9
+ */
10
+ export class AuthService<CTX = AnyObject> {
11
+ constructor(cfg: AuthServiceCfg<CTX>) {
12
+ this.cfg = {
13
+ authEnabled: true,
14
+ ...cfg,
15
+ }
16
+ }
17
+
18
+ cfg: AuthServiceCfg<CTX> & { authEnabled: boolean }
19
+
20
+ /**
21
+ * Responds JSON 401 / 403.
22
+ */
23
+ getMiddleware(cfg: AuthMiddlewareCfg<CTX> = {}): AuthMiddleware {
24
+ return reqPermissions => this.createHandler(cfg, reqPermissions)
25
+ }
26
+
27
+ /**
28
+ * Like `getMiddleware`, but 401 redirects to the login page.
29
+ */
30
+ getLoginRedirectMiddleware(cfg: AuthLoginRedirectMiddlewareCfg<CTX>): AuthMiddleware {
31
+ const loginHref = this.getLoginHref(cfg)
32
+ return reqPermissions => this.createHandler(cfg, reqPermissions, loginHref)
33
+ }
34
+
35
+ private createHandler(
36
+ cfg: AuthMiddlewareCfg<CTX>,
37
+ reqPermissions: string[] = [],
38
+ loginHref?: string,
39
+ ): BackendRequestHandler {
40
+ const {
41
+ getResolved = req => this.resolve(req),
42
+ getCtx,
43
+ andComparison = true,
44
+ secureHeader,
45
+ } = cfg
46
+
47
+ return async (req, res, next) => {
48
+ if (!this.cfg.authEnabled) return next()
49
+
50
+ if (secureHeader) {
51
+ const providedHeader = req.get(secureHeader.name)
52
+ if (providedHeader) {
53
+ if (timingSafeStringEqual(providedHeader, secureHeader.value)) {
54
+ return next()
55
+ }
56
+
57
+ return next(
58
+ new AppError('secureHeader or authentication required', this.authRequiredData()),
59
+ )
60
+ }
61
+ }
62
+
63
+ try {
64
+ const resolved = await getResolved(req)
65
+ if (!resolved?.subject && loginHref) {
66
+ res.status(401).send(this.getLoginHtmlRedirect(loginHref))
67
+ return
68
+ }
69
+
70
+ this.require(resolved, reqPermissions, { andComparison, ctx: getCtx?.(req) })
71
+ return next()
72
+ } catch (err) {
73
+ return next(err)
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Runs `authenticate`, then `getPermissions` and `isAlwaysAuthorized` if there's a subject.
80
+ * Ignores `authEnabled`.
81
+ */
82
+ async resolve(req: BackendRequest): Promise<ResolvedAuth> {
83
+ const { authenticate, getPermissions, isAlwaysAuthorized } = this.cfg
84
+
85
+ const subject = await authenticate(req)
86
+ if (!subject) return {}
87
+
88
+ const permissions = await getPermissions(subject, req)
89
+ const alwaysAuthorized = isAlwaysAuthorized
90
+ ? await isAlwaysAuthorized(subject, permissions, req)
91
+ : false
92
+
93
+ return { subject, permissions, alwaysAuthorized }
94
+ }
95
+
96
+ /**
97
+ * Throws 401 if there's no subject, 403 if not granted.
98
+ * Returns the granted permissions (all of the subject's if alwaysAuthorized),
99
+ * or `{ subject: 'authDisabled', permissions: [] }` if auth is disabled.
100
+ */
101
+ require(
102
+ resolved: ResolvedAuth | undefined,
103
+ reqPermissions: string[] = [],
104
+ opt: AuthCheckOptions<CTX> = {},
105
+ ): PermissionInfo {
106
+ if (!this.cfg.authEnabled) return { subject: 'authDisabled', permissions: [] }
107
+
108
+ this.assertAuthenticated(resolved)
109
+ const { subject, permissions, alwaysAuthorized } = resolved
110
+
111
+ const grantedPermissions = this.checkSubject(resolved, reqPermissions, opt)
112
+ this.assertGranted(subject, grantedPermissions, reqPermissions)
113
+
114
+ if (alwaysAuthorized) {
115
+ return { subject, permissions: permissions?.slice() || [] }
116
+ }
117
+
118
+ return { subject, permissions: grantedPermissions.slice() }
119
+ }
120
+
121
+ private checkSubject(
122
+ resolved: AuthenticatedAuth,
123
+ reqPermissions: string[],
124
+ opt: AuthCheckOptions<CTX>,
125
+ ): string[] | undefined {
126
+ const { subject, permissions, alwaysAuthorized = false } = resolved
127
+ const { andComparison = true, ctx } = opt
128
+
129
+ const grantedPermissions = alwaysAuthorized
130
+ ? reqPermissions
131
+ : this.getGrantedPermissions(permissions, reqPermissions, andComparison)
132
+
133
+ this.cfg.onCheck?.(
134
+ {
135
+ subject,
136
+ granted: !!grantedPermissions,
137
+ alwaysAuthorized,
138
+ reqPermissions,
139
+ checkedPermissions: grantedPermissions || reqPermissions,
140
+ },
141
+ ctx,
142
+ )
143
+
144
+ return grantedPermissions
145
+ }
146
+
147
+ private assertAuthenticated(
148
+ resolved: ResolvedAuth | undefined,
149
+ ): asserts resolved is AuthenticatedAuth {
150
+ _assert(resolved?.subject, 'authentication required', this.authRequiredData())
151
+ }
152
+
153
+ private authRequiredData(): ErrorData {
154
+ return {
155
+ authRequired: true,
156
+ backendResponseStatusCode: 401,
157
+ userFriendly: true,
158
+ }
159
+ }
160
+
161
+ private assertGranted(
162
+ subject: string,
163
+ grantedPermissions: string[] | undefined,
164
+ reqPermissions: string[],
165
+ ): asserts grantedPermissions is string[] {
166
+ _assert(grantedPermissions, `Permissions required: [${reqPermissions.join(', ')}]`, {
167
+ permissionsRequired: reqPermissions,
168
+ subject,
169
+ backendResponseStatusCode: 403,
170
+ userFriendly: true,
171
+ })
172
+ }
173
+
174
+ /**
175
+ * undefined - not granted.
176
+ */
177
+ private getGrantedPermissions(
178
+ permissions: string[] | undefined,
179
+ reqPermissions: string[],
180
+ andComparison: boolean,
181
+ ): string[] | undefined {
182
+ if (!permissions) return
183
+ const grantedPermissions = reqPermissions.filter(p => permissions.includes(p))
184
+ const granted = andComparison
185
+ ? grantedPermissions.length === reqPermissions.length
186
+ : grantedPermissions.length > 0
187
+ return granted ? grantedPermissions : undefined
188
+ }
189
+
190
+ private getLoginHref(cfg: AuthLoginRedirectMiddlewareCfg<CTX>): string {
191
+ return `${cfg.loginHtmlPath}?autoLogin=1&returnUrl=\${encodeURIComponent(location.href)}`
192
+ }
193
+
194
+ private getLoginHtmlRedirect(href: string): string {
195
+ return `
196
+ <html>
197
+ <body>401 Authentication Required
198
+ <script>
199
+ document.write(\`: <a href="${href}" id="loginLink">Login</a>\`)
200
+ document.getElementById('loginLink').click()
201
+ </script>
202
+ </body>
203
+ </html>
204
+ `
205
+ }
206
+ }
207
+
208
+ export interface AuthServiceCfg<CTX> {
209
+ /**
210
+ * Returns the subject, or undefined if not authenticated.
211
+ */
212
+ authenticate: (req: BackendRequest) => Promise<string | undefined>
213
+
214
+ /**
215
+ * undefined - no permission set (never granted).
216
+ */
217
+ getPermissions: (subject: string, req: BackendRequest) => Promise<string[] | undefined>
218
+
219
+ /**
220
+ * true - every check is granted for this subject.
221
+ */
222
+ isAlwaysAuthorized?: (
223
+ subject: string,
224
+ permissions: string[] | undefined,
225
+ req: BackendRequest,
226
+ ) => Promisable<boolean>
227
+
228
+ /**
229
+ * Called on every `require` check that has a subject, while auth is enabled. Must not throw.
230
+ */
231
+ onCheck?: (check: AuthCheck, ctx: (CTX & AnyObject) | undefined) => void
232
+
233
+ /**
234
+ * false - every check is granted. For tests / local debugging. Read on every check.
235
+ *
236
+ * @default true
237
+ */
238
+ authEnabled?: boolean
239
+ }
240
+
241
+ export interface AuthCheck {
242
+ subject: string
243
+ granted: boolean
244
+ alwaysAuthorized: boolean
245
+ reqPermissions: string[]
246
+ /** The granted permissions, or reqPermissions if denied */
247
+ checkedPermissions: string[]
248
+ }
249
+
250
+ export interface AuthLoginRedirectMiddlewareCfg<CTX> extends AuthMiddlewareCfg<CTX> {
251
+ loginHtmlPath: string
252
+ }
253
+
254
+ export interface AuthMiddlewareCfg<CTX> {
255
+ /**
256
+ * Defaults to `resolve(req)`.
257
+ */
258
+ getResolved?: (req: BackendRequest) => Promisable<ResolvedAuth | undefined>
259
+ getCtx?: (req: BackendRequest) => (CTX & AnyObject) | undefined
260
+ /**
261
+ * @default true
262
+ */
263
+ andComparison?: boolean
264
+ /**
265
+ * A request with this header passes without auth. A wrong value responds 401.
266
+ */
267
+ secureHeader?: SecureHeaderCfg
268
+ }
269
+
270
+ export interface SecureHeaderCfg {
271
+ /** e.g. 'Authorization' */
272
+ name: string
273
+ value: string
274
+ }
275
+
276
+ export type AuthMiddleware = (reqPermissions?: string[]) => BackendRequestHandler
277
+
278
+ export interface AuthCheckOptions<CTX> {
279
+ /**
280
+ * false - one granted permission is enough.
281
+ *
282
+ * @default true
283
+ */
284
+ andComparison?: boolean
285
+ /** Passed to onCheck: the app's CTX, plus any extra keys */
286
+ ctx?: CTX & AnyObject
287
+ }
288
+
289
+ export interface PermissionInfo {
290
+ subject: string
291
+ permissions: string[]
292
+ }
293
+
294
+ type AuthenticatedAuth = ResolvedAuth & { subject: string }
295
+
296
+ export interface ResolvedAuth {
297
+ /** undefined - not authenticated */
298
+ subject?: string
299
+ /** undefined - no permission set */
300
+ permissions?: string[]
301
+ /** Result of `isAlwaysAuthorized` */
302
+ alwaysAuthorized?: boolean
303
+ }
@@ -0,0 +1 @@
1
+ export * from './auth.service.js'
@@ -1,8 +1,10 @@
1
1
  import { _anyToError, _isErrorObject } from '@naturalcycles/js-lib/error'
2
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log'
2
3
  import type { CommonLogger, CommonLogLevel } from '@naturalcycles/js-lib/log'
4
+ import { _omit } from '@naturalcycles/js-lib/object/object.util.js'
3
5
  import type { Primitive, StringMap } from '@naturalcycles/js-lib/types'
4
6
  import type { InspectAnyOptions } from '@naturalcycles/nodejs-lib'
5
- import { _inspect } from '@naturalcycles/nodejs-lib'
7
+ import { _inspect, logEntryToString } from '@naturalcycles/nodejs-lib'
6
8
  import type { Breadcrumb, SeverityLevel } from '@sentry/node-core/light'
7
9
  import type * as SentryLib from '@sentry/node-core/light'
8
10
  import { getRequestLogger } from '../server/asyncLocalStorageMiddleware.js'
@@ -78,7 +80,7 @@ export class SentrySharedService {
78
80
 
79
81
  // Using request-aware logger here
80
82
  // Log both the error and attached ErrorData (if any)
81
- getRequestLogger().error(...[err_, data].filter(Boolean))
83
+ getRequestLogger().error({ err: err_, ...data })
82
84
 
83
85
  if (data?.report === false) {
84
86
  // Skip reporting the error
@@ -106,7 +108,7 @@ export class SentrySharedService {
106
108
  * Returns "eventId"
107
109
  */
108
110
  captureMessage(msg: string, level?: SeverityLevel): string {
109
- getRequestLogger()[sentrySeverityMap[level!] || 'log']('captureMessage:', msg)
111
+ getRequestLogger()[sentrySeverityMap[level!] || 'log'](`captureMessage: ${msg}`)
110
112
  return this.sentry.captureMessage(msg, level)
111
113
  }
112
114
 
@@ -122,19 +124,15 @@ export class SentrySharedService {
122
124
  * @experimental
123
125
  */
124
126
  getCommonLogger(): CommonLogger {
125
- return {
126
- debug: () => {}, // noop
127
- log: () => {}, // noop
128
- warn: () => {}, // noop
129
- error: (...args) => {
130
- const message = args.map(arg => _inspect(arg, INSPECT_OPT)).join(' ')
131
-
132
- this.sentry.addBreadcrumb({
133
- message,
134
- })
135
-
136
- this.sentry.captureException(_anyToError(args.length === 1 ? args[0] : args))
137
- },
138
- }
127
+ return commonLoggerCreate(entry => {
128
+ if (entry.level !== 'error') return // noop
129
+ const { msg, err } = entry
130
+
131
+ this.sentry.addBreadcrumb({
132
+ message: logEntryToString(entry, INSPECT_OPT),
133
+ })
134
+
135
+ this.sentry.captureException(_anyToError(err ?? msg ?? _omit(entry, ['level'])))
136
+ })
139
137
  }
140
138
  }
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks'
2
2
  import { _lazyValue } from '@naturalcycles/js-lib'
3
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log'
3
4
  import type { CommonLogger } from '@naturalcycles/js-lib/log'
4
5
  import { ciLogger, devLogger, gcpStructuredLogger } from './logMiddleware.js'
5
6
  import type { BackendRequest, BackendRequestHandler } from './server.model.js'
@@ -53,9 +54,6 @@ export function getRequestLogger(): CommonLogger {
53
54
  *
54
55
  * @experimental
55
56
  */
56
- export const requestLogger: CommonLogger = {
57
- debug: (...args) => getRequestLogger().debug(...args),
58
- log: (...args) => getRequestLogger().log(...args),
59
- warn: (...args) => getRequestLogger().warn(...args),
60
- error: (...args) => getRequestLogger().error(...args),
61
- }
57
+ export const requestLogger: CommonLogger = commonLoggerCreate(({ level, ...data }) =>
58
+ getRequestLogger()[level](data),
59
+ )
@@ -75,9 +75,9 @@ export function respondWithError(req: BackendRequest, res: BackendResponse, err:
75
75
  // because errorService was not provided - we are going to log the error here
76
76
 
77
77
  if (headersSent) {
78
- req.error(`error after headersSent:`, err)
78
+ req.error({ msg: `error after headersSent:`, err })
79
79
  } else {
80
- req.error(err)
80
+ req.error({ err })
81
81
  }
82
82
 
83
83
  // todo: add endpoint to the log
@@ -1,11 +1,11 @@
1
1
  import { inspect } from 'node:util'
2
- import { _isPlainObject } from '@naturalcycles/js-lib'
3
2
  import { _anyToErrorObject } from '@naturalcycles/js-lib/error'
4
- import type { CommonLogger } from '@naturalcycles/js-lib/log'
3
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log'
4
+ import type { CommonLogger, CommonLogLevel, LogEntry } from '@naturalcycles/js-lib/log'
5
5
  import { _safeJsonStringify } from '@naturalcycles/js-lib/string/safeJsonStringify.js'
6
6
  import { _objectAssign } from '@naturalcycles/js-lib/types'
7
7
  import type { AnyObject } from '@naturalcycles/js-lib/types'
8
- import { _inspect } from '@naturalcycles/nodejs-lib'
8
+ import { logEntryToString } from '@naturalcycles/nodejs-lib'
9
9
  import { dimGrey } from '@naturalcycles/nodejs-lib/colors'
10
10
  import type { BackendRequestHandler } from './server.model.js'
11
11
 
@@ -22,85 +22,54 @@ let reqCounter = 0
22
22
  * Logger that logs in "GCP structured log" format.
23
23
  * To be used in outside-of-request situations (otherwise req.log should be used).
24
24
  */
25
- export const gcpStructuredLogger: CommonLogger = {
26
- debug: (...args) => writeGCPStructuredLog({ severity: 'DEBUG' }, args),
27
- log: (...args) => writeGCPStructuredLog({}, args),
28
- warn: (...args) => writeGCPStructuredLog({ severity: 'WARNING' }, args),
29
- error: (...args) => writeGCPStructuredLog({ severity: 'ERROR' }, args),
30
- }
25
+ export const gcpStructuredLogger: CommonLogger = commonLoggerCreate(entry =>
26
+ writeGCPStructuredLog({}, entry),
27
+ )
31
28
 
32
29
  /**
33
30
  * Fancy development logger, to be used in outside-of-request situations
34
31
  * (otherwise req.log should be used).
35
32
  */
36
- export const devLogger: CommonLogger = {
37
- debug: (...args) => logToDev(null, args),
38
- log: (...args) => logToDev(null, args),
39
- warn: (...args) => logToDev(null, args),
40
- error: (...args) => logToDev(null, args),
41
- }
33
+ export const devLogger: CommonLogger = commonLoggerCreate(entry => logToDev(null, entry))
42
34
 
43
35
  /**
44
36
  * Same as devLogger, but without colors (e.g to not confuse Sentry).
45
37
  */
46
- export const ciLogger: CommonLogger = {
47
- debug: (...args) => logToCI(args),
48
- log: (...args) => logToCI(args),
49
- warn: (...args) => logToCI(args),
50
- error: (...args) => logToCI(args),
38
+ export const ciLogger: CommonLogger = commonLoggerCreate(logToCI)
39
+
40
+ const gcpSeverityByLevel: Record<CommonLogLevel, string> = {
41
+ debug: 'DEBUG',
42
+ log: 'INFO',
43
+ warn: 'WARNING',
44
+ error: 'ERROR',
51
45
  }
52
46
 
53
47
  // Documented here: https://cloud.google.com/logging/docs/structured-logging
54
48
  // Cloud Run logging: https://cloud.google.com/run/docs/logging
55
- function writeGCPStructuredLog(meta: AnyObject, args: any[]): void {
56
- if (args.length !== 1 || !_isPlainObject(args[0])) {
57
- console.log(
58
- JSON.stringify({
59
- message: args.map(a => (typeof a === 'string' ? a : inspect(a))).join(' '),
60
- ...meta,
61
- }),
62
- )
63
- return
64
- }
65
-
66
- const { msg, err, ...entry } = args[0]
49
+ function writeGCPStructuredLog(meta: AnyObject, entry: LogEntry): void {
50
+ const { level, msg, err, ...fields } = entry
67
51
  if (err instanceof Error) {
68
- entry['err'] = _anyToErrorObject(err)
69
- entry['message'] = msg ? `${msg}\n${inspect(err)}` : inspect(err)
52
+ fields['err'] = _anyToErrorObject(err)
53
+ fields['message'] = msg ? `${msg}\n${inspect(err)}` : inspect(err)
70
54
  } else {
71
- if (err !== undefined) entry['err'] = err
72
- if (msg !== undefined) entry['message'] = msg
55
+ if (err !== undefined) fields['err'] = err
56
+ if (msg !== undefined) fields['message'] = msg
73
57
  }
74
- console.log(_safeJsonStringify({ ...entry, ...meta }))
58
+ console.log(_safeJsonStringify({ ...fields, ...meta, severity: gcpSeverityByLevel[level] }))
75
59
  }
76
60
 
77
- function logToDev(requestId: string | null, args: any[]): void {
61
+ function logToDev(requestId: string | null, entry: LogEntry): void {
78
62
  // Run on local machine
79
- if (args.length !== 1 || !_isPlainObject(args[0])) {
80
- console.log(
81
- [
82
- requestId ? [dimGrey(`[${requestId}]`)] : [],
83
- ...args.map(a => _inspect(a, { includeErrorStack: true, colors: true })),
84
- ].join(' '),
85
- )
86
- return
87
- }
88
-
89
- const { msg, err, ...rest } = args[0]
90
- const parts: string[] = []
91
- if (requestId) parts.push(dimGrey(`[${requestId}]`))
92
- if (msg !== undefined) parts.push(String(msg))
93
- if (err !== undefined) parts.push(_inspect(err, { includeErrorStack: true, colors: true }))
94
- if (Object.keys(rest).length) parts.push(dimGrey(_inspect(rest, { colors: false })))
95
- console.log(parts.join(' '))
63
+ const line = logEntryToString(entry, { colors: true })
64
+ console.log(requestId ? `${dimGrey(`[${requestId}]`)} ${line}` : line)
96
65
  }
97
66
 
98
67
  /**
99
68
  * Same as logToDev, but without request and without colors.
100
69
  * This is to not confuse e.g Sentry when it picks up messages with colors
101
70
  */
102
- function logToCI(args: any[]): void {
103
- console.log(args.map(a => _inspect(a, { includeErrorStack: true, colors: false })).join(' '))
71
+ function logToCI(entry: LogEntry): void {
72
+ console.log(logEntryToString(entry, { colors: false }))
104
73
  }
105
74
 
106
75
  export function logMiddleware(): BackendRequestHandler {
@@ -130,12 +99,10 @@ export function logMiddleware(): BackendRequestHandler {
130
99
  meta['appengine.googleapis.com/request_id'] = req.header('x-appengine-request-log-id')
131
100
  }
132
101
 
133
- _objectAssign(req, {
134
- debug: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'DEBUG' }, args),
135
- log: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'INFO' }, args),
136
- warn: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'WARNING' }, args),
137
- error: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'ERROR' }, args),
138
- } satisfies CommonLogger)
102
+ _objectAssign(
103
+ req,
104
+ commonLoggerCreate(entry => writeGCPStructuredLog(meta, entry)),
105
+ )
139
106
 
140
107
  next()
141
108
  }
@@ -146,11 +113,10 @@ export function logMiddleware(): BackendRequestHandler {
146
113
  return function devLogHandler(req, _res, next) {
147
114
  // Local machine
148
115
  req.requestId = String(++reqCounter)
149
- req.debug =
150
- req.log =
151
- req.warn =
152
- req.error =
153
- (...args: any[]) => logToDev(req.requestId!, args)
116
+ _objectAssign(
117
+ req,
118
+ commonLoggerCreate(entry => logToDev(req.requestId!, entry)),
119
+ )
154
120
  next()
155
121
  }
156
122
  }
@@ -158,7 +124,7 @@ export function logMiddleware(): BackendRequestHandler {
158
124
  // Otherwise, return "simple" logger
159
125
  // This includes: unit tests, CI environments
160
126
  return function simpleLogHandler(req, _res, next) {
161
- req.debug = req.log = req.warn = req.error = (...args: any[]) => logToCI(args)
127
+ _objectAssign(req, ciLogger)
162
128
  next()
163
129
  }
164
130
  }
@@ -1,4 +1,4 @@
1
- import type { CommonLogFunction } from '@naturalcycles/js-lib/log'
1
+ import type { CommonLogger } from '@naturalcycles/js-lib/log'
2
2
  import type { Promisable } from '@naturalcycles/js-lib/types'
3
3
  import type { Application, IRouter, NextFunction, Request, Response } from 'express'
4
4
 
@@ -9,12 +9,7 @@ import type { Application, IRouter, NextFunction, Request, Response } from 'expr
9
9
  * `BackendRequest` seems to not conflict with anything right now.
10
10
  * Previous name `ExpressRequest` was clashing with Sentry.
11
11
  */
12
- export interface BackendRequest<BODY = unknown> extends Request {
13
- debug: CommonLogFunction
14
- log: CommonLogFunction
15
- warn: CommonLogFunction
16
- error: CommonLogFunction
17
-
12
+ export interface BackendRequest<BODY = unknown> extends Request, CommonLogger {
18
13
  requestId?: string
19
14
  /**
20
15
  * Only used for request logging purposes.
@@ -71,12 +66,7 @@ export type BackendRouter = IRouter
71
66
  export type BackendApplication = Application
72
67
 
73
68
  declare module 'http' {
74
- interface IncomingMessage {
75
- debug: CommonLogFunction
76
- log: CommonLogFunction
77
- warn: CommonLogFunction
78
- error: CommonLogFunction
79
-
69
+ interface IncomingMessage extends CommonLogger {
80
70
  requestId?: string
81
71
 
82
72
  requestTimeout?: NodeJS.Timeout