From b37d4ec8c89b68ad4ddd8f703b7b54b09eb4b3b1 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 27 Dec 2020 18:57:02 +0100 Subject: [PATCH] Improving notification and CSRF error logging --- src/backend/middlewares/RenderingMWs.ts | 2 +- src/backend/model/NotifocationManager.ts | 29 +++++++++++++---- src/backend/routes/ErrorRouter.ts | 9 +++++- src/common/entities/Error.ts | 11 ++++++- src/common/entities/NotificationDTO.ts | 5 +++ .../app/model/notification.service.ts | 32 ++++++++++++++++--- .../app/ui/admin/admin.component.html | 12 ++++--- .../app/ui/frame/frame.component.html | 8 ++--- 8 files changed, 87 insertions(+), 21 deletions(-) diff --git a/src/backend/middlewares/RenderingMWs.ts b/src/backend/middlewares/RenderingMWs.ts index 8d68fecd..1a2b3a39 100644 --- a/src/backend/middlewares/RenderingMWs.ts +++ b/src/backend/middlewares/RenderingMWs.ts @@ -105,7 +105,7 @@ export class RenderingMWs { const message = new Message(err, null); return res.json(message); } - NotificationManager.error('Unknown server error', err); + NotificationManager.error('Unknown server error', err, req); return next(err); } diff --git a/src/backend/model/NotifocationManager.ts b/src/backend/model/NotifocationManager.ts index 4826d3ab..0f5b4a0a 100644 --- a/src/backend/model/NotifocationManager.ts +++ b/src/backend/model/NotifocationManager.ts @@ -1,4 +1,5 @@ import {NotificationDTO, NotificationType} from '../../common/entities/NotificationDTO'; +import {Request} from 'express'; export class NotificationManager { public static notifications: NotificationDTO[] = []; @@ -11,19 +12,35 @@ export class NotificationManager { ]; - public static error(message: string, details?: any) { - NotificationManager.notifications.push({ + public static error(message: string, details?: any, req?: Request) { + const noti: NotificationDTO = { type: NotificationType.error, message: message, details: details - }); + }; + if (req) { + noti.request = { + method: req.method, + url: req.url, + statusCode: req.statusCode + }; + } + NotificationManager.notifications.push(noti); } - public static warning(message: string, details?: any) { - NotificationManager.notifications.push({ + public static warning(message: string, details?: any, req?: Request) { + const noti: NotificationDTO = { type: NotificationType.warning, message: message, details: details - }); + }; + if (req) { + noti.request = { + method: req.method, + url: req.url, + statusCode: req.statusCode + }; + } + NotificationManager.notifications.push(noti); } } diff --git a/src/backend/routes/ErrorRouter.ts b/src/backend/routes/ErrorRouter.ts index 652333d8..d4a72c43 100644 --- a/src/backend/routes/ErrorRouter.ts +++ b/src/backend/routes/ErrorRouter.ts @@ -24,11 +24,18 @@ export class ErrorRouter { res.status(401); return next(new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Invalid token')); } + if (err.name === 'ForbiddenError' && err.code === 'EBADCSRFTOKEN') { + // jwt authentication error + res.status(401); + return next(new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Invalid CSRF token', err, req)); + } + + console.log(err); // Flush out the stack to the console Logger.error('Unexpected error:'); console.error(err); - return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'Unknown server side error', err)); + return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'Unknown server side error', err, req)); }, RenderingMWs.renderError ); diff --git a/src/common/entities/Error.ts b/src/common/entities/Error.ts index 95535a39..7f2fe0c4 100644 --- a/src/common/entities/Error.ts +++ b/src/common/entities/Error.ts @@ -1,3 +1,5 @@ +import {Request} from 'express'; + export enum ErrorCodes { NOT_AUTHENTICATED = 1, ALREADY_AUTHENTICATED = 2, @@ -25,9 +27,16 @@ export enum ErrorCodes { export class ErrorDTO { public detailsStr: string; + public request: { + method: string, url: string + } = {method: '', url: ''}; - constructor(public code: ErrorCodes, public message?: string, public details?: any) { + constructor(public code: ErrorCodes, public message?: string, public details?: any, req?: Request) { this.detailsStr = (this.details ? this.details.toString() : '') || ErrorCodes[code]; + this.request = { + method: req.method, + url: req.url + }; } toString(): string { diff --git a/src/common/entities/NotificationDTO.ts b/src/common/entities/NotificationDTO.ts index 2395021c..f8b30f98 100644 --- a/src/common/entities/NotificationDTO.ts +++ b/src/common/entities/NotificationDTO.ts @@ -6,4 +6,9 @@ export interface NotificationDTO { type: NotificationType; message: string; details?: any; + request?: { + method: string, + url: string, + statusCode: number + }; } diff --git a/src/frontend/app/model/notification.service.ts b/src/frontend/app/model/notification.service.ts index f50288cf..45b8fdb3 100644 --- a/src/frontend/app/model/notification.service.ts +++ b/src/frontend/app/model/notification.service.ts @@ -6,6 +6,10 @@ import {NotificationDTO, NotificationType} from '../../../common/entities/Notifi import {UserDTO, UserRoles} from '../../../common/entities/UserDTO'; import {I18n} from '@ngx-translate/i18n-polyfill'; +export interface CountedNotificationDTO extends NotificationDTO { + count: number; +} + @Injectable() export class NotificationService { @@ -13,7 +17,8 @@ export class NotificationService { positionClass: 'toast-top-center', animate: 'flyLeft' }; - notifications: NotificationDTO[] = []; + countedNotifications: CountedNotificationDTO[] = []; + numberOfNotifications = 0; lastUser: UserDTO = null; constructor(private _toastr: ToastrService, @@ -36,11 +41,30 @@ export class NotificationService { return this._toastr; } + groupNotifications(notifications: NotificationDTO[]) { + const groups: { [key: string]: { notification: NotificationDTO, count: number } } = {}; + notifications.forEach(n => { + let key = n.message; + if (n.details) { + key += JSON.stringify(n.details); + } + groups[key] = groups[key] || {notification: n, count: 0}; + groups[key].count++; + }); + this.numberOfNotifications = notifications.length; + this.countedNotifications = []; + for (const key of Object.keys(groups)) { + (groups[key].notification as CountedNotificationDTO).count = groups[key].count; + this.countedNotifications.push(groups[key].notification as CountedNotificationDTO); + } + + } + async getServerNotifications() { try { - this.notifications = (await this._networkService.getJson('/notifications')) || []; - this.notifications.forEach((noti) => { - let msg = noti.message; + this.groupNotifications((await this._networkService.getJson('/notifications')) || []); + this.countedNotifications.forEach((noti) => { + let msg = '(' + noti.count + ') ' + noti.message; if (noti.details) { msg += ' Details: ' + JSON.stringify(noti.details); } diff --git a/src/frontend/app/ui/admin/admin.component.html b/src/frontend/app/ui/admin/admin.component.html index 9565c2c7..fe6fef19 100644 --- a/src/frontend/app/ui/admin/admin.component.html +++ b/src/frontend/app/ui/admin/admin.component.html @@ -12,17 +12,21 @@
-
+
Server notifications
- +
@@ -40,7 +44,7 @@ class="version" href="https://github.com/bpatrik/pigallery2/releases"> App version: {{'v'+((settingsService.settings | async).Server.Environment.appVersion || '----')}} + i18n>App version: {{'v' + ((settingsService.settings | async).Server.Environment.appVersion || '----')}}
diff --git a/src/frontend/app/ui/frame/frame.component.html b/src/frontend/app/ui/frame/frame.component.html index ce656e54..c58fb515 100644 --- a/src/frontend/app/ui/frame/frame.component.html +++ b/src/frontend/app/ui/frame/frame.component.html @@ -38,8 +38,8 @@ type="button" class="btn btn-dark dropdown-toggle" aria-controls="dropdown-alignment"> - {{notificationService.notifications.length}} + {{notificationService.numberOfNotifications}}