Security Hotfix (#1415)

* Updated ngx-extended-pdf-viewer to 14.5.2 + misc security vuln

* Hooked up remove from want to read AND fixed a bug in the logic that was removing everything BUT what was passed.

Allow for bookmarks to have date info for better ordering.

* Implemented a quick way to set darkneses level on manga reader for when nightlight just isn't dark enough

* Added Japanese Series name support in the Parser

* Updated our security file with our Huntr.

* Fixed a security vulnerability where through the API, an unauthorized user could delete/modify reading lists that did not belong to them.

Fixed a bug where when creating a reading list with the name of another users, the API would throw an exception (but reading list would still get created)

* Ensure all reading list apis are authorized

* Ensured all APIs require authentication, except those that explicitly don't. All APIs are default requiring Authentication.

Fixed a security vulnerability which would allow a user to take over an admin account.

* Fixed a bug where cover-upload would accept filenames that were not expected.

* Explicitly check that a user has access to the pdf file before we serve it back.

* Enabled lock out when invalid user auth occurs. After 5 invalid auths, the user account will be locked out for 10 mins.
This commit is contained in:
Joseph Milazzo 2022-08-08 15:47:37 -05:00 committed by GitHub
parent 331e0d0ca9
commit 88b5ebeb69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1988 additions and 358 deletions

View file

@ -290,6 +290,12 @@ export class ActionFactoryService {
title: 'Add to Want To Read',
callback: this.dummyCallback,
requiresAdmin: false
},
{
action: Action.RemoveFromWantToReadList,
title: 'Remove from Want To Read',
callback: this.dummyCallback,
requiresAdmin: false
}
];

View file

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Injectable } from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { ReplaySubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Action, ActionFactoryService, ActionItem } from '../_services/action-factory.service';
@ -23,6 +23,7 @@ export class BulkSelectionService {
private selectedCards: { [key: string]: {[key: number]: boolean} } = {};
private dataSourceMax: { [key: string]: number} = {};
public isShiftDown: boolean = false;
private activeRoute: string = '';
private actionsSource = new ReplaySubject<ActionItem<any>[]>(1);
public actions$ = this.actionsSource.asObservable();
@ -33,14 +34,16 @@ export class BulkSelectionService {
*/
public selections$ = this.selectionsSource.asObservable();
constructor(private router: Router, private actionFactory: ActionFactoryService) {
constructor(private router: Router, private actionFactory: ActionFactoryService, private route: ActivatedRoute) {
router.events
.pipe(filter(event => event instanceof NavigationStart))
.subscribe((event) => {
this.deselectAll();
this.dataSourceMax = {};
this.prevIndex = 0;
this.activeRoute = this.router.url;
});
}
handleCardSelection(dataSource: DataSource, index: number, maxIndex: number, wasSelected: boolean) {
@ -143,7 +146,14 @@ export class BulkSelectionService {
// else returns volume/chapter items
const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection, Action.Delete, Action.AddToWantToReadList, Action.RemoveFromWantToReadList];
if (Object.keys(this.selectedCards).filter(item => item === 'series').length > 0) {
return this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action));
let actions = this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action));
if (this.activeRoute.startsWith('/want-to-read')) {
const removeFromWantToRead = {...actions[0]};
removeFromWantToRead.action = Action.RemoveFromWantToReadList;
removeFromWantToRead.title = 'Remove from Want to Read';
actions.push(removeFromWantToRead);
}
return actions;
}
if (Object.keys(this.selectedCards).filter(item => item === 'bookmark').length > 0) {

View file

@ -117,7 +117,6 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges,
// }
// this.hasResumedJumpKey = true;
// });
console.log(this.noDataTemplate);
}
ngOnChanges(): void {

View file

@ -33,9 +33,10 @@
</div>
</ng-container>
<div (click)="toggleMenu()" class="reading-area" [ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
<div (click)="toggleMenu()" class="reading-area"
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
<ng-container *ngIf="readerMode !== ReaderMode.Webtoon; else webtoon">
<div class="image-container" [ngClass]="{'d-none': !renderWithCanvas }">
<div class="image-container" [ngClass]="{'d-none': !renderWithCanvas }" [style.filter]="'brightness(' + generalSettingsForm.get('darkness')?.value + '%)'">
<canvas #content class="{{getFittingOptionClass()}}"
ondragstart="return false;" onselectstart="return false;">
</canvas>
@ -64,7 +65,8 @@
<div class="image-container {{getFittingOptionClass()}}" [ngClass]="{'d-none': renderWithCanvas, 'center-double': ShouldRenderDoublePage,
'fit-to-width-double-offset' : FittingOption === FITTING_OPTION.WIDTH && ShouldRenderDoublePage,
'fit-to-height-double-offset': FittingOption === FITTING_OPTION.HEIGHT && ShouldRenderDoublePage,
'original-double-offset' : FittingOption === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage}">
'original-double-offset' : FittingOption === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage}"
[style.filter]="'brightness(' + generalSettingsForm.get('darkness')?.value + '%)' | safeStyle">
<img #image [src]="canvasImage.src" id="image-1"
class="{{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}">
@ -214,6 +216,14 @@
</div>
</div>
</div>
<div class="row mb-2">
<div class="col-md-6 col-sm-12">
<label for="darkness" class="form-label range-label">Darkess</label>
<input type="range" class="form-range" id="darkness"
min="10" max="100" step="1" formControlName="darkness">
<span class="range-text">{{generalSettingsForm.get('darkness')?.value + '%'}}</span>
</div>
</div>
</form>
</div>
</div>

View file

@ -480,11 +480,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
autoCloseMenu: this.autoCloseMenu,
pageSplitOption: this.pageSplitOption,
fittingOption: this.translateScalingOption(this.scalingOption),
layoutMode: this.layoutMode
layoutMode: this.layoutMode,
darkness: 100
});
this.updateForm();
this.generalSettingsForm.get('darkness')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
console.log('brightness: ', val);
//this.cdRef.markForCheck();
});
this.generalSettingsForm.get('layoutMode')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
const changeOccurred = parseInt(val, 10) !== this.layoutMode;

View file

@ -9,6 +9,7 @@ import { NgxSliderModule } from '@angular-slider/ngx-slider';
import { InfiniteScrollerComponent } from './infinite-scroller/infinite-scroller.component';
import { ReaderSharedModule } from '../reader-shared/reader-shared.module';
import { FullscreenIconPipe } from './fullscreen-icon.pipe';
import { PipeModule } from '../pipe/pipe.module';
@NgModule({
declarations: [
@ -20,6 +21,7 @@ import { FullscreenIconPipe } from './fullscreen-icon.pipe';
CommonModule,
MangaReaderRoutingModule,
ReactiveFormsModule,
PipeModule,
NgbDropdownModule,
NgxSliderModule,

View file

@ -13,6 +13,7 @@ import { AgeRatingPipe } from './age-rating.pipe';
import { MangaFormatPipe } from './manga-format.pipe';
import { MangaFormatIconPipe } from './manga-format-icon.pipe';
import { LibraryTypePipe } from './library-type.pipe';
import { SafeStylePipe } from './safe-style.pipe';
@ -30,7 +31,8 @@ import { LibraryTypePipe } from './library-type.pipe';
AgeRatingPipe,
MangaFormatPipe,
MangaFormatIconPipe,
LibraryTypePipe
LibraryTypePipe,
SafeStylePipe
],
imports: [
CommonModule,
@ -48,7 +50,8 @@ import { LibraryTypePipe } from './library-type.pipe';
AgeRatingPipe,
MangaFormatPipe,
MangaFormatIconPipe,
LibraryTypePipe
LibraryTypePipe,
SafeStylePipe
]
})
export class PipeModule { }

View file

@ -0,0 +1,16 @@
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
@Pipe({
name: 'safeStyle'
})
export class SafeStylePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer){
}
transform(style: string) {
return this.sanitizer.bypassSecurityTrustStyle(style);
}
}

View file

@ -49,7 +49,6 @@ export class SideNavComponent implements OnInit, OnDestroy {
}
});
});
}
ngOnInit(): void {

View file

@ -54,6 +54,7 @@ export class WantToReadComponent implements OnInit, OnDestroy {
case Action.RemoveFromWantToReadList:
this.actionService.removeMultipleSeriesFromWantToReadList(selectedSeries.map(s => s.id), () => {
this.bulkSelectionService.deselectAll();
this.loadPage();
this.cdRef.markForCheck();
});
break;