diff --git a/packages/server/app.ts b/packages/server/app.ts index 430e49954..91dac08eb 100644 --- a/packages/server/app.ts +++ b/packages/server/app.ts @@ -11,7 +11,10 @@ import { createTerminus } from '@godaddy/terminus' import * as Sentry from '@sentry/node' import Logging from '@/logging' import { startupLogger, shutdownLogger } from '@/logging/logging' -import { LoggingExpressMiddleware } from '@/logging/expressLogging' +import { + DetermineRequestIdMiddleware, + LoggingExpressMiddleware +} from '@/logging/expressLogging' import { errorLoggingMiddleware } from '@/logging/errorLogging' import prometheusClient from 'prom-client' @@ -183,9 +186,8 @@ export async function init() { // Should perhaps be done manually? await knex.migrate.latest() - if (process.env.NODE_ENV !== 'test') { - app.use(LoggingExpressMiddleware) - } + app.use(DetermineRequestIdMiddleware) + app.use(LoggingExpressMiddleware) if (process.env.COMPRESSION) { app.use(compression()) @@ -217,12 +219,10 @@ export async function init() { // Expose prometheus metrics app.get('/metrics', async (req, res) => { - const boundLogger = startupLogger.child({ endpoint: '/metrics' }) try { res.set('Content-Type', prometheusClient.register.contentType) res.end(await prometheusClient.register.metrics()) } catch (ex: unknown) { - boundLogger.error(ex, 'Error while retrieving metrics.') res.status(500).end(ex instanceof Error ? ex.message : `${ex}`) } }) diff --git a/packages/server/logging/expressLogging.ts b/packages/server/logging/expressLogging.ts index 2f9ae6ba6..219e02b5e 100644 --- a/packages/server/logging/expressLogging.ts +++ b/packages/server/logging/expressLogging.ts @@ -1,7 +1,86 @@ import { logger } from './logging' +import { randomUUID } from 'crypto' import HttpLogger from 'pino-http' +import { IncomingMessage } from 'http' +import { NextFunction, Response } from 'express' +import pino, { SerializedResponse } from 'pino' +import { GenReqId } from 'pino-http' + +const REQUEST_ID_HEADER = 'x-request-id' + +const GenerateRequestId: GenReqId = (req: IncomingMessage) => DetermineRequestId(req) + +const DetermineRequestId = ( + req: IncomingMessage, + uuidGenerator: () => string = randomUUID +): string => { + const headers = req.headers[REQUEST_ID_HEADER] + if (!Array.isArray(headers)) return headers || uuidGenerator() + return headers[0] || uuidGenerator() +} export const LoggingExpressMiddleware = HttpLogger({ logger, - autoLogging: false + autoLogging: true, + genReqId: GenerateRequestId, + customLogLevel: (req, res, err) => { + if (res.statusCode >= 400 && res.statusCode < 500) { + return 'info' + } else if (res.statusCode >= 500 || err) { + return 'error' + } else if (res.statusCode >= 300 && res.statusCode < 400) { + return 'silent' + } + return 'info' + }, + + // we need to redact any potential sensitive data from being logged. + // as we do not know what headers may be sent in a request by a user or client + // we have to allow list selected headers + serializers: { + req: pino.stdSerializers.wrapRequestSerializer((req) => { + return { + id: req.raw.id, + method: req.raw.method, + path: req.raw.url?.split('?')[0], // Remove query params which might be sensitive + // Allowlist useful headers + headers: { + host: req.raw.headers.host, + 'user-agent': req.raw.headers['user-agent'], + 'x-request-id': req.raw.headers[REQUEST_ID_HEADER], + referer: req.raw.headers.referer + } + } + }), + res: pino.stdSerializers.wrapResponseSerializer((res) => { + const resRaw = res as SerializedResponse & { + raw: { + headers: Record + } + } + return { + statusCode: res.raw.statusCode, + // Allowlist useful headers + headers: { + 'content-length': resRaw.raw.headers['content-length'], + 'content-type': resRaw.raw.headers['content-type'], + 'retry-after': resRaw.raw.headers['retry-after'], + 'x-ratelimit-remaining': resRaw.raw.headers['x-ratelimit-remaining'], + 'x-ratelimit-reset': resRaw.raw.headers['x-ratelimit-reset'], + 'x-request-id': resRaw.raw.headers['x-request-id'], + 'x-speckle-meditation': resRaw.raw.headers['x-speckle-meditation'] + } + } + }) + } }) + +export const DetermineRequestIdMiddleware = ( + req: IncomingMessage, + res: Response, + next: NextFunction +) => { + const id = DetermineRequestId(req) + res.setHeader(REQUEST_ID_HEADER, id) + next() +} diff --git a/packages/server/logging/logging.ts b/packages/server/logging/logging.ts index cf0f3058f..bec9a439f 100644 --- a/packages/server/logging/logging.ts +++ b/packages/server/logging/logging.ts @@ -23,5 +23,4 @@ export const uploadEndpointLogger = extendLoggerComponent(logger, 'upload-endpoi export const dbLogger = extendLoggerComponent(logger, 'db') export const servicesLogger = extendLoggerComponent(logger, 'services') export const rateLimiterLogger = extendLoggerComponent(logger, 'rate-limiter') -export const authLogger = extendLoggerComponent(logger, 'auth') export const redisLogger = extendLoggerComponent(logger, 'redis') diff --git a/packages/server/modules/auth/rest/index.js b/packages/server/modules/auth/rest/index.js index 0cbc14528..a10b3cc4d 100644 --- a/packages/server/modules/auth/rest/index.js +++ b/packages/server/modules/auth/rest/index.js @@ -14,7 +14,6 @@ const { revokeRefreshToken } = require(`@/modules/auth/services/apps`) const { validateScopes } = require(`@/modules/shared`) const { InvalidAccessCodeRequestError } = require('@/modules/auth/errors') const { ForbiddenError } = require('apollo-server-errors') -const { authLogger } = require('@/logging/logging') // TODO: Secure these endpoints! module.exports = (app) => { @@ -23,7 +22,6 @@ module.exports = (app) => { TODO: ensure same origin. */ app.get('/auth/accesscode', async (req, res) => { - const boundLogger = authLogger.child({ endpoint: '/auth/accesscode' }) try { const appId = req.query.appId const app = await getApp({ id: appId }) @@ -49,14 +47,11 @@ module.exports = (app) => { err instanceof InvalidAccessCodeRequestError || err instanceof ForbiddenError ) { - boundLogger.info( - { err }, - 'Invalid access code request error, or Forbidden error.' - ) + res.log.info({ err }, 'Invalid access code request error, or Forbidden error.') return res.status(400).send(err.message) } else { sentry({ err }) - boundLogger.error(err) + res.log.error(err) return res .status(500) .send('Something went wrong while processing your request') @@ -69,7 +64,6 @@ module.exports = (app) => { */ app.options('/auth/token', cors()) app.post('/auth/token', cors(), async (req, res) => { - const boundLogger = authLogger.child({ endpoint: '/auth/token' }) try { // Token refresh if (req.body.refreshToken) { @@ -104,7 +98,7 @@ module.exports = (app) => { return res.send(authResponse) } catch (err) { sentry({ err }) - boundLogger.warn(err) + res.log.info({ err }, 'Error while trying to generate a new token.') return res.status(401).send({ err: err.message }) } }) @@ -113,7 +107,6 @@ module.exports = (app) => { Ensures a user is logged out by invalidating their token and refresh token. */ app.post('/auth/logout', async (req, res) => { - const boundLogger = authLogger.child({ endpoint: '/auth/logout' }) try { const token = req.body.token const refreshToken = req.body.refreshToken @@ -126,7 +119,7 @@ module.exports = (app) => { return res.status(200).send({ message: 'You have logged out.' }) } catch (err) { sentry({ err }) - boundLogger.error(err) + res.log.info({ err }, 'Error while trying to logout.') return res.status(400).send('Something went wrong while trying to logout.') } }) diff --git a/packages/server/modules/auth/strategies/azure-ad.js b/packages/server/modules/auth/strategies/azure-ad.js index 9eeeffadf..3d03d316d 100644 --- a/packages/server/modules/auth/strategies/azure-ad.js +++ b/packages/server/modules/auth/strategies/azure-ad.js @@ -12,7 +12,6 @@ const { resolveAuthRedirectPath } = require('@/modules/serverinvites/services/inviteProcessingService') const { passportAuthenticate } = require('@/modules/auth/services/passportService') -const { authLogger } = require('@/logging/logging') const { UserInputError } = require('@/modules/core/errors/userinput') module.exports = async (app, session, sessionStorage, finalizeAuth) => { @@ -51,7 +50,6 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => { session, passportAuthenticate('azuread-openidconnect'), async (req, res, next) => { - let boundLogger = authLogger.child({ endpoint: '/auth/azure/callback' }) const serverInfo = await getServerInfo() try { @@ -112,7 +110,7 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => { // ID is used later for verifying access token req.user.id = myUser.id - boundLogger = boundLogger.child({ userId: myUser.id }) + req.log = req.log.child({ userId: myUser.id }) // use the invite await finalizeInvitedServerRegistration(user.email, myUser.id) @@ -125,10 +123,13 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => { } catch (err) { switch (err.constructor) { case UserInputError: - boundLogger.info(err) + req.log.info( + { err }, + 'User input error during Azure AD authentication callback.' + ) break default: - boundLogger.error(err) + req.log.error(err, 'Error during Azure AD authentication callback.') } return next() } diff --git a/packages/server/modules/auth/strategies/local.js b/packages/server/modules/auth/strategies/local.js index 93849aee3..99926abcd 100644 --- a/packages/server/modules/auth/strategies/local.js +++ b/packages/server/modules/auth/strategies/local.js @@ -17,7 +17,6 @@ const { resolveAuthRedirectPath } = require('@/modules/serverinvites/services/inviteProcessingService') const { getIpFromRequest } = require('@/modules/shared/utils/ip') -const { authLogger } = require('@/logging/logging') const { NoInviteFoundError } = require('@/modules/serverinvites/errors') const { UserInputError, @@ -38,23 +37,22 @@ module.exports = async (app, session, sessionAppId, finalizeAuth) => { session, sessionAppId, async (req, res, next) => { - const boundLogger = authLogger.child({ endpoint: '/auth/local/login' }) try { const valid = await validatePasssword({ email: req.body.email, password: req.body.password }) - if (!valid) throw new UserInputError('Invalid credentials') + if (!valid) throw new UserInputError('Invalid credentials.') const user = await getUserByEmail({ email: req.body.email }) - if (!user) throw new UserInputError('Invalid credentials') + if (!user) throw new UserInputError('Invalid credentials.') req.user = { id: user.id } return next() } catch (err) { - boundLogger.info({ err }, 'Error while logging in.') - return res.status(401).send({ err: true, message: 'Invalid credentials' }) + res.log.info({ err }, 'Error while logging in.') + return res.status(401).send({ err: true, message: 'Invalid credentials.' }) } }, finalizeAuth @@ -65,7 +63,6 @@ module.exports = async (app, session, sessionAppId, finalizeAuth) => { session, sessionAppId, async (req, res, next) => { - let boundLogger = authLogger.child({ endpoint: '/auth/local/register' }) const serverInfo = await getServerInfo() try { if (!req.body.password) throw new UserInputError('Password missing') @@ -102,7 +99,7 @@ module.exports = async (app, session, sessionAppId, finalizeAuth) => { // so we go ahead and register the user const userId = await createUser(user) req.user = { id: userId, email: user.email } - boundLogger = boundLogger.child({ userId }) + req.log = req.log.child({ userId }) // 4. use up all server-only invites the email had attached to it await finalizeInvitedServerRegistration(user.email, userId) @@ -116,10 +113,10 @@ module.exports = async (app, session, sessionAppId, finalizeAuth) => { case PasswordTooShortError: case UserInputError: case NoInviteFoundError: - boundLogger.info({ err }, 'Error while registering user.') + res.log.info({ err }, 'Error while registering.') return res.status(400).send({ err: err.message }) default: - boundLogger.error(err, 'Error while registering user.') + res.log.error(err, 'Error while registering.') return res.status(500).send({ err: err.message }) } } diff --git a/packages/server/modules/blobstorage/index.js b/packages/server/modules/blobstorage/index.js index da82dc910..4f6bb41c0 100644 --- a/packages/server/modules/blobstorage/index.js +++ b/packages/server/modules/blobstorage/index.js @@ -80,7 +80,6 @@ exports.init = async (app) => { allowForAllRegisteredUsersOnPublicStreamsWithPublicComments ]), async (req, res) => { - let boundLogger = logger.child({ endpoint: '/api/stream/:streamId/blob' }) // no checking of startup conditions, just dont init the endpoints if not configured right //authorize request const uploadOperations = {} @@ -90,12 +89,12 @@ exports.init = async (app) => { limits: { fileSize: getFileSizeLimit() } }) const streamId = req.params.streamId - boundLogger = boundLogger.child({ streamId, userId: req.context.userId }) + req.log = req.log.child({ streamId, userId: req.context.userId }) busboy.on('file', (formKey, file, info) => { const { filename: fileName } = info const fileType = fileName.split('.').pop().toLowerCase() - boundLogger = boundLogger.child({ fileName, fileType }) + req.log = req.log.child({ fileName, fileType }) const registerUploadResult = (processingPromise) => { finalizePromises.push( processingPromise.then((resultItem) => ({ ...resultItem, formKey })) @@ -112,7 +111,7 @@ exports.init = async (app) => { } } - boundLogger = boundLogger.child({ blobId }) + req.log = req.log.child({ blobId }) uploadOperations[blobId] = uploadFileStream( storeFileStream, @@ -153,7 +152,7 @@ exports.init = async (app) => { }) busboy.on('error', async (err) => { - boundLogger.info({ err }, 'Upload request error.') + res.log.info({ err }, 'Upload request error.') //delete all started uploads await Promise.all( Object.keys(uploadOperations).map((blobId) => @@ -162,7 +161,7 @@ exports.init = async (app) => { ) const status = 400 - const response = 'Upload request error. The server logs may have more details' + const response = 'Upload request error. The server logs may have more details.' res.status(status).end(response) }) diff --git a/packages/server/modules/core/rest/diffDownload.js b/packages/server/modules/core/rest/diffDownload.js index dde36807d..fdd1d67e0 100644 --- a/packages/server/modules/core/rest/diffDownload.js +++ b/packages/server/modules/core/rest/diffDownload.js @@ -7,16 +7,14 @@ const { SpeckleObjectsStream } = require('./speckleObjectsStream') const { getObjectsStream } = require('../services/objects') const { pipeline, PassThrough } = require('stream') -const { logger } = require('@/logging/logging') module.exports = (app) => { app.options('/api/getobjects/:streamId', cors()) app.post('/api/getobjects/:streamId', cors(), async (req, res) => { - const boundLogger = logger.child({ + req.log = req.log.child({ userId: req.context.userId || '-', - streamId: req.params.streamId, - endpoint: '/api/getobjects/:streamId' + streamId: req.params.streamId }) const hasStreamAccess = await validatePermissionsReadStream( req.params.streamId, @@ -45,9 +43,9 @@ module.exports = (app) => { res, (err) => { if (err) { - boundLogger.error(err, `App error streaming objects`) + req.log.error(err, `App error streaming objects`) } else { - boundLogger.info( + req.log.info( `Streamed ${childrenList.length} objects (size: ${ gzipStream.bytesWritten / 1000000 } MB)` @@ -72,7 +70,7 @@ module.exports = (app) => { }) } } catch (ex) { - boundLogger.error(ex, `DB Error streaming objects`) + req.log.error(ex, `DB Error streaming objects`) speckleObjStream.emit('error', new Error('Database streaming error')) } speckleObjStream.end() diff --git a/packages/server/modules/core/rest/diffUpload.js b/packages/server/modules/core/rest/diffUpload.js index 22bff6443..1bf1421ac 100644 --- a/packages/server/modules/core/rest/diffUpload.js +++ b/packages/server/modules/core/rest/diffUpload.js @@ -5,16 +5,14 @@ const cors = require('cors') const { validatePermissionsWriteStream } = require('./authUtils') const { hasObjects } = require('../services/objects') -const { logger } = require('@/logging/logging') module.exports = (app) => { app.options('/api/diff/:streamId', cors()) app.post('/api/diff/:streamId', cors(), async (req, res) => { - const boundLogger = logger.child({ + req.log = req.log.child({ userId: req.context.userId || '-', - streamId: req.params.streamId, - endpoint: '/api/diff/:streamId' + streamId: req.params.streamId }) const hasStreamAccess = await validatePermissionsWriteStream( req.params.streamId, @@ -26,13 +24,13 @@ module.exports = (app) => { const objectList = JSON.parse(req.body.objects) - boundLogger.info(`Diffing ${objectList.length} objects.`) + req.log.info(`Diffing ${objectList.length} objects.`) const response = await hasObjects({ streamId: req.params.streamId, objectIds: objectList }) - boundLogger.debug(response) + req.log.debug(response) res.writeHead(200, { 'Content-Encoding': 'gzip', 'Content-Type': 'application/json' diff --git a/packages/server/modules/core/rest/upload.js b/packages/server/modules/core/rest/upload.js index dce44904e..fd658b488 100644 --- a/packages/server/modules/core/rest/upload.js +++ b/packages/server/modules/core/rest/upload.js @@ -6,7 +6,6 @@ const Busboy = require('busboy') const { validatePermissionsWriteStream } = require('./authUtils') const { createObjectsBatched } = require('../services/objects') -const { uploadEndpointLogger } = require('@/logging/logging') const MAX_FILE_SIZE = 50 * 1024 * 1024 @@ -14,10 +13,9 @@ module.exports = (app) => { app.options('/objects/:streamId', cors()) app.post('/objects/:streamId', cors(), async (req, res) => { - const boundLogger = uploadEndpointLogger.child({ + req.log = req.log.child({ userId: req.context.userId || '-', - streamId: req.params.streamId, - endpoint: '/objects/:streamId' + streamId: req.params.streamId }) const hasStreamAccess = await validatePermissionsWriteStream( @@ -54,7 +52,7 @@ module.exports = (app) => { const gzippedBuffer = Buffer.concat(buffer) if (gzippedBuffer.length > MAX_FILE_SIZE) { - boundLogger.error( + req.log.error( `Upload error: Batch size too large (${gzippedBuffer.length} > ${MAX_FILE_SIZE})` ) if (!requestDropped) @@ -68,7 +66,7 @@ module.exports = (app) => { const gunzippedBuffer = zlib.gunzipSync(gzippedBuffer).toString() if (gunzippedBuffer.length > MAX_FILE_SIZE) { - boundLogger.error( + req.log.error( `Upload error: Batch size too large (${gunzippedBuffer.length} > ${MAX_FILE_SIZE})` ) if (!requestDropped) @@ -83,7 +81,7 @@ module.exports = (app) => { try { objs = JSON.parse(gunzippedBuffer) } catch (e) { - boundLogger.error(`Upload error: Batch not in JSON format`) + req.log.error(`Upload error: Batch not in JSON format`) if (!requestDropped) res.status(400).send('Failed to parse data.') requestDropped = true } @@ -98,7 +96,7 @@ module.exports = (app) => { } const promise = createObjectsBatched(req.params.streamId, objs).catch((e) => { - boundLogger.error(e, `Upload error.`) + req.log.error(e, `Upload error.`) if (!requestDropped) res .status(400) @@ -111,7 +109,7 @@ module.exports = (app) => { await promise - boundLogger.info( + req.log.info( { durationSeconds: (Date.now() - t0) / 1000, crtMemUsageMB: process.memoryUsage().heapUsed / 1024 / 1024, @@ -138,7 +136,7 @@ module.exports = (app) => { let objs = [] if (buffer.length > MAX_FILE_SIZE) { - boundLogger.error( + req.log.error( `Upload error: Batch size too large (${buffer.length} > ${MAX_FILE_SIZE})` ) if (!requestDropped) @@ -151,7 +149,7 @@ module.exports = (app) => { try { objs = JSON.parse(buffer) } catch (e) { - boundLogger.error(`Upload error: Batch not in JSON format`) + req.log.error(`Upload error: Batch not in JSON format`) if (!requestDropped) res.status(400).send('Failed to parse data.') requestDropped = true } @@ -165,7 +163,7 @@ module.exports = (app) => { } const promise = createObjectsBatched(req.params.streamId, objs).catch((e) => { - boundLogger.error(e, `Upload error.`) + req.log.error(e, `Upload error.`) if (!requestDropped) res .status(400) @@ -177,7 +175,7 @@ module.exports = (app) => { promises.push(promise) await promise - boundLogger.info( + req.log.info( { uploadedSizeMB: buffer.length / 1000000, durationSeconds: (Date.now() - t0) / 1000, @@ -188,7 +186,7 @@ module.exports = (app) => { ) }) } else { - boundLogger.error(`Invalid ContentType header: ${mimeType}`) + req.log.info(`Invalid ContentType header: ${mimeType}`) if (!requestDropped) res .status(400) @@ -202,7 +200,7 @@ module.exports = (app) => { busboy.on('finish', async () => { if (requestDropped) return - boundLogger.info( + req.log.info( { crtMemUsageMB: process.memoryUsage().heapUsed / 1024 / 1024 }, @@ -219,7 +217,7 @@ module.exports = (app) => { }) busboy.on('error', async (err) => { - boundLogger.info(`Upload error: ${err}`) + req.log.info(`Upload error: ${err}`) if (!requestDropped) res.status(400).end('Upload request error. The server logs have more details') requestDropped = true diff --git a/packages/server/modules/emails/rest/index.js b/packages/server/modules/emails/rest/index.js index ad7bb8078..2e3953866 100644 --- a/packages/server/modules/emails/rest/index.js +++ b/packages/server/modules/emails/rest/index.js @@ -1,4 +1,3 @@ -const { authLogger } = require('@/logging/logging') const { EmailVerificationFinalizationError } = require('@/modules/emails/errors') const { finalizeEmailVerification @@ -6,7 +5,6 @@ const { module.exports = (app) => { app.get('/auth/verifyemail', async (req, res) => { - const boundLogger = authLogger.child({ endpoint: '/auth/verifyemail' }) try { await finalizeEmailVerification(req.query.t) return res.redirect('/?emailverifiedstatus=true') @@ -15,7 +13,7 @@ module.exports = (app) => { error instanceof EmailVerificationFinalizationError ? error.message : 'Email verification unexpectedly failed' - boundLogger.info({ err: error }, 'Email verification failed.') + req.log.info({ err: error }, 'Email verification failed.') return res.redirect(`/?emailverifiederror=${msg}`) } }) diff --git a/packages/server/modules/fileuploads/index.js b/packages/server/modules/fileuploads/index.js index 88796de6e..8ab4a3a35 100644 --- a/packages/server/modules/fileuploads/index.js +++ b/packages/server/modules/fileuploads/index.js @@ -5,7 +5,7 @@ const { saveUploadFile } = require('./services/fileuploads') const request = require('request') const { streamWritePermissions } = require('@/modules/shared/authz') const { authMiddlewareCreator } = require('@/modules/shared/middleware') -const { moduleLogger, logger } = require('@/logging/logging') +const { moduleLogger } = require('@/logging/logging') const saveFileUploads = async ({ userId, streamId, branchName, uploadResults }) => { await Promise.all( @@ -35,8 +35,7 @@ exports.init = async (app) => { '/api/file/:fileType/:streamId/:branchName?', authMiddlewareCreator(streamWritePermissions), async (req, res) => { - const boundLogger = logger.child({ - endpoint: '/api/file/:fileType/:streamId/:branchName?', + req.log = req.log.child({ streamId: req.params.streamId, userId: req.context.userId, branchName: req.params.branchName ?? 'main' @@ -46,7 +45,7 @@ exports.init = async (app) => { `${process.env.CANONICAL_URL}/api/stream/${req.params.streamId}/blob`, async (err, response, body) => { if (err) { - boundLogger.error(err) + res.log.error(err, 'Error while uploading blob.') res.status(500).send(err.message) return } diff --git a/packages/server/modules/pwdreset/rest/index.ts b/packages/server/modules/pwdreset/rest/index.ts index 0883322fc..cb75fa0b6 100644 --- a/packages/server/modules/pwdreset/rest/index.ts +++ b/packages/server/modules/pwdreset/rest/index.ts @@ -1,4 +1,3 @@ -import { logger } from '@/logging/logging' import { finalizePasswordReset } from '@/modules/pwdreset/services/finalize' import { requestPasswordRecovery } from '@/modules/pwdreset/services/request' import { ensureError } from '@/modules/shared/helpers/errorHelper' @@ -7,28 +6,26 @@ import { Express } from 'express' export default function (app: Express) { // sends a password recovery email. app.post('/auth/pwdreset/request', async (req, res) => { - const boundLogger = logger.child({ endpoint: 'auth/pwdreset/request' }) try { const email = req.body.email await requestPasswordRecovery(email) return res.status(200).send('Password reset email sent.') } catch (e: unknown) { - boundLogger.info({ err: e }, 'Error while requesting password recovery.') + req.log.info({ err: e }, 'Error while requesting password recovery.') res.status(400).send(ensureError(e).message) } }) // Finalizes password recovery. app.post('/auth/pwdreset/finalize', async (req, res) => { - const boundLogger = logger.child({ endpoint: 'auth/pwdreset/finalize' }) try { if (!req.body.tokenId || !req.body.password) throw new Error('Invalid request.') await finalizePasswordReset(req.body.tokenId, req.body.password) return res.status(200).send('Password reset. Please log in.') } catch (e: unknown) { - boundLogger.info({ err: e }, 'Error while finalizing password recovery.') + req.log.info({ err: e }, 'Error while finalizing password recovery.') res.status(400).send(ensureError(e).message) } })