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:
Mahi
2026-02-08 02:10:09 -04:00
parent 80566bf0a2
commit 2c10cd5662
51 changed files with 6094 additions and 656 deletions

View File

@@ -1,4 +1,5 @@
import { Component, OnInit, inject, signal } from '@angular/core';
import { Component, OnInit, OnDestroy, inject, signal, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { MatCardModule } from '@angular/material/card';
@@ -151,12 +152,14 @@ import { ApprovalResponseDto } from '../../../api/models';
`,
],
})
export class PendingListComponent implements OnInit {
export class PendingListComponent implements OnInit, OnDestroy {
private readonly approvalService = inject(ApprovalService);
private readonly notification = inject(NotificationService);
private readonly dialog = inject(MatDialog);
private readonly destroyRef = inject(DestroyRef);
readonly loading = signal(true);
readonly hasError = signal(false);
readonly approvals = signal<ApprovalResponseDto[]>([]);
readonly totalItems = signal(0);
readonly pageSize = signal(10);
@@ -170,15 +173,19 @@ export class PendingListComponent implements OnInit {
loadApprovals(): void {
this.loading.set(true);
this.hasError.set(false);
this.approvalService
.getPendingApprovals(this.pageIndex() + 1, this.pageSize())
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: (response) => {
this.approvals.set(response.data);
this.totalItems.set(response.total);
this.approvals.set(response?.data ?? []);
this.totalItems.set(response?.total ?? 0);
this.loading.set(false);
},
error: () => {
this.hasError.set(true);
this.loading.set(false);
},
});
@@ -194,15 +201,28 @@ export class PendingListComponent implements OnInit {
approval: ApprovalResponseDto,
action: 'approve' | 'reject' | 'changes'
): void {
if (!approval) return;
const dialogRef = this.dialog.open(ApprovalActionComponent, {
data: { approval, action },
width: '500px',
});
dialogRef.afterClosed().subscribe((result) => {
if (result) {
this.loadApprovals();
}
});
dialogRef.afterClosed()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((result) => {
if (result) {
this.loadApprovals();
}
});
}
ngOnDestroy(): void {
// Cleanup handled by DestroyRef/takeUntilDestroyed
}
/** Retry loading data - clears error state */
retryLoad(): void {
this.loadApprovals();
}
}