1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

Improving notification and CSRF error logging

This commit is contained in:
Patrik J. Braun 2020-12-27 18:57:02 +01:00
parent 3b82b71203
commit b37d4ec8c8
8 changed files with 87 additions and 21 deletions

View File

@ -105,7 +105,7 @@ export class RenderingMWs {
const message = new Message<any>(err, null); const message = new Message<any>(err, null);
return res.json(message); return res.json(message);
} }
NotificationManager.error('Unknown server error', err); NotificationManager.error('Unknown server error', err, req);
return next(err); return next(err);
} }

View File

@ -1,4 +1,5 @@
import {NotificationDTO, NotificationType} from '../../common/entities/NotificationDTO'; import {NotificationDTO, NotificationType} from '../../common/entities/NotificationDTO';
import {Request} from 'express';
export class NotificationManager { export class NotificationManager {
public static notifications: NotificationDTO[] = []; public static notifications: NotificationDTO[] = [];
@ -11,19 +12,35 @@ export class NotificationManager {
]; ];
public static error(message: string, details?: any) { public static error(message: string, details?: any, req?: Request) {
NotificationManager.notifications.push({ const noti: NotificationDTO = {
type: NotificationType.error, type: NotificationType.error,
message: message, message: message,
details: details 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) { public static warning(message: string, details?: any, req?: Request) {
NotificationManager.notifications.push({ const noti: NotificationDTO = {
type: NotificationType.warning, type: NotificationType.warning,
message: message, message: message,
details: details details: details
}); };
if (req) {
noti.request = {
method: req.method,
url: req.url,
statusCode: req.statusCode
};
}
NotificationManager.notifications.push(noti);
} }
} }

View File

@ -24,11 +24,18 @@ export class ErrorRouter {
res.status(401); res.status(401);
return next(new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Invalid token')); 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 // Flush out the stack to the console
Logger.error('Unexpected error:'); Logger.error('Unexpected error:');
console.error(err); 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 RenderingMWs.renderError
); );

View File

@ -1,3 +1,5 @@
import {Request} from 'express';
export enum ErrorCodes { export enum ErrorCodes {
NOT_AUTHENTICATED = 1, NOT_AUTHENTICATED = 1,
ALREADY_AUTHENTICATED = 2, ALREADY_AUTHENTICATED = 2,
@ -25,9 +27,16 @@ export enum ErrorCodes {
export class ErrorDTO { export class ErrorDTO {
public detailsStr: string; 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.detailsStr = (this.details ? this.details.toString() : '') || ErrorCodes[code];
this.request = {
method: req.method,
url: req.url
};
} }
toString(): string { toString(): string {

View File

@ -6,4 +6,9 @@ export interface NotificationDTO {
type: NotificationType; type: NotificationType;
message: string; message: string;
details?: any; details?: any;
request?: {
method: string,
url: string,
statusCode: number
};
} }

View File

@ -6,6 +6,10 @@ import {NotificationDTO, NotificationType} from '../../../common/entities/Notifi
import {UserDTO, UserRoles} from '../../../common/entities/UserDTO'; import {UserDTO, UserRoles} from '../../../common/entities/UserDTO';
import {I18n} from '@ngx-translate/i18n-polyfill'; import {I18n} from '@ngx-translate/i18n-polyfill';
export interface CountedNotificationDTO extends NotificationDTO {
count: number;
}
@Injectable() @Injectable()
export class NotificationService { export class NotificationService {
@ -13,7 +17,8 @@ export class NotificationService {
positionClass: 'toast-top-center', positionClass: 'toast-top-center',
animate: 'flyLeft' animate: 'flyLeft'
}; };
notifications: NotificationDTO[] = []; countedNotifications: CountedNotificationDTO[] = [];
numberOfNotifications = 0;
lastUser: UserDTO = null; lastUser: UserDTO = null;
constructor(private _toastr: ToastrService, constructor(private _toastr: ToastrService,
@ -36,11 +41,30 @@ export class NotificationService {
return this._toastr; 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() { async getServerNotifications() {
try { try {
this.notifications = (await this._networkService.getJson<NotificationDTO[]>('/notifications')) || []; this.groupNotifications((await this._networkService.getJson<NotificationDTO[]>('/notifications')) || []);
this.notifications.forEach((noti) => { this.countedNotifications.forEach((noti) => {
let msg = noti.message; let msg = '(' + noti.count + ') ' + noti.message;
if (noti.details) { if (noti.details) {
msg += ' Details: ' + JSON.stringify(noti.details); msg += ' Details: ' + JSON.stringify(noti.details);
} }

View File

@ -12,17 +12,21 @@
<app-frame> <app-frame>
<div body class="container-fluid"> <div body class="container-fluid">
<div class="card mb-4" *ngIf="notificationService.notifications.length>0"> <div class="card mb-4" *ngIf="notificationService.countedNotifications.length>0">
<h5 class="card-header" i18n> <h5 class="card-header" i18n>
Server notifications Server notifications
</h5> </h5>
<div class="card-body"> <div class="card-body">
<ng-container *ngFor="let notification of notificationService.notifications"> <ng-container *ngFor="let notification of notificationService.countedNotifications">
<div class="alert alert-{{getCss(notification.type)}}" role="alert"> <div class="alert alert-{{getCss(notification.type)}}" role="alert">
{{notification.message}} ({{notification.count}}) {{notification.message}}
<br *ngIf="notification.details"/> <br *ngIf="notification.details"/>
{{notification.details | json}} {{notification.details | json}}
<ng-container *ngIf="notification.request">
<br/>
Request: "{{notification.request.method}}", url: "{{notification.request.url}}", status code: "{{notification.request.statusCode}}"
</ng-container>
</div> </div>
</ng-container> </ng-container>
</div> </div>
@ -40,7 +44,7 @@
class="version" class="version"
href="https://github.com/bpatrik/pigallery2/releases"> href="https://github.com/bpatrik/pigallery2/releases">
<span <span
i18n>App version:</span>&nbsp;<span>{{'v'+((settingsService.settings | async).Server.Environment.appVersion || '----')}}</span> i18n>App version:</span>&nbsp;<span>{{'v' + ((settingsService.settings | async).Server.Environment.appVersion || '----')}}</span>
</a> </a>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -38,8 +38,8 @@
type="button" class="btn btn-dark dropdown-toggle" type="button" class="btn btn-dark dropdown-toggle"
aria-controls="dropdown-alignment"> aria-controls="dropdown-alignment">
<span class="oi oi-menu"></span> <span class="oi oi-menu"></span>
<span *ngIf="isAdmin() && notificationService.notifications.length>0" <span *ngIf="isAdmin() && notificationService.numberOfNotifications>0"
class="navbar-badge badge badge-warning">{{notificationService.notifications.length}}</span> class="navbar-badge badge badge-warning">{{notificationService.numberOfNotifications}}</span>
</button> </button>
<ul id="dropdown-alignment" *dropdownMenu <ul id="dropdown-alignment" *dropdownMenu
class="dropdown-menu dropdown-menu-right" class="dropdown-menu dropdown-menu-right"
@ -54,8 +54,8 @@
<li role="menuitem" *ngIf="isAdmin()"> <li role="menuitem" *ngIf="isAdmin()">
<a class="dropdown-item" [routerLink]="['/admin']"> <a class="dropdown-item" [routerLink]="['/admin']">
<span class="oi oi-wrench"></span> <span class="oi oi-wrench"></span>
<span *ngIf="notificationService.notifications.length>0" <span *ngIf="notificationService.numberOfNotifications>0"
class="badge">{{notificationService.notifications.length}}</span> class="badge">{{notificationService.numberOfNotifications}}</span>
<ng-container i18n>Settings</ng-container> <ng-container i18n>Settings</ng-container>
</a> </a>
</li> </li>