ng-secure-fetch 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ng-secure-fetch might be problematic. Click here for more details.

package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prathamesh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,410 @@
1
+ # ng-secure-fetch
2
+
3
+ Zero-dependency Angular library providing **RSA-2048-OAEP + AES-256-GCM hybrid encryption** with automatic HTTP request/response encryption and sessionStorage caching.
4
+
5
+ **Browser-only library** using the Web Crypto API - requires browser environment.
6
+
7
+ ## Features
8
+
9
+ - Hybrid encryption: RSA-2048-OAEP + AES-256-GCM
10
+ - Automatic HTTP interceptors (transparent encryption/decryption)
11
+ - SessionStorage caching (95% faster page refresh)
12
+ - Public key pinning (SHA-256 hash verification)
13
+ - Advanced pattern matching with dynamic segments
14
+ - Per-request unique AES keys
15
+ - Zero dependencies (Web Crypto API)
16
+ - Browser environment only
17
+
18
+ ## Compatibility
19
+
20
+ - **Angular**: 17+ (compatible with Angular 17 and all future versions)
21
+ - **Environment**: Browser only (uses `window.crypto` Web Crypto API)
22
+ - **Node.js**: Not supported in standard Node.js environments without polyfills
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install ng-secure-fetch
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Import and Configure
33
+
34
+ ```typescript
35
+ import { ApplicationConfig } from '@angular/core';
36
+ import { provideNgSecureFetch } from 'ng-secure-fetch';
37
+ import { environment } from './environments/environment';
38
+
39
+ // In your app.config.ts (standalone) or app.module.ts
40
+ export const appConfig: ApplicationConfig = {
41
+ providers: [
42
+ provideNgSecureFetch({
43
+ enableEncryption: true,
44
+ publicKeyEndpoint: environment.apiUrl + '/crypto/init',
45
+ expectedPublicKeyHash: 'your-sha256-hash-here',
46
+ encryptDecryptForEndpoints: ['*'],
47
+ skipEncryptDecryptForEndpoints: ['/public/*'],
48
+ publicKeyHeaderName: 'X-Client-Init',
49
+ aesKeyHeaderName: 'X-Request-Context',
50
+ ivHeaderName: 'X-Client-Ref',
51
+ enableDebugLogging: true,
52
+ publicKeyFetchRetries: 3
53
+ })
54
+ ]
55
+ };
56
+ ```
57
+
58
+ ### 2. Get Your Public Key Hash
59
+
60
+ Enable debug logging temporarily to get your public key hash:
61
+
62
+ ```typescript
63
+ enableDebugLogging: true
64
+ ```
65
+
66
+ Check console output:
67
+ ```
68
+ [ng-secure-fetch] Public key hash: abc123def456...your-actual-hash...
69
+ ```
70
+
71
+ Copy the hash to `expectedPublicKeyHash` in your configuration.
72
+
73
+ ### 3. Done!
74
+
75
+ All HTTP requests matching your endpoint patterns are now automatically encrypted.
76
+
77
+ ## Configuration
78
+
79
+ ```typescript
80
+ interface SecurityConfig {
81
+ enableEncryption?: boolean; // Master switch (default: true)
82
+ publicKeyEndpoint?: string; // API endpoint for public key
83
+ expectedPublicKeyHash?: string; // SHA-256 hash for MITM prevention
84
+ encryptDecryptForEndpoints?: string[]; // Endpoint patterns (default: ['*'])
85
+ skipEncryptDecryptForEndpoints?: string[]; // Exclude patterns (higher priority)
86
+ publicKeyHeaderName?: string; // Custom public key header (default: 'X-Client-Init')
87
+ aesKeyHeaderName?: string; // Custom AES key header (default: 'X-Request-Context')
88
+ ivHeaderName?: string; // Custom IV header (default: 'X-Client-Ref')
89
+ enableDebugLogging?: boolean; // Console logs (default: false)
90
+ publicKeyFetchRetries?: number; // Retry attempts (default: 3)
91
+ }
92
+ ```
93
+
94
+ ## Endpoint Pattern Matching
95
+
96
+ Supports advanced pattern matching with dynamic segments and exclusion patterns:
97
+
98
+ | Pattern | Description | Examples |
99
+ |---------|-------------|----------|
100
+ | `['*']` | Match all endpoints | Any URL |
101
+ | `['/api/*']` | Glob pattern | `/api/users`, `/api/products` |
102
+ | `['/exact/path']` | Exact substring | URLs containing `/exact/path` |
103
+ | `['v1/{ignore}/users']` | Dynamic segment | `v1/MT1/users`, `v1/MT2/users` |
104
+ | `['/api/{ignore}/items/{ignore}']` | Multiple dynamic | `/api/v1/items/123` |
105
+
106
+ ### Dynamic Segments with `{ignore}`
107
+
108
+ ```typescript
109
+ // Pattern: 'v1/universal/business/applicationform/{ignore}/pocketInsurance'
110
+ // Matches:
111
+ // ✅ v1/universal/business/applicationform/MT1/pocketInsurance
112
+ // ✅ v1/universal/business/applicationform/MT2/pocketInsurance
113
+ // ✅ v1/universal/business/applicationform/ABC123/pocketInsurance
114
+ // ❌ v1/universal/business/applicationform/pocketInsurance (missing segment)
115
+
116
+ encryptDecryptForEndpoints: [
117
+ 'v1/universal/business/applicationform/{ignore}/pocketInsurance'
118
+ ]
119
+ ```
120
+
121
+ The `{ignore}` placeholder matches any non-slash characters, perfect for dynamic IDs or version numbers.
122
+
123
+ ### Skip Patterns (Exclusion with Higher Priority)
124
+
125
+ Use `skipEncryptDecryptForEndpoints` to exclude specific endpoints even if they match `encryptDecryptForEndpoints`:
126
+
127
+ ```typescript
128
+ provideNgSecureFetch({
129
+ enableEncryption: true,
130
+
131
+ // Encrypt all API endpoints
132
+ encryptDecryptForEndpoints: ['*'],
133
+
134
+ // But skip these specific endpoints (higher priority)
135
+ skipEncryptDecryptForEndpoints: [
136
+ '/v1/public/*', // Skip all public APIs
137
+ '/v1/health/check', // Skip health check
138
+ 'v1/user/{ignore}/profile/image' // Skip profile image endpoints
139
+ ],
140
+
141
+ // ...other config
142
+ })
143
+ ```
144
+
145
+ **Priority:**
146
+ 1. `skipEncryptDecryptForEndpoints` is checked **first** (if match → skip encryption)
147
+ 2. `encryptDecryptForEndpoints` is checked **second** (if match → encrypt)
148
+ 3. If neither matches → no encryption
149
+
150
+ **Example Scenarios:**
151
+ ```typescript
152
+ // Scenario 1: Encrypt all except public endpoints
153
+ encryptDecryptForEndpoints: ['*']
154
+ skipEncryptDecryptForEndpoints: ['/v1/public/*']
155
+ // Result: /v1/public/data → not encrypted
156
+ // /v1/private/data → encrypted
157
+
158
+ // Scenario 2: Encrypt specific endpoints but skip one
159
+ encryptDecryptForEndpoints: ['v1/api/{ignore}/process']
160
+ skipEncryptDecryptForEndpoints: ['v1/api/health/process']
161
+ // Result: v1/api/MT1/process → encrypted
162
+ // v1/api/health/process → not encrypted (skip has priority)
163
+
164
+ // Scenario 3: Fine-grained control
165
+ encryptDecryptForEndpoints: ['/v1/business/*', '/v2/universal/*']
166
+ skipEncryptDecryptForEndpoints: ['/v1/business/public/*']
167
+ // Result: /v1/business/public/data → not encrypted (skip wins)
168
+ // /v1/business/private/data → encrypted
169
+ // /v2/universal/anything → encrypted
170
+ ```
171
+
172
+ ## How It Works
173
+
174
+ ### Initialization
175
+ 1. Check sessionStorage for cached PEM key
176
+ 2. If found: validate hash, import key, continue (5-10ms)
177
+ 3. If not found: fetch from API, verify hash, cache, continue (200-300ms)
178
+
179
+ ### Request Encryption (POST/PUT/PATCH)
180
+ 1. Generate random AES-256-GCM key + IV
181
+ 2. Encrypt payload with AES-GCM
182
+ 3. Encrypt AES key + IV with RSA public key
183
+ 4. Send encrypted data in body, keys in headers
184
+
185
+ ### Response Decryption
186
+ 1. Extract AES key + IV from response headers
187
+ 2. Decrypt response body with AES-GCM
188
+ 3. Return decrypted data to component
189
+
190
+ ## Backend Requirements
191
+
192
+ ### 1. Public Key Endpoint (GET /crypto/init)
193
+
194
+ ```
195
+ HTTP/1.1 200 OK
196
+ X-Client-Init: -----BEGIN PUBLIC KEY-----
197
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
198
+ -----END PUBLIC KEY-----
199
+ Access-Control-Expose-Headers: X-Client-Init
200
+ ```
201
+
202
+ **CORS is critical:** Without `Access-Control-Expose-Headers`, Angular cannot access the header even if visible in Network tab.
203
+
204
+ ### 2. Decrypt Requests
205
+
206
+ - Extract `X-Request-Context` (RSA-encrypted AES key)
207
+ - Extract `X-Client-Ref` (RSA-encrypted IV)
208
+ - Decrypt with RSA private key
209
+ - Decrypt request body with AES-256-GCM
210
+ - Remove padding (split by `||`)
211
+
212
+ ### 3. Encrypt Responses
213
+
214
+ ```
215
+ HTTP/1.1 200 OK
216
+ X-Request-Context: <base64-raw-aes-key>
217
+ X-Client-Ref: <base64-raw-iv>
218
+ Access-Control-Expose-Headers: X-Request-Context, X-Client-Ref
219
+
220
+ <Base64 encrypted data>
221
+ ```
222
+
223
+ **Important:** Response headers contain raw AES key/IV (not RSA-encrypted), just base64-encoded.
224
+
225
+ ## Custom Header Names
226
+
227
+ By default, the library expects the public key in the `X-Client-Init` header. You can customize this:
228
+
229
+ ```typescript
230
+ // app.config.ts or app.module.ts
231
+ provideNgSecureFetch({
232
+ enableEncryption: true,
233
+ publicKeyEndpoint: environment.apiUrl + '/crypto/init',
234
+ publicKeyHeaderName: 'X-Custom-Public-Key', // Custom header name
235
+ expectedPublicKeyHash: 'your-sha256-hash-here',
236
+ encryptDecryptForEndpoints: ['*'],
237
+ enableDebugLogging: false,
238
+ publicKeyFetchRetries: 3
239
+ })
240
+ ```
241
+
242
+ **Backend Response:**
243
+ ```
244
+ HTTP/1.1 200 OK
245
+ X-Custom-Public-Key: -----BEGIN PUBLIC KEY-----
246
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
247
+ -----END PUBLIC KEY-----
248
+ Access-Control-Expose-Headers: X-Custom-Public-Key
249
+ ```
250
+
251
+ **Use Cases:**
252
+ - Corporate proxy requirements (some proxies block certain headers)
253
+ - Compliance with company header naming conventions
254
+ - Avoiding conflicts with existing headers
255
+ - Custom security policies
256
+
257
+ ### Custom AES Key and IV Headers
258
+
259
+ You can also customize the headers used for AES key and IV exchange:
260
+
261
+ ```typescript
262
+ // app.config.ts or app.module.ts
263
+ provideNgSecureFetch({
264
+ enableEncryption: true,
265
+ publicKeyEndpoint: environment.apiUrl + '/crypto/init',
266
+ publicKeyHeaderName: 'X-Custom-Public-Key',
267
+ aesKeyHeaderName: 'X-Custom-AES-Key', // Custom AES key header
268
+ ivHeaderName: 'X-Custom-IV', // Custom IV header
269
+ expectedPublicKeyHash: 'your-sha256-hash-here',
270
+ encryptDecryptForEndpoints: ['*'],
271
+ enableDebugLogging: false,
272
+ publicKeyFetchRetries: 3
273
+ })
274
+ ```
275
+
276
+ **Request Headers (Frontend sends):**
277
+ ```http
278
+ POST /api/data
279
+ X-Custom-AES-Key: <encrypted-aes-key-base64>
280
+ X-Custom-IV: <encrypted-iv-base64>
281
+ Content-Type: text/plain
282
+
283
+ <encrypted-payload>
284
+ ```
285
+
286
+ **Response Headers (Backend sends):**
287
+ ```http
288
+ HTTP/1.1 200 OK
289
+ X-Custom-AES-Key: <raw-aes-key-base64>
290
+ X-Custom-IV: <raw-iv-base64>
291
+ Access-Control-Expose-Headers: X-Custom-AES-Key, X-Custom-IV
292
+
293
+ <encrypted-response>
294
+ ```
295
+
296
+ **Important:**
297
+ - Request headers contain RSA-encrypted AES key and IV (frontend → backend)
298
+ - Response headers contain raw (base64) AES key and IV (backend → frontend)
299
+ - All custom headers must be included in `Access-Control-Expose-Headers`
300
+
301
+ ## Troubleshooting
302
+
303
+ | Issue | Solution |
304
+ |-------|----------|
305
+ | Hash mismatch | Check console for actual hash, update `expectedPublicKeyHash` |
306
+ | Header not found | Backend must add: `Access-Control-Expose-Headers: X-Client-Init` (or custom header name) |
307
+ | Custom header not found | Set `publicKeyHeaderName` in config and ensure backend sends that header |
308
+ | Custom AES/IV headers not found | Set `aesKeyHeaderName` and `ivHeaderName` in config; backend must send custom headers and expose them in CORS |
309
+ | App won't load | Check network tab for `/crypto/init`, verify endpoint reachable |
310
+ | Not encrypting | Check URL matches endpoint patterns, try `['*']` to test |
311
+ | Decryption fails | Verify backend sends AES key and IV headers (default: `X-Request-Context`, `X-Client-Ref` or your custom names) |
312
+ | Pattern not matching | Enable debug logging to see pattern matching details |
313
+
314
+ ## Key Improvements
315
+
316
+ ### SessionStorage Caching
317
+ - First load: ~200-300ms (API call + RSA import)
318
+ - Page refresh: ~5-10ms (cached key) - **95% faster**
319
+ - Cache cleared on: browser close, hash mismatch, validation failure
320
+
321
+ ### Centralized Pattern Matching
322
+ - `matchesPattern()` method in `CryptoService`
323
+ - Shared between encryption and decryption interceptors
324
+ - Supports dynamic segments with `{ignore}` placeholder
325
+
326
+ ### CORS-Aware Error Messages
327
+ - Clear error messages when headers are blocked by CORS
328
+ - Distinguishes between "header not sent" vs "header blocked by browser"
329
+
330
+ ## Architecture
331
+
332
+ ### File Structure
333
+
334
+ ```
335
+ ng-secure-fetch/
336
+ ├── config/ng-secure-fetch.config.ts # provideNgSecureFetch()
337
+ ├── initializers/ng-secure-fetch.initializer.ts # APP_INITIALIZER
338
+ ├── interceptors/
339
+ │ ├── encryption.interceptor.ts # Request encryption
340
+ │ └── decryption.interceptor.ts # Response decryption
341
+ ├── models/
342
+ │ ├── ng-secure-fetch-config.interface.ts # Config interface
343
+ │ └── encrypted-payload.interface.ts # Payload structure
344
+ └── services/crypto.service.ts # Core crypto + pattern matching
345
+ ```
346
+
347
+ ### Key Concepts
348
+
349
+ - **RSA-OAEP**: Asymmetric encryption for key exchange (slow, secure)
350
+ - **AES-256-GCM**: Symmetric encryption for data (fast, secure, authenticated)
351
+ - **Hybrid Encryption**: Use RSA to encrypt AES key, use AES to encrypt data
352
+ - **Web Crypto API**: Browser-native crypto (no external libraries)
353
+ - **Browser Only**: Requires `window.crypto` - not compatible with standard Node.js
354
+ - **APP_INITIALIZER**: Angular DI token to run code before app loads
355
+ - **HTTP Interceptors**: Middleware for HTTP requests/responses
356
+ - **SessionStorage**: Browser storage that persists across page refreshes (tab-scoped)
357
+
358
+ ## Changelog
359
+
360
+ ### v1.0.0 (Current)
361
+ - Renamed from "security" to "ng-secure-fetch"
362
+ - Updated function name from `provideSecurity()` to `provideNgSecureFetch()`
363
+ - Added npm publishing metadata
364
+ - Browser environment only (Web Crypto API)
365
+ - `skipEncryptDecryptForEndpoints` configuration (exclusion patterns with higher priority)
366
+ - Error response decryption (4xx, 5xx status codes)
367
+ - Enhanced error structure with `Object.assign()` for backward compatibility
368
+ - Updated `matchesPattern()` to support skip patterns
369
+ - Added debug logging for POST/PUT/PATCH request bodies
370
+ - Added dynamic segment support with `{ignore}` placeholder
371
+ - Moved `matchesPattern()` to `CryptoService` (DRY principle)
372
+ - Improved CORS error messages
373
+ - SessionStorage caching (95% faster refresh)
374
+ - Automatic HTTP interceptors
375
+ - Public key pinning
376
+
377
+ ## Building the Library
378
+
379
+ To build the library for distribution:
380
+
381
+ ```bash
382
+ ng build ng-secure-fetch
383
+ ```
384
+
385
+ Build artifacts will be in `dist/ng-secure-fetch/`.
386
+
387
+ ## Publishing to npm
388
+
389
+ ```bash
390
+ cd dist/ng-secure-fetch
391
+ npm publish
392
+ ```
393
+
394
+ ## Running Tests
395
+
396
+ ```bash
397
+ ng test ng-secure-fetch
398
+ ```
399
+
400
+ ## License
401
+
402
+ MIT
403
+
404
+ ## Contributing
405
+
406
+ Contributions are welcome! Please open an issue or submit a pull request.
407
+
408
+ ## Support
409
+
410
+ For issues and questions, please use the [GitHub Issues](https://github.com/yourusername/ng-secure-fetch/issues) page.
@@ -0,0 +1,557 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, signal, Injectable, makeEnvironmentProviders, APP_INITIALIZER } from '@angular/core';
3
+ import { HttpResponse, HttpErrorResponse, HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
4
+ import { from, switchMap, of, catchError, throwError, Observable, retry, timer, map } from 'rxjs';
5
+
6
+ /** Default header name for public key exchange */
7
+ const DEFAULT_PUBLIC_KEY_HEADER_NAME = 'X-Client-Init';
8
+ /** Default header name for AES key in request/response */
9
+ const DEFAULT_AES_KEY_HEADER_NAME = 'X-Request-Context';
10
+ /** Default header name for IV in request/response */
11
+ const DEFAULT_IV_HEADER_NAME = 'X-Client-Ref';
12
+ /** Injection token for security config */
13
+ const SECURITY_CONFIG = new InjectionToken('SecurityConfig');
14
+ /** Default configuration with placeholder values - replace with your actual values */
15
+ const DEFAULT_SECURITY_CONFIG = {
16
+ publicKeyEndpoint: 'https://your-api-domain.com/crypto/init',
17
+ expectedPublicKeyHash: 'your-sha256-hash-replace-this-with-actual-hash-from-console',
18
+ encryptDecryptForEndpoints: ['*'],
19
+ skipEncryptDecryptForEndpoints: [],
20
+ publicKeyHeaderName: DEFAULT_PUBLIC_KEY_HEADER_NAME,
21
+ aesKeyHeaderName: DEFAULT_AES_KEY_HEADER_NAME,
22
+ ivHeaderName: DEFAULT_IV_HEADER_NAME,
23
+ enableDebugLogging: false,
24
+ enableEncryption: true,
25
+ publicKeyFetchRetries: 3
26
+ };
27
+
28
+ class CryptoService {
29
+ config = inject(SECURITY_CONFIG);
30
+ publicKey = signal(null, ...(ngDevMode ? [{ debugName: "publicKey" }] : []));
31
+ STORAGE_KEY = 'ng_secure_fetch_public_key_pem';
32
+ /**
33
+ * Pattern matcher with support for dynamic segments:
34
+ * - '*' = matches all URLs
35
+ * - '/api/*' = matches URLs containing '/api/'
36
+ * - '/exact/path' = exact substring match
37
+ * - '/path/{ignore}/resource' = matches with dynamic segment (e.g., '/path/anything/resource')
38
+ * - Multiple {ignore} placeholders supported: '/api/{ignore}/items/{ignore}/details'
39
+ *
40
+ * @param url - The URL to check
41
+ * @param patterns - Array of patterns to match against
42
+ * @param skipPatterns - Optional array of patterns to exclude (higher priority)
43
+ * @returns true if URL matches patterns and doesn't match skipPatterns
44
+ */
45
+ matchesPattern(url, patterns, skipPatterns) {
46
+ // Check skip patterns first (higher priority)
47
+ if (skipPatterns && skipPatterns.length > 0) {
48
+ const shouldSkip = skipPatterns.some(pattern => {
49
+ if (pattern === '*')
50
+ return true;
51
+ if (pattern.endsWith('/*')) {
52
+ return url.includes(pattern.slice(0, -2));
53
+ }
54
+ if (pattern.includes('{ignore}')) {
55
+ let regexPattern = pattern
56
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
57
+ .replace(/\\{ignore\\}/g, '[^/]+');
58
+ const regex = new RegExp(regexPattern);
59
+ return regex.test(url);
60
+ }
61
+ return url.includes(pattern);
62
+ });
63
+ if (shouldSkip) {
64
+ this.config.enableDebugLogging && console.log(`[ng-secure-fetch] Skipped ${url}`);
65
+ return false;
66
+ }
67
+ }
68
+ // Check if URL matches any include pattern
69
+ return patterns.some(pattern => {
70
+ // Match all
71
+ if (pattern === '*')
72
+ return true;
73
+ // Glob pattern (e.g., '/api/*')
74
+ if (pattern.endsWith('/*')) {
75
+ return url.includes(pattern.slice(0, -2));
76
+ }
77
+ // Pattern with {ignore} placeholders for dynamic segments
78
+ if (pattern.includes('{ignore}')) {
79
+ // Convert pattern to regex:
80
+ // 'v1/universal/business/applicationform/{ignore}/pocketInsurance'
81
+ // becomes: v1\/universal\/business\/applicationform\/[^\/]+\/pocketInsurance
82
+ // Escape special regex characters except {ignore}
83
+ let regexPattern = pattern
84
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape special chars
85
+ .replace(/\\{ignore\\}/g, '[^/]+'); // Replace {ignore} with pattern to match any non-slash chars
86
+ // Create regex that checks if the pattern exists anywhere in the URL
87
+ const regex = new RegExp(regexPattern);
88
+ if (this.config.enableDebugLogging) {
89
+ console.log(`[ng-secure-fetch] Pattern "${pattern}" ${regex.test(url) ? 'matched' : 'failed'} for ${url}`);
90
+ }
91
+ return regex.test(url);
92
+ }
93
+ // Exact substring match
94
+ return url.includes(pattern);
95
+ });
96
+ }
97
+ /** Verify public key hash (SHA-256) to prevent MITM attacks */
98
+ async verifyPublicKeyHash(pemKey) {
99
+ const expectedHash = this.config.expectedPublicKeyHash || 'REPLACE_WITH_YOUR_PUBLIC_KEY_HASH';
100
+ try {
101
+ const encoder = new TextEncoder();
102
+ const data = encoder.encode(pemKey);
103
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
104
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
105
+ const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
106
+ this.config.enableDebugLogging && console.log(`[ng-secure-fetch] Public key hash: ${hashHex}`);
107
+ if (expectedHash === 'REPLACE_WITH_YOUR_PUBLIC_KEY_HASH') {
108
+ console.warn(`[ng-secure-fetch] Public key pinning not configured! Use hash: ${hashHex}`);
109
+ return false;
110
+ }
111
+ const matches = hashHex === expectedHash;
112
+ if (!matches) {
113
+ console.error(`[ng-secure-fetch] Hash mismatch! Expected: ${expectedHash.substring(0, 16)}..., Got: ${hashHex.substring(0, 16)}...`);
114
+ }
115
+ else {
116
+ this.config.enableDebugLogging && console.log('[ng-secure-fetch] Public key hash verified');
117
+ }
118
+ return matches;
119
+ }
120
+ catch (error) {
121
+ console.error('[ng-secure-fetch] Hash verification error:', error);
122
+ return false;
123
+ }
124
+ }
125
+ /** Set RSA public key for encryption and cache in sessionStorage */
126
+ setPublicKey(key, pemKey) {
127
+ this.publicKey.set(key);
128
+ if (pemKey) {
129
+ try {
130
+ sessionStorage.setItem(this.STORAGE_KEY, pemKey);
131
+ this.config.enableDebugLogging && console.log('[ng-secure-fetch] Public key cached');
132
+ }
133
+ catch (error) {
134
+ console.warn('[ng-secure-fetch] Cache storage failed:', error);
135
+ }
136
+ }
137
+ }
138
+ /** Get current RSA public key */
139
+ getPublicKey() {
140
+ return this.publicKey();
141
+ }
142
+ /** Get cached PEM key from sessionStorage */
143
+ getCachedPemKey() {
144
+ try {
145
+ return sessionStorage.getItem(this.STORAGE_KEY);
146
+ }
147
+ catch (error) {
148
+ console.warn('[ng-secure-fetch] Cache read failed:', error);
149
+ return null;
150
+ }
151
+ }
152
+ /** Clear cached public key from sessionStorage */
153
+ clearCachedPublicKey() {
154
+ try {
155
+ sessionStorage.removeItem(this.STORAGE_KEY);
156
+ this.config.enableDebugLogging && console.log('[ng-secure-fetch] Cache cleared');
157
+ }
158
+ catch (error) {
159
+ console.warn('[ng-secure-fetch] Cache clear failed:', error);
160
+ }
161
+ }
162
+ /** Generate random AES-256-GCM key */
163
+ async generateAESKey() {
164
+ return await window.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
165
+ }
166
+ /** Import AES key from raw bytes (one-time use from response header) */
167
+ async importAESKey(keyBytes) {
168
+ return await window.crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM', length: 256 }, false, ['decrypt']);
169
+ }
170
+ /** Generate random 12-byte IV for AES-GCM */
171
+ generateIV() {
172
+ return window.crypto.getRandomValues(new Uint8Array(12));
173
+ }
174
+ /** Generate random string for padding and obfuscation */
175
+ generateRandomString(minLength, maxLength) {
176
+ const length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
177
+ const randomBytes = window.crypto.getRandomValues(new Uint8Array(length));
178
+ return this.arrayBufferToBase64(randomBytes.buffer);
179
+ }
180
+ /** Encrypt data using AES-GCM */
181
+ async encryptWithAES(data, aesKey, iv) {
182
+ const encoder = new TextEncoder();
183
+ const dataBuffer = encoder.encode(data);
184
+ return await window.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, aesKey, dataBuffer);
185
+ }
186
+ /** Encrypt AES key with RSA-OAEP public key */
187
+ async encryptAESKeyWithRSA(aesKey, rsaPublicKey) {
188
+ const aesKeyBuffer = await window.crypto.subtle.exportKey('raw', aesKey);
189
+ return await window.crypto.subtle.encrypt({ name: 'RSA-OAEP' }, rsaPublicKey, aesKeyBuffer);
190
+ }
191
+ /** Encrypt IV with RSA-OAEP public key */
192
+ async encryptIVWithRSA(iv, rsaPublicKey) {
193
+ return await window.crypto.subtle.encrypt({ name: 'RSA-OAEP' }, rsaPublicKey, iv);
194
+ }
195
+ /** Convert ArrayBuffer to Base64 */
196
+ arrayBufferToBase64(buffer) {
197
+ const bytes = new Uint8Array(buffer);
198
+ let binary = '';
199
+ for (let i = 0; i < bytes.length; i++) {
200
+ binary += String.fromCharCode(bytes[i]);
201
+ }
202
+ return btoa(binary);
203
+ }
204
+ /** Convert Base64 to ArrayBuffer */
205
+ base64ToArrayBuffer(base64) {
206
+ const binary = atob(base64);
207
+ const bytes = new Uint8Array(binary.length);
208
+ for (let i = 0; i < binary.length; i++) {
209
+ bytes[i] = binary.charCodeAt(i);
210
+ }
211
+ return bytes.buffer;
212
+ }
213
+ /** Encrypt payload with AES-GCM + variable padding + RSA-encrypted keys */
214
+ async encryptPayload(payload) {
215
+ const rsaPublicKey = this.getPublicKey();
216
+ if (!rsaPublicKey)
217
+ return null;
218
+ const aesKey = await this.generateAESKey();
219
+ const iv = this.generateIV();
220
+ const payloadString = JSON.stringify(payload);
221
+ const padding = this.generateRandomString(16, 256);
222
+ const paddedData = `${payloadString}||${padding}`;
223
+ const [encryptedData, encryptedAesKey, encryptedIV] = await Promise.all([
224
+ this.encryptWithAES(paddedData, aesKey, iv),
225
+ this.encryptAESKeyWithRSA(aesKey, rsaPublicKey),
226
+ this.encryptIVWithRSA(iv, rsaPublicKey)
227
+ ]);
228
+ return {
229
+ encryptedData: this.arrayBufferToBase64(encryptedData),
230
+ encryptedAesKey: this.arrayBufferToBase64(encryptedAesKey),
231
+ iv: this.arrayBufferToBase64(encryptedIV)
232
+ };
233
+ }
234
+ /** Generate RSA-encrypted AES key and IV for GET requests (backend decrypts, uses for response) */
235
+ async generateEncryptedHeaders() {
236
+ const rsaPublicKey = this.getPublicKey();
237
+ if (!rsaPublicKey)
238
+ return null;
239
+ const aesKey = await this.generateAESKey();
240
+ const iv = this.generateIV();
241
+ const [encryptedAesKey, encryptedIV] = await Promise.all([
242
+ this.encryptAESKeyWithRSA(aesKey, rsaPublicKey),
243
+ this.encryptIVWithRSA(iv, rsaPublicKey)
244
+ ]);
245
+ const result = {
246
+ encryptedAesKey: this.arrayBufferToBase64(encryptedAesKey),
247
+ encryptedIV: this.arrayBufferToBase64(encryptedIV)
248
+ };
249
+ this.config.enableDebugLogging && console.log('[ng-secure-fetch] Generated encrypted headers');
250
+ return result;
251
+ }
252
+ /** Create obfuscated envelope with short field names (d=data, k=key, s=salt/IV) */
253
+ async createEncryptedEnvelope(originalPayload) {
254
+ const encryptedPayload = await this.encryptPayload(originalPayload);
255
+ if (!encryptedPayload)
256
+ return originalPayload;
257
+ return {
258
+ d: encryptedPayload.encryptedData,
259
+ k: encryptedPayload.encryptedAesKey,
260
+ s: encryptedPayload.iv
261
+ };
262
+ }
263
+ /** Decrypt response using raw AES key and IV from headers (sent over HTTPS) */
264
+ async decryptResponsePayload(encryptedData, aesKeyBase64, ivBase64) {
265
+ const encryptedBytes = this.base64ToArrayBuffer(encryptedData);
266
+ const aesKeyBytes = this.base64ToArrayBuffer(aesKeyBase64);
267
+ const ivBytes = this.base64ToArrayBuffer(ivBase64);
268
+ const aesKey = await this.importAESKey(aesKeyBytes);
269
+ const decryptedBuffer = await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv: ivBytes }, aesKey, encryptedBytes);
270
+ const decoder = new TextDecoder();
271
+ const decryptedWithPadding = decoder.decode(decryptedBuffer);
272
+ const [actualData] = decryptedWithPadding.split('||');
273
+ this.config.enableDebugLogging && console.log('[ng-secure-fetch] Response decrypted');
274
+ return JSON.parse(actualData);
275
+ }
276
+ /** Import RSA public key from PEM format to CryptoKey */
277
+ async importPublicKey(pemKey) {
278
+ const pemHeader = '-----BEGIN PUBLIC KEY-----';
279
+ const pemFooter = '-----END PUBLIC KEY-----';
280
+ const pemContents = pemKey.replace(pemHeader, '').replace(pemFooter, '').replace(/\s/g, '');
281
+ this.config.enableDebugLogging && console.log('[ng-secure-fetch] Importing PEM key');
282
+ const binaryDer = atob(pemContents);
283
+ const binaryDerArray = new Uint8Array(binaryDer.length);
284
+ for (let i = 0; i < binaryDer.length; i++) {
285
+ binaryDerArray[i] = binaryDer.charCodeAt(i);
286
+ }
287
+ const cryptoKey = await window.crypto.subtle.importKey('spki', binaryDerArray.buffer, { name: 'RSA-OAEP', hash: 'SHA-256' }, true, ['encrypt']);
288
+ this.config.enableDebugLogging && console.log('[ng-secure-fetch] Key imported successfully');
289
+ return cryptoKey;
290
+ }
291
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CryptoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
292
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CryptoService, providedIn: 'root' });
293
+ }
294
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CryptoService, decorators: [{
295
+ type: Injectable,
296
+ args: [{
297
+ providedIn: 'root'
298
+ }]
299
+ }] });
300
+
301
+ /** Encrypts requests: POST/PUT/PATCH body + headers, GET generates keys for response encryption */
302
+ const encryptionInterceptor = (req, next) => {
303
+ const cryptoService = inject(CryptoService);
304
+ const config = inject(SECURITY_CONFIG, { optional: true });
305
+ if (config?.enableEncryption === false)
306
+ return next(req);
307
+ const encryptPatterns = config?.encryptDecryptForEndpoints || ['*'];
308
+ const skipPatterns = config?.skipEncryptDecryptForEndpoints;
309
+ const shouldEncrypt = cryptoService.matchesPattern(req.url, encryptPatterns, skipPatterns);
310
+ if (!shouldEncrypt)
311
+ return next(req);
312
+ if (config?.publicKeyEndpoint && req.url === config.publicKeyEndpoint)
313
+ return next(req);
314
+ const aesKeyHeader = config?.aesKeyHeaderName || DEFAULT_AES_KEY_HEADER_NAME;
315
+ const ivHeader = config?.ivHeaderName || DEFAULT_IV_HEADER_NAME;
316
+ if (req.method === 'GET') {
317
+ return from(cryptoService.generateEncryptedHeaders()).pipe(switchMap((headers) => {
318
+ if (!headers)
319
+ return next(req);
320
+ const encryptedReq = req.clone({
321
+ setHeaders: {
322
+ [aesKeyHeader]: headers.encryptedAesKey,
323
+ [ivHeader]: headers.encryptedIV
324
+ }
325
+ });
326
+ return next(encryptedReq);
327
+ }));
328
+ }
329
+ if (!req.body || typeof req.body !== 'object')
330
+ return next(req);
331
+ if (req.method === 'DELETE' || req.method === 'HEAD' || req.method === 'OPTIONS')
332
+ return next(req);
333
+ config?.enableDebugLogging && console.log(`[ng-secure-fetch] Encrypting ${req.method} request`);
334
+ return from(cryptoService.createEncryptedEnvelope(req.body)).pipe(switchMap((encryptedBody) => {
335
+ const envelope = encryptedBody;
336
+ const encryptedReq = req.clone({
337
+ body: envelope['d'],
338
+ setHeaders: {
339
+ 'Content-Type': 'text/plain',
340
+ [aesKeyHeader]: envelope['k'],
341
+ [ivHeader]: envelope['s']
342
+ }
343
+ });
344
+ return next(encryptedReq);
345
+ }));
346
+ };
347
+
348
+ /** Decrypts responses using AES keys from headers (X-Request-Context, X-Client-Ref) */
349
+ const decryptionInterceptor = (req, next) => {
350
+ const cryptoService = inject(CryptoService);
351
+ const config = inject(SECURITY_CONFIG, { optional: true });
352
+ return next(req).pipe(switchMap((event) => {
353
+ if (!(event instanceof HttpResponse))
354
+ return of(event);
355
+ if (config?.enableEncryption === false)
356
+ return of(event);
357
+ const encryptPatterns = config?.encryptDecryptForEndpoints || ['*'];
358
+ const skipPatterns = config?.skipEncryptDecryptForEndpoints;
359
+ const shouldDecrypt = cryptoService.matchesPattern(req.url, encryptPatterns, skipPatterns);
360
+ if (!shouldDecrypt)
361
+ return of(event);
362
+ if (config?.publicKeyEndpoint && req.url === config.publicKeyEndpoint)
363
+ return of(event);
364
+ return decryptResponse(event, cryptoService, config);
365
+ }), catchError((error) => {
366
+ // Handle error responses (4xx, 5xx)
367
+ if (config?.enableEncryption === false)
368
+ return throwError(() => error);
369
+ const encryptPatterns = config?.encryptDecryptForEndpoints || ['*'];
370
+ const skipPatterns = config?.skipEncryptDecryptForEndpoints;
371
+ const shouldDecrypt = cryptoService.matchesPattern(req.url, encryptPatterns, skipPatterns);
372
+ if (!shouldDecrypt)
373
+ return throwError(() => error);
374
+ if (config?.publicKeyEndpoint && req.url === config.publicKeyEndpoint)
375
+ return throwError(() => error);
376
+ const aesKeyHeader = config?.aesKeyHeaderName || DEFAULT_AES_KEY_HEADER_NAME;
377
+ const ivHeader = config?.ivHeaderName || DEFAULT_IV_HEADER_NAME;
378
+ config?.enableDebugLogging && console.log('[ng-secure-fetch] Error response received, attempting decryption');
379
+ // Check if error has encryption headers
380
+ const aesKeyBase64 = error.headers?.get(aesKeyHeader) || error.headers?.get(aesKeyHeader.toLowerCase());
381
+ const ivBase64 = error.headers?.get(ivHeader) || error.headers?.get(ivHeader.toLowerCase());
382
+ if (!aesKeyBase64 || !ivBase64 || !error.error) {
383
+ config?.enableDebugLogging && console.warn('[ng-secure-fetch] Error response not encrypted');
384
+ return throwError(() => error);
385
+ }
386
+ // Decrypt error response
387
+ return from(cryptoService.decryptResponsePayload(typeof error.error === 'string' ? error.error : JSON.stringify(error.error), aesKeyBase64, ivBase64).then((decrypted) => {
388
+ if (decrypted) {
389
+ config?.enableDebugLogging && console.log('[ng-secure-fetch] Error response decrypted');
390
+ // Return decrypted error - spread decrypted data to top level for easier access
391
+ // This allows components to access error.errors[0] instead of error.error.errors[0]
392
+ const decryptedError = new HttpErrorResponse({
393
+ error: decrypted,
394
+ headers: error.headers,
395
+ status: error.status,
396
+ statusText: error.statusText,
397
+ url: error.url || undefined
398
+ });
399
+ // Merge decrypted properties to top level of error object
400
+ Object.assign(decryptedError, decrypted);
401
+ throw decryptedError;
402
+ }
403
+ config?.enableDebugLogging && console.warn('[ng-secure-fetch] Error decryption returned null');
404
+ throw error;
405
+ }, (decryptError) => {
406
+ config?.enableDebugLogging && console.error('[ng-secure-fetch] Failed to decrypt error response:', decryptError);
407
+ throw error;
408
+ }));
409
+ }));
410
+ };
411
+ /** Helper function to decrypt successful responses */
412
+ function decryptResponse(event, cryptoService, config) {
413
+ const aesKeyHeader = config?.aesKeyHeaderName || DEFAULT_AES_KEY_HEADER_NAME;
414
+ const ivHeader = config?.ivHeaderName || DEFAULT_IV_HEADER_NAME;
415
+ const aesKeyBase64 = event.headers.get(aesKeyHeader) || event.headers.get(aesKeyHeader.toLowerCase());
416
+ const ivBase64 = event.headers.get(ivHeader) || event.headers.get(ivHeader.toLowerCase());
417
+ if (!aesKeyBase64 || !ivBase64 || !event.body) {
418
+ config?.enableDebugLogging && event.body && console.warn('[ng-secure-fetch] Missing encryption headers');
419
+ return of(event);
420
+ }
421
+ config?.enableDebugLogging && console.log('[ng-secure-fetch] Decrypting response');
422
+ return from(cryptoService.decryptResponsePayload(event.body, aesKeyBase64, ivBase64)).pipe(switchMap((decrypted) => {
423
+ if (decrypted) {
424
+ config?.enableDebugLogging && console.log('[ng-secure-fetch] Response decrypted successfully');
425
+ return of(event.clone({ body: decrypted }));
426
+ }
427
+ config?.enableDebugLogging && console.warn('[ng-secure-fetch] Decryption returned null');
428
+ return of(event);
429
+ }));
430
+ }
431
+
432
+ /** APP_INITIALIZER: Fetches public key, verifies hash, blocks app until ready */
433
+ function initializeNgSecureFetchFactory() {
434
+ const http = inject(HttpClient);
435
+ const cryptoService = inject(CryptoService);
436
+ const config = inject(SECURITY_CONFIG);
437
+ return () => {
438
+ if (config.enableEncryption === false) {
439
+ config.enableDebugLogging && console.log('[ng-secure-fetch] Encryption disabled');
440
+ return of(void 0);
441
+ }
442
+ if (!config.publicKeyEndpoint) {
443
+ console.error('[ng-secure-fetch] publicKeyEndpoint required');
444
+ return throwError(() => new Error('publicKeyEndpoint required'));
445
+ }
446
+ // Check for cached public key in sessionStorage first
447
+ return new Observable((subscriber) => {
448
+ const cachedPemKey = cryptoService.getCachedPemKey();
449
+ if (cachedPemKey) {
450
+ config.enableDebugLogging && console.log('[ng-secure-fetch] Using cached key');
451
+ validateAndSetPublicKey(cryptoService, config, cachedPemKey).then(() => {
452
+ config.enableDebugLogging && console.log('[ng-secure-fetch] Cached key validated');
453
+ subscriber.next();
454
+ subscriber.complete();
455
+ }).catch(err => {
456
+ config.enableDebugLogging && console.warn('[ng-secure-fetch] Cache invalid, fetching fresh key');
457
+ cryptoService.clearCachedPublicKey();
458
+ fetchPublicKeyFromAPI(http, cryptoService, config).subscribe({
459
+ next: () => {
460
+ subscriber.next();
461
+ subscriber.complete();
462
+ },
463
+ error: (err) => subscriber.error(err)
464
+ });
465
+ });
466
+ }
467
+ else {
468
+ config.enableDebugLogging && console.log('[ng-secure-fetch] Fetching public key from API');
469
+ fetchPublicKeyFromAPI(http, cryptoService, config).subscribe({
470
+ next: () => {
471
+ subscriber.next();
472
+ subscriber.complete();
473
+ },
474
+ error: (err) => subscriber.error(err)
475
+ });
476
+ }
477
+ });
478
+ };
479
+ }
480
+ /** Validate PEM key hash and import it (reusable for both cached and API keys) */
481
+ async function validateAndSetPublicKey(cryptoService, config, pemKey) {
482
+ const isValid = await cryptoService.verifyPublicKeyHash(pemKey);
483
+ if (!isValid) {
484
+ throw new Error('Public key hash mismatch - possible MITM attack');
485
+ }
486
+ const cryptoKey = await cryptoService.importPublicKey(pemKey);
487
+ cryptoService.setPublicKey(cryptoKey, pemKey);
488
+ config.enableDebugLogging && console.log('[ng-secure-fetch] Public key imported & validated');
489
+ }
490
+ /** Helper function to fetch public key from API */
491
+ function fetchPublicKeyFromAPI(http, cryptoService, config) {
492
+ const retries = config.publicKeyFetchRetries || 3;
493
+ return http.get(config.publicKeyEndpoint, { observe: 'response' }).pipe(retry({
494
+ count: retries,
495
+ delay: (_error, retryCount) => {
496
+ config.enableDebugLogging && console.warn(`[ng-secure-fetch] Retry ${retryCount}/${retries}`);
497
+ return timer(Math.pow(2, retryCount - 1) * 1000);
498
+ }
499
+ }), switchMap((response) => {
500
+ const headerName = config.publicKeyHeaderName || DEFAULT_PUBLIC_KEY_HEADER_NAME;
501
+ const pemKey = response.headers.get(headerName) || response.headers.get(headerName.toLowerCase());
502
+ if (!pemKey) {
503
+ const availableHeaders = response.headers.keys().join(', ');
504
+ console.error(`[ng-secure-fetch] ${headerName} header missing. Available: ${availableHeaders}`);
505
+ throw new Error(`${headerName} header not found. Set Access-Control-Expose-Headers`);
506
+ }
507
+ config.enableDebugLogging && console.log(`[ng-secure-fetch] Public key received from ${headerName}`);
508
+ return validateAndSetPublicKey(cryptoService, config, pemKey);
509
+ }), map(() => {
510
+ config.enableDebugLogging && console.log('[ng-secure-fetch] Initialized - encryption ready');
511
+ return void 0;
512
+ }), catchError((error) => {
513
+ console.error('[ng-secure-fetch] Initialization failed. Set enableEncryption: false to bypass');
514
+ return throwError(() => error);
515
+ }));
516
+ }
517
+
518
+ /**
519
+ * Complete ng-secure-fetch provider: config + interceptors + APP_INITIALIZER
520
+ * App blocks until public key is fetched and verified
521
+ *
522
+ * @example
523
+ * provideNgSecureFetch({
524
+ * enableEncryption: true,
525
+ * publicKeyEndpoint: environment.apiUrl + '/crypto/init',
526
+ * expectedPublicKeyHash: 'your-sha256-hash-here',
527
+ * encryptDecryptForEndpoints: ['/insurance/*'],
528
+ * skipEncryptDecryptForEndpoints: ['/public/*'],
529
+ * publicKeyHeaderName: 'X-Client-Init',
530
+ * aesKeyHeaderName: 'X-Request-Context',
531
+ * ivHeaderName: 'X-Client-Ref',
532
+ * enableDebugLogging: false,
533
+ * publicKeyFetchRetries: 3
534
+ * })
535
+ */
536
+ function provideNgSecureFetch(config) {
537
+ return makeEnvironmentProviders([
538
+ { provide: SECURITY_CONFIG, useValue: config },
539
+ provideHttpClient(withInterceptors([encryptionInterceptor, decryptionInterceptor])),
540
+ { provide: APP_INITIALIZER, useFactory: initializeNgSecureFetchFactory, multi: true }
541
+ ]);
542
+ }
543
+
544
+ /*
545
+ * Public API Surface of ng-secure-fetch
546
+ *
547
+ * Users should only import from this public API surface.
548
+ * All other modules are considered internal implementation details.
549
+ */
550
+ // Configuration Provider
551
+
552
+ /**
553
+ * Generated bundle index. Do not edit.
554
+ */
555
+
556
+ export { DEFAULT_AES_KEY_HEADER_NAME, DEFAULT_IV_HEADER_NAME, DEFAULT_PUBLIC_KEY_HEADER_NAME, DEFAULT_SECURITY_CONFIG, SECURITY_CONFIG, provideNgSecureFetch };
557
+ //# sourceMappingURL=ng-secure-fetch.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ng-secure-fetch.mjs","sources":["../../../projects/ng-secure-fetch/src/lib/models/ng-secure-fetch-config.interface.ts","../../../projects/ng-secure-fetch/src/lib/services/crypto.service.ts","../../../projects/ng-secure-fetch/src/lib/interceptors/encryption.interceptor.ts","../../../projects/ng-secure-fetch/src/lib/interceptors/decryption.interceptor.ts","../../../projects/ng-secure-fetch/src/lib/initializers/ng-secure-fetch.initializer.ts","../../../projects/ng-secure-fetch/src/lib/config/ng-secure-fetch.config.ts","../../../projects/ng-secure-fetch/src/public-api.ts","../../../projects/ng-secure-fetch/src/ng-secure-fetch.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\r\n\r\n/** Default header name for public key exchange */\r\nexport const DEFAULT_PUBLIC_KEY_HEADER_NAME = 'X-Client-Init';\r\n\r\n/** Default header name for AES key in request/response */\r\nexport const DEFAULT_AES_KEY_HEADER_NAME = 'X-Request-Context';\r\n\r\n/** Default header name for IV in request/response */\r\nexport const DEFAULT_IV_HEADER_NAME = 'X-Client-Ref';\r\n\r\n/** Security library configuration */\r\nexport interface SecurityConfig {\r\n /** Master switch: true = fetch key + encrypt/decrypt, false = bypass all (default: true) */\r\n enableEncryption?: boolean;\r\n\r\n /** Full public key endpoint URL (required if enableEncryption is true) */\r\n publicKeyEndpoint?: string;\r\n\r\n /** Expected SHA-256 hash for public key pinning (MITM protection) */\r\n expectedPublicKeyHash?: string;\r\n\r\n /** Endpoint patterns to encrypt: ['*'] = all, ['/api/*'] = glob, ['/exact/path'] = exact (default: ['*']) */\r\n encryptDecryptForEndpoints?: string[];\r\n\r\n /** Endpoint patterns to skip encryption (higher priority than encryptDecryptForEndpoints) */\r\n skipEncryptDecryptForEndpoints?: string[];\r\n\r\n /** Custom header name for public key exchange (default: 'X-Client-Init') */\r\n publicKeyHeaderName?: string;\r\n\r\n /** Custom header name for AES key in request/response (default: 'X-Request-Context') */\r\n aesKeyHeaderName?: string;\r\n\r\n /** Custom header name for IV in request/response (default: 'X-Client-Ref') */\r\n ivHeaderName?: string;\r\n\r\n /** Enable debug logging (default: true) */\r\n enableDebugLogging?: boolean;\r\n\r\n /** Retry attempts for public key fetch (default: 3) */\r\n publicKeyFetchRetries?: number;\r\n}\r\n\r\n/** Injection token for security config */\r\nexport const SECURITY_CONFIG = new InjectionToken<SecurityConfig>('SecurityConfig');\r\n\r\n/** Default configuration with placeholder values - replace with your actual values */\r\nexport const DEFAULT_SECURITY_CONFIG: SecurityConfig = {\r\n publicKeyEndpoint: 'https://your-api-domain.com/crypto/init',\r\n expectedPublicKeyHash: 'your-sha256-hash-replace-this-with-actual-hash-from-console',\r\n encryptDecryptForEndpoints: ['*'],\r\n skipEncryptDecryptForEndpoints: [],\r\n publicKeyHeaderName: DEFAULT_PUBLIC_KEY_HEADER_NAME,\r\n aesKeyHeaderName: DEFAULT_AES_KEY_HEADER_NAME,\r\n ivHeaderName: DEFAULT_IV_HEADER_NAME,\r\n enableDebugLogging: false,\r\n enableEncryption: true,\r\n publicKeyFetchRetries: 3\r\n};\r\n","import { Injectable, signal, inject } from '@angular/core';\r\nimport { SECURITY_CONFIG } from '../models/ng-secure-fetch-config.interface';\r\nimport { EncryptedPayload } from '../models/encrypted-payload.interface';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class CryptoService {\r\n private readonly config = inject(SECURITY_CONFIG);\r\n private readonly publicKey = signal<CryptoKey | null>(null);\r\n private readonly STORAGE_KEY = 'ng_secure_fetch_public_key_pem';\r\n\r\n /** \r\n * Pattern matcher with support for dynamic segments:\r\n * - '*' = matches all URLs\r\n * - '/api/*' = matches URLs containing '/api/'\r\n * - '/exact/path' = exact substring match\r\n * - '/path/{ignore}/resource' = matches with dynamic segment (e.g., '/path/anything/resource')\r\n * - Multiple {ignore} placeholders supported: '/api/{ignore}/items/{ignore}/details'\r\n * \r\n * @param url - The URL to check\r\n * @param patterns - Array of patterns to match against\r\n * @param skipPatterns - Optional array of patterns to exclude (higher priority)\r\n * @returns true if URL matches patterns and doesn't match skipPatterns\r\n */\r\n matchesPattern(url: string, patterns: string[], skipPatterns?: string[]): boolean {\r\n // Check skip patterns first (higher priority)\r\n if (skipPatterns && skipPatterns.length > 0) {\r\n const shouldSkip = skipPatterns.some(pattern => {\r\n if (pattern === '*') return true;\r\n \r\n if (pattern.endsWith('/*')) {\r\n return url.includes(pattern.slice(0, -2));\r\n }\r\n \r\n if (pattern.includes('{ignore}')) {\r\n let regexPattern = pattern\r\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n .replace(/\\\\{ignore\\\\}/g, '[^/]+');\r\n const regex = new RegExp(regexPattern);\r\n return regex.test(url);\r\n }\r\n \r\n return url.includes(pattern);\r\n });\r\n \r\n if (shouldSkip) {\r\n this.config.enableDebugLogging && console.log(`[ng-secure-fetch] Skipped ${url}`);\r\n return false;\r\n }\r\n }\r\n \r\n // Check if URL matches any include pattern\r\n return patterns.some(pattern => {\r\n // Match all\r\n if (pattern === '*') return true;\r\n \r\n // Glob pattern (e.g., '/api/*')\r\n if (pattern.endsWith('/*')) {\r\n return url.includes(pattern.slice(0, -2));\r\n }\r\n \r\n // Pattern with {ignore} placeholders for dynamic segments\r\n if (pattern.includes('{ignore}')) {\r\n // Convert pattern to regex:\r\n // 'v1/universal/business/applicationform/{ignore}/pocketInsurance'\r\n // becomes: v1\\/universal\\/business\\/applicationform\\/[^\\/]+\\/pocketInsurance\r\n \r\n // Escape special regex characters except {ignore}\r\n let regexPattern = pattern\r\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special chars\r\n .replace(/\\\\{ignore\\\\}/g, '[^/]+'); // Replace {ignore} with pattern to match any non-slash chars\r\n \r\n // Create regex that checks if the pattern exists anywhere in the URL\r\n const regex = new RegExp(regexPattern);\r\n \r\n if (this.config.enableDebugLogging) {\r\n console.log(`[ng-secure-fetch] Pattern \"${pattern}\" ${regex.test(url) ? 'matched' : 'failed'} for ${url}`);\r\n }\r\n \r\n return regex.test(url);\r\n }\r\n \r\n // Exact substring match\r\n return url.includes(pattern);\r\n });\r\n }\r\n\r\n /** Verify public key hash (SHA-256) to prevent MITM attacks */\r\n async verifyPublicKeyHash(pemKey: string): Promise<boolean> {\r\n const expectedHash = this.config.expectedPublicKeyHash || 'REPLACE_WITH_YOUR_PUBLIC_KEY_HASH';\r\n\r\n try {\r\n const encoder = new TextEncoder();\r\n const data = encoder.encode(pemKey);\r\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\r\n const hashArray = Array.from(new Uint8Array(hashBuffer));\r\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\r\n \r\n this.config.enableDebugLogging && console.log(`[ng-secure-fetch] Public key hash: ${hashHex}`);\r\n \r\n if (expectedHash === 'REPLACE_WITH_YOUR_PUBLIC_KEY_HASH') {\r\n console.warn(`[ng-secure-fetch] Public key pinning not configured! Use hash: ${hashHex}`);\r\n return false;\r\n }\r\n \r\n const matches = hashHex === expectedHash;\r\n \r\n if (!matches) {\r\n console.error(`[ng-secure-fetch] Hash mismatch! Expected: ${expectedHash.substring(0, 16)}..., Got: ${hashHex.substring(0, 16)}...`);\r\n } else {\r\n this.config.enableDebugLogging && console.log('[ng-secure-fetch] Public key hash verified');\r\n }\r\n \r\n return matches;\r\n } catch (error) {\r\n console.error('[ng-secure-fetch] Hash verification error:', error);\r\n return false;\r\n }\r\n }\r\n\r\n /** Set RSA public key for encryption and cache in sessionStorage */\r\n setPublicKey(key: CryptoKey, pemKey?: string): void {\r\n this.publicKey.set(key);\r\n \r\n if (pemKey) {\r\n try {\r\n sessionStorage.setItem(this.STORAGE_KEY, pemKey);\r\n this.config.enableDebugLogging && console.log('[ng-secure-fetch] Public key cached');\r\n } catch (error) {\r\n console.warn('[ng-secure-fetch] Cache storage failed:', error);\r\n }\r\n }\r\n }\r\n\r\n /** Get current RSA public key */\r\n getPublicKey(): CryptoKey | null {\r\n return this.publicKey();\r\n }\r\n\r\n /** Get cached PEM key from sessionStorage */\r\n getCachedPemKey(): string | null {\r\n try {\r\n return sessionStorage.getItem(this.STORAGE_KEY);\r\n } catch (error) {\r\n console.warn('[ng-secure-fetch] Cache read failed:', error);\r\n return null;\r\n }\r\n }\r\n\r\n /** Clear cached public key from sessionStorage */\r\n clearCachedPublicKey(): void {\r\n try {\r\n sessionStorage.removeItem(this.STORAGE_KEY);\r\n this.config.enableDebugLogging && console.log('[ng-secure-fetch] Cache cleared');\r\n } catch (error) {\r\n console.warn('[ng-secure-fetch] Cache clear failed:', error);\r\n }\r\n }\r\n\r\n /** Generate random AES-256-GCM key */\r\n private async generateAESKey(): Promise<CryptoKey> {\r\n return await window.crypto.subtle.generateKey(\r\n { name: 'AES-GCM', length: 256 },\r\n true,\r\n ['encrypt', 'decrypt']\r\n );\r\n }\r\n\r\n /** Import AES key from raw bytes (one-time use from response header) */\r\n private async importAESKey(keyBytes: ArrayBuffer): Promise<CryptoKey> {\r\n return await window.crypto.subtle.importKey(\r\n 'raw',\r\n keyBytes,\r\n { name: 'AES-GCM', length: 256 },\r\n false,\r\n ['decrypt']\r\n );\r\n }\r\n\r\n /** Generate random 12-byte IV for AES-GCM */\r\n private generateIV(): Uint8Array {\r\n return window.crypto.getRandomValues(new Uint8Array(12));\r\n }\r\n\r\n /** Generate random string for padding and obfuscation */\r\n private generateRandomString(minLength: number, maxLength: number): string {\r\n const length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;\r\n const randomBytes = window.crypto.getRandomValues(new Uint8Array(length));\r\n return this.arrayBufferToBase64(randomBytes.buffer);\r\n }\r\n\r\n /** Encrypt data using AES-GCM */\r\n private async encryptWithAES(data: string, aesKey: CryptoKey, iv: Uint8Array): Promise<ArrayBuffer> {\r\n const encoder = new TextEncoder();\r\n const dataBuffer = encoder.encode(data);\r\n return await window.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv as BufferSource }, aesKey, dataBuffer);\r\n }\r\n\r\n /** Encrypt AES key with RSA-OAEP public key */\r\n private async encryptAESKeyWithRSA(aesKey: CryptoKey, rsaPublicKey: CryptoKey): Promise<ArrayBuffer> {\r\n const aesKeyBuffer = await window.crypto.subtle.exportKey('raw', aesKey);\r\n return await window.crypto.subtle.encrypt({ name: 'RSA-OAEP' }, rsaPublicKey, aesKeyBuffer);\r\n }\r\n\r\n /** Encrypt IV with RSA-OAEP public key */\r\n private async encryptIVWithRSA(iv: Uint8Array, rsaPublicKey: CryptoKey): Promise<ArrayBuffer> {\r\n return await window.crypto.subtle.encrypt({ name: 'RSA-OAEP' }, rsaPublicKey, iv as BufferSource);\r\n }\r\n\r\n /** Convert ArrayBuffer to Base64 */\r\n private arrayBufferToBase64(buffer: ArrayBuffer): string {\r\n const bytes = new Uint8Array(buffer);\r\n let binary = '';\r\n for (let i = 0; i < bytes.length; i++) {\r\n binary += String.fromCharCode(bytes[i]);\r\n }\r\n return btoa(binary);\r\n }\r\n\r\n /** Convert Base64 to ArrayBuffer */\r\n private base64ToArrayBuffer(base64: string): ArrayBuffer {\r\n const binary = atob(base64);\r\n const bytes = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i++) {\r\n bytes[i] = binary.charCodeAt(i);\r\n }\r\n return bytes.buffer;\r\n }\r\n\r\n /** Encrypt payload with AES-GCM + variable padding + RSA-encrypted keys */\r\n async encryptPayload(payload: Record<string, unknown>): Promise<EncryptedPayload | null> {\r\n const rsaPublicKey = this.getPublicKey();\r\n if (!rsaPublicKey) return null;\r\n\r\n const aesKey = await this.generateAESKey();\r\n const iv = this.generateIV();\r\n const payloadString = JSON.stringify(payload);\r\n const padding = this.generateRandomString(16, 256);\r\n const paddedData = `${payloadString}||${padding}`;\r\n\r\n const [encryptedData, encryptedAesKey, encryptedIV] = await Promise.all([\r\n this.encryptWithAES(paddedData, aesKey, iv),\r\n this.encryptAESKeyWithRSA(aesKey, rsaPublicKey),\r\n this.encryptIVWithRSA(iv, rsaPublicKey)\r\n ]);\r\n\r\n return {\r\n encryptedData: this.arrayBufferToBase64(encryptedData),\r\n encryptedAesKey: this.arrayBufferToBase64(encryptedAesKey),\r\n iv: this.arrayBufferToBase64(encryptedIV)\r\n };\r\n }\r\n\r\n /** Generate RSA-encrypted AES key and IV for GET requests (backend decrypts, uses for response) */\r\n async generateEncryptedHeaders(): Promise<{ encryptedAesKey: string; encryptedIV: string } | null> {\r\n const rsaPublicKey = this.getPublicKey();\r\n if (!rsaPublicKey) return null;\r\n\r\n const aesKey = await this.generateAESKey();\r\n const iv = this.generateIV();\r\n\r\n const [encryptedAesKey, encryptedIV] = await Promise.all([\r\n this.encryptAESKeyWithRSA(aesKey, rsaPublicKey),\r\n this.encryptIVWithRSA(iv, rsaPublicKey)\r\n ]);\r\n\r\n const result = {\r\n encryptedAesKey: this.arrayBufferToBase64(encryptedAesKey),\r\n encryptedIV: this.arrayBufferToBase64(encryptedIV)\r\n }\r\n\r\n this.config.enableDebugLogging && console.log('[ng-secure-fetch] Generated encrypted headers');\r\n\r\n return result;\r\n }\r\n\r\n /** Create obfuscated envelope with short field names (d=data, k=key, s=salt/IV) */\r\n async createEncryptedEnvelope(originalPayload: Record<string, unknown>): Promise<Record<string, unknown>> {\r\n const encryptedPayload = await this.encryptPayload(originalPayload);\r\n if (!encryptedPayload) return originalPayload;\r\n\r\n return {\r\n d: encryptedPayload.encryptedData,\r\n k: encryptedPayload.encryptedAesKey,\r\n s: encryptedPayload.iv\r\n };\r\n }\r\n\r\n /** Decrypt response using raw AES key and IV from headers (sent over HTTPS) */\r\n async decryptResponsePayload(\r\n encryptedData: string,\r\n aesKeyBase64: string,\r\n ivBase64: string\r\n ): Promise<Record<string, unknown> | null> {\r\n const encryptedBytes = this.base64ToArrayBuffer(encryptedData);\r\n const aesKeyBytes = this.base64ToArrayBuffer(aesKeyBase64);\r\n const ivBytes = this.base64ToArrayBuffer(ivBase64);\r\n const aesKey = await this.importAESKey(aesKeyBytes);\r\n\r\n const decryptedBuffer = await window.crypto.subtle.decrypt(\r\n { name: 'AES-GCM', iv: ivBytes },\r\n aesKey,\r\n encryptedBytes\r\n );\r\n\r\n const decoder = new TextDecoder();\r\n const decryptedWithPadding = decoder.decode(decryptedBuffer);\r\n const [actualData] = decryptedWithPadding.split('||');\r\n\r\n this.config.enableDebugLogging && console.log('[ng-secure-fetch] Response decrypted');\r\n\r\n return JSON.parse(actualData);\r\n }\r\n\r\n /** Import RSA public key from PEM format to CryptoKey */\r\n async importPublicKey(pemKey: string): Promise<CryptoKey> {\r\n const pemHeader = '-----BEGIN PUBLIC KEY-----';\r\n const pemFooter = '-----END PUBLIC KEY-----';\r\n const pemContents = pemKey.replace(pemHeader, '').replace(pemFooter, '').replace(/\\s/g, '');\r\n \r\n this.config.enableDebugLogging && console.log('[ng-secure-fetch] Importing PEM key');\r\n \r\n const binaryDer = atob(pemContents);\r\n const binaryDerArray = new Uint8Array(binaryDer.length);\r\n for (let i = 0; i < binaryDer.length; i++) {\r\n binaryDerArray[i] = binaryDer.charCodeAt(i);\r\n }\r\n\r\n const cryptoKey = await window.crypto.subtle.importKey(\r\n 'spki',\r\n binaryDerArray.buffer,\r\n { name: 'RSA-OAEP', hash: 'SHA-256' },\r\n true,\r\n ['encrypt']\r\n );\r\n\r\n this.config.enableDebugLogging && console.log('[ng-secure-fetch] Key imported successfully');\r\n\r\n return cryptoKey;\r\n }\r\n}\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { from, switchMap } from 'rxjs';\r\nimport { CryptoService } from '../services/crypto.service';\r\nimport { SECURITY_CONFIG, SecurityConfig, DEFAULT_AES_KEY_HEADER_NAME, DEFAULT_IV_HEADER_NAME } from '../models/ng-secure-fetch-config.interface';\r\n\r\n/** Encrypts requests: POST/PUT/PATCH body + headers, GET generates keys for response encryption */\r\nexport const encryptionInterceptor: HttpInterceptorFn = (req, next) => {\r\n const cryptoService = inject(CryptoService);\r\n const config = inject(SECURITY_CONFIG, { optional: true }) as SecurityConfig | null;\r\n\r\n if (config?.enableEncryption === false) return next(req);\r\n\r\n const encryptPatterns = config?.encryptDecryptForEndpoints || ['*'];\r\n const skipPatterns = config?.skipEncryptDecryptForEndpoints;\r\n const shouldEncrypt = cryptoService.matchesPattern(req.url, encryptPatterns, skipPatterns);\r\n\r\n if (!shouldEncrypt) return next(req);\r\n if (config?.publicKeyEndpoint && req.url === config.publicKeyEndpoint) return next(req);\r\n\r\n const aesKeyHeader = config?.aesKeyHeaderName || DEFAULT_AES_KEY_HEADER_NAME;\r\n const ivHeader = config?.ivHeaderName || DEFAULT_IV_HEADER_NAME;\r\n\r\n if (req.method === 'GET') {\r\n return from(cryptoService.generateEncryptedHeaders()).pipe(\r\n switchMap((headers: { encryptedAesKey: string; encryptedIV: string } | null) => {\r\n if (!headers) return next(req);\r\n const encryptedReq = req.clone({\r\n setHeaders: {\r\n [aesKeyHeader]: headers.encryptedAesKey,\r\n [ivHeader]: headers.encryptedIV\r\n }\r\n });\r\n return next(encryptedReq);\r\n })\r\n );\r\n }\r\n\r\n if (!req.body || typeof req.body !== 'object') return next(req);\r\n if (req.method === 'DELETE' || req.method === 'HEAD' || req.method === 'OPTIONS') return next(req);\r\n\r\n config?.enableDebugLogging && console.log(`[ng-secure-fetch] Encrypting ${req.method} request`);\r\n\r\n return from(cryptoService.createEncryptedEnvelope(req.body as Record<string, unknown>)).pipe(\r\n switchMap((encryptedBody: Record<string, unknown>) => {\r\n const envelope = encryptedBody as Record<string, unknown>;\r\n const encryptedReq = req.clone({\r\n body: envelope['d'],\r\n setHeaders: {\r\n 'Content-Type': 'text/plain',\r\n [aesKeyHeader]: envelope['k'] as string,\r\n [ivHeader]: envelope['s'] as string\r\n }\r\n });\r\n return next(encryptedReq);\r\n })\r\n );\r\n};\r\n","import { HttpInterceptorFn, HttpResponse, HttpErrorResponse } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { from, switchMap, of, throwError, catchError } from 'rxjs';\r\nimport { CryptoService } from '../services/crypto.service';\r\nimport { SECURITY_CONFIG, SecurityConfig, DEFAULT_AES_KEY_HEADER_NAME, DEFAULT_IV_HEADER_NAME } from '../models/ng-secure-fetch-config.interface';\r\n\r\n/** Decrypts responses using AES keys from headers (X-Request-Context, X-Client-Ref) */\r\nexport const decryptionInterceptor: HttpInterceptorFn = (req, next) => {\r\n const cryptoService = inject(CryptoService);\r\n const config = inject(SECURITY_CONFIG, { optional: true }) as SecurityConfig | null;\r\n\r\n return next(req).pipe(\r\n switchMap((event: any) => {\r\n if (!(event instanceof HttpResponse)) return of(event);\r\n if (config?.enableEncryption === false) return of(event);\r\n\r\n const encryptPatterns = config?.encryptDecryptForEndpoints || ['*'];\r\n const skipPatterns = config?.skipEncryptDecryptForEndpoints;\r\n const shouldDecrypt = cryptoService.matchesPattern(req.url, encryptPatterns, skipPatterns);\r\n\r\n if (!shouldDecrypt) return of(event);\r\n if (config?.publicKeyEndpoint && req.url === config.publicKeyEndpoint) return of(event);\r\n\r\n return decryptResponse(event, cryptoService, config);\r\n }),\r\n catchError((error: HttpErrorResponse) => {\r\n // Handle error responses (4xx, 5xx)\r\n if (config?.enableEncryption === false) return throwError(() => error);\r\n\r\n const encryptPatterns = config?.encryptDecryptForEndpoints || ['*'];\r\n const skipPatterns = config?.skipEncryptDecryptForEndpoints;\r\n const shouldDecrypt = cryptoService.matchesPattern(req.url, encryptPatterns, skipPatterns);\r\n\r\n if (!shouldDecrypt) return throwError(() => error);\r\n if (config?.publicKeyEndpoint && req.url === config.publicKeyEndpoint) return throwError(() => error);\r\n\r\n const aesKeyHeader = config?.aesKeyHeaderName || DEFAULT_AES_KEY_HEADER_NAME;\r\n const ivHeader = config?.ivHeaderName || DEFAULT_IV_HEADER_NAME;\r\n\r\n config?.enableDebugLogging && console.log('[ng-secure-fetch] Error response received, attempting decryption');\r\n\r\n // Check if error has encryption headers\r\n const aesKeyBase64 = error.headers?.get(aesKeyHeader) || error.headers?.get(aesKeyHeader.toLowerCase());\r\n const ivBase64 = error.headers?.get(ivHeader) || error.headers?.get(ivHeader.toLowerCase());\r\n\r\n if (!aesKeyBase64 || !ivBase64 || !error.error) {\r\n config?.enableDebugLogging && console.warn('[ng-secure-fetch] Error response not encrypted');\r\n return throwError(() => error);\r\n }\r\n\r\n // Decrypt error response\r\n return from(\r\n cryptoService.decryptResponsePayload(\r\n typeof error.error === 'string' ? error.error : JSON.stringify(error.error),\r\n aesKeyBase64,\r\n ivBase64\r\n ).then(\r\n (decrypted: Record<string, unknown> | null) => {\r\n if (decrypted) {\r\n config?.enableDebugLogging && console.log('[ng-secure-fetch] Error response decrypted');\r\n \r\n // Return decrypted error - spread decrypted data to top level for easier access\r\n // This allows components to access error.errors[0] instead of error.error.errors[0]\r\n const decryptedError = new HttpErrorResponse({\r\n error: decrypted,\r\n headers: error.headers,\r\n status: error.status,\r\n statusText: error.statusText,\r\n url: error.url || undefined\r\n });\r\n \r\n // Merge decrypted properties to top level of error object\r\n Object.assign(decryptedError, decrypted);\r\n \r\n throw decryptedError;\r\n }\r\n config?.enableDebugLogging && console.warn('[ng-secure-fetch] Error decryption returned null');\r\n throw error;\r\n },\r\n (decryptError: any) => {\r\n config?.enableDebugLogging && console.error('[ng-secure-fetch] Failed to decrypt error response:', decryptError);\r\n throw error;\r\n }\r\n )\r\n );\r\n })\r\n );\r\n};\r\n\r\n/** Helper function to decrypt successful responses */\r\nfunction decryptResponse(\r\n event: HttpResponse<any>,\r\n cryptoService: CryptoService,\r\n config: SecurityConfig | null\r\n) {\r\n const aesKeyHeader = config?.aesKeyHeaderName || DEFAULT_AES_KEY_HEADER_NAME;\r\n const ivHeader = config?.ivHeaderName || DEFAULT_IV_HEADER_NAME;\r\n\r\n const aesKeyBase64 = event.headers.get(aesKeyHeader) || event.headers.get(aesKeyHeader.toLowerCase());\r\n const ivBase64 = event.headers.get(ivHeader) || event.headers.get(ivHeader.toLowerCase());\r\n\r\n if (!aesKeyBase64 || !ivBase64 || !event.body) {\r\n config?.enableDebugLogging && event.body && console.warn('[ng-secure-fetch] Missing encryption headers');\r\n return of(event);\r\n }\r\n\r\n config?.enableDebugLogging && console.log('[ng-secure-fetch] Decrypting response');\r\n\r\n return from(\r\n cryptoService.decryptResponsePayload(event.body as string, aesKeyBase64, ivBase64)\r\n ).pipe(\r\n switchMap((decrypted: Record<string, unknown> | null) => {\r\n if (decrypted) {\r\n config?.enableDebugLogging && console.log('[ng-secure-fetch] Response decrypted successfully');\r\n return of(event.clone({ body: decrypted }));\r\n }\r\n config?.enableDebugLogging && console.warn('[ng-secure-fetch] Decryption returned null');\r\n return of(event);\r\n })\r\n );\r\n}\r\n","import { inject } from '@angular/core';\r\nimport { HttpClient, HttpResponse } from '@angular/common/http';\r\nimport { Observable, of, throwError, retry, catchError, switchMap, map, timer, Subscriber } from 'rxjs';\r\nimport { CryptoService } from '../services/crypto.service';\r\nimport { SECURITY_CONFIG, SecurityConfig, DEFAULT_PUBLIC_KEY_HEADER_NAME } from '../models/ng-secure-fetch-config.interface';\r\n\r\n/** APP_INITIALIZER: Fetches public key, verifies hash, blocks app until ready */\r\nexport function initializeNgSecureFetchFactory(): () => Observable<void> {\r\n const http = inject(HttpClient);\r\n const cryptoService = inject(CryptoService);\r\n const config = inject(SECURITY_CONFIG);\r\n\r\n return () => {\r\n if (config.enableEncryption === false) {\r\n config.enableDebugLogging && console.log('[ng-secure-fetch] Encryption disabled');\r\n return of(void 0);\r\n }\r\n\r\n if (!config.publicKeyEndpoint) {\r\n console.error('[ng-secure-fetch] publicKeyEndpoint required');\r\n return throwError(() => new Error('publicKeyEndpoint required'));\r\n }\r\n\r\n // Check for cached public key in sessionStorage first\r\n return new Observable<void>((subscriber: Subscriber<void>) => {\r\n const cachedPemKey = cryptoService.getCachedPemKey();\r\n \r\n if (cachedPemKey) {\r\n config.enableDebugLogging && console.log('[ng-secure-fetch] Using cached key');\r\n \r\n validateAndSetPublicKey(cryptoService, config, cachedPemKey).then(() => {\r\n config.enableDebugLogging && console.log('[ng-secure-fetch] Cached key validated');\r\n subscriber.next();\r\n subscriber.complete();\r\n }).catch(err => {\r\n config.enableDebugLogging && console.warn('[ng-secure-fetch] Cache invalid, fetching fresh key');\r\n cryptoService.clearCachedPublicKey();\r\n \r\n fetchPublicKeyFromAPI(http, cryptoService, config).subscribe({\r\n next: () => {\r\n subscriber.next();\r\n subscriber.complete();\r\n },\r\n error: (err: any) => subscriber.error(err)\r\n });\r\n });\r\n } else {\r\n config.enableDebugLogging && console.log('[ng-secure-fetch] Fetching public key from API');\r\n \r\n fetchPublicKeyFromAPI(http, cryptoService, config).subscribe({\r\n next: () => {\r\n subscriber.next();\r\n subscriber.complete();\r\n },\r\n error: (err: any) => subscriber.error(err)\r\n });\r\n }\r\n });\r\n };\r\n}\r\n\r\n/** Validate PEM key hash and import it (reusable for both cached and API keys) */\r\nasync function validateAndSetPublicKey(\r\n cryptoService: CryptoService,\r\n config: SecurityConfig,\r\n pemKey: string\r\n): Promise<void> {\r\n const isValid = await cryptoService.verifyPublicKeyHash(pemKey);\r\n if (!isValid) {\r\n throw new Error('Public key hash mismatch - possible MITM attack');\r\n }\r\n \r\n const cryptoKey = await cryptoService.importPublicKey(pemKey);\r\n cryptoService.setPublicKey(cryptoKey, pemKey);\r\n config.enableDebugLogging && console.log('[ng-secure-fetch] Public key imported & validated');\r\n}\r\n\r\n/** Helper function to fetch public key from API */\r\nfunction fetchPublicKeyFromAPI(http: HttpClient, cryptoService: CryptoService, config: SecurityConfig): Observable<void> {\r\n const retries = config.publicKeyFetchRetries || 3;\r\n\r\n return http.get<void>(config.publicKeyEndpoint!, { observe: 'response' }).pipe(\r\n retry({\r\n count: retries,\r\n delay: (_error: any, retryCount: number) => {\r\n config.enableDebugLogging && console.warn(`[ng-secure-fetch] Retry ${retryCount}/${retries}`);\r\n return timer(Math.pow(2, retryCount - 1) * 1000);\r\n }\r\n }),\r\n\r\n switchMap((response: HttpResponse<void>) => {\r\n const headerName = config.publicKeyHeaderName || DEFAULT_PUBLIC_KEY_HEADER_NAME;\r\n const pemKey = response.headers.get(headerName) || response.headers.get(headerName.toLowerCase());\r\n \r\n if (!pemKey) {\r\n const availableHeaders = response.headers.keys().join(', ');\r\n console.error(`[ng-secure-fetch] ${headerName} header missing. Available: ${availableHeaders}`);\r\n throw new Error(`${headerName} header not found. Set Access-Control-Expose-Headers`);\r\n }\r\n \r\n config.enableDebugLogging && console.log(`[ng-secure-fetch] Public key received from ${headerName}`);\r\n return validateAndSetPublicKey(cryptoService, config, pemKey);\r\n }),\r\n\r\n map(() => {\r\n config.enableDebugLogging && console.log('[ng-secure-fetch] Initialized - encryption ready');\r\n return void 0;\r\n }),\r\n\r\n catchError((error: any) => {\r\n console.error('[ng-secure-fetch] Initialization failed. Set enableEncryption: false to bypass');\r\n return throwError(() => error);\r\n })\r\n );\r\n}\r\n","import { EnvironmentProviders, makeEnvironmentProviders, APP_INITIALIZER } from '@angular/core';\r\nimport { provideHttpClient, withInterceptors } from '@angular/common/http';\r\nimport { SecurityConfig, SECURITY_CONFIG } from '../models/ng-secure-fetch-config.interface';\r\nimport { encryptionInterceptor } from '../interceptors/encryption.interceptor';\r\nimport { decryptionInterceptor } from '../interceptors/decryption.interceptor';\r\nimport { initializeNgSecureFetchFactory } from '../initializers/ng-secure-fetch.initializer';\r\n\r\n/**\r\n * Complete ng-secure-fetch provider: config + interceptors + APP_INITIALIZER\r\n * App blocks until public key is fetched and verified\r\n * \r\n * @example\r\n * provideNgSecureFetch({\r\n * enableEncryption: true,\r\n * publicKeyEndpoint: environment.apiUrl + '/crypto/init',\r\n * expectedPublicKeyHash: 'your-sha256-hash-here',\r\n * encryptDecryptForEndpoints: ['/insurance/*'],\r\n * skipEncryptDecryptForEndpoints: ['/public/*'],\r\n * publicKeyHeaderName: 'X-Client-Init',\r\n * aesKeyHeaderName: 'X-Request-Context',\r\n * ivHeaderName: 'X-Client-Ref',\r\n * enableDebugLogging: false,\r\n * publicKeyFetchRetries: 3\r\n * })\r\n */\r\nexport function provideNgSecureFetch(config: SecurityConfig): EnvironmentProviders {\r\n return makeEnvironmentProviders([\r\n { provide: SECURITY_CONFIG, useValue: config },\r\n provideHttpClient(withInterceptors([encryptionInterceptor, decryptionInterceptor])),\r\n { provide: APP_INITIALIZER, useFactory: initializeNgSecureFetchFactory, multi: true }\r\n ]);\r\n}\r\n","/*\r\n * Public API Surface of ng-secure-fetch\r\n * \r\n * Users should only import from this public API surface.\r\n * All other modules are considered internal implementation details.\r\n */\r\n\r\n// Configuration Provider\r\nexport * from './lib/config/ng-secure-fetch.config';\r\n\r\n// Configuration Interface\r\nexport * from './lib/models/ng-secure-fetch-config.interface';\r\n\r\n// Data Models\r\nexport * from './lib/models/encrypted-payload.interface';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;AAEA;AACO,MAAM,8BAA8B,GAAG;AAE9C;AACO,MAAM,2BAA2B,GAAG;AAE3C;AACO,MAAM,sBAAsB,GAAG;AAmCtC;MACa,eAAe,GAAG,IAAI,cAAc,CAAiB,gBAAgB;AAElF;AACO,MAAM,uBAAuB,GAAmB;AACrD,IAAA,iBAAiB,EAAE,yCAAyC;AAC5D,IAAA,qBAAqB,EAAE,6DAA6D;IACpF,0BAA0B,EAAE,CAAC,GAAG,CAAC;AACjC,IAAA,8BAA8B,EAAE,EAAE;AAClC,IAAA,mBAAmB,EAAE,8BAA8B;AACnD,IAAA,gBAAgB,EAAE,2BAA2B;AAC7C,IAAA,YAAY,EAAE,sBAAsB;AACpC,IAAA,kBAAkB,EAAE,KAAK;AACzB,IAAA,gBAAgB,EAAE,IAAI;AACtB,IAAA,qBAAqB,EAAE;;;MCnDZ,aAAa,CAAA;AACP,IAAA,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC;AAChC,IAAA,SAAS,GAAG,MAAM,CAAmB,IAAI,qDAAC;IAC1C,WAAW,GAAG,gCAAgC;AAE/D;;;;;;;;;;;;AAYG;AACH,IAAA,cAAc,CAAC,GAAW,EAAE,QAAkB,EAAE,YAAuB,EAAA;;QAErE,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;YAC3C,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,IAAG;gBAC7C,IAAI,OAAO,KAAK,GAAG;AAAE,oBAAA,OAAO,IAAI;AAEhC,gBAAA,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;AAC1B,oBAAA,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC3C;AAEA,gBAAA,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE;oBAChC,IAAI,YAAY,GAAG;AAChB,yBAAA,OAAO,CAAC,qBAAqB,EAAE,MAAM;AACrC,yBAAA,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC;AACpC,oBAAA,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC;AACtC,oBAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;gBACxB;AAEA,gBAAA,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;AAC9B,YAAA,CAAC,CAAC;YAEF,IAAI,UAAU,EAAE;AACd,gBAAA,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,CAAA,0BAAA,EAA6B,GAAG,CAAA,CAAE,CAAC;AACjF,gBAAA,OAAO,KAAK;YACd;QACF;;AAGA,QAAA,OAAO,QAAQ,CAAC,IAAI,CAAC,OAAO,IAAG;;YAE7B,IAAI,OAAO,KAAK,GAAG;AAAE,gBAAA,OAAO,IAAI;;AAGhC,YAAA,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;AAC1B,gBAAA,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC3C;;AAGA,YAAA,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE;;;;;gBAMhC,IAAI,YAAY,GAAG;AAChB,qBAAA,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC;AACtC,qBAAA,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;;AAGrC,gBAAA,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC;AAEtC,gBAAA,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE;oBAClC,OAAO,CAAC,GAAG,CAAC,CAAA,2BAAA,EAA8B,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAA,KAAA,EAAQ,GAAG,CAAA,CAAE,CAAC;gBAC5G;AAEA,gBAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;YACxB;;AAGA,YAAA,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;AAC9B,QAAA,CAAC,CAAC;IACJ;;IAGA,MAAM,mBAAmB,CAAC,MAAc,EAAA;QACtC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,IAAI,mCAAmC;AAE7F,QAAA,IAAI;AACF,YAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE;YACjC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;AACnC,YAAA,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC;AAC9D,YAAA,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;AACxD,YAAA,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AAE5E,YAAA,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,CAAA,mCAAA,EAAsC,OAAO,CAAA,CAAE,CAAC;AAE9F,YAAA,IAAI,YAAY,KAAK,mCAAmC,EAAE;AACxD,gBAAA,OAAO,CAAC,IAAI,CAAC,kEAAkE,OAAO,CAAA,CAAE,CAAC;AACzF,gBAAA,OAAO,KAAK;YACd;AAEA,YAAA,MAAM,OAAO,GAAG,OAAO,KAAK,YAAY;YAExC,IAAI,CAAC,OAAO,EAAE;gBACZ,OAAO,CAAC,KAAK,CAAC,CAAA,2CAAA,EAA8C,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA,UAAA,EAAa,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA,GAAA,CAAK,CAAC;YACtI;iBAAO;gBACL,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC;YAC7F;AAEA,YAAA,OAAO,OAAO;QAChB;QAAE,OAAO,KAAK,EAAE;AACd,YAAA,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC;AAClE,YAAA,OAAO,KAAK;QACd;IACF;;IAGA,YAAY,CAAC,GAAc,EAAE,MAAe,EAAA;AAC1C,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;QAEvB,IAAI,MAAM,EAAE;AACV,YAAA,IAAI;gBACF,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;gBAChD,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC;YACtF;YAAE,OAAO,KAAK,EAAE;AACd,gBAAA,OAAO,CAAC,IAAI,CAAC,yCAAyC,EAAE,KAAK,CAAC;YAChE;QACF;IACF;;IAGA,YAAY,GAAA;AACV,QAAA,OAAO,IAAI,CAAC,SAAS,EAAE;IACzB;;IAGA,eAAe,GAAA;AACb,QAAA,IAAI;YACF,OAAO,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;QACjD;QAAE,OAAO,KAAK,EAAE;AACd,YAAA,OAAO,CAAC,IAAI,CAAC,sCAAsC,EAAE,KAAK,CAAC;AAC3D,YAAA,OAAO,IAAI;QACb;IACF;;IAGA,oBAAoB,GAAA;AAClB,QAAA,IAAI;AACF,YAAA,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC;QAClF;QAAE,OAAO,KAAK,EAAE;AACd,YAAA,OAAO,CAAC,IAAI,CAAC,uCAAuC,EAAE,KAAK,CAAC;QAC9D;IACF;;AAGQ,IAAA,MAAM,cAAc,GAAA;QAC1B,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAC3C,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,EAChC,IAAI,EACJ,CAAC,SAAS,EAAE,SAAS,CAAC,CACvB;IACH;;IAGQ,MAAM,YAAY,CAAC,QAAqB,EAAA;AAC9C,QAAA,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CACzC,KAAK,EACL,QAAQ,EACR,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,EAChC,KAAK,EACL,CAAC,SAAS,CAAC,CACZ;IACH;;IAGQ,UAAU,GAAA;AAChB,QAAA,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAC1D;;IAGQ,oBAAoB,CAAC,SAAiB,EAAE,SAAiB,EAAA;QAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,SAAS,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS;AAClF,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,MAAM,CAAC;IACrD;;AAGQ,IAAA,MAAM,cAAc,CAAC,IAAY,EAAE,MAAiB,EAAE,EAAc,EAAA;AAC1E,QAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE;QACjC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;QACvC,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,EAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC;IAC5G;;AAGQ,IAAA,MAAM,oBAAoB,CAAC,MAAiB,EAAE,YAAuB,EAAA;AAC3E,QAAA,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC;AACxE,QAAA,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,YAAY,EAAE,YAAY,CAAC;IAC7F;;AAGQ,IAAA,MAAM,gBAAgB,CAAC,EAAc,EAAE,YAAuB,EAAA;AACpE,QAAA,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,YAAY,EAAE,EAAkB,CAAC;IACnG;;AAGQ,IAAA,mBAAmB,CAAC,MAAmB,EAAA;AAC7C,QAAA,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC;QACpC,IAAI,MAAM,GAAG,EAAE;AACf,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACrC,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzC;AACA,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB;;AAGQ,IAAA,mBAAmB,CAAC,MAAc,EAAA;AACxC,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;AAC3C,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACtC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QACjC;QACA,OAAO,KAAK,CAAC,MAAM;IACrB;;IAGA,MAAM,cAAc,CAAC,OAAgC,EAAA;AACnD,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE;AACxC,QAAA,IAAI,CAAC,YAAY;AAAE,YAAA,OAAO,IAAI;AAE9B,QAAA,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE;AAC1C,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE;QAC5B,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,oBAAoB,CAAC,EAAE,EAAE,GAAG,CAAC;AAClD,QAAA,MAAM,UAAU,GAAG,CAAA,EAAG,aAAa,CAAA,EAAA,EAAK,OAAO,EAAE;AAEjD,QAAA,MAAM,CAAC,aAAa,EAAE,eAAe,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACtE,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;AAC3C,YAAA,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,YAAY,CAAC;AAC/C,YAAA,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAE,YAAY;AACvC,SAAA,CAAC;QAEF,OAAO;AACL,YAAA,aAAa,EAAE,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC;AACtD,YAAA,eAAe,EAAE,IAAI,CAAC,mBAAmB,CAAC,eAAe,CAAC;AAC1D,YAAA,EAAE,EAAE,IAAI,CAAC,mBAAmB,CAAC,WAAW;SACzC;IACH;;AAGA,IAAA,MAAM,wBAAwB,GAAA;AAC5B,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE;AACxC,QAAA,IAAI,CAAC,YAAY;AAAE,YAAA,OAAO,IAAI;AAE9B,QAAA,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE;AAC1C,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE;QAE5B,MAAM,CAAC,eAAe,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;AACvD,YAAA,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,YAAY,CAAC;AAC/C,YAAA,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAE,YAAY;AACvC,SAAA,CAAC;AAEF,QAAA,MAAM,MAAM,GAAG;AACb,YAAA,eAAe,EAAE,IAAI,CAAC,mBAAmB,CAAC,eAAe,CAAC;AAC1D,YAAA,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,WAAW;SAClD;QAED,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC;AAE9F,QAAA,OAAO,MAAM;IACf;;IAGA,MAAM,uBAAuB,CAAC,eAAwC,EAAA;QACpE,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC;AACnE,QAAA,IAAI,CAAC,gBAAgB;AAAE,YAAA,OAAO,eAAe;QAE7C,OAAO;YACL,CAAC,EAAE,gBAAgB,CAAC,aAAa;YACjC,CAAC,EAAE,gBAAgB,CAAC,eAAe;YACnC,CAAC,EAAE,gBAAgB,CAAC;SACrB;IACH;;AAGA,IAAA,MAAM,sBAAsB,CAC1B,aAAqB,EACrB,YAAoB,EACpB,QAAgB,EAAA;QAEhB,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC;QAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC;QAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC;QAClD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC;QAEnD,MAAM,eAAe,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CACxD,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,EAChC,MAAM,EACN,cAAc,CACf;AAED,QAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE;QACjC,MAAM,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC;QAC5D,MAAM,CAAC,UAAU,CAAC,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC;QAErD,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC;AAErF,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;IAC/B;;IAGA,MAAM,eAAe,CAAC,MAAc,EAAA;QAClC,MAAM,SAAS,GAAG,4BAA4B;QAC9C,MAAM,SAAS,GAAG,0BAA0B;QAC5C,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QAE3F,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC;AAEpF,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC;QACnC,MAAM,cAAc,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC;AACvD,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACzC,cAAc,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;QAC7C;AAEA,QAAA,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CACpD,MAAM,EACN,cAAc,CAAC,MAAM,EACrB,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EACrC,IAAI,EACJ,CAAC,SAAS,CAAC,CACZ;QAED,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC;AAE5F,QAAA,OAAO,SAAS;IAClB;wGA7UW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAb,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,aAAa,cAFZ,MAAM,EAAA,CAAA;;4FAEP,aAAa,EAAA,UAAA,EAAA,CAAA;kBAHzB,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE;AACb,iBAAA;;;ACAD;AACO,MAAM,qBAAqB,GAAsB,CAAC,GAAG,EAAE,IAAI,KAAI;AACpE,IAAA,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;AAC3C,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAA0B;AAEnF,IAAA,IAAI,MAAM,EAAE,gBAAgB,KAAK,KAAK;AAAE,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC;IAExD,MAAM,eAAe,GAAG,MAAM,EAAE,0BAA0B,IAAI,CAAC,GAAG,CAAC;AACnE,IAAA,MAAM,YAAY,GAAG,MAAM,EAAE,8BAA8B;AAC3D,IAAA,MAAM,aAAa,GAAG,aAAa,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,YAAY,CAAC;AAE1F,IAAA,IAAI,CAAC,aAAa;AAAE,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC;IACpC,IAAI,MAAM,EAAE,iBAAiB,IAAI,GAAG,CAAC,GAAG,KAAK,MAAM,CAAC,iBAAiB;AAAE,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC;AAEvF,IAAA,MAAM,YAAY,GAAG,MAAM,EAAE,gBAAgB,IAAI,2BAA2B;AAC5E,IAAA,MAAM,QAAQ,GAAG,MAAM,EAAE,YAAY,IAAI,sBAAsB;AAE/D,IAAA,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE;AACxB,QAAA,OAAO,IAAI,CAAC,aAAa,CAAC,wBAAwB,EAAE,CAAC,CAAC,IAAI,CACxD,SAAS,CAAC,CAAC,OAAgE,KAAI;AAC7E,YAAA,IAAI,CAAC,OAAO;AAAE,gBAAA,OAAO,IAAI,CAAC,GAAG,CAAC;AAC9B,YAAA,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC;AAC7B,gBAAA,UAAU,EAAE;AACV,oBAAA,CAAC,YAAY,GAAG,OAAO,CAAC,eAAe;AACvC,oBAAA,CAAC,QAAQ,GAAG,OAAO,CAAC;AACrB;AACF,aAAA,CAAC;AACF,YAAA,OAAO,IAAI,CAAC,YAAY,CAAC;QAC3B,CAAC,CAAC,CACH;IACH;IAEA,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ;AAAE,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC;AAC/D,IAAA,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;AAAE,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC;AAElG,IAAA,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,CAAA,6BAAA,EAAgC,GAAG,CAAC,MAAM,CAAA,QAAA,CAAU,CAAC;AAE/F,IAAA,OAAO,IAAI,CAAC,aAAa,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAA+B,CAAC,CAAC,CAAC,IAAI,CAC1F,SAAS,CAAC,CAAC,aAAsC,KAAI;QACnD,MAAM,QAAQ,GAAG,aAAwC;AACzD,QAAA,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC;AAC7B,YAAA,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC;AACnB,YAAA,UAAU,EAAE;AACV,gBAAA,cAAc,EAAE,YAAY;AAC5B,gBAAA,CAAC,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAW;AACvC,gBAAA,CAAC,QAAQ,GAAG,QAAQ,CAAC,GAAG;AACzB;AACF,SAAA,CAAC;AACF,QAAA,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC,CAAC,CACH;AACH,CAAC;;ACnDD;AACO,MAAM,qBAAqB,GAAsB,CAAC,GAAG,EAAE,IAAI,KAAI;AACpE,IAAA,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;AAC3C,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAA0B;AAEnF,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CACnB,SAAS,CAAC,CAAC,KAAU,KAAI;AACvB,QAAA,IAAI,EAAE,KAAK,YAAY,YAAY,CAAC;AAAE,YAAA,OAAO,EAAE,CAAC,KAAK,CAAC;AACtD,QAAA,IAAI,MAAM,EAAE,gBAAgB,KAAK,KAAK;AAAE,YAAA,OAAO,EAAE,CAAC,KAAK,CAAC;QAExD,MAAM,eAAe,GAAG,MAAM,EAAE,0BAA0B,IAAI,CAAC,GAAG,CAAC;AACnE,QAAA,MAAM,YAAY,GAAG,MAAM,EAAE,8BAA8B;AAC3D,QAAA,MAAM,aAAa,GAAG,aAAa,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,YAAY,CAAC;AAE1F,QAAA,IAAI,CAAC,aAAa;AAAE,YAAA,OAAO,EAAE,CAAC,KAAK,CAAC;QACpC,IAAI,MAAM,EAAE,iBAAiB,IAAI,GAAG,CAAC,GAAG,KAAK,MAAM,CAAC,iBAAiB;AAAE,YAAA,OAAO,EAAE,CAAC,KAAK,CAAC;QAEvF,OAAO,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,CAAC;AACtD,IAAA,CAAC,CAAC,EACF,UAAU,CAAC,CAAC,KAAwB,KAAI;;AAEtC,QAAA,IAAI,MAAM,EAAE,gBAAgB,KAAK,KAAK;AAAE,YAAA,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC;QAEtE,MAAM,eAAe,GAAG,MAAM,EAAE,0BAA0B,IAAI,CAAC,GAAG,CAAC;AACnE,QAAA,MAAM,YAAY,GAAG,MAAM,EAAE,8BAA8B;AAC3D,QAAA,MAAM,aAAa,GAAG,aAAa,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,YAAY,CAAC;AAE1F,QAAA,IAAI,CAAC,aAAa;AAAE,YAAA,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC;QAClD,IAAI,MAAM,EAAE,iBAAiB,IAAI,GAAG,CAAC,GAAG,KAAK,MAAM,CAAC,iBAAiB;AAAE,YAAA,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC;AAErG,QAAA,MAAM,YAAY,GAAG,MAAM,EAAE,gBAAgB,IAAI,2BAA2B;AAC5E,QAAA,MAAM,QAAQ,GAAG,MAAM,EAAE,YAAY,IAAI,sBAAsB;QAE/D,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC;;QAG7G,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC;QACvG,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAE3F,IAAI,CAAC,YAAY,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;YAC9C,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,gDAAgD,CAAC;AAC5F,YAAA,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC;QAChC;;AAGA,QAAA,OAAO,IAAI,CACT,aAAa,CAAC,sBAAsB,CAClC,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,GAAG,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAC3E,YAAY,EACZ,QAAQ,CACT,CAAC,IAAI,CACJ,CAAC,SAAyC,KAAI;YAC5C,IAAI,SAAS,EAAE;gBACb,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC;;;AAIvF,gBAAA,MAAM,cAAc,GAAG,IAAI,iBAAiB,CAAC;AAC3C,oBAAA,KAAK,EAAE,SAAS;oBAChB,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,UAAU,EAAE,KAAK,CAAC,UAAU;AAC5B,oBAAA,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI;AACnB,iBAAA,CAAC;;AAGF,gBAAA,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,CAAC;AAExC,gBAAA,MAAM,cAAc;YACtB;YACA,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,kDAAkD,CAAC;AAC9F,YAAA,MAAM,KAAK;AACb,QAAA,CAAC,EACD,CAAC,YAAiB,KAAI;YACpB,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,KAAK,CAAC,qDAAqD,EAAE,YAAY,CAAC;AAChH,YAAA,MAAM,KAAK;QACb,CAAC,CACF,CACF;IACH,CAAC,CAAC,CACH;AACH,CAAC;AAED;AACA,SAAS,eAAe,CACtB,KAAwB,EACxB,aAA4B,EAC5B,MAA6B,EAAA;AAE7B,IAAA,MAAM,YAAY,GAAG,MAAM,EAAE,gBAAgB,IAAI,2BAA2B;AAC5E,IAAA,MAAM,QAAQ,GAAG,MAAM,EAAE,YAAY,IAAI,sBAAsB;IAE/D,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC;IACrG,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IAEzF,IAAI,CAAC,YAAY,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;AAC7C,QAAA,MAAM,EAAE,kBAAkB,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,8CAA8C,CAAC;AACxG,QAAA,OAAO,EAAE,CAAC,KAAK,CAAC;IAClB;IAEA,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC;IAElF,OAAO,IAAI,CACT,aAAa,CAAC,sBAAsB,CAAC,KAAK,CAAC,IAAc,EAAE,YAAY,EAAE,QAAQ,CAAC,CACnF,CAAC,IAAI,CACJ,SAAS,CAAC,CAAC,SAAyC,KAAI;QACtD,IAAI,SAAS,EAAE;YACb,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC;AAC9F,YAAA,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC7C;QACA,MAAM,EAAE,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,4CAA4C,CAAC;AACxF,QAAA,OAAO,EAAE,CAAC,KAAK,CAAC;IAClB,CAAC,CAAC,CACH;AACH;;AClHA;SACgB,8BAA8B,GAAA;AAC5C,IAAA,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;AAC/B,IAAA,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;AAC3C,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC;AAEtC,IAAA,OAAO,MAAK;AACV,QAAA,IAAI,MAAM,CAAC,gBAAgB,KAAK,KAAK,EAAE;YACrC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC;AACjF,YAAA,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QACnB;AAEA,QAAA,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE;AAC7B,YAAA,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC;YAC7D,OAAO,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAClE;;AAGA,QAAA,OAAO,IAAI,UAAU,CAAO,CAAC,UAA4B,KAAI;AAC3D,YAAA,MAAM,YAAY,GAAG,aAAa,CAAC,eAAe,EAAE;YAEpD,IAAI,YAAY,EAAE;gBAChB,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC;gBAE9E,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,MAAK;oBACrE,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC;oBAClF,UAAU,CAAC,IAAI,EAAE;oBACjB,UAAU,CAAC,QAAQ,EAAE;AACvB,gBAAA,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,IAAG;oBACb,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,qDAAqD,CAAC;oBAChG,aAAa,CAAC,oBAAoB,EAAE;oBAEpC,qBAAqB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,SAAS,CAAC;wBAC3D,IAAI,EAAE,MAAK;4BACT,UAAU,CAAC,IAAI,EAAE;4BACjB,UAAU,CAAC,QAAQ,EAAE;wBACvB,CAAC;wBACD,KAAK,EAAE,CAAC,GAAQ,KAAK,UAAU,CAAC,KAAK,CAAC,GAAG;AAC1C,qBAAA,CAAC;AACJ,gBAAA,CAAC,CAAC;YACJ;iBAAO;gBACL,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC;gBAE1F,qBAAqB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,SAAS,CAAC;oBAC3D,IAAI,EAAE,MAAK;wBACT,UAAU,CAAC,IAAI,EAAE;wBACjB,UAAU,CAAC,QAAQ,EAAE;oBACvB,CAAC;oBACD,KAAK,EAAE,CAAC,GAAQ,KAAK,UAAU,CAAC,KAAK,CAAC,GAAG;AAC1C,iBAAA,CAAC;YACJ;AACF,QAAA,CAAC,CAAC;AACJ,IAAA,CAAC;AACH;AAEA;AACA,eAAe,uBAAuB,CACpC,aAA4B,EAC5B,MAAsB,EACtB,MAAc,EAAA;IAEd,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,mBAAmB,CAAC,MAAM,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE;AACZ,QAAA,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC;IACpE;IAEA,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC,MAAM,CAAC;AAC7D,IAAA,aAAa,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC;IAC7C,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC;AAC/F;AAEA;AACA,SAAS,qBAAqB,CAAC,IAAgB,EAAE,aAA4B,EAAE,MAAsB,EAAA;AACnG,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,qBAAqB,IAAI,CAAC;AAEjD,IAAA,OAAO,IAAI,CAAC,GAAG,CAAO,MAAM,CAAC,iBAAkB,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,CAC5E,KAAK,CAAC;AACJ,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,KAAK,EAAE,CAAC,MAAW,EAAE,UAAkB,KAAI;AACzC,YAAA,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA,wBAAA,EAA2B,UAAU,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE,CAAC;AAC7F,YAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC;QAClD;AACD,KAAA,CAAC,EAEF,SAAS,CAAC,CAAC,QAA4B,KAAI;AACzC,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,mBAAmB,IAAI,8BAA8B;QAC/E,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;QAEjG,IAAI,CAAC,MAAM,EAAE;AACX,YAAA,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;YAC3D,OAAO,CAAC,KAAK,CAAC,CAAA,kBAAA,EAAqB,UAAU,CAAA,4BAAA,EAA+B,gBAAgB,CAAA,CAAE,CAAC;AAC/F,YAAA,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,CAAA,oDAAA,CAAsD,CAAC;QACtF;QAEA,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,CAAA,2CAAA,EAA8C,UAAU,CAAA,CAAE,CAAC;QACpG,OAAO,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC;AAC/D,IAAA,CAAC,CAAC,EAEF,GAAG,CAAC,MAAK;QACP,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC;QAC5F,OAAO,KAAK,CAAC;AACf,IAAA,CAAC,CAAC,EAEF,UAAU,CAAC,CAAC,KAAU,KAAI;AACxB,QAAA,OAAO,CAAC,KAAK,CAAC,gFAAgF,CAAC;AAC/F,QAAA,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC;IAChC,CAAC,CAAC,CACH;AACH;;AC3GA;;;;;;;;;;;;;;;;;AAiBG;AACG,SAAU,oBAAoB,CAAC,MAAsB,EAAA;AACzD,IAAA,OAAO,wBAAwB,CAAC;AAC9B,QAAA,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE;QAC9C,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,CAAC,CAAC;QACnF,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,8BAA8B,EAAE,KAAK,EAAE,IAAI;AACpF,KAAA,CAAC;AACJ;;AC/BA;;;;;AAKG;AAEH;;ACPA;;AAEG;;;;"}
package/index.d.ts ADDED
@@ -0,0 +1,84 @@
1
+ import { InjectionToken, EnvironmentProviders } from '@angular/core';
2
+
3
+ /** Default header name for public key exchange */
4
+ declare const DEFAULT_PUBLIC_KEY_HEADER_NAME = "X-Client-Init";
5
+ /** Default header name for AES key in request/response */
6
+ declare const DEFAULT_AES_KEY_HEADER_NAME = "X-Request-Context";
7
+ /** Default header name for IV in request/response */
8
+ declare const DEFAULT_IV_HEADER_NAME = "X-Client-Ref";
9
+ /** Security library configuration */
10
+ interface SecurityConfig {
11
+ /** Master switch: true = fetch key + encrypt/decrypt, false = bypass all (default: true) */
12
+ enableEncryption?: boolean;
13
+ /** Full public key endpoint URL (required if enableEncryption is true) */
14
+ publicKeyEndpoint?: string;
15
+ /** Expected SHA-256 hash for public key pinning (MITM protection) */
16
+ expectedPublicKeyHash?: string;
17
+ /** Endpoint patterns to encrypt: ['*'] = all, ['/api/*'] = glob, ['/exact/path'] = exact (default: ['*']) */
18
+ encryptDecryptForEndpoints?: string[];
19
+ /** Endpoint patterns to skip encryption (higher priority than encryptDecryptForEndpoints) */
20
+ skipEncryptDecryptForEndpoints?: string[];
21
+ /** Custom header name for public key exchange (default: 'X-Client-Init') */
22
+ publicKeyHeaderName?: string;
23
+ /** Custom header name for AES key in request/response (default: 'X-Request-Context') */
24
+ aesKeyHeaderName?: string;
25
+ /** Custom header name for IV in request/response (default: 'X-Client-Ref') */
26
+ ivHeaderName?: string;
27
+ /** Enable debug logging (default: true) */
28
+ enableDebugLogging?: boolean;
29
+ /** Retry attempts for public key fetch (default: 3) */
30
+ publicKeyFetchRetries?: number;
31
+ }
32
+ /** Injection token for security config */
33
+ declare const SECURITY_CONFIG: InjectionToken<SecurityConfig>;
34
+ /** Default configuration with placeholder values - replace with your actual values */
35
+ declare const DEFAULT_SECURITY_CONFIG: SecurityConfig;
36
+
37
+ /**
38
+ * Complete ng-secure-fetch provider: config + interceptors + APP_INITIALIZER
39
+ * App blocks until public key is fetched and verified
40
+ *
41
+ * @example
42
+ * provideNgSecureFetch({
43
+ * enableEncryption: true,
44
+ * publicKeyEndpoint: environment.apiUrl + '/crypto/init',
45
+ * expectedPublicKeyHash: 'your-sha256-hash-here',
46
+ * encryptDecryptForEndpoints: ['/insurance/*'],
47
+ * skipEncryptDecryptForEndpoints: ['/public/*'],
48
+ * publicKeyHeaderName: 'X-Client-Init',
49
+ * aesKeyHeaderName: 'X-Request-Context',
50
+ * ivHeaderName: 'X-Client-Ref',
51
+ * enableDebugLogging: false,
52
+ * publicKeyFetchRetries: 3
53
+ * })
54
+ */
55
+ declare function provideNgSecureFetch(config: SecurityConfig): EnvironmentProviders;
56
+
57
+ /**
58
+ * Encrypted payload structure for hybrid encryption
59
+ */
60
+ interface EncryptedPayload {
61
+ /**
62
+ * AES-GCM encrypted data (Base64 encoded)
63
+ */
64
+ encryptedData: string;
65
+ /**
66
+ * RSA-OAEP encrypted AES key (Base64 encoded)
67
+ */
68
+ encryptedAesKey: string;
69
+ /**
70
+ * RSA-OAEP encrypted IV (Base64 encoded)
71
+ */
72
+ iv: string;
73
+ }
74
+ /**
75
+ * Obfuscated encrypted envelope with short field names
76
+ */
77
+ interface EncryptedEnvelope {
78
+ d: string;
79
+ k: string;
80
+ s: string;
81
+ }
82
+
83
+ export { DEFAULT_AES_KEY_HEADER_NAME, DEFAULT_IV_HEADER_NAME, DEFAULT_PUBLIC_KEY_HEADER_NAME, DEFAULT_SECURITY_CONFIG, SECURITY_CONFIG, provideNgSecureFetch };
84
+ export type { EncryptedEnvelope, EncryptedPayload, SecurityConfig };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "ng-secure-fetch",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency Angular library providing RSA-2048-OAEP + AES-256-GCM hybrid encryption with automatic HTTP request/response encryption and sessionStorage caching",
5
+ "keywords": [
6
+ "angular",
7
+ "encryption",
8
+ "security",
9
+ "http",
10
+ "interceptor",
11
+ "rsa",
12
+ "aes",
13
+ "hybrid-encryption",
14
+ "web-crypto-api",
15
+ "zero-dependencies"
16
+ ],
17
+ "author": "Prathamesh Vasekar <prathameshvasekar4.com>",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/prathameshv4/ng-secure-fetch.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/prathameshv4/ng-secure-fetch/issues"
25
+ },
26
+ "homepage": "https://github.com/prathameshv4/ng-secure-fetch#readme",
27
+ "peerDependencies": {
28
+ "@angular/common": ">=17.0.0",
29
+ "@angular/core": ">=17.0.0"
30
+ },
31
+ "dependencies": {
32
+ "tslib": "^2.3.0"
33
+ },
34
+ "sideEffects": false,
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
38
+ "browser": {
39
+ "crypto": false
40
+ },
41
+ "module": "fesm2022/ng-secure-fetch.mjs",
42
+ "typings": "index.d.ts",
43
+ "exports": {
44
+ "./package.json": {
45
+ "default": "./package.json"
46
+ },
47
+ ".": {
48
+ "types": "./index.d.ts",
49
+ "default": "./fesm2022/ng-secure-fetch.mjs"
50
+ }
51
+ }
52
+ }