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 +21 -0
- package/README.md +410 -0
- package/fesm2022/ng-secure-fetch.mjs +557 -0
- package/fesm2022/ng-secure-fetch.mjs.map +1 -0
- package/index.d.ts +84 -0
- package/package.json +52 -0
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":"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"}
|
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
|
+
}
|