@angular-helpers/security 21.4.2 → 21.4.3
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.
|
@@ -922,6 +922,7 @@ const DEFAULT_ALLOWED_TAGS = [
|
|
|
922
922
|
const DEFAULT_ALLOWED_ATTRIBUTES = {
|
|
923
923
|
a: ['href'],
|
|
924
924
|
};
|
|
925
|
+
const URL_ATTRIBUTES = new Set(['href', 'src', 'action', 'formaction', 'data', 'poster']);
|
|
925
926
|
/**
|
|
926
927
|
* Sanitizes an HTML string by allowlist. Browser-only: requires DOMParser.
|
|
927
928
|
*
|
|
@@ -980,7 +981,7 @@ function sanitizeElementAttributes(element, tagName, allowedAttributes) {
|
|
|
980
981
|
attrsToRemove.push(attr.name);
|
|
981
982
|
continue;
|
|
982
983
|
}
|
|
983
|
-
if (attr.name
|
|
984
|
+
if (URL_ATTRIBUTES.has(attr.name) && sanitizeUrlString(attr.value) === null) {
|
|
984
985
|
attrsToRemove.push(attr.name);
|
|
985
986
|
}
|
|
986
987
|
}
|
|
@@ -1512,13 +1513,23 @@ class RateLimiterService {
|
|
|
1512
1513
|
this.configure(key, policy);
|
|
1513
1514
|
}
|
|
1514
1515
|
}
|
|
1515
|
-
this.destroyRef.onDestroy(() =>
|
|
1516
|
+
this.destroyRef.onDestroy(() => {
|
|
1517
|
+
for (const bucket of this.buckets.values()) {
|
|
1518
|
+
if (bucket.timerId)
|
|
1519
|
+
clearTimeout(bucket.timerId);
|
|
1520
|
+
}
|
|
1521
|
+
this.buckets.clear();
|
|
1522
|
+
});
|
|
1516
1523
|
}
|
|
1517
1524
|
/**
|
|
1518
1525
|
* Registers or updates the policy for `key`. Re-configuring an existing key resets its state.
|
|
1519
1526
|
*/
|
|
1520
1527
|
configure(key, policy) {
|
|
1521
1528
|
validatePolicy(policy);
|
|
1529
|
+
const existing = this.buckets.get(key);
|
|
1530
|
+
if (existing && existing.timerId) {
|
|
1531
|
+
clearTimeout(existing.timerId);
|
|
1532
|
+
}
|
|
1522
1533
|
const now = Date.now();
|
|
1523
1534
|
const initialRemaining = policy.type === 'token-bucket' ? policy.capacity : policy.max;
|
|
1524
1535
|
this.buckets.set(key, {
|
|
@@ -1553,6 +1564,7 @@ class RateLimiterService {
|
|
|
1553
1564
|
}
|
|
1554
1565
|
bucket.tokens -= tokens;
|
|
1555
1566
|
bucket.remaining.set(Math.floor(bucket.tokens));
|
|
1567
|
+
this.scheduleAutoRefill(key, bucket);
|
|
1556
1568
|
return;
|
|
1557
1569
|
}
|
|
1558
1570
|
// sliding-window
|
|
@@ -1566,6 +1578,7 @@ class RateLimiterService {
|
|
|
1566
1578
|
for (let i = 0; i < tokens; i++)
|
|
1567
1579
|
bucket.timestamps.push(now);
|
|
1568
1580
|
bucket.remaining.set(bucket.policy.max - bucket.timestamps.length);
|
|
1581
|
+
this.scheduleAutoRefill(key, bucket);
|
|
1569
1582
|
}
|
|
1570
1583
|
/**
|
|
1571
1584
|
* Reactive signal indicating whether a single unit can be consumed from `key` right now.
|
|
@@ -1594,6 +1607,10 @@ class RateLimiterService {
|
|
|
1594
1607
|
const bucket = this.buckets.get(key);
|
|
1595
1608
|
if (!bucket)
|
|
1596
1609
|
return;
|
|
1610
|
+
if (bucket.timerId) {
|
|
1611
|
+
clearTimeout(bucket.timerId);
|
|
1612
|
+
bucket.timerId = undefined;
|
|
1613
|
+
}
|
|
1597
1614
|
bucket.timestamps = [];
|
|
1598
1615
|
if (bucket.policy.type === 'token-bucket') {
|
|
1599
1616
|
bucket.tokens = bucket.policy.capacity;
|
|
@@ -1615,6 +1632,41 @@ class RateLimiterService {
|
|
|
1615
1632
|
bucket.lastRefillAt = now;
|
|
1616
1633
|
bucket.remaining.set(Math.floor(bucket.tokens));
|
|
1617
1634
|
}
|
|
1635
|
+
scheduleAutoRefill(key, bucket) {
|
|
1636
|
+
if (bucket.timerId) {
|
|
1637
|
+
clearTimeout(bucket.timerId);
|
|
1638
|
+
bucket.timerId = undefined;
|
|
1639
|
+
}
|
|
1640
|
+
const now = Date.now();
|
|
1641
|
+
if (bucket.policy.type === 'token-bucket') {
|
|
1642
|
+
if (bucket.tokens >= bucket.policy.capacity) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
const currentFloor = Math.floor(bucket.tokens);
|
|
1646
|
+
const nextInteger = currentFloor + 1;
|
|
1647
|
+
const deficit = nextInteger - bucket.tokens;
|
|
1648
|
+
const timeToNextTokenMs = Math.max(10, Math.ceil((deficit / bucket.policy.refillPerSecond) * 1000));
|
|
1649
|
+
bucket.timerId = setTimeout(() => {
|
|
1650
|
+
const tNow = Date.now();
|
|
1651
|
+
this.refillTokenBucket(bucket, tNow);
|
|
1652
|
+
this.scheduleAutoRefill(key, bucket);
|
|
1653
|
+
}, timeToNextTokenMs);
|
|
1654
|
+
}
|
|
1655
|
+
else {
|
|
1656
|
+
// sliding-window
|
|
1657
|
+
const windowStart = now - bucket.policy.windowMs;
|
|
1658
|
+
bucket.timestamps = bucket.timestamps.filter((t) => t > windowStart);
|
|
1659
|
+
bucket.remaining.set(bucket.policy.max - bucket.timestamps.length);
|
|
1660
|
+
if (bucket.timestamps.length === 0) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
const oldest = bucket.timestamps[0];
|
|
1664
|
+
const timeToExpiryMs = Math.max(10, oldest + bucket.policy.windowMs - now);
|
|
1665
|
+
bucket.timerId = setTimeout(() => {
|
|
1666
|
+
this.scheduleAutoRefill(key, bucket);
|
|
1667
|
+
}, timeToExpiryMs);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1618
1670
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: RateLimiterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1619
1671
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: RateLimiterService });
|
|
1620
1672
|
}
|
package/package.json
CHANGED
|
@@ -675,6 +675,7 @@ declare class RateLimiterService {
|
|
|
675
675
|
*/
|
|
676
676
|
reset(key: string): void;
|
|
677
677
|
private refillTokenBucket;
|
|
678
|
+
private scheduleAutoRefill;
|
|
678
679
|
static ɵfac: i0.ɵɵFactoryDeclaration<RateLimiterService, never>;
|
|
679
680
|
static ɵprov: i0.ɵɵInjectableDeclaration<RateLimiterService>;
|
|
680
681
|
}
|