@angular-helpers/security 21.3.0 → 21.4.1
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.
package/README.md
CHANGED
|
@@ -76,6 +76,19 @@ Security package for Angular applications that prevents common attacks like ReDo
|
|
|
76
76
|
- **Verified auto-clear**: reads back the clipboard before clearing to avoid clobbering unrelated content.
|
|
77
77
|
- **Password-manager semantics**: default 15-second clear, configurable.
|
|
78
78
|
|
|
79
|
+
### **Session Inactivity Monitor**
|
|
80
|
+
|
|
81
|
+
- **NgZone-optimized**: DOM events tracked outside Angular change detection.
|
|
82
|
+
- **Security interop**: Can automatically clear SecureStorage and SensitiveClipboard upon timeout.
|
|
83
|
+
- **Warning states**: Configurable thresholds to warn users before expiration.
|
|
84
|
+
|
|
85
|
+
### **Secure Cross-Window Messaging**
|
|
86
|
+
|
|
87
|
+
- **HMAC-SHA-256 signatures**: Every message is signed; tampered payloads are discarded.
|
|
88
|
+
- **Origin whitelist**: Messages from non-allowed origins are rejected before any crypto work.
|
|
89
|
+
- **Anti-replay protection**: Envelope includes `timestamp + nonce`; messages older than 30s are discarded.
|
|
90
|
+
- **SSR-safe**: No-op on the server — no `window` access.
|
|
91
|
+
|
|
79
92
|
### **Builder Pattern**
|
|
80
93
|
|
|
81
94
|
- **Fluent API**: Intuitively build regular expressions.
|
|
@@ -568,6 +581,87 @@ export class ApiKeyPanel {
|
|
|
568
581
|
The service reads the clipboard before clearing and skips the clear if the content no longer
|
|
569
582
|
matches what was copied — so third-party copies by the user are never overwritten.
|
|
570
583
|
|
|
584
|
+
### **SessionIdleService**
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
import { SessionIdleService } from '@angular-helpers/security';
|
|
588
|
+
|
|
589
|
+
export class AppComponent {
|
|
590
|
+
private sessionIdle = inject(SessionIdleService);
|
|
591
|
+
|
|
592
|
+
ngOnInit() {
|
|
593
|
+
this.sessionIdle.start({
|
|
594
|
+
timeoutMs: 15 * 60 * 1000, // 15 minutes
|
|
595
|
+
warningThresholdMs: 60 * 1000, // 1 minute warning
|
|
596
|
+
autoClearStorage: true,
|
|
597
|
+
autoClearClipboard: true,
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// React to states
|
|
601
|
+
effect(() => {
|
|
602
|
+
if (this.sessionIdle.isWarning()) {
|
|
603
|
+
console.warn(`Session will expire in ${this.sessionIdle.timeRemaining()}ms`);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// React to timeout
|
|
608
|
+
this.sessionIdle.onTimeout.subscribe(() => {
|
|
609
|
+
this.authService.logout();
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
The service tracks DOM events (`mousemove`, `keydown`, etc.) outside the Angular Zone to prevent change detection spam. It can automatically clear `SecureStorageService` and `SensitiveClipboardService` when the session times out.
|
|
616
|
+
|
|
617
|
+
### **SecureMessageService**
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
import { SecureMessageService, provideSecureMessage } from '@angular-helpers/security';
|
|
621
|
+
|
|
622
|
+
// app.config.ts
|
|
623
|
+
bootstrapApplication(AppComponent, {
|
|
624
|
+
providers: [provideSecureMessage()],
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// parent-app.component.ts
|
|
628
|
+
export class ParentComponent {
|
|
629
|
+
private channel = inject(SecureMessageService);
|
|
630
|
+
|
|
631
|
+
async ngOnInit() {
|
|
632
|
+
// 1. Generate a shared key (transport it to the iframe via a secure channel)
|
|
633
|
+
const key = await this.channel.generateChannelKey();
|
|
634
|
+
|
|
635
|
+
// 2. Configure: only accept messages from the iframe origin
|
|
636
|
+
this.channel.configure({
|
|
637
|
+
allowedOrigins: ['https://child-app.example.com'],
|
|
638
|
+
signingKey: key,
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// 3. React to incoming messages
|
|
642
|
+
this.channel.messages$<{ type: string; payload: unknown }>().subscribe(({ data, origin }) => {
|
|
643
|
+
console.log('Verified message from', origin, data);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// 4. Or use the Signal for reactive UI
|
|
647
|
+
effect(() => {
|
|
648
|
+
const msg = this.channel.lastMessage()();
|
|
649
|
+
if (msg) console.log('Last message:', msg.data);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async sendToChild(iframe: HTMLIFrameElement) {
|
|
654
|
+
await this.channel.send(
|
|
655
|
+
iframe.contentWindow!,
|
|
656
|
+
{ type: 'INIT', payload: { userId: 42 } },
|
|
657
|
+
'https://child-app.example.com', // targetOrigin — never '*'
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
> **Key exchange**: `SecureMessageService` does not perform automatic key negotiation — transport the `CryptoKey` to the other context via a secure out-of-band channel (e.g., derive it from a shared secret using `WebCryptoService.generateHmacKey()` seeded by a passphrase). Once both sides share the key, all message integrity is handled automatically.
|
|
664
|
+
|
|
571
665
|
## 🔧 Advanced Configuration
|
|
572
666
|
|
|
573
667
|
### **Security Options**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DestroyRef, Injectable, PLATFORM_ID, InjectionToken, signal, computed, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
4
|
-
import { Observable } from 'rxjs';
|
|
2
|
+
import { inject, DestroyRef, Injectable, PLATFORM_ID, InjectionToken, signal, computed, NgZone, Injector, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser, DOCUMENT } from '@angular/common';
|
|
4
|
+
import { Observable, Subject, fromEvent, merge } from 'rxjs';
|
|
5
|
+
import { throttleTime } from 'rxjs/operators';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Security service for regular expressions that prevents ReDoS
|
|
@@ -1349,6 +1350,20 @@ class SensitiveClipboardService {
|
|
|
1349
1350
|
return 'read-denied';
|
|
1350
1351
|
}
|
|
1351
1352
|
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Forcefully clears the clipboard unconditionally.
|
|
1355
|
+
*/
|
|
1356
|
+
async clear() {
|
|
1357
|
+
if (!this.isSupported())
|
|
1358
|
+
return;
|
|
1359
|
+
this.cancelPendingClear();
|
|
1360
|
+
try {
|
|
1361
|
+
await navigator.clipboard.writeText('');
|
|
1362
|
+
}
|
|
1363
|
+
catch {
|
|
1364
|
+
// ignore
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1352
1367
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SensitiveClipboardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1353
1368
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SensitiveClipboardService });
|
|
1354
1369
|
}
|
|
@@ -1716,6 +1731,221 @@ function withCsrfHeader(options = {}) {
|
|
|
1716
1731
|
};
|
|
1717
1732
|
}
|
|
1718
1733
|
|
|
1734
|
+
const DEFAULT_EVENTS = ['mousemove', 'keydown', 'mousedown', 'touchstart', 'scroll'];
|
|
1735
|
+
class SessionIdleService {
|
|
1736
|
+
ngZone = inject(NgZone);
|
|
1737
|
+
document = inject(DOCUMENT);
|
|
1738
|
+
injector = inject(Injector);
|
|
1739
|
+
destroyRef = inject(DestroyRef);
|
|
1740
|
+
_isIdle = signal(false, ...(ngDevMode ? [{ debugName: "_isIdle" }] : /* istanbul ignore next */ []));
|
|
1741
|
+
_isWarning = signal(false, ...(ngDevMode ? [{ debugName: "_isWarning" }] : /* istanbul ignore next */ []));
|
|
1742
|
+
_timeRemaining = signal(null, ...(ngDevMode ? [{ debugName: "_timeRemaining" }] : /* istanbul ignore next */ []));
|
|
1743
|
+
_timeoutSubject = new Subject();
|
|
1744
|
+
isIdle = this._isIdle.asReadonly();
|
|
1745
|
+
isWarning = this._isWarning.asReadonly();
|
|
1746
|
+
timeRemaining = this._timeRemaining.asReadonly();
|
|
1747
|
+
onTimeout = this._timeoutSubject.asObservable();
|
|
1748
|
+
config = null;
|
|
1749
|
+
lastActivityTime = 0;
|
|
1750
|
+
timerInterval = null;
|
|
1751
|
+
eventSubscription;
|
|
1752
|
+
constructor() {
|
|
1753
|
+
this.destroyRef.onDestroy(() => this.stop());
|
|
1754
|
+
}
|
|
1755
|
+
start(config) {
|
|
1756
|
+
this.stop();
|
|
1757
|
+
this.config = config;
|
|
1758
|
+
this._isIdle.set(false);
|
|
1759
|
+
this._isWarning.set(false);
|
|
1760
|
+
this._timeRemaining.set(config.timeoutMs);
|
|
1761
|
+
this.lastActivityTime = Date.now();
|
|
1762
|
+
const eventsToTrack = config.events || DEFAULT_EVENTS;
|
|
1763
|
+
this.ngZone.runOutsideAngular(() => {
|
|
1764
|
+
const observables = eventsToTrack.map((ev) => fromEvent(this.document, ev));
|
|
1765
|
+
this.eventSubscription = merge(...observables)
|
|
1766
|
+
.pipe(throttleTime(500))
|
|
1767
|
+
.subscribe(() => {
|
|
1768
|
+
this.lastActivityTime = Date.now();
|
|
1769
|
+
});
|
|
1770
|
+
this.timerInterval = setInterval(() => this.checkIdle(), 1000);
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
stop() {
|
|
1774
|
+
if (this.timerInterval) {
|
|
1775
|
+
clearInterval(this.timerInterval);
|
|
1776
|
+
this.timerInterval = null;
|
|
1777
|
+
}
|
|
1778
|
+
if (this.eventSubscription) {
|
|
1779
|
+
this.eventSubscription.unsubscribe();
|
|
1780
|
+
this.eventSubscription = undefined;
|
|
1781
|
+
}
|
|
1782
|
+
this.config = null;
|
|
1783
|
+
this._timeRemaining.set(null);
|
|
1784
|
+
this._isIdle.set(false);
|
|
1785
|
+
this._isWarning.set(false);
|
|
1786
|
+
}
|
|
1787
|
+
reset() {
|
|
1788
|
+
if (!this.config)
|
|
1789
|
+
return;
|
|
1790
|
+
this.lastActivityTime = Date.now();
|
|
1791
|
+
this._timeRemaining.set(this.config.timeoutMs);
|
|
1792
|
+
this._isIdle.set(false);
|
|
1793
|
+
this._isWarning.set(false);
|
|
1794
|
+
}
|
|
1795
|
+
checkIdle() {
|
|
1796
|
+
if (!this.config || this._isIdle())
|
|
1797
|
+
return;
|
|
1798
|
+
const elapsed = Date.now() - this.lastActivityTime;
|
|
1799
|
+
const remaining = Math.max(0, this.config.timeoutMs - elapsed);
|
|
1800
|
+
if (remaining === 0) {
|
|
1801
|
+
this.triggerTimeout();
|
|
1802
|
+
}
|
|
1803
|
+
else {
|
|
1804
|
+
const warningThreshold = this.config.warningThresholdMs || 0;
|
|
1805
|
+
const shouldBeWarning = remaining <= warningThreshold;
|
|
1806
|
+
this.ngZone.run(() => {
|
|
1807
|
+
this._timeRemaining.set(remaining);
|
|
1808
|
+
if (this._isWarning() !== shouldBeWarning) {
|
|
1809
|
+
this._isWarning.set(shouldBeWarning);
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
triggerTimeout() {
|
|
1815
|
+
const config = this.config;
|
|
1816
|
+
this.stop();
|
|
1817
|
+
// Restore config temporarily to check for autoClear flags
|
|
1818
|
+
this.config = config;
|
|
1819
|
+
this.ngZone.run(() => {
|
|
1820
|
+
this._timeRemaining.set(0);
|
|
1821
|
+
this._isIdle.set(true);
|
|
1822
|
+
this._timeoutSubject.next();
|
|
1823
|
+
if (this.config?.autoClearStorage) {
|
|
1824
|
+
const storage = this.injector.get(SecureStorageService, null);
|
|
1825
|
+
if (storage)
|
|
1826
|
+
storage.clear();
|
|
1827
|
+
}
|
|
1828
|
+
if (this.config?.autoClearClipboard) {
|
|
1829
|
+
const clipboard = this.injector.get(SensitiveClipboardService, null);
|
|
1830
|
+
if (clipboard)
|
|
1831
|
+
clipboard.clear();
|
|
1832
|
+
}
|
|
1833
|
+
this.config = null;
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SessionIdleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1837
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SessionIdleService, providedIn: 'root' });
|
|
1838
|
+
}
|
|
1839
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SessionIdleService, decorators: [{
|
|
1840
|
+
type: Injectable,
|
|
1841
|
+
args: [{
|
|
1842
|
+
providedIn: 'root',
|
|
1843
|
+
}]
|
|
1844
|
+
}], ctorParameters: () => [] });
|
|
1845
|
+
|
|
1846
|
+
const REPLAY_WINDOW_MS = 30_000;
|
|
1847
|
+
class SecureMessageService {
|
|
1848
|
+
ngZone = inject(NgZone);
|
|
1849
|
+
platformId = inject(PLATFORM_ID);
|
|
1850
|
+
document = inject(DOCUMENT);
|
|
1851
|
+
crypto = inject(WebCryptoService);
|
|
1852
|
+
config = null;
|
|
1853
|
+
_lastMessage = signal(null, ...(ngDevMode ? [{ debugName: "_lastMessage" }] : /* istanbul ignore next */ []));
|
|
1854
|
+
_messages$ = new Subject();
|
|
1855
|
+
messageHandler = null;
|
|
1856
|
+
get targetWindow() {
|
|
1857
|
+
return this.document.defaultView;
|
|
1858
|
+
}
|
|
1859
|
+
/** Returns a new HMAC-SHA-256 CryptoKey ready to use in `configure()`. */
|
|
1860
|
+
generateChannelKey() {
|
|
1861
|
+
return this.crypto.generateHmacKey('HMAC-SHA-256');
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Configures the service with a signing key and allowed origins.
|
|
1865
|
+
* Starts listening for incoming messages.
|
|
1866
|
+
*/
|
|
1867
|
+
configure(config) {
|
|
1868
|
+
this.destroy();
|
|
1869
|
+
this.config = config;
|
|
1870
|
+
if (!isPlatformBrowser(this.platformId))
|
|
1871
|
+
return;
|
|
1872
|
+
this.ngZone.runOutsideAngular(() => {
|
|
1873
|
+
this.messageHandler = (event) => this.handleMessage(event);
|
|
1874
|
+
this.targetWindow.addEventListener('message', this.messageHandler);
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Signs and sends a payload to a target window.
|
|
1879
|
+
* @throws if `targetOrigin` is `'*'`
|
|
1880
|
+
*/
|
|
1881
|
+
async send(target, payload, targetOrigin) {
|
|
1882
|
+
if (targetOrigin === '*') {
|
|
1883
|
+
throw new Error('SecureMessageService: targetOrigin must be an explicit origin, not "*".');
|
|
1884
|
+
}
|
|
1885
|
+
if (!this.config) {
|
|
1886
|
+
throw new Error('SecureMessageService: call configure() before send().');
|
|
1887
|
+
}
|
|
1888
|
+
const timestamp = Date.now();
|
|
1889
|
+
const nonce = this.targetWindow.crypto.randomUUID();
|
|
1890
|
+
const body = { payload, timestamp, nonce };
|
|
1891
|
+
const signature = await this.crypto.sign(this.config.signingKey, JSON.stringify(body));
|
|
1892
|
+
const envelope = { __signed: true, ...body, signature };
|
|
1893
|
+
target.postMessage(envelope, targetOrigin);
|
|
1894
|
+
}
|
|
1895
|
+
/** Observable of verified incoming messages. */
|
|
1896
|
+
messages$() {
|
|
1897
|
+
return this._messages$.asObservable();
|
|
1898
|
+
}
|
|
1899
|
+
/** Signal with the last verified incoming message (null before first message). */
|
|
1900
|
+
lastMessage() {
|
|
1901
|
+
return this._lastMessage;
|
|
1902
|
+
}
|
|
1903
|
+
/** Removes the window listener and completes the internal Subject. */
|
|
1904
|
+
destroy() {
|
|
1905
|
+
if (this.messageHandler && isPlatformBrowser(this.platformId)) {
|
|
1906
|
+
this.targetWindow.removeEventListener('message', this.messageHandler);
|
|
1907
|
+
this.messageHandler = null;
|
|
1908
|
+
}
|
|
1909
|
+
this.config = null;
|
|
1910
|
+
}
|
|
1911
|
+
async handleMessage(event) {
|
|
1912
|
+
if (!this.config)
|
|
1913
|
+
return;
|
|
1914
|
+
// 1. Origin whitelist
|
|
1915
|
+
if (!this.config.allowedOrigins.includes(event.origin))
|
|
1916
|
+
return;
|
|
1917
|
+
// 2. Envelope shape
|
|
1918
|
+
const env = event.data;
|
|
1919
|
+
if (env?.__signed !== true)
|
|
1920
|
+
return;
|
|
1921
|
+
const { payload, timestamp, nonce, signature } = env;
|
|
1922
|
+
if (!payload || !timestamp || !nonce || !signature)
|
|
1923
|
+
return;
|
|
1924
|
+
// 3. Replay window
|
|
1925
|
+
if (Date.now() - timestamp > REPLAY_WINDOW_MS)
|
|
1926
|
+
return;
|
|
1927
|
+
// 4. Signature verification
|
|
1928
|
+
const body = { payload, timestamp, nonce };
|
|
1929
|
+
const valid = await this.crypto.verify(this.config.signingKey, JSON.stringify(body), signature);
|
|
1930
|
+
if (!valid)
|
|
1931
|
+
return;
|
|
1932
|
+
// 5. Emit inside NgZone so signals trigger CD
|
|
1933
|
+
this.ngZone.run(() => {
|
|
1934
|
+
const message = { data: payload, origin: event.origin, timestamp };
|
|
1935
|
+
this._lastMessage.set(message);
|
|
1936
|
+
this._messages$.next(message);
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SecureMessageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1940
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SecureMessageService, providedIn: 'root' });
|
|
1941
|
+
}
|
|
1942
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SecureMessageService, decorators: [{
|
|
1943
|
+
type: Injectable,
|
|
1944
|
+
args: [{
|
|
1945
|
+
providedIn: 'root',
|
|
1946
|
+
}]
|
|
1947
|
+
}] });
|
|
1948
|
+
|
|
1719
1949
|
const defaultSecurityConfig = {
|
|
1720
1950
|
enableRegexSecurity: true,
|
|
1721
1951
|
enableWebCrypto: true,
|
|
@@ -1727,6 +1957,8 @@ const defaultSecurityConfig = {
|
|
|
1727
1957
|
enableHibp: false,
|
|
1728
1958
|
enableRateLimiter: false,
|
|
1729
1959
|
enableCsrf: false,
|
|
1960
|
+
enableSessionIdle: false,
|
|
1961
|
+
enableSecureMessage: false,
|
|
1730
1962
|
defaultTimeout: 5000,
|
|
1731
1963
|
safeMode: false,
|
|
1732
1964
|
};
|
|
@@ -1755,6 +1987,10 @@ function provideSecurity(config = {}) {
|
|
|
1755
1987
|
if (mergedConfig.enableCsrf) {
|
|
1756
1988
|
providers.push(WebCryptoService, CsrfService);
|
|
1757
1989
|
}
|
|
1990
|
+
if (mergedConfig.enableSessionIdle)
|
|
1991
|
+
providers.push(SessionIdleService);
|
|
1992
|
+
if (mergedConfig.enableSecureMessage)
|
|
1993
|
+
providers.push(SecureMessageService);
|
|
1758
1994
|
return makeEnvironmentProviders(providers);
|
|
1759
1995
|
}
|
|
1760
1996
|
function provideRegexSecurity() {
|
|
@@ -1804,9 +2040,15 @@ function provideCsrf(config) {
|
|
|
1804
2040
|
...(config ? [{ provide: CSRF_CONFIG, useValue: config }] : []),
|
|
1805
2041
|
]);
|
|
1806
2042
|
}
|
|
2043
|
+
function provideSessionIdle() {
|
|
2044
|
+
return makeEnvironmentProviders([SessionIdleService]);
|
|
2045
|
+
}
|
|
2046
|
+
function provideSecureMessage() {
|
|
2047
|
+
return makeEnvironmentProviders([WebCryptoService, SecureMessageService]);
|
|
2048
|
+
}
|
|
1807
2049
|
|
|
1808
2050
|
/**
|
|
1809
2051
|
* Generated bundle index. Do not edit.
|
|
1810
2052
|
*/
|
|
1811
2053
|
|
|
1812
|
-
export { CSRF_CONFIG, ClipboardUnsupportedError, CsrfService, DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_TAGS, HIBP_CONFIG, HibpService, InputSanitizerService, InvalidJwtError, JwtService, PasswordStrengthService, RATE_LIMITER_CONFIG, RateLimitExceededError, RateLimiterService, RegexSecurityBuilder, RegexSecurityService, SANITIZER_CONFIG, SECURE_STORAGE_CONFIG, SecureStorageService, SensitiveClipboardService, WebCryptoService, assessPasswordStrength, containsScriptInjection, containsSqlInjectionHints, defaultSecurityConfig, isHtmlSafe, isUrlSafe, provideCsrf, provideHibp, provideInputSanitizer, provideJwt, providePasswordStrength, provideRateLimiter, provideRegexSecurity, provideSecureStorage, provideSecurity, provideSensitiveClipboard, provideWebCrypto, sanitizeHtmlString, sanitizeUrlString, withCsrfHeader };
|
|
2054
|
+
export { CSRF_CONFIG, ClipboardUnsupportedError, CsrfService, DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_TAGS, HIBP_CONFIG, HibpService, InputSanitizerService, InvalidJwtError, JwtService, PasswordStrengthService, RATE_LIMITER_CONFIG, RateLimitExceededError, RateLimiterService, RegexSecurityBuilder, RegexSecurityService, SANITIZER_CONFIG, SECURE_STORAGE_CONFIG, SecureMessageService, SecureStorageService, SensitiveClipboardService, SessionIdleService, WebCryptoService, assessPasswordStrength, containsScriptInjection, containsSqlInjectionHints, defaultSecurityConfig, isHtmlSafe, isUrlSafe, provideCsrf, provideHibp, provideInputSanitizer, provideJwt, providePasswordStrength, provideRateLimiter, provideRegexSecurity, provideSecureMessage, provideSecureStorage, provideSecurity, provideSensitiveClipboard, provideSessionIdle, provideWebCrypto, sanitizeHtmlString, sanitizeUrlString, withCsrfHeader };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-helpers/security",
|
|
3
|
-
"version": "21.
|
|
3
|
+
"version": "21.4.1",
|
|
4
4
|
"description": "Angular security helpers for preventing ReDoS and other security vulnerabilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
"xss",
|
|
17
17
|
"sanitization",
|
|
18
18
|
"password-strength",
|
|
19
|
-
"secure-storage"
|
|
19
|
+
"secure-storage",
|
|
20
|
+
"session-idle",
|
|
21
|
+
"inactivity",
|
|
22
|
+
"postmessage",
|
|
23
|
+
"iframe",
|
|
24
|
+
"secure-channel"
|
|
20
25
|
],
|
|
21
26
|
"author": "Angular Helpers Team",
|
|
22
27
|
"license": "MIT",
|
|
@@ -64,5 +69,6 @@
|
|
|
64
69
|
"default": "./fesm2022/angular-helpers-security-signal-forms.mjs"
|
|
65
70
|
}
|
|
66
71
|
},
|
|
67
|
-
"sideEffects": false
|
|
72
|
+
"sideEffects": false,
|
|
73
|
+
"type": "module"
|
|
68
74
|
}
|
|
@@ -537,6 +537,10 @@ declare class SensitiveClipboardService {
|
|
|
537
537
|
*/
|
|
538
538
|
cancelPendingClear(): void;
|
|
539
539
|
private safeClear;
|
|
540
|
+
/**
|
|
541
|
+
* Forcefully clears the clipboard unconditionally.
|
|
542
|
+
*/
|
|
543
|
+
clear(): Promise<void>;
|
|
540
544
|
static ɵfac: i0.ɵɵFactoryDeclaration<SensitiveClipboardService, never>;
|
|
541
545
|
static ɵprov: i0.ɵɵInjectableDeclaration<SensitiveClipboardService>;
|
|
542
546
|
}
|
|
@@ -745,6 +749,82 @@ interface CsrfHeaderOptions {
|
|
|
745
749
|
*/
|
|
746
750
|
declare function withCsrfHeader(options?: CsrfHeaderOptions): HttpInterceptorFn;
|
|
747
751
|
|
|
752
|
+
interface SessionIdleConfig {
|
|
753
|
+
timeoutMs: number;
|
|
754
|
+
warningThresholdMs?: number;
|
|
755
|
+
autoClearStorage?: boolean;
|
|
756
|
+
autoClearClipboard?: boolean;
|
|
757
|
+
events?: string[];
|
|
758
|
+
}
|
|
759
|
+
declare class SessionIdleService {
|
|
760
|
+
private ngZone;
|
|
761
|
+
private document;
|
|
762
|
+
private injector;
|
|
763
|
+
private destroyRef;
|
|
764
|
+
private _isIdle;
|
|
765
|
+
private _isWarning;
|
|
766
|
+
private _timeRemaining;
|
|
767
|
+
private _timeoutSubject;
|
|
768
|
+
readonly isIdle: Signal<boolean>;
|
|
769
|
+
readonly isWarning: Signal<boolean>;
|
|
770
|
+
readonly timeRemaining: Signal<number | null>;
|
|
771
|
+
readonly onTimeout: Observable<void>;
|
|
772
|
+
private config;
|
|
773
|
+
private lastActivityTime;
|
|
774
|
+
private timerInterval;
|
|
775
|
+
private eventSubscription?;
|
|
776
|
+
constructor();
|
|
777
|
+
start(config: SessionIdleConfig): void;
|
|
778
|
+
stop(): void;
|
|
779
|
+
reset(): void;
|
|
780
|
+
private checkIdle;
|
|
781
|
+
private triggerTimeout;
|
|
782
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SessionIdleService, never>;
|
|
783
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<SessionIdleService>;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
interface SecureMessageConfig {
|
|
787
|
+
allowedOrigins: string[];
|
|
788
|
+
signingKey: CryptoKey;
|
|
789
|
+
}
|
|
790
|
+
interface SecureMessage<T = unknown> {
|
|
791
|
+
data: T;
|
|
792
|
+
origin: string;
|
|
793
|
+
timestamp: number;
|
|
794
|
+
}
|
|
795
|
+
declare class SecureMessageService {
|
|
796
|
+
private readonly ngZone;
|
|
797
|
+
private readonly platformId;
|
|
798
|
+
private readonly document;
|
|
799
|
+
private readonly crypto;
|
|
800
|
+
private config;
|
|
801
|
+
private _lastMessage;
|
|
802
|
+
private _messages$;
|
|
803
|
+
private messageHandler;
|
|
804
|
+
private get targetWindow();
|
|
805
|
+
/** Returns a new HMAC-SHA-256 CryptoKey ready to use in `configure()`. */
|
|
806
|
+
generateChannelKey(): Promise<CryptoKey>;
|
|
807
|
+
/**
|
|
808
|
+
* Configures the service with a signing key and allowed origins.
|
|
809
|
+
* Starts listening for incoming messages.
|
|
810
|
+
*/
|
|
811
|
+
configure(config: SecureMessageConfig): void;
|
|
812
|
+
/**
|
|
813
|
+
* Signs and sends a payload to a target window.
|
|
814
|
+
* @throws if `targetOrigin` is `'*'`
|
|
815
|
+
*/
|
|
816
|
+
send<T>(target: Window, payload: T, targetOrigin: string): Promise<void>;
|
|
817
|
+
/** Observable of verified incoming messages. */
|
|
818
|
+
messages$<T = unknown>(): Observable<SecureMessage<T>>;
|
|
819
|
+
/** Signal with the last verified incoming message (null before first message). */
|
|
820
|
+
lastMessage<T = unknown>(): Signal<SecureMessage<T> | null>;
|
|
821
|
+
/** Removes the window listener and completes the internal Subject. */
|
|
822
|
+
destroy(): void;
|
|
823
|
+
private handleMessage;
|
|
824
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SecureMessageService, never>;
|
|
825
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<SecureMessageService>;
|
|
826
|
+
}
|
|
827
|
+
|
|
748
828
|
interface SecurityConfig {
|
|
749
829
|
enableRegexSecurity?: boolean;
|
|
750
830
|
enableWebCrypto?: boolean;
|
|
@@ -756,6 +836,8 @@ interface SecurityConfig {
|
|
|
756
836
|
enableHibp?: boolean;
|
|
757
837
|
enableRateLimiter?: boolean;
|
|
758
838
|
enableCsrf?: boolean;
|
|
839
|
+
enableSessionIdle?: boolean;
|
|
840
|
+
enableSecureMessage?: boolean;
|
|
759
841
|
defaultTimeout?: number;
|
|
760
842
|
safeMode?: boolean;
|
|
761
843
|
}
|
|
@@ -771,6 +853,8 @@ declare function provideSensitiveClipboard(): EnvironmentProviders;
|
|
|
771
853
|
declare function provideHibp(config?: HibpConfig): EnvironmentProviders;
|
|
772
854
|
declare function provideRateLimiter(config?: RateLimiterConfig): EnvironmentProviders;
|
|
773
855
|
declare function provideCsrf(config?: CsrfConfig): EnvironmentProviders;
|
|
856
|
+
declare function provideSessionIdle(): EnvironmentProviders;
|
|
857
|
+
declare function provideSecureMessage(): EnvironmentProviders;
|
|
774
858
|
|
|
775
|
-
export { CSRF_CONFIG, ClipboardUnsupportedError, CsrfService, DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_TAGS, HIBP_CONFIG, HibpService, InputSanitizerService, InvalidJwtError, JwtService, PasswordStrengthService, RATE_LIMITER_CONFIG, RateLimitExceededError, RateLimiterService, RegexSecurityBuilder, RegexSecurityService, SANITIZER_CONFIG, SECURE_STORAGE_CONFIG, SecureStorageService, SensitiveClipboardService, WebCryptoService, assessPasswordStrength, containsScriptInjection, containsSqlInjectionHints, defaultSecurityConfig, isHtmlSafe, isUrlSafe, provideCsrf, provideHibp, provideInputSanitizer, provideJwt, providePasswordStrength, provideRateLimiter, provideRegexSecurity, provideSecureStorage, provideSecurity, provideSensitiveClipboard, provideWebCrypto, sanitizeHtmlString, sanitizeUrlString, withCsrfHeader };
|
|
776
|
-
export type { AesEncryptResult, AesKeyLength, CopyStatus, CsrfConfig, CsrfHeaderOptions, CsrfStorageTarget, HashAlgorithm, HibpConfig, HibpResult, HmacAlgorithm, HtmlSanitizerOptions, HttpMethod, JwtStandardClaims, PasswordAssessment, PasswordLabel, PasswordScore, PasswordStrengthResult, RateLimitPolicy, RateLimiterConfig, RegexBuilderOptions, RegexSecurityConfig, RegexSecurityResult, RegexTestResult, SanitizerConfig, SecureStorageConfig, SecurityConfig, SensitiveCopyOptions, StorageTarget };
|
|
859
|
+
export { CSRF_CONFIG, ClipboardUnsupportedError, CsrfService, DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_TAGS, HIBP_CONFIG, HibpService, InputSanitizerService, InvalidJwtError, JwtService, PasswordStrengthService, RATE_LIMITER_CONFIG, RateLimitExceededError, RateLimiterService, RegexSecurityBuilder, RegexSecurityService, SANITIZER_CONFIG, SECURE_STORAGE_CONFIG, SecureMessageService, SecureStorageService, SensitiveClipboardService, SessionIdleService, WebCryptoService, assessPasswordStrength, containsScriptInjection, containsSqlInjectionHints, defaultSecurityConfig, isHtmlSafe, isUrlSafe, provideCsrf, provideHibp, provideInputSanitizer, provideJwt, providePasswordStrength, provideRateLimiter, provideRegexSecurity, provideSecureMessage, provideSecureStorage, provideSecurity, provideSensitiveClipboard, provideSessionIdle, provideWebCrypto, sanitizeHtmlString, sanitizeUrlString, withCsrfHeader };
|
|
860
|
+
export type { AesEncryptResult, AesKeyLength, CopyStatus, CsrfConfig, CsrfHeaderOptions, CsrfStorageTarget, HashAlgorithm, HibpConfig, HibpResult, HmacAlgorithm, HtmlSanitizerOptions, HttpMethod, JwtStandardClaims, PasswordAssessment, PasswordLabel, PasswordScore, PasswordStrengthResult, RateLimitPolicy, RateLimiterConfig, RegexBuilderOptions, RegexSecurityConfig, RegexSecurityResult, RegexTestResult, SanitizerConfig, SecureMessage, SecureMessageConfig, SecureStorageConfig, SecurityConfig, SensitiveCopyOptions, SessionIdleConfig, StorageTarget };
|