clean code, bugfixes & XSS security

This commit is contained in:
Chneemann 2024-05-25 18:04:35 +02:00
parent b79ad59d2e
commit 6086d41474
12 changed files with 68 additions and 14 deletions

View file

@ -37,8 +37,8 @@
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kb",
"maximumError": "8kb"
"maximumWarning": "6kb",
"maximumError": "12kb"
}
],
"outputHashing": "all"

View file

@ -254,6 +254,7 @@
#subtask="ngModel"
placeholder="{{ 'addTask.subtaskFormField' | translate }}"
[(ngModel)]="subtaskValue"
(input)="updateSubtaskValue()"
autocomplete="off"
/>
@if (subtaskValue) {

View file

@ -9,6 +9,7 @@ import { OverlayService } from '../../services/overlay.service';
import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form-btn.component';
import { ActivatedRoute, Route, Router } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { SharedService } from '../../services/shared.service';
@Component({
selector: 'app-add-task',
@ -42,6 +43,7 @@ export class AddTaskComponent implements OnInit {
constructor(
public firebaseService: FirebaseService,
private overlayService: OverlayService,
private sharedService: SharedService,
private route: ActivatedRoute,
private router: Router
) {}
@ -142,6 +144,7 @@ export class AddTaskComponent implements OnInit {
}
updateSearchInput() {
this.searchValue = this.sharedService.replaceXSSChars(this.searchValue);
if (this.searchValue) {
this.searchInput = this.searchValue.toLowerCase().length > 0;
} else {
@ -150,6 +153,10 @@ export class AddTaskComponent implements OnInit {
return this.searchInput;
}
updateSubtaskValue() {
this.subtaskValue = this.sharedService.replaceXSSChars(this.subtaskValue);
}
receiveAssigned(assigned: string[]) {
this.taskData.assigned = assigned;
}
@ -177,6 +184,7 @@ export class AddTaskComponent implements OnInit {
}
saveTaskData() {
this.checkCrossSiteScripting();
localStorage.setItem('taskData', JSON.stringify(this.taskData));
}
@ -244,6 +252,18 @@ export class AddTaskComponent implements OnInit {
}
}
checkCrossSiteScripting() {
this.taskData.title = this.sharedService.replaceXSSChars(
this.taskData.title
);
this.taskData.description = this.sharedService.replaceXSSChars(
this.taskData.description
);
this.taskData.subtasksTitle = this.taskData.subtasksTitle.map((title) =>
this.sharedService.replaceXSSChars(title)
);
}
deleteTaskData(overlayData: string) {
this.firebaseService.deleteTask(overlayData);
this.closeOverlay();

View file

@ -92,6 +92,7 @@ export class BoardComponent {
}
updateSearchInput() {
this.searchValue = this.sharedService.replaceXSSChars(this.searchValue);
if (this.searchValue) {
this.searchInput = this.searchValue.toLowerCase().length > 0;
} else {

View file

@ -1,4 +1,10 @@
import { Component, HostListener, Input } from '@angular/core';
import {
Component,
EventEmitter,
HostListener,
Input,
Output,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { ContactsComponent } from '../contacts.component';
import { Router } from '@angular/router';
@ -23,6 +29,7 @@ import { ContactEditNewComponent } from '../contact-edit-new/contact-edit-new.co
})
export class ContactDetailComponent {
@Input() currentUserId: string | undefined;
@Output() closeContactEmitter = new EventEmitter<boolean>();
constructor(
private router: Router,
@ -33,6 +40,7 @@ export class ContactDetailComponent {
closeUserDetails() {
this.sharedService.currentUserId = '';
this.closeContactEmitter.emit();
}
checkUserData(userId: string) {

View file

@ -19,14 +19,17 @@
: ('contactDialogForm.firstName' | translate)
}}"
[(ngModel)]="contactData.firstName"
pattern="[A-Za-z]+"
autocomplete="off"
required
/>
@if (sharedService.isContactViewMedia) {
<div class="error-msg">
@if (!firstName.valid && firstName.touched) {
@if (firstName.hasError('pattern') && firstName.touched) {
<p>{{ "contactDialogForm.invalidPattern" | translate }}</p>
} @else { @if (!firstName.valid && firstName.touched) {
<p>{{ "contactDialogForm.invalidFirstName" | translate }}</p>
}
}}
</div>
}
<input
@ -41,26 +44,34 @@
: ('contactDialogForm.lastName' | translate)
}}"
[(ngModel)]="contactData.lastName"
pattern="[A-Za-z]+"
autocomplete="off"
required
/>
</div>
@if (sharedService.isContactViewMedia) {
<div class="error-msg">
@if (!lastName.valid && lastName.touched) {
@if (lastName.hasError('pattern') && lastName.touched) {
<p>{{ "contactDialogForm.invalidPattern" | translate }}</p>
} @else { @if (!lastName.valid && lastName.touched) {
<p>{{ "contactDialogForm.invalidLastName" | translate }}</p>
}
}}
</div>
} @if (!sharedService.isContactViewMedia) {
<div class="error-msg">
@if ((!firstName.valid && firstName.touched) && (!lastName.valid &&
lastName.touched)) {
@if (firstName.hasError('pattern') || lastName.hasError('pattern') &&
firstName.touched) {
<p>{{ "contactDialogForm.invalidPattern" | translate }}</p>
} @else { @if ((!firstName.valid && firstName.touched) && (!lastName.valid
&& lastName.touched)) {
<p>{{ "contactDialogForm.invalidName" | translate }}</p>
} @else { @if (!firstName.valid && firstName.touched) {
} @else { @if (!firstName.valid && firstName.touched &&
!firstName.hasError('pattern')) {
<p>{{ "contactDialogForm.invalidFirstName" | translate }}</p>
} @else { @if (!lastName.valid && lastName.touched) {
} @else { @if (!lastName.valid && lastName.touched &&
!lastName.hasError('pattern')) {
<p>{{ "contactDialogForm.invalidLastName" | translate }}</p>
} } }
} } } }
</div>
}
<input

View file

@ -121,7 +121,6 @@ export class ContactFormComponent implements OnInit, OnChanges {
}
checkIfUserEmailIsValid(emailValue: string) {
const channelNameLenght = emailValue.length;
const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
if (emailRegex.test(emailValue)) {
return true;

View file

@ -69,6 +69,7 @@
'd-none': !showAllUsers && sharedService.currentUserId == undefined
}"
[currentUserId]="sharedService.currentUserId"
(closeContactEmitter)="closeContactEmitter()"
></app-contact-detail>
</div>
</section>

View file

@ -18,7 +18,7 @@ export class ContactsComponent {
allUsers: User[] = [];
usersFirstLetter: string[] = [];
usersByFirstLetter: { [key: string]: string[] } = {};
showAllUsers!: boolean;
showAllUsers: boolean = false;
constructor(
public firebaseService: FirebaseService,
@ -32,6 +32,7 @@ export class ContactsComponent {
showUserId(userId: string = '') {
this.sharedService.currentUserId = userId;
this.onResize();
}
@HostListener('window:resize', ['$event'])
@ -43,6 +44,10 @@ export class ContactsComponent {
}
}
closeContactEmitter() {
this.onResize();
}
loadAllUserWithoutGuest(): User[] {
return this.firebaseService
.getAllUsers()

View file

@ -69,4 +69,10 @@ export class SharedService {
const b = bigint & 255;
return { r, g, b };
}
// SECURITY
replaceXSSChars(input: string) {
return input.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
}

View file

@ -76,6 +76,7 @@
"btnCancel": "Abbrechen",
"btnDelete": "Löschen",
"btnSave": "Sichern",
"invalidPattern": "Es sind nur Buchstaben erlaubt!",
"invalidFirstName": "Ein Vorname ist erforderlich!",
"invalidLastName": "Ein Nachname ist erforderlich!",
"invalidName": "Ein Vorname & Nachname ist erforderlich!",

View file

@ -76,6 +76,7 @@
"btnCancel": "Cancel",
"btnDelete": "Delete",
"btnSave": "Save",
"invalidPattern": "Only letters are allowed!",
"invalidFirstName": "A first name is required!",
"invalidLastName": "A last name is required!",
"invalidName": "A first name & last name is required!",