@universis/janitor 2.1.1 → 2.4.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.
package/dist/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import 'core-js/modules/es.string.replace-all';
2
2
  import { ApplicationService, TraceUtils, ConfigurationStrategy, Args, HttpForbiddenError, DataError, HttpError } from '@themost/common';
3
3
  import { rateLimit } from 'express-rate-limit';
4
- import express from 'express';
4
+ import express, { Router } from 'express';
5
5
  import path from 'path';
6
6
  import { BehaviorSubject } from 'rxjs';
7
7
  import slowDown from 'express-slow-down';
@@ -9,10 +9,17 @@ import { RedisStore } from 'rate-limit-redis';
9
9
  import { Redis } from 'ioredis';
10
10
  import '@themost/promise-sequence';
11
11
  import url, { URL } from 'url';
12
+ import { ScopeString as ScopeString$1 } from '@universis/janitor';
13
+ import '@themost/express';
12
14
  import { Request } from 'superagent';
13
15
  import jwt from 'jsonwebtoken';
14
16
  import BearerStrategy from 'passport-http-bearer';
15
17
  import passport from 'passport';
18
+ import cloneDeep from 'lodash/cloneDeep';
19
+ import MD5 from 'crypto-js/md5';
20
+ import { ValueFormatter, UserService, SchemaLoaderStrategy } from '@themost/data';
21
+ import ipRangeCheck from 'ip-range-check';
22
+ import { take } from 'rxjs/operators';
16
23
 
17
24
  class RateLimitService extends ApplicationService {
18
25
  /**
@@ -802,23 +809,106 @@ class ExtendScopeAccessConfiguration extends ApplicationService {
802
809
  }
803
810
  }
804
811
 
805
- function validateScope() {
806
- return (req, res, next) => {
812
+ class InvalidClientScopeError extends HttpForbiddenError {
813
+ constructor() {
814
+ super('Access denied due to invalid client scopes.');
815
+ }
816
+ }
817
+
818
+ class VerifyUserClientScope extends ApplicationService {
819
+
820
+ constructor(app) {
821
+ super(app);
822
+ // Sets the pre-defined OpenID Connect client scopes that are whitelisted and should be omitted during validation
823
+ this.options = {
824
+ whitelist: [
825
+ 'profile',
826
+ 'email',
827
+ 'openid']
828
+
829
+ };
830
+ }
831
+
832
+ /**
833
+ * Verify user scope access against its assigned groups,
834
+ * following the client scopes that are available for the user groups.
835
+ * @param {ExpressDataContext} context
836
+ * @param {boolean=} throwError
837
+ * @returns {Promise<boolean>}
838
+ */
839
+ async verify(context, throwError) {
840
+ // noinspection JSUnresolvedReference
841
+ const user = await context.getUser();
842
+ if (user == null) {
843
+ return false;
844
+ }
845
+ const groups = user.groups.map((group) => group.id);
846
+ /**
847
+ * @type {import('@themost/data').DataModel}
848
+ */
849
+ const ClientScopes = context.model('Group').convert({}).property('scopes').getBaseModel();
850
+ // set conditional caching
851
+ ClientScopes.caching = 'conditional';
852
+ /**
853
+ * get client scopes assigned to user groups
854
+ * @type {Array<{ scope: string }>}
855
+ */
856
+ const items = await ClientScopes.where('group').equal(groups).select('scope').cache(true).take(-1).silent().getItems();
807
857
  /**
808
- * @type {ScopeAccessConfiguration}
858
+ * @type {string[]}
809
859
  */
810
- let scopeAccessConfiguration = req.context.getApplication().getConfiguration().getStrategy(ScopeAccessConfiguration);
811
- if (typeof scopeAccessConfiguration === 'undefined') {
812
- return next(new Error('Invalid application configuration. Scope access configuration strategy is missing or is in accessible.'));
860
+ const assignedScopes = items.map((item) => item.scope);
861
+ const contextScopes = new ScopeString$1(context.user.authenticationScope).split().filter((scope) => {
862
+ return this.options.whitelist.includes(scope) === false;
863
+ });
864
+
865
+ const unassigned = contextScopes.filter((scope) => {
866
+ return !assignedScopes.includes(scope);
867
+ });
868
+ if (unassigned.length) {
869
+ if (!!throwError) {
870
+ TraceUtils.error(`Access denied for user "${context.user.name}" due to invalid client scope(s) "${unassigned.join(',')}"`);
871
+ throw new InvalidClientScopeError();
872
+ }
873
+ return false;
813
874
  }
814
- scopeAccessConfiguration.verify(req).then((value) => {
815
- if (value) {
875
+ return true;
876
+ }
877
+ }
878
+
879
+ class DisableUserClientScopeVerification extends VerifyUserClientScope {
880
+ constructor(app) {
881
+ super(app);
882
+ }
883
+ async verify(context, throwError) {
884
+ return true;
885
+ }
886
+ }
887
+
888
+ function validateClientScope() {
889
+ return function (req, res, next) {
890
+ try {
891
+ /**
892
+ * @type {VerifyUserClientScope}
893
+ */
894
+ const service = req.context.getApplication().getService(VerifyUserClientScope);
895
+ if (service == null) {
816
896
  return next();
817
897
  }
818
- return next(new HttpForbiddenError('Access denied due to authorization scopes.'));
819
- }).catch((reason) => {
820
- return next(reason);
821
- });
898
+ void service.verify(req).then(() => {
899
+ return next();
900
+ }).catch((reason) => {
901
+ return next(reason);
902
+ });
903
+ } catch (err) {
904
+ return next(err);
905
+ }
906
+ };
907
+ }
908
+
909
+ function validateScope() {
910
+ return (req, res, next) => {
911
+ return validateClientScope();
822
912
  };
823
913
  }
824
914
 
@@ -1335,5 +1425,489 @@ class PassportService extends ApplicationService {
1335
1425
 
1336
1426
  }
1337
1427
 
1338
- export { AppRateLimitService, AppSpeedLimitService, DefaultScopeAccessConfiguration, EnableScopeAccessConfiguration, ExtendScopeAccessConfiguration, HttpAccountDisabled, HttpBearerStrategy, HttpBearerTokenExpired, HttpBearerTokenNotFound, HttpBearerTokenRequired, HttpRemoteAddrForbiddenError, OAuth2ClientService, PassportService, RateLimitService, RedisClientStore, RemoteAddressValidator, ScopeAccessConfiguration, ScopeString, SpeedLimitService, validateScope };
1428
+ /**
1429
+ *
1430
+ * @param {{profiles:Map<string, import('./outputCacheRouter').OutputCachingProfile>,elements: import('./outputCacheRouter').OutputCachingElement[]}} options
1431
+ * @returns {import('express').Router}
1432
+ */
1433
+ function outputCache({ profiles, elements }) {
1434
+ const router = new Router();
1435
+ // prepare regular expressions
1436
+ const outputCacheElements = Array.isArray(elements) ? elements : [];
1437
+ outputCacheElements.forEach((element) => {
1438
+ if (!(element.expr instanceof RegExp)) {
1439
+ element.expr = new RegExp('^' + element.resource, 'g');
1440
+ }
1441
+ });
1442
+ router.use((req, res, next) => {
1443
+ // only GET requests are cacheable
1444
+ if (req.method !== 'GET') {
1445
+ return next();
1446
+ }
1447
+ // if cache-control header is set to no-cache then skip caching
1448
+ const cacheControl = req.headers['cache-control'];
1449
+ if (cacheControl && cacheControl.includes('no-cache')) {
1450
+ return next();
1451
+ }
1452
+ for (let element of outputCacheElements) {
1453
+ if (element.expr.test(req.path)) {
1454
+ let profile;
1455
+ if (typeof element.profile === 'string') {
1456
+ profile = profiles.get(element.profile);
1457
+ } else if (typeof element.profile === 'object') {
1458
+ profile = element.profile;
1459
+ }
1460
+ if (profile) {
1461
+ // clone profile to avoid mutation
1462
+ Object.assign(req, {
1463
+ outputCaching: cloneDeep(profile)
1464
+ });
1465
+ }
1466
+ return next();
1467
+ }
1468
+ }
1469
+ return next();
1470
+ });
1471
+ router.use((req, res, next) => {
1472
+ try {
1473
+ const shouldCached = req.outputCaching && (req.outputCaching.location === 'any' || req.outputCaching.location === 'client');
1474
+ if (!shouldCached) {
1475
+ return next();
1476
+ }
1477
+ // determine cache location
1478
+ let cacheLocation = req.outputCaching.location === 'any' ? 'public' : 'private';
1479
+ if (req.headers.authorization && req.headers.authorization.length > 0) {
1480
+ // if authorization header is present, we should not cache the response publicly
1481
+ cacheLocation = 'private';
1482
+ }
1483
+ if (req.outputCaching.duration) {
1484
+ res.setHeader('Cache-Control', `${cacheLocation}, max-age=${req.outputCaching.duration}`);
1485
+ } else {
1486
+ res.setHeader('Cache-Control', cacheLocation);
1487
+ }
1488
+ const { varyByContentEncoding, varyByHeader, varyByParam, location, duration } = req.outputCaching;
1489
+ if (varyByContentEncoding) {
1490
+ res.setHeader('Vary', 'Accept-Encoding');
1491
+ }
1492
+ const varyByReqHeader = varyByHeader || [];
1493
+ if (varyByReqHeader.length) {
1494
+ res.setHeader('Vary', varyByReqHeader.join(', '));
1495
+ }
1496
+ let varyByQueryParam = varyByParam || [];
1497
+ if (varyByQueryParam.length === 1 && varyByQueryParam[0] === '*') {
1498
+ varyByQueryParam = Object.keys(req.query).sort((a, b) => {
1499
+ if (a < b) {
1500
+ return -1;
1501
+ }
1502
+ if (a > b) {
1503
+ return 1;
1504
+ }
1505
+ return 0;
1506
+ });
1507
+ }
1508
+
1509
+ const headers = varyByReqHeader.reduce((acc, val) => {
1510
+ acc[val] = req.get(val);
1511
+ return acc;
1512
+ }, {});
1513
+
1514
+ const params = varyByQueryParam.reduce((acc, val) => {
1515
+ if (req.query[val]) {
1516
+ acc[val] = req.query[val];
1517
+ }
1518
+ return acc;
1519
+ }, {});
1520
+
1521
+ // vary by query parameters is not supported by HTTP caching, but we can use it to create a cache key
1522
+ const entry = {
1523
+ path: req.path,
1524
+ location: location,
1525
+ contentEncoding: varyByContentEncoding,
1526
+ headers,
1527
+ params,
1528
+ customParams: null,
1529
+ duration: duration
1530
+ };
1531
+
1532
+ (async () => {
1533
+ const { varyByExpr } = req.outputCaching;
1534
+ if (varyByExpr && typeof varyByExpr === 'object') {
1535
+ // noinspection JSCheckFunctionSignatures
1536
+ const formatter = new ValueFormatter(req.context, null, req);
1537
+ entry.customParams = await formatter.format(varyByExpr);
1538
+ }
1539
+ return `W/"${MD5(JSON.stringify({
1540
+ path: entry.path,
1541
+ location: entry.location,
1542
+ contentEncoding: entry.contentEncoding,
1543
+ headers: entry.headers,
1544
+ params: entry.params,
1545
+ customParams: entry.customParams,
1546
+ duration: entry.duration
1547
+ })).toString()}"`;
1548
+ })().then((entityTag) => {
1549
+ if (req.headers['if-none-match'] === entityTag) {
1550
+ return res.status(304).end();
1551
+ }
1552
+ res.setHeader('ETag', entityTag);
1553
+ res.setHeader('Date', new Date().toUTCString());
1554
+ return next();
1555
+ }).catch((err) => {
1556
+ return next(err);
1557
+ });
1558
+ } catch (err) {
1559
+ return next(err);
1560
+ }
1561
+ });
1562
+ return router;
1563
+ }
1564
+
1565
+ class OutputCachingService extends ApplicationService {
1566
+ constructor(app) {
1567
+ super(app);
1568
+ let options = app.getConfiguration().getSourceAt('settings/universis/outputCaching') || {
1569
+ profiles: [],
1570
+ elements: []
1571
+ };
1572
+ if (typeof options.extends === 'string') {
1573
+ const configurationDir = this.getApplication().getConfiguration().getConfigurationPath();
1574
+ const parentOptions = require(path.resolve(configurationDir, options.extends));
1575
+ options = Object.assign({}, parentOptions, options);
1576
+ }
1577
+ /**
1578
+ * @type {Map<string, OutputCachingProfile>}
1579
+ */
1580
+ this.profiles = new Map(options.profiles || []);
1581
+ /**
1582
+ * @type {OutputCachingElement[]}
1583
+ */
1584
+ this.elements = options.elements || [];
1585
+ if (app && app.container) {
1586
+ app.container.subscribe((container) => {
1587
+ if (container == null) {
1588
+ return;
1589
+ }
1590
+ container.use(outputCache({
1591
+ profiles: this.profiles,
1592
+ elements: this.elements
1593
+ }));
1594
+ const stack = container._router && container._router.stack;
1595
+ if (Array.isArray(stack)) {
1596
+ let index = stack.findIndex((item) => item.name === 'dataContextMiddleware');
1597
+ if (index > -1) {
1598
+ stack.splice(index, 0, stack.pop());
1599
+ }
1600
+ }
1601
+ });
1602
+ }
1603
+ }
1604
+ }
1605
+
1606
+ class UserWithNetworkAddress extends UserService {
1607
+ constructor(app) {
1608
+ super(app);
1609
+ }
1610
+
1611
+ /**
1612
+ * @protected
1613
+ * @param {import('@themost/data').DataContext} context
1614
+ * @param {string} name
1615
+ * @return {import('@themost/data').DataQueryable}
1616
+ */
1617
+ queryUser(context, name) {
1618
+ return context.model('User').asQueryable().expand('groups', 'networkAddress').
1619
+ where('name').equal(name);
1620
+ }
1621
+
1622
+ /**
1623
+ * @protected
1624
+ * @param {import('@themost/data').DataContext} context
1625
+ * @param {string} name
1626
+ * @return {import('@themost/data').DataQueryable}
1627
+ */
1628
+ queryGroup(context, name) {
1629
+ return context.model('Group').asQueryable().expand('groups', 'networkAddress').
1630
+ where('name').equal(name);
1631
+ }
1632
+
1633
+ /**
1634
+ * Gets user by name
1635
+ * @param context
1636
+ * @param name
1637
+ * @returns {Promise<any>}
1638
+ */
1639
+ getUser(context, name) {
1640
+ // noinspection JSCheckFunctionSignatures
1641
+ /**
1642
+ * @type {import('./data-cache').DataCacheStrategy}
1643
+ */
1644
+ // noinspection JSCheckFunctionSignatures
1645
+ const cache = context.getConfiguration().getStrategy(function DataCacheStrategy() {});
1646
+ if (cache) {
1647
+ return cache.getOrDefault(`/User/${name}`, () => {
1648
+ return this.queryUser(context, name).silent().getItem().then((user) => {
1649
+ return user;
1650
+ });
1651
+ });
1652
+ }
1653
+ return this.queryUser(context, name).silent().getItem().then((user) => {
1654
+ return user;
1655
+ });
1656
+ }
1657
+
1658
+ /**
1659
+ * Gets group by name
1660
+ * @param context
1661
+ * @param name
1662
+ * @returns {Promise<any>}
1663
+ */
1664
+ getGroup(context, name) {
1665
+ // noinspection JSCheckFunctionSignatures
1666
+ /**
1667
+ * @type {import('./data-cache').DataCacheStrategy}
1668
+ */
1669
+ // noinspection JSCheckFunctionSignatures
1670
+ const cache = context.getConfiguration().getStrategy(function DataCacheStrategy() {});
1671
+ if (cache) {
1672
+ return cache.getOrDefault(`/Group/${name}`, () => {
1673
+ return this.queryGroup(context, name).silent().getItem().then((group) => {
1674
+ return group;
1675
+ });
1676
+ });
1677
+ }
1678
+ return this.queryGroup(context, name).silent().getItem().then((group) => {
1679
+ return group;
1680
+ });
1681
+ }
1682
+
1683
+ /**
1684
+ * Gets anonymous user
1685
+ * @param context
1686
+ * @returns {Promise<any>}
1687
+ */
1688
+ getAnonymousUser(context) {
1689
+ return this.getUser(context, 'anonymous');
1690
+ }
1691
+
1692
+ }
1693
+
1694
+ const AccountNetworkAddress = {
1695
+ "$schema": "https://themost-framework.github.io/themost/models/2018/2/schema.json",
1696
+ "@id": "https://universis.io/schemas/AccountNetworkAddress",
1697
+ "name": "AccountNetworkAddress",
1698
+ "hidden": true,
1699
+ "source": "AccountNetworkAddress",
1700
+ "view": "AccountNetworkAddress",
1701
+ "version": "1.0.0",
1702
+ "fields": [
1703
+ {
1704
+ "name": "id",
1705
+ "type": "Counter",
1706
+ "nullable": false,
1707
+ "primary": true,
1708
+ "many": false
1709
+ },
1710
+ {
1711
+ "name": "account",
1712
+ "type": "Account",
1713
+ "nullable": false,
1714
+ "many": false,
1715
+ "indexed": true
1716
+ },
1717
+ {
1718
+ "name": "value",
1719
+ "type": "Text",
1720
+ "nullable": false,
1721
+ "many": false,
1722
+ "indexed": false
1723
+ }],
1724
+
1725
+ "constraints": [
1726
+ {
1727
+ "type": "unique",
1728
+ "fields": [
1729
+ "account",
1730
+ "value"]
1731
+
1732
+ }],
1733
+
1734
+ "privileges": [
1735
+ {
1736
+ "mask": 15,
1737
+ "type": "global"
1738
+ },
1739
+ {
1740
+ "mask": 15,
1741
+ "type": "global",
1742
+ "account": "Administrators"
1743
+ }]
1744
+
1745
+ };
1746
+
1747
+ class HttpNetworkAccessForbiddenError extends HttpForbiddenError {
1748
+ constructor() {
1749
+ super('Access is denied due to network address conflict.');
1750
+ this.statusCode = 403.6;
1751
+ }
1752
+
1753
+ }
1754
+
1755
+ class NetworkRestrictAccess extends ApplicationService {
1756
+ constructor(app) {
1757
+ super(app);
1758
+ Object.defineProperty(this, 'proxyAddressForwarding', {
1759
+ configurable: true,
1760
+ enumerable: false,
1761
+ value: !!app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding'),
1762
+ writable: false
1763
+ });
1764
+ this.apply();
1765
+ }
1766
+
1767
+ apply() {
1768
+ // get schema loader
1769
+ const schemaLoader = this.getApplication().
1770
+ getConfiguration().
1771
+ getStrategy(SchemaLoaderStrategy);
1772
+ const accountNetworkAddressModel = schemaLoader.getModelDefinition('AccountNetworkAddress');
1773
+ if (accountNetworkAddressModel == null) {
1774
+ schemaLoader.setModelDefinition(AccountNetworkAddress);
1775
+ }
1776
+ // get model definition
1777
+ const model = schemaLoader.getModelDefinition('Account');
1778
+ if (model == null) {
1779
+ TraceUtils.warn('NetworkRestrictAccess: Account model definition not found.');
1780
+ return;
1781
+ }
1782
+ const found = model.fields.find((field) => {
1783
+ return field.name === 'networkAddress';
1784
+ });
1785
+ if (!found) {
1786
+ model.fields.push({
1787
+ "name": "networkAddress",
1788
+ "type": "Text",
1789
+ "many": true,
1790
+ "mapping": {
1791
+ "parentModel": "Account",
1792
+ "parentField": "id",
1793
+ "associationType": "junction",
1794
+ "associationAdapter": "AccountNetworkAddress",
1795
+ "cascade": "delete",
1796
+ "associationObjectField": "account",
1797
+ "associationValueField": "value"
1798
+ }
1799
+ });
1800
+ }
1801
+ model.eventListeners = model.eventListeners || [];
1802
+ let index = model.eventListeners.findIndex((listener) => {
1803
+ return listener.type === '@themost/data/previous-state-listener';
1804
+ });
1805
+ if (index < 0) {
1806
+ model.eventListeners.push({
1807
+ type: '@themost/data/previous-state-listener'
1808
+ });
1809
+ index = model.eventListeners.length - 1;
1810
+ }
1811
+ let otherIndex = model.eventListeners.findIndex((listener) => {
1812
+ return listener.type === path.resolve(__dirname, './OnAccountUpdate');
1813
+ });
1814
+ if (otherIndex < 0) {
1815
+ model.eventListeners.splice(index, 0, {
1816
+ type: path.resolve(__dirname, './OnAccountUpdate')
1817
+ });
1818
+ }
1819
+ schemaLoader.setModelDefinition(model);
1820
+ }
1821
+
1822
+ /**
1823
+ * Gets remote address from request
1824
+ * @param {import('express').Request} req
1825
+ * @returns
1826
+ */
1827
+ getRemoteAddress(req) {
1828
+ let remoteAddress;
1829
+ if (this.proxyAddressForwarding) {
1830
+ // get proxy headers or remote address
1831
+ // noinspection JSDeprecatedSymbols
1832
+ remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
1833
+ } else {
1834
+ // noinspection JSDeprecatedSymbols
1835
+ remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
1836
+ }
1837
+ return remoteAddress;
1838
+ }
1839
+ }
1840
+
1841
+
1842
+
1843
+ /**
1844
+ * Validate network address for the current request against the assigned network addresses for the user account.
1845
+ * @returns {import('express').Handler}
1846
+ */
1847
+ function validateNetworkAddress() {
1848
+ /**
1849
+ * @param {import('express').Request} req
1850
+ * @param {import('express').Response} res
1851
+ * @param {import('express').NextFunction} next
1852
+ */
1853
+ return function (req, res, next) {
1854
+ try {
1855
+ const service = req.context.getApplication().getService(NetworkRestrictAccess);
1856
+ if (service == null) {
1857
+ return next();
1858
+ }
1859
+ req.context.user$.pipe(
1860
+ take(1)
1861
+ ).subscribe({
1862
+ next: (user) => {
1863
+ if (user == null) {
1864
+ return next();
1865
+ }
1866
+ if (Array.isArray(user.networkAddress)) {
1867
+ if (user.networkAddress.length > 0) {
1868
+ // get remote address
1869
+ const remoteAddress = service.getRemoteAddress(req);
1870
+ const result = ipRangeCheck(remoteAddress, user.networkAddress);
1871
+ if (result !== true) {
1872
+ return next(new HttpNetworkAccessForbiddenError());
1873
+ }
1874
+ }
1875
+ }
1876
+ return next();
1877
+ },
1878
+ error: (err) => {
1879
+ return next(err);
1880
+ }
1881
+ });
1882
+ } catch (err) {
1883
+ return next(err);
1884
+ }
1885
+
1886
+ };
1887
+ }
1888
+
1889
+ class EnableNetworkRestrictAccess extends NetworkRestrictAccess {
1890
+ constructor(app) {
1891
+ super(app);
1892
+ if (app && app.serviceRouter) {
1893
+ app.serviceRouter.subscribe((serviceRouter) => {
1894
+ if (serviceRouter == null) {
1895
+ return;
1896
+ }
1897
+ const addRouter = express.Router();
1898
+ addRouter.use(validateNetworkAddress());
1899
+ // insert router at the beginning of serviceRouter.stack
1900
+ serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
1901
+ });
1902
+ }
1903
+ }
1904
+ }
1905
+
1906
+ class DisableNetworkRestrictAccess extends NetworkRestrictAccess {
1907
+ constructor(app) {
1908
+ super(app);
1909
+ }
1910
+ }
1911
+
1912
+ export { AppRateLimitService, AppSpeedLimitService, DefaultScopeAccessConfiguration, DisableNetworkRestrictAccess, DisableUserClientScopeVerification, EnableNetworkRestrictAccess, EnableScopeAccessConfiguration, ExtendScopeAccessConfiguration, HttpAccountDisabled, HttpBearerStrategy, HttpBearerTokenExpired, HttpBearerTokenNotFound, HttpBearerTokenRequired, HttpNetworkAccessForbiddenError, HttpRemoteAddrForbiddenError, InvalidClientScopeError, NetworkRestrictAccess, OAuth2ClientService, OutputCachingService, PassportService, RateLimitService, RedisClientStore, RemoteAddressValidator, ScopeAccessConfiguration, ScopeString, SpeedLimitService, UserWithNetworkAddress, VerifyUserClientScope, outputCache, validateClientScope, validateNetworkAddress, validateScope };
1339
1913
  //# sourceMappingURL=index.esm.js.map