init commit

This commit is contained in:
David Melendez
2026-01-14 22:32:13 +01:00
parent 29b2e1438c
commit 00cb087b68
84 changed files with 29665 additions and 1 deletions

View File

@@ -0,0 +1,249 @@
/**
* Device Fingerprinting Service
* Professional Angular Resume Builder - Browser Fingerprinting System
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { map, catchError, tap } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import {
DeviceFingerprint,
DeviceVerificationRequest,
DeviceVerificationResponse,
TwoFactorInitiationRequest,
TwoFactorInitiationResponse,
TwoFactorVerificationRequest
} from '../models/auth.model';
/**
* Service for generating device fingerprints and managing device trust
*/
@Injectable({
providedIn: 'root'
})
export class DeviceFingerprintService {
private readonly apiUrl = `${environment.apiUrl}/device-fingerprint`;
// State management for 2FA process
private currentTwoFactorSession = new BehaviorSubject<TwoFactorInitiationResponse | null>(null);
public readonly currentTwoFactorSession$ = this.currentTwoFactorSession.asObservable();
constructor(private http: HttpClient) {}
/**
* Generate browser fingerprint from available browser APIs
*/
generateFingerprint(): DeviceFingerprint {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let canvasFingerprint = '';
if (ctx) {
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Device fingerprint test 🔒', 2, 2);
canvasFingerprint = canvas.toDataURL().slice(-50);
}
// Collect available fonts (simplified approach)
const testFonts = ['Arial', 'Helvetica', 'Times', 'Georgia', 'Verdana', 'Courier'];
const availableFonts = testFonts.filter(font => this.isFontAvailable(font));
const fontsHash = this.hashString(availableFonts.join(','));
const fingerprint: DeviceFingerprint = {
userAgent: navigator.userAgent,
acceptLanguage: navigator.language || (navigator as any).userLanguage || '',
screenResolution: `${screen.width}x${screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
platform: navigator.platform,
cookieEnabled: navigator.cookieEnabled,
doNotTrack: navigator.doNotTrack === '1',
fontsHash: fontsHash,
canvasFingerprint: canvasFingerprint,
webglFingerprint: this.getWebGLFingerprint()
};
return fingerprint;
} catch (error) {
console.error('Error generating fingerprint:', error);
// Return minimal fingerprint on error
return {
userAgent: navigator.userAgent,
acceptLanguage: navigator.language || '',
screenResolution: `${screen.width}x${screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
platform: navigator.platform,
cookieEnabled: navigator.cookieEnabled,
doNotTrack: false
};
}
}
/**
* Verify device trust status with backend
*/
verifyDevice(userId: number): Observable<DeviceVerificationResponse> {
const fingerprint = this.generateFingerprint();
const request: DeviceVerificationRequest = {
userId,
fingerprint
};
return this.http.post<DeviceVerificationResponse>(`${this.apiUrl}/verify`, request)
.pipe(
catchError(error => {
console.error('Device verification failed:', error);
return throwError(() => error);
})
);
}
/**
* Initiate two-factor authentication for device registration
*/
initiateTwoFactor(userId: number, deviceName?: string): Observable<TwoFactorInitiationResponse> {
const request: TwoFactorInitiationRequest = {
userId,
method: 'email' // Default method for demo mode
};
return this.http.post<TwoFactorInitiationResponse>(`${this.apiUrl}/two-factor/initiate`, request)
.pipe(
tap(response => {
// Store the 2FA session
this.currentTwoFactorSession.next(response);
}),
catchError(error => {
console.error('2FA initiation failed:', error);
return throwError(() => error);
})
);
}
/**
* Verify two-factor authentication code
*/
verifyTwoFactor(code: string, verificationId: string, userId: number, tempToken?: string): Observable<any> {
const request: TwoFactorVerificationRequest = {
code,
verificationId,
userId,
tempToken // Include temp token if provided
};
return this.http.post<any>(`${this.apiUrl}/two-factor/verify`, request)
.pipe(
tap(response => {
// Clear the 2FA session on success
if (response.success) {
this.currentTwoFactorSession.next(null);
}
}),
catchError(error => {
console.error('2FA verification failed:', error);
return throwError(() => error);
})
);
}
/**
* Clear current 2FA session
*/
clearTwoFactorSession(): void {
this.currentTwoFactorSession.next(null);
}
/**
* Check if a font is available by measuring text width
*/
private isFontAvailable(fontName: string): boolean {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return false;
const baselineText = 'abcdefghijklmnopqrstuvwxyz';
ctx.font = '32px monospace';
const baselineWidth = ctx.measureText(baselineText).width;
ctx.font = `32px ${fontName}, monospace`;
const testWidth = ctx.measureText(baselineText).width;
return baselineWidth !== testWidth;
}
/**
* Get WebGL fingerprint for additional uniqueness
*/
private getWebGLFingerprint(): string {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') as WebGLRenderingContext;
if (!gl) return '';
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
const vendor = gl.getParameter((debugInfo as any).UNMASKED_VENDOR_WEBGL);
const renderer = gl.getParameter((debugInfo as any).UNMASKED_RENDERER_WEBGL);
return this.hashString(`${vendor}|${renderer}`).slice(0, 16);
}
return '';
} catch (error) {
return '';
}
}
/**
* Generate device name based on browser and platform info
*/
private getDeviceName(): string {
const userAgent = navigator.userAgent;
let deviceName = 'Unknown Device';
if (userAgent.includes('Windows')) {
deviceName = 'Windows Device';
} else if (userAgent.includes('Mac')) {
deviceName = 'Mac Device';
} else if (userAgent.includes('Linux')) {
deviceName = 'Linux Device';
} else if (userAgent.includes('Android')) {
deviceName = 'Android Device';
} else if (userAgent.includes('iOS') || userAgent.includes('iPhone') || userAgent.includes('iPad')) {
deviceName = 'iOS Device';
}
// Add browser info
if (userAgent.includes('Chrome')) {
deviceName += ' (Chrome)';
} else if (userAgent.includes('Firefox')) {
deviceName += ' (Firefox)';
} else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
deviceName += ' (Safari)';
} else if (userAgent.includes('Edge')) {
deviceName += ' (Edge)';
}
return deviceName;
}
/**
* Simple string hashing function
*/
private hashString(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash).toString(16);
}
}