Security hardening and edge case fixes across frontend
Security Improvements: - Add input sanitization utilities (XSS, SQL injection prevention) - Add token validation with JWT structure verification - Add secure form validators with pattern enforcement - Implement proper token storage with encryption support Service Hardening: - Add timeout (30s) and retry logic (3 attempts) to all API calls - Add UUID validation for all ID parameters - Add null/undefined checks with defensive defaults - Proper error propagation with typed error handling Component Fixes: - Fix memory leaks with takeUntilDestroyed pattern - Remove mock data fallbacks in error handlers - Add proper loading/error state management - Add form field length limits and validation Files affected: 51 (6000+ lines added for security)
This commit is contained in:
@@ -15,6 +15,13 @@ import { Clipboard } from '@angular/cdk/clipboard';
|
||||
import { DocumentService } from '../services/document.service';
|
||||
import { NotificationService } from '../../../core/services/notification.service';
|
||||
import { DocumentType, DocumentResponseDto } from '../../../api/models';
|
||||
import {
|
||||
INPUT_LIMITS,
|
||||
noScriptValidator,
|
||||
noNullBytesValidator,
|
||||
normalizeWhitespace,
|
||||
} from '../../../shared/utils/form-validators';
|
||||
import { createSubmitDebounce } from '../../../shared/utils/form-utils';
|
||||
|
||||
export interface DocumentUploadDialogData {
|
||||
requestId: string;
|
||||
@@ -145,7 +152,15 @@ type UploadState = 'idle' | 'uploading' | 'processing' | 'complete' | 'error';
|
||||
formControlName="description"
|
||||
rows="2"
|
||||
placeholder="Add any additional notes about this document"
|
||||
[maxlength]="limits.DESCRIPTION_MAX"
|
||||
></textarea>
|
||||
@if (form.controls.description.hasError('maxlength')) {
|
||||
<mat-error>Maximum {{ limits.DESCRIPTION_MAX }} characters allowed</mat-error>
|
||||
}
|
||||
@if (form.controls.description.hasError('dangerousContent')) {
|
||||
<mat-error>Invalid characters detected</mat-error>
|
||||
}
|
||||
<mat-hint align="end">{{ form.controls.description.value?.length || 0 }}/{{ limits.DESCRIPTION_MAX }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Drop Zone -->
|
||||
@@ -819,6 +834,12 @@ export class DocumentUploadComponent {
|
||||
private readonly data: DocumentUploadDialogData = inject(MAT_DIALOG_DATA);
|
||||
private readonly clipboard = inject(Clipboard);
|
||||
|
||||
/** Debounce handler to prevent double-click submissions */
|
||||
private readonly submitDebounce = createSubmitDebounce(500);
|
||||
|
||||
/** Input limits exposed for template binding */
|
||||
readonly limits = INPUT_LIMITS;
|
||||
|
||||
// State signals
|
||||
readonly uploadState = signal<UploadState>('idle');
|
||||
readonly selectedFile = signal<File | null>(null);
|
||||
@@ -846,7 +867,11 @@ export class DocumentUploadComponent {
|
||||
|
||||
readonly form = this.fb.nonNullable.group({
|
||||
docType: ['' as DocumentType, [Validators.required]],
|
||||
description: [''],
|
||||
description: ['', [
|
||||
Validators.maxLength(INPUT_LIMITS.DESCRIPTION_MAX),
|
||||
noScriptValidator(),
|
||||
noNullBytesValidator(),
|
||||
]],
|
||||
});
|
||||
|
||||
canUpload(): boolean {
|
||||
@@ -981,12 +1006,21 @@ export class DocumentUploadComponent {
|
||||
}
|
||||
|
||||
onUpload(): void {
|
||||
// Debounce to prevent double-click submissions
|
||||
this.submitDebounce(() => this.performUpload());
|
||||
}
|
||||
|
||||
private performUpload(): void {
|
||||
const file = this.selectedFile();
|
||||
if (!file || this.form.invalid) return;
|
||||
if (!file || this.form.invalid || this.uploadState() === 'uploading') return;
|
||||
|
||||
this.uploadState.set('uploading');
|
||||
this.uploadProgress.set(0);
|
||||
const { docType, description } = this.form.getRawValue();
|
||||
const rawValues = this.form.getRawValue();
|
||||
|
||||
// Normalize description
|
||||
const docType = rawValues.docType;
|
||||
const description = normalizeWhitespace(rawValues.description);
|
||||
|
||||
this.documentService.uploadDocumentWithProgress(this.data.requestId, file, docType, description).subscribe({
|
||||
next: (progress) => {
|
||||
|
||||
Reference in New Issue
Block a user