@nxgt/janus 0.11.0 → 0.12.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/README.md +101 -15
- package/dist/auth/config/janus-config.d.ts +2 -0
- package/dist/auth/config/janus-config.d.ts.map +1 -1
- package/dist/auth/config/resolved-config.d.ts +1 -0
- package/dist/auth/config/resolved-config.d.ts.map +1 -1
- package/dist/auth/index.d.ts +2 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/port/assert-stores.d.ts.map +1 -1
- package/dist/auth/port/memory/sessions.d.ts.map +1 -1
- package/dist/auth/port/types/sessions.d.ts +16 -1
- package/dist/auth/port/types/sessions.d.ts.map +1 -1
- package/dist/auth/port/types/tokens.d.ts +7 -2
- package/dist/auth/port/types/tokens.d.ts.map +1 -1
- package/dist/auth/second-factor/{regenerate-attempts.d.ts → app-code-attempts.d.ts} +13 -10
- package/dist/auth/second-factor/app-code-attempts.d.ts.map +1 -0
- package/dist/auth/sessions/authenticate.d.ts.map +1 -1
- package/dist/auth/sessions/fresh.d.ts +26 -0
- package/dist/auth/sessions/fresh.d.ts.map +1 -0
- package/dist/auth/sessions/standing.d.ts +20 -0
- package/dist/auth/sessions/standing.d.ts.map +1 -0
- package/dist/auth/step-up/codes.d.ts +29 -0
- package/dist/auth/step-up/codes.d.ts.map +1 -0
- package/dist/auth/step-up/flows.d.ts +19 -0
- package/dist/auth/step-up/flows.d.ts.map +1 -0
- package/dist/auth/step-up/open.d.ts +23 -0
- package/dist/auth/step-up/open.d.ts.map +1 -0
- package/dist/auth/step-up/request.d.ts +12 -0
- package/dist/auth/step-up/request.d.ts.map +1 -0
- package/dist/auth/types/index.d.ts +1 -0
- package/dist/auth/types/index.d.ts.map +1 -1
- package/dist/auth/types/janus.d.ts +2 -1
- package/dist/auth/types/janus.d.ts.map +1 -1
- package/dist/auth/types/step-up.d.ts +79 -0
- package/dist/auth/types/step-up.d.ts.map +1 -0
- package/dist/auth/users/any-type-api.d.ts +2 -2
- package/dist/auth/users/any-type-api.d.ts.map +1 -1
- package/dist/auth/users/type-api.d.ts +1 -1
- package/dist/auth/users/type-api.d.ts.map +1 -1
- package/dist/chunks/{index-5e2fw7he.js → index-j2gd8fry.js} +12 -3
- package/dist/chunks/{index-5e2fw7he.js.map → index-j2gd8fry.js.map} +4 -4
- package/dist/chunks/{index-5tm04t5p.js → index-jp2kka6d.js} +2 -2
- package/dist/chunks/{index-6f6wja93.js → index-km7ffctx.js} +2 -2
- package/dist/chunks/{index-4m49cye4.js → index-rd2rddvv.js} +15 -12
- package/dist/chunks/{index-4m49cye4.js.map → index-rd2rddvv.js.map} +3 -3
- package/dist/conformance/cases/outage.d.ts.map +1 -1
- package/dist/conformance/cases/sessions/index.d.ts.map +1 -1
- package/dist/conformance/cases/sessions/reauthentication.d.ts +4 -0
- package/dist/conformance/cases/sessions/reauthentication.d.ts.map +1 -0
- package/dist/conformance/cases/tokens/index.d.ts.map +1 -1
- package/dist/conformance/cases/tokens/kinds.d.ts +4 -0
- package/dist/conformance/cases/tokens/kinds.d.ts.map +1 -0
- package/dist/conformance/index.js +147 -60
- package/dist/conformance/index.js.map +8 -6
- package/dist/errors/janus-error/codes.d.ts +7 -0
- package/dist/errors/janus-error/codes.d.ts.map +1 -1
- package/dist/errors/janus-error/index.d.ts +1 -1
- package/dist/errors/janus-error/index.d.ts.map +1 -1
- package/dist/errors/janus-error/refusals.d.ts +8 -0
- package/dist/errors/janus-error/refusals.d.ts.map +1 -1
- package/dist/errors/janus-error/status.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +175 -18
- package/dist/index.js.map +16 -10
- package/dist/permissions/index.js +3 -3
- package/docs/README.md +1 -0
- package/docs/guide/adapters.md +38 -4
- package/docs/guide/errors.md +1 -0
- package/docs/guide/second-factor.md +22 -16
- package/docs/guide/sessions.md +7 -0
- package/docs/guide/step-up.md +178 -0
- package/docs/guide/users.md +1 -0
- package/docs/guide/vocabulary.md +3 -2
- package/docs/roadmap.md +12 -16
- package/docs/troubleshooting.md +182 -7
- package/package.json +1 -1
- package/dist/auth/second-factor/regenerate-attempts.d.ts.map +0 -1
- /package/dist/chunks/{index-5tm04t5p.js.map → index-jp2kka6d.js.map} +0 -0
- /package/dist/chunks/{index-6f6wja93.js.map → index-km7ffctx.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
MAX_PAGE_SIZE2,
|
|
8
8
|
pageLimit2,
|
|
9
9
|
invalidCursor2
|
|
10
|
-
} from "./chunks/index-
|
|
10
|
+
} from "./chunks/index-jp2kka6d.js";
|
|
11
11
|
import {
|
|
12
12
|
mintId2,
|
|
13
13
|
isId2,
|
|
14
14
|
mintedAt2,
|
|
15
15
|
createMemoryStores2
|
|
16
|
-
} from "./chunks/index-
|
|
16
|
+
} from "./chunks/index-rd2rddvv.js";
|
|
17
17
|
import {
|
|
18
18
|
JanusError2,
|
|
19
19
|
UserInvalidError2,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
UserInactiveError2,
|
|
22
22
|
TokenError2,
|
|
23
23
|
SecondFactorError2,
|
|
24
|
+
StepUpRequiredError2,
|
|
24
25
|
InvalidCursorError2,
|
|
25
26
|
UnsupportedError2,
|
|
26
27
|
PermissionDepthError2,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
formatTuple2,
|
|
38
39
|
parseTuple2,
|
|
39
40
|
parseSubject2
|
|
40
|
-
} from "./chunks/index-
|
|
41
|
+
} from "./chunks/index-j2gd8fry.js";
|
|
41
42
|
|
|
42
43
|
// src/auth/hashers.ts
|
|
43
44
|
import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
|
|
@@ -382,7 +383,8 @@ function resolveTokenTtls(config, where) {
|
|
|
382
383
|
return {
|
|
383
384
|
verifyEmail: parseDuration(config.tokens?.verifyEmail ?? "24h", `${where}: tokens.verifyEmail`),
|
|
384
385
|
resetPassword: parseDuration(config.tokens?.resetPassword ?? "1h", `${where}: tokens.resetPassword`),
|
|
385
|
-
signInCode: parseDuration(config.tokens?.signInCode ?? "10m", `${where}: tokens.signInCode`)
|
|
386
|
+
signInCode: parseDuration(config.tokens?.signInCode ?? "10m", `${where}: tokens.signInCode`),
|
|
387
|
+
stepUp: parseDuration(config.tokens?.stepUp ?? "10m", `${where}: tokens.stepUp`)
|
|
386
388
|
};
|
|
387
389
|
}
|
|
388
390
|
// src/auth/context/create-context.ts
|
|
@@ -662,6 +664,7 @@ var REQUIRED = {
|
|
|
662
664
|
"insertSession",
|
|
663
665
|
"findSessionByTokenHash",
|
|
664
666
|
"extendSession",
|
|
667
|
+
"reauthenticateSession",
|
|
665
668
|
"revokeSession",
|
|
666
669
|
"revokeUserSessions",
|
|
667
670
|
"deleteUserSessions"
|
|
@@ -774,17 +777,24 @@ function headersOf(request) {
|
|
|
774
777
|
return inner;
|
|
775
778
|
return request;
|
|
776
779
|
}
|
|
780
|
+
// src/auth/sessions/standing.ts
|
|
781
|
+
async function standingSession(context, request) {
|
|
782
|
+
const token = presentedToken(request, context.config.cookie.name);
|
|
783
|
+
if (token === null)
|
|
784
|
+
return null;
|
|
785
|
+
const session = await context.store.sessions.findSessionByTokenHash(hashSecret(token));
|
|
786
|
+
const now = context.clock.now().getTime();
|
|
787
|
+
return session === null || session.revokedAt !== null || session.expiresAt.getTime() <= now ? null : { token, session };
|
|
788
|
+
}
|
|
789
|
+
|
|
777
790
|
// src/auth/sessions/authenticate.ts
|
|
778
791
|
async function authenticate(context, request, options) {
|
|
779
792
|
const { store, clock, config } = context;
|
|
780
|
-
const
|
|
781
|
-
if (
|
|
793
|
+
const presented = await standingSession(context, request);
|
|
794
|
+
if (presented === null)
|
|
782
795
|
return null;
|
|
783
|
-
const session =
|
|
796
|
+
const { token, session } = presented;
|
|
784
797
|
const now = clock.now().getTime();
|
|
785
|
-
if (session === null || session.revokedAt !== null || session.expiresAt.getTime() <= now) {
|
|
786
|
-
return null;
|
|
787
|
-
}
|
|
788
798
|
const user = await store.users.findUser(session.userId);
|
|
789
799
|
if (user === null || !user.active || options?.type !== undefined && user.type !== options.type) {
|
|
790
800
|
return null;
|
|
@@ -1411,10 +1421,10 @@ function accountOf(type, record, where) {
|
|
|
1411
1421
|
return String(record.fields[type.password.login]);
|
|
1412
1422
|
}
|
|
1413
1423
|
|
|
1414
|
-
// src/auth/second-factor/
|
|
1424
|
+
// src/auth/second-factor/app-code-attempts.ts
|
|
1415
1425
|
import { createHmac as createHmac3, hkdfSync as hkdfSync2 } from "node:crypto";
|
|
1416
|
-
var
|
|
1417
|
-
var WINDOW_MINUTES =
|
|
1426
|
+
var APP_CODE_WINDOW_MS = 15 * 60000;
|
|
1427
|
+
var WINDOW_MINUTES = APP_CODE_WINDOW_MS / 60000;
|
|
1418
1428
|
var PURPOSE2 = "janus/second-factor/regenerate-attempts/v1";
|
|
1419
1429
|
var MAX_LINKS = 32;
|
|
1420
1430
|
var derived2 = new WeakMap;
|
|
@@ -1436,10 +1446,10 @@ function linkHash(sealer, record, window, link) {
|
|
|
1436
1446
|
];
|
|
1437
1447
|
return hashSecret(createHmac3("sha256", counterKey(sealer)).update(parts.join("\x00")).digest("base64url"));
|
|
1438
1448
|
}
|
|
1439
|
-
async function
|
|
1449
|
+
async function countAppCodeAttempt(context, sealer, record, where) {
|
|
1440
1450
|
const now = context.clock.now().getTime();
|
|
1441
|
-
const window = Math.floor(now /
|
|
1442
|
-
const times = { now, expiresAt: (window + 2) *
|
|
1451
|
+
const window = Math.floor(now / APP_CODE_WINDOW_MS);
|
|
1452
|
+
const times = { now, expiresAt: (window + 2) * APP_CODE_WINDOW_MS };
|
|
1443
1453
|
let spent = 0;
|
|
1444
1454
|
for (let link = 0;link < MAX_LINKS; link += 1) {
|
|
1445
1455
|
const hash = linkHash(sealer, record, window, link);
|
|
@@ -1493,7 +1503,7 @@ async function regenerateRecoveryCodes(context, type, user, code, options, where
|
|
|
1493
1503
|
if (!isActive(factor)) {
|
|
1494
1504
|
throw refusal(type, "SECOND_FACTOR_NOT_ENROLLED", "the user has no active second factor — recovery codes come with one", where, record);
|
|
1495
1505
|
}
|
|
1496
|
-
const left = await
|
|
1506
|
+
const left = await countAppCodeAttempt(context, configured.sealer, record, where);
|
|
1497
1507
|
const accepted = acceptCode(configured, record, factor, String(code), now, where);
|
|
1498
1508
|
if (accepted === null) {
|
|
1499
1509
|
throw codeInvalid(where, record.id, type.name, left);
|
|
@@ -1624,6 +1634,142 @@ async function confirmCode(context, type, finish, challenge, code, at) {
|
|
|
1624
1634
|
return finish(proved, where);
|
|
1625
1635
|
}
|
|
1626
1636
|
|
|
1637
|
+
// src/auth/step-up/codes.ts
|
|
1638
|
+
var spend = (context, secret, where) => spendOneTime(context, secret, "stepUp", where, "challenge");
|
|
1639
|
+
async function checkEmailedCode(context, type, user, counted, code, where) {
|
|
1640
|
+
const { token, secret, attemptsLeft } = counted;
|
|
1641
|
+
if (isActive(user.secondFactor)) {
|
|
1642
|
+
await spend(context, secret, where);
|
|
1643
|
+
throw new SecondFactorError2("SECOND_FACTOR_ACTIVE", `${where}: the user's second factor became active since the code was sent — request a step-up again`, { operation: where, userId: user.id, userType: type.name });
|
|
1644
|
+
}
|
|
1645
|
+
if (!codeMatches(token, secret, code)) {
|
|
1646
|
+
if (attemptsLeft === 0)
|
|
1647
|
+
await burnOneTime(context, secret, "stepUp");
|
|
1648
|
+
throw codeInvalid(where, user.id, type.name, attemptsLeft);
|
|
1649
|
+
}
|
|
1650
|
+
await spend(context, secret, where);
|
|
1651
|
+
refuseStale(type, user, token, where, "code");
|
|
1652
|
+
}
|
|
1653
|
+
async function checkAppCode(context, type, user, counted, code, where) {
|
|
1654
|
+
const { secret, attemptsLeft } = counted;
|
|
1655
|
+
const factor = user.secondFactor;
|
|
1656
|
+
if (!isActive(factor)) {
|
|
1657
|
+
await spend(context, secret, where);
|
|
1658
|
+
throw new SecondFactorError2("SECOND_FACTOR_NOT_ENROLLED", `${where}: the user no longer has a second factor — request a step-up again`, { operation: where, userId: user.id, userType: type.name });
|
|
1659
|
+
}
|
|
1660
|
+
const configured = requireSettings(context, where, "the user's second factor is active");
|
|
1661
|
+
const windowLeft = await countAppCodeAttempt(context, configured.sealer, user, where);
|
|
1662
|
+
const now = context.clock.now();
|
|
1663
|
+
const accepted = acceptCode(configured, user, factor, code, now, where);
|
|
1664
|
+
if (accepted === null) {
|
|
1665
|
+
if (attemptsLeft === 0)
|
|
1666
|
+
await burnOneTime(context, secret, "stepUp");
|
|
1667
|
+
throw codeInvalid(where, user.id, type.name, Math.min(attemptsLeft, windowLeft));
|
|
1668
|
+
}
|
|
1669
|
+
await context.store.users.updateUser(user.id, { secondFactor: accepted, updatedAt: now }, user.version);
|
|
1670
|
+
await spend(context, secret, where);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// src/auth/step-up/open.ts
|
|
1674
|
+
async function openStepUp(context, type, request, token, secret, where) {
|
|
1675
|
+
const session = (await standingSession(context, request))?.session ?? null;
|
|
1676
|
+
if (session === null || session.userId !== token.userId) {
|
|
1677
|
+
throw await unknownChallenge(context, token, secret, where);
|
|
1678
|
+
}
|
|
1679
|
+
const user = await findRecord(context, token.userId, type.name);
|
|
1680
|
+
if (user === null) {
|
|
1681
|
+
throw await unknownChallenge(context, token, secret, where);
|
|
1682
|
+
}
|
|
1683
|
+
if (!user.active) {
|
|
1684
|
+
await spendOneTime(context, secret, "stepUp", where, "challenge");
|
|
1685
|
+
throw new UserInactiveError2(`${where}: the user is inactive`, {
|
|
1686
|
+
userId: user.id,
|
|
1687
|
+
userType: type.name
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
return { session, user };
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// src/auth/step-up/request.ts
|
|
1694
|
+
async function requestStepUp(context, type, user, where) {
|
|
1695
|
+
const record = await getRecord(context, idOf(user), type.name, where);
|
|
1696
|
+
if (!record.active) {
|
|
1697
|
+
throw new UserInactiveError2(`${where}: the user is inactive`, {
|
|
1698
|
+
userId: record.id,
|
|
1699
|
+
userType: type.name
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
const issued = isActive(record.secondFactor) ? await byApp(context, record, where) : await byEmail(context, type, record, where);
|
|
1703
|
+
await context.store.tokens.spendUserTokens(record.id, "stepUp", context.clock.now(), hashSecret(issued.challenge));
|
|
1704
|
+
return issued;
|
|
1705
|
+
}
|
|
1706
|
+
async function byApp(context, record, where) {
|
|
1707
|
+
requireSettings(context, where, "the user's second factor is active");
|
|
1708
|
+
const { secret, expiresAt } = await issueOneTime(context, {
|
|
1709
|
+
kind: "stepUp",
|
|
1710
|
+
userId: record.id,
|
|
1711
|
+
address: "",
|
|
1712
|
+
ttlMs: context.config.tokenTtlMs.stepUp
|
|
1713
|
+
});
|
|
1714
|
+
return {
|
|
1715
|
+
via: "secondFactor",
|
|
1716
|
+
challenge: secret,
|
|
1717
|
+
expiresAt,
|
|
1718
|
+
user: toUser(record)
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
async function byEmail(context, type, record, where) {
|
|
1722
|
+
const email = emailOf(type, record.fields);
|
|
1723
|
+
if (email === null) {
|
|
1724
|
+
throw new NotFoundError2(`${where}: the user has no e-mail`, {
|
|
1725
|
+
userId: record.id,
|
|
1726
|
+
userType: type.name,
|
|
1727
|
+
operation: where
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
const { secret, code, expiresAt } = await issueCode(context, {
|
|
1731
|
+
kind: "stepUp",
|
|
1732
|
+
userId: record.id,
|
|
1733
|
+
address: email,
|
|
1734
|
+
ttlMs: context.config.tokenTtlMs.stepUp
|
|
1735
|
+
});
|
|
1736
|
+
return {
|
|
1737
|
+
via: "email",
|
|
1738
|
+
code,
|
|
1739
|
+
challenge: secret,
|
|
1740
|
+
email,
|
|
1741
|
+
expiresAt,
|
|
1742
|
+
user: toUser(record)
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/auth/step-up/flows.ts
|
|
1747
|
+
function stepUpFlows(context, type, at) {
|
|
1748
|
+
return {
|
|
1749
|
+
async request(user) {
|
|
1750
|
+
return requestStepUp(context, type, user, at("stepUp.request"));
|
|
1751
|
+
},
|
|
1752
|
+
async confirm(request, challenge, code) {
|
|
1753
|
+
return confirmStepUp(context, type, request, String(challenge), String(code), at("stepUp.confirm"));
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
async function confirmStepUp(context, type, request, secret, code, where) {
|
|
1758
|
+
const { token, attemptsLeft } = await countCodeAttempt(context, secret, "stepUp", where, type.name);
|
|
1759
|
+
const { session, user } = await openStepUp(context, type, request, token, secret, where);
|
|
1760
|
+
const counted = { token, secret, attemptsLeft };
|
|
1761
|
+
if (token.codeHash === null) {
|
|
1762
|
+
await checkAppCode(context, type, user, counted, code, where);
|
|
1763
|
+
} else {
|
|
1764
|
+
await checkEmailedCode(context, type, user, counted, code, where);
|
|
1765
|
+
}
|
|
1766
|
+
const confirmed = await context.store.sessions.reauthenticateSession(session.id, context.clock.now());
|
|
1767
|
+
if (confirmed === null) {
|
|
1768
|
+
throw new TokenError2("TOKEN_UNKNOWN", `${where}: the session was signed out while the code was checked`, { operation: where });
|
|
1769
|
+
}
|
|
1770
|
+
return toSession(confirmed);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1627
1773
|
// src/auth/users/by-login.ts
|
|
1628
1774
|
var byLogin = async (context, type, login) => isStorable(login) ? context.store.users.findUserByLogin(type.name, login) : null;
|
|
1629
1775
|
|
|
@@ -1837,6 +1983,7 @@ function typeApi(context, type) {
|
|
|
1837
1983
|
...passwordFlows(context, type, at, secondFactor.finish),
|
|
1838
1984
|
secondFactor: secondFactor.api,
|
|
1839
1985
|
signInCode,
|
|
1986
|
+
stepUp: stepUpFlows(context, type, at),
|
|
1840
1987
|
...emailFlows(context, type, at)
|
|
1841
1988
|
};
|
|
1842
1989
|
}
|
|
@@ -1873,6 +2020,14 @@ function janus(config) {
|
|
|
1873
2020
|
const surface = resolved.single ? { ...shared, ...types.user } : { ...shared, ...types };
|
|
1874
2021
|
return surface;
|
|
1875
2022
|
}
|
|
2023
|
+
// src/auth/sessions/fresh.ts
|
|
2024
|
+
function assertFresh(session, maxAge, clock = systemClock) {
|
|
2025
|
+
const maxAgeMs = parseDuration(maxAge, "assertFresh: maxAge");
|
|
2026
|
+
const age = clock.now().getTime() - session.authenticatedAt.getTime();
|
|
2027
|
+
if (age >= maxAgeMs) {
|
|
2028
|
+
throw new StepUpRequiredError2("assertFresh: the session proved who it is longer ago than maxAge — confirm with a step-up", { userId: session.userId });
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
1876
2031
|
export {
|
|
1877
2032
|
CredentialError2 as CredentialError,
|
|
1878
2033
|
DEFAULT_PAGE_SIZE2 as DEFAULT_PAGE_SIZE,
|
|
@@ -1882,12 +2037,14 @@ export {
|
|
|
1882
2037
|
NotFoundError2 as NotFoundError,
|
|
1883
2038
|
PermissionDepthError2 as PermissionDepthError,
|
|
1884
2039
|
SecondFactorError2 as SecondFactorError,
|
|
2040
|
+
StepUpRequiredError2 as StepUpRequiredError,
|
|
1885
2041
|
StoreConflict2 as StoreConflict,
|
|
1886
2042
|
StoreFailure2 as StoreFailure,
|
|
1887
2043
|
TokenError2 as TokenError,
|
|
1888
2044
|
UnsupportedError2 as UnsupportedError,
|
|
1889
2045
|
UserInactiveError2 as UserInactiveError,
|
|
1890
2046
|
UserInvalidError2 as UserInvalidError,
|
|
2047
|
+
assertFresh,
|
|
1891
2048
|
assertStores,
|
|
1892
2049
|
bunHasher,
|
|
1893
2050
|
createMemoryStores2 as createMemoryStores,
|
|
@@ -1913,5 +2070,5 @@ export {
|
|
|
1913
2070
|
systemClock
|
|
1914
2071
|
};
|
|
1915
2072
|
|
|
1916
|
-
//# debugId=
|
|
2073
|
+
//# debugId=301D5A6A554F106664756E2164756E21
|
|
1917
2074
|
//# sourceMappingURL=index.js.map
|