@lvce-editor/auth-worker 1.14.0 → 1.15.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.
@@ -1188,6 +1188,7 @@ const toBackendAuthState = value => {
1188
1188
  return {
1189
1189
  authAccessToken: getString(value.accessToken),
1190
1190
  authErrorMessage: getString(value.error),
1191
+ authRefreshToken: getString(value.refreshToken),
1191
1192
  userName: getString(value.userName),
1192
1193
  userState: value.accessToken ? 'loggedIn' : 'loggedOut',
1193
1194
  userSubscriptionPlan: getString(value.subscriptionPlan),
@@ -1261,7 +1262,22 @@ const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalM
1261
1262
  return getLoggedOutBackendAuthState(lastErrorMessage);
1262
1263
  };
1263
1264
 
1264
- if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) ;
1265
+ let USER_AGENT;
1266
+ if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
1267
+ const NAME = 'oauth4webapi';
1268
+ const VERSION = 'v3.8.6';
1269
+ USER_AGENT = `${NAME}/${VERSION}`;
1270
+ }
1271
+ function looseInstanceOf(input, expected) {
1272
+ if (input == null) {
1273
+ return false;
1274
+ }
1275
+ try {
1276
+ return input instanceof expected || Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag];
1277
+ } catch {
1278
+ return false;
1279
+ }
1280
+ }
1265
1281
  const ERR_INVALID_ARG_VALUE = 'ERR_INVALID_ARG_VALUE';
1266
1282
  const ERR_INVALID_ARG_TYPE = 'ERR_INVALID_ARG_TYPE';
1267
1283
  function CodedTypeError(message, code, cause) {
@@ -1273,6 +1289,11 @@ function CodedTypeError(message, code, cause) {
1273
1289
  });
1274
1290
  return err;
1275
1291
  }
1292
+ const allowInsecureRequests = Symbol();
1293
+ const clockSkew = Symbol();
1294
+ const clockTolerance = Symbol();
1295
+ const customFetch = Symbol();
1296
+ const jweDecrypt = Symbol();
1276
1297
  const encoder = new TextEncoder();
1277
1298
  const decoder = new TextDecoder();
1278
1299
  function buf(input) {
@@ -1336,6 +1357,83 @@ function b64u(input) {
1336
1357
  }
1337
1358
  return encodeBase64Url(input);
1338
1359
  }
1360
+ class UnsupportedOperationError extends Error {
1361
+ code;
1362
+ constructor(message, options) {
1363
+ super(message, options);
1364
+ this.name = this.constructor.name;
1365
+ this.code = UNSUPPORTED_OPERATION;
1366
+ Error.captureStackTrace?.(this, this.constructor);
1367
+ }
1368
+ }
1369
+ class OperationProcessingError extends Error {
1370
+ code;
1371
+ constructor(message, options) {
1372
+ super(message, options);
1373
+ this.name = this.constructor.name;
1374
+ if (options?.code) {
1375
+ this.code = options?.code;
1376
+ }
1377
+ Error.captureStackTrace?.(this, this.constructor);
1378
+ }
1379
+ }
1380
+ function OPE(message, code, cause) {
1381
+ return new OperationProcessingError(message, {
1382
+ code,
1383
+ cause
1384
+ });
1385
+ }
1386
+ function isJsonObject(input) {
1387
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
1388
+ return false;
1389
+ }
1390
+ return true;
1391
+ }
1392
+ function prepareHeaders(input) {
1393
+ if (looseInstanceOf(input, Headers)) {
1394
+ input = Object.fromEntries(input.entries());
1395
+ }
1396
+ const headers = new Headers(input ?? {});
1397
+ if (USER_AGENT && !headers.has('user-agent')) {
1398
+ headers.set('user-agent', USER_AGENT);
1399
+ }
1400
+ if (headers.has('authorization')) {
1401
+ throw CodedTypeError('"options.headers" must not include the "authorization" header name', ERR_INVALID_ARG_VALUE);
1402
+ }
1403
+ return headers;
1404
+ }
1405
+ function signal(url, value) {
1406
+ if (value !== undefined) {
1407
+ if (typeof value === 'function') {
1408
+ value = value(url.href);
1409
+ }
1410
+ if (!(value instanceof AbortSignal)) {
1411
+ throw CodedTypeError('"options.signal" must return or be an instance of AbortSignal', ERR_INVALID_ARG_TYPE);
1412
+ }
1413
+ return value;
1414
+ }
1415
+ return undefined;
1416
+ }
1417
+ function assertNumber(input, allow0, it, code, cause) {
1418
+ try {
1419
+ if (typeof input !== 'number' || !Number.isFinite(input)) {
1420
+ throw CodedTypeError(`${it} must be a number`, ERR_INVALID_ARG_TYPE, cause);
1421
+ }
1422
+ if (input > 0) return;
1423
+ if (allow0) {
1424
+ if (input !== 0) {
1425
+ throw CodedTypeError(`${it} must be a non-negative number`, ERR_INVALID_ARG_VALUE, cause);
1426
+ }
1427
+ return;
1428
+ }
1429
+ throw CodedTypeError(`${it} must be a positive number`, ERR_INVALID_ARG_VALUE, cause);
1430
+ } catch (err) {
1431
+ if (code) {
1432
+ throw OPE(err.message, code, cause);
1433
+ }
1434
+ throw err;
1435
+ }
1436
+ }
1339
1437
  function assertString(input, it, code, cause) {
1340
1438
  try {
1341
1439
  if (typeof input !== 'string') {
@@ -1345,9 +1443,32 @@ function assertString(input, it, code, cause) {
1345
1443
  throw CodedTypeError(`${it} must not be empty`, ERR_INVALID_ARG_VALUE, cause);
1346
1444
  }
1347
1445
  } catch (err) {
1446
+ if (code) {
1447
+ throw OPE(err.message, code, cause);
1448
+ }
1348
1449
  throw err;
1349
1450
  }
1350
1451
  }
1452
+ function assertApplicationJson(response) {
1453
+ assertContentType(response, 'application/json');
1454
+ }
1455
+ function notJson(response, ...types) {
1456
+ let msg = '"response" content-type must be ';
1457
+ if (types.length > 2) {
1458
+ const last = types.pop();
1459
+ msg += `${types.join(', ')}, or ${last}`;
1460
+ } else if (types.length === 2) {
1461
+ msg += `${types[0]} or ${types[1]}`;
1462
+ } else {
1463
+ msg += types[0];
1464
+ }
1465
+ return OPE(msg, RESPONSE_IS_NOT_JSON, response);
1466
+ }
1467
+ function assertContentType(response, contentType) {
1468
+ if (getContentType(response) !== contentType) {
1469
+ throw notJson(response, contentType);
1470
+ }
1471
+ }
1351
1472
  function randomBytes() {
1352
1473
  return b64u(crypto.getRandomValues(new Uint8Array(32)));
1353
1474
  }
@@ -1358,6 +1479,708 @@ async function calculatePKCECodeChallenge(codeVerifier) {
1358
1479
  assertString(codeVerifier, 'codeVerifier');
1359
1480
  return b64u(await crypto.subtle.digest('SHA-256', buf(codeVerifier)));
1360
1481
  }
1482
+ function getClockSkew(client) {
1483
+ const skew = client?.[clockSkew];
1484
+ return typeof skew === 'number' && Number.isFinite(skew) ? skew : 0;
1485
+ }
1486
+ function getClockTolerance(client) {
1487
+ const tolerance = client?.[clockTolerance];
1488
+ return typeof tolerance === 'number' && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1 ? tolerance : 30;
1489
+ }
1490
+ function epochTime() {
1491
+ return Math.floor(Date.now() / 1000);
1492
+ }
1493
+ function assertAs(as) {
1494
+ if (typeof as !== 'object' || as === null) {
1495
+ throw CodedTypeError('"as" must be an object', ERR_INVALID_ARG_TYPE);
1496
+ }
1497
+ assertString(as.issuer, '"as.issuer"');
1498
+ }
1499
+ function assertClient(client) {
1500
+ if (typeof client !== 'object' || client === null) {
1501
+ throw CodedTypeError('"client" must be an object', ERR_INVALID_ARG_TYPE);
1502
+ }
1503
+ assertString(client.client_id, '"client.client_id"');
1504
+ }
1505
+ function None() {
1506
+ return (_as, client, body, _headers) => {
1507
+ body.set('client_id', client.client_id);
1508
+ };
1509
+ }
1510
+ const URLParse = URL.parse ? (url, base) => URL.parse(url, base) : (url, base) => {
1511
+ try {
1512
+ return new URL(url, base);
1513
+ } catch {
1514
+ return null;
1515
+ }
1516
+ };
1517
+ function checkProtocol(url, enforceHttps) {
1518
+ if (enforceHttps && url.protocol !== 'https:') {
1519
+ throw OPE('only requests to HTTPS are allowed', HTTP_REQUEST_FORBIDDEN, url);
1520
+ }
1521
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
1522
+ throw OPE('only HTTP and HTTPS requests are allowed', REQUEST_PROTOCOL_FORBIDDEN, url);
1523
+ }
1524
+ }
1525
+ function validateEndpoint(value, endpoint, useMtlsAlias, enforceHttps) {
1526
+ let url;
1527
+ if (typeof value !== 'string' || !(url = URLParse(value))) {
1528
+ throw OPE(`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`, value === undefined ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA, {
1529
+ attribute: useMtlsAlias ? `mtls_endpoint_aliases.${endpoint}` : endpoint
1530
+ });
1531
+ }
1532
+ checkProtocol(url, enforceHttps);
1533
+ return url;
1534
+ }
1535
+ function resolveEndpoint(as, endpoint, useMtlsAlias, enforceHttps) {
1536
+ if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
1537
+ return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias, enforceHttps);
1538
+ }
1539
+ return validateEndpoint(as[endpoint], endpoint, useMtlsAlias, enforceHttps);
1540
+ }
1541
+ class ResponseBodyError extends Error {
1542
+ cause;
1543
+ code;
1544
+ error;
1545
+ status;
1546
+ error_description;
1547
+ response;
1548
+ constructor(message, options) {
1549
+ super(message, options);
1550
+ this.name = this.constructor.name;
1551
+ this.code = RESPONSE_BODY_ERROR;
1552
+ this.cause = options.cause;
1553
+ this.error = options.cause.error;
1554
+ this.status = options.response.status;
1555
+ this.error_description = options.cause.error_description;
1556
+ Object.defineProperty(this, 'response', {
1557
+ enumerable: false,
1558
+ value: options.response
1559
+ });
1560
+ Error.captureStackTrace?.(this, this.constructor);
1561
+ }
1562
+ }
1563
+ class WWWAuthenticateChallengeError extends Error {
1564
+ cause;
1565
+ code;
1566
+ response;
1567
+ status;
1568
+ constructor(message, options) {
1569
+ super(message, options);
1570
+ this.name = this.constructor.name;
1571
+ this.code = WWW_AUTHENTICATE_CHALLENGE;
1572
+ this.cause = options.cause;
1573
+ this.status = options.response.status;
1574
+ this.response = options.response;
1575
+ Object.defineProperty(this, 'response', {
1576
+ enumerable: false
1577
+ });
1578
+ Error.captureStackTrace?.(this, this.constructor);
1579
+ }
1580
+ }
1581
+ const tokenMatch = "[a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+";
1582
+ const token68Match = '[a-zA-Z0-9\\-\\._\\~\\+\\/]+={0,2}';
1583
+ const quotedMatch = '"((?:[^"\\\\]|\\\\[\\s\\S])*)"';
1584
+ const quotedParamMatcher = '(' + tokenMatch + ')\\s*=\\s*' + quotedMatch;
1585
+ const paramMatcher = '(' + tokenMatch + ')\\s*=\\s*(' + tokenMatch + ')';
1586
+ const schemeRE = new RegExp('^[,\\s]*(' + tokenMatch + ')');
1587
+ const quotedParamRE = new RegExp('^[,\\s]*' + quotedParamMatcher + '[,\\s]*(.*)');
1588
+ const unquotedParamRE = new RegExp('^[,\\s]*' + paramMatcher + '[,\\s]*(.*)');
1589
+ const token68ParamRE = new RegExp('^(' + token68Match + ')(?:$|[,\\s])(.*)');
1590
+ function parseWwwAuthenticateChallenges(response) {
1591
+ if (!looseInstanceOf(response, Response)) {
1592
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1593
+ }
1594
+ const header = response.headers.get('www-authenticate');
1595
+ if (header === null) {
1596
+ return undefined;
1597
+ }
1598
+ const challenges = [];
1599
+ let rest = header;
1600
+ while (rest) {
1601
+ let match = rest.match(schemeRE);
1602
+ const scheme = match?.['1'].toLowerCase();
1603
+ if (!scheme) {
1604
+ return undefined;
1605
+ }
1606
+ const afterScheme = rest.substring(match[0].length);
1607
+ if (afterScheme && !afterScheme.match(/^[\s,]/)) {
1608
+ return undefined;
1609
+ }
1610
+ const spaceMatch = afterScheme.match(/^\s+(.*)$/);
1611
+ const hasParameters = !!spaceMatch;
1612
+ rest = spaceMatch ? spaceMatch[1] : undefined;
1613
+ const parameters = {};
1614
+ let token68;
1615
+ if (hasParameters) {
1616
+ while (rest) {
1617
+ let key;
1618
+ let value;
1619
+ if (match = rest.match(quotedParamRE)) {
1620
+ [, key, value, rest] = match;
1621
+ if (value.includes('\\')) {
1622
+ try {
1623
+ value = JSON.parse(`"${value}"`);
1624
+ } catch {}
1625
+ }
1626
+ parameters[key.toLowerCase()] = value;
1627
+ continue;
1628
+ }
1629
+ if (match = rest.match(unquotedParamRE)) {
1630
+ [, key, value, rest] = match;
1631
+ parameters[key.toLowerCase()] = value;
1632
+ continue;
1633
+ }
1634
+ if (match = rest.match(token68ParamRE)) {
1635
+ if (Object.keys(parameters).length) {
1636
+ break;
1637
+ }
1638
+ [, token68, rest] = match;
1639
+ break;
1640
+ }
1641
+ return undefined;
1642
+ }
1643
+ } else {
1644
+ rest = afterScheme || undefined;
1645
+ }
1646
+ const challenge = {
1647
+ scheme,
1648
+ parameters
1649
+ };
1650
+ if (token68) {
1651
+ challenge.token68 = token68;
1652
+ }
1653
+ challenges.push(challenge);
1654
+ }
1655
+ if (!challenges.length) {
1656
+ return undefined;
1657
+ }
1658
+ return challenges;
1659
+ }
1660
+ async function parseOAuthResponseErrorBody(response) {
1661
+ if (response.status > 399 && response.status < 500) {
1662
+ assertReadableResponse(response);
1663
+ assertApplicationJson(response);
1664
+ try {
1665
+ const json = await response.clone().json();
1666
+ if (isJsonObject(json) && typeof json.error === 'string' && json.error.length) {
1667
+ return json;
1668
+ }
1669
+ } catch {}
1670
+ }
1671
+ return undefined;
1672
+ }
1673
+ async function checkOAuthBodyError(response, expected, label) {
1674
+ if (response.status !== expected) {
1675
+ checkAuthenticationChallenges(response);
1676
+ let err;
1677
+ if (err = await parseOAuthResponseErrorBody(response)) {
1678
+ await response.body?.cancel();
1679
+ throw new ResponseBodyError('server responded with an error in the response body', {
1680
+ cause: err,
1681
+ response
1682
+ });
1683
+ }
1684
+ throw OPE(`"response" is not a conform ${label} response (unexpected HTTP status code)`, RESPONSE_IS_NOT_CONFORM, response);
1685
+ }
1686
+ }
1687
+ function assertDPoP(option) {
1688
+ if (!branded.has(option)) {
1689
+ throw CodedTypeError('"options.DPoP" is not a valid DPoPHandle', ERR_INVALID_ARG_VALUE);
1690
+ }
1691
+ }
1692
+ function getContentType(input) {
1693
+ return input.headers.get('content-type')?.split(';')[0];
1694
+ }
1695
+ async function authenticatedRequest(as, client, clientAuthentication, url, body, headers, options) {
1696
+ await clientAuthentication(as, client, body, headers);
1697
+ headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
1698
+ return (options?.[customFetch] || fetch)(url.href, {
1699
+ body,
1700
+ headers: Object.fromEntries(headers.entries()),
1701
+ method: 'POST',
1702
+ redirect: 'manual',
1703
+ signal: signal(url, options?.signal)
1704
+ });
1705
+ }
1706
+ async function tokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
1707
+ const url = resolveEndpoint(as, 'token_endpoint', client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
1708
+ parameters.set('grant_type', grantType);
1709
+ const headers = prepareHeaders(options?.headers);
1710
+ headers.set('accept', 'application/json');
1711
+ if (options?.DPoP !== undefined) {
1712
+ assertDPoP(options.DPoP);
1713
+ await options.DPoP.addProof(url, headers, 'POST');
1714
+ }
1715
+ const response = await authenticatedRequest(as, client, clientAuthentication, url, parameters, headers, options);
1716
+ options?.DPoP?.cacheNonce(response, url);
1717
+ return response;
1718
+ }
1719
+ const idTokenClaims = new WeakMap();
1720
+ const jwtRefs = new WeakMap();
1721
+ function getValidatedIdTokenClaims(ref) {
1722
+ if (!ref.id_token) {
1723
+ return undefined;
1724
+ }
1725
+ const claims = idTokenClaims.get(ref);
1726
+ if (!claims) {
1727
+ throw CodedTypeError('"ref" was already garbage collected or did not resolve from the proper sources', ERR_INVALID_ARG_VALUE);
1728
+ }
1729
+ return claims;
1730
+ }
1731
+ async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, decryptFn, recognizedTokenTypes) {
1732
+ assertAs(as);
1733
+ assertClient(client);
1734
+ if (!looseInstanceOf(response, Response)) {
1735
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1736
+ }
1737
+ await checkOAuthBodyError(response, 200, 'Token Endpoint');
1738
+ assertReadableResponse(response);
1739
+ const json = await getResponseJsonBody(response);
1740
+ assertString(json.access_token, '"response" body "access_token" property', INVALID_RESPONSE, {
1741
+ body: json
1742
+ });
1743
+ assertString(json.token_type, '"response" body "token_type" property', INVALID_RESPONSE, {
1744
+ body: json
1745
+ });
1746
+ json.token_type = json.token_type.toLowerCase();
1747
+ if (json.expires_in !== undefined) {
1748
+ let expiresIn = typeof json.expires_in !== 'number' ? parseFloat(json.expires_in) : json.expires_in;
1749
+ assertNumber(expiresIn, true, '"response" body "expires_in" property', INVALID_RESPONSE, {
1750
+ body: json
1751
+ });
1752
+ json.expires_in = expiresIn;
1753
+ }
1754
+ if (json.refresh_token !== undefined) {
1755
+ assertString(json.refresh_token, '"response" body "refresh_token" property', INVALID_RESPONSE, {
1756
+ body: json
1757
+ });
1758
+ }
1759
+ if (json.scope !== undefined && typeof json.scope !== 'string') {
1760
+ throw OPE('"response" body "scope" property must be a string', INVALID_RESPONSE, {
1761
+ body: json
1762
+ });
1763
+ }
1764
+ if (json.id_token !== undefined) {
1765
+ assertString(json.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
1766
+ body: json
1767
+ });
1768
+ const requiredClaims = ['aud', 'exp', 'iat', 'iss', 'sub'];
1769
+ if (client.require_auth_time === true) {
1770
+ requiredClaims.push('auth_time');
1771
+ }
1772
+ if (client.default_max_age !== undefined) {
1773
+ assertNumber(client.default_max_age, true, '"client.default_max_age"');
1774
+ requiredClaims.push('auth_time');
1775
+ }
1776
+ if (additionalRequiredIdTokenClaims?.length) {
1777
+ requiredClaims.push(...additionalRequiredIdTokenClaims);
1778
+ }
1779
+ const {
1780
+ claims,
1781
+ jwt
1782
+ } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported, 'RS256'), getClockSkew(client), getClockTolerance(client), decryptFn).then(validatePresence.bind(undefined, requiredClaims)).then(validateIssuer.bind(undefined, as)).then(validateAudience.bind(undefined, client.client_id));
1783
+ if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
1784
+ if (claims.azp === undefined) {
1785
+ throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, {
1786
+ claims,
1787
+ claim: 'aud'
1788
+ });
1789
+ }
1790
+ if (claims.azp !== client.client_id) {
1791
+ throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, {
1792
+ expected: client.client_id,
1793
+ claims,
1794
+ claim: 'azp'
1795
+ });
1796
+ }
1797
+ }
1798
+ if (claims.auth_time !== undefined) {
1799
+ assertNumber(claims.auth_time, true, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, {
1800
+ claims
1801
+ });
1802
+ }
1803
+ jwtRefs.set(response, jwt);
1804
+ idTokenClaims.set(json, claims);
1805
+ }
1806
+ if (recognizedTokenTypes?.[json.token_type] !== undefined) {
1807
+ recognizedTokenTypes[json.token_type](response, json);
1808
+ } else if (json.token_type !== 'dpop' && json.token_type !== 'bearer') {
1809
+ throw new UnsupportedOperationError('unsupported `token_type` value', {
1810
+ cause: {
1811
+ body: json
1812
+ }
1813
+ });
1814
+ }
1815
+ return json;
1816
+ }
1817
+ function checkAuthenticationChallenges(response) {
1818
+ let challenges;
1819
+ if (challenges = parseWwwAuthenticateChallenges(response)) {
1820
+ throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', {
1821
+ cause: challenges,
1822
+ response
1823
+ });
1824
+ }
1825
+ }
1826
+ function validateAudience(expected, result) {
1827
+ if (Array.isArray(result.claims.aud)) {
1828
+ if (!result.claims.aud.includes(expected)) {
1829
+ throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
1830
+ expected,
1831
+ claims: result.claims,
1832
+ claim: 'aud'
1833
+ });
1834
+ }
1835
+ } else if (result.claims.aud !== expected) {
1836
+ throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
1837
+ expected,
1838
+ claims: result.claims,
1839
+ claim: 'aud'
1840
+ });
1841
+ }
1842
+ return result;
1843
+ }
1844
+ function validateIssuer(as, result) {
1845
+ const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
1846
+ if (result.claims.iss !== expected) {
1847
+ throw OPE('unexpected JWT "iss" (issuer) claim value', JWT_CLAIM_COMPARISON, {
1848
+ expected,
1849
+ claims: result.claims,
1850
+ claim: 'iss'
1851
+ });
1852
+ }
1853
+ return result;
1854
+ }
1855
+ const branded = new WeakSet();
1856
+ const nopkce = Symbol();
1857
+ async function authorizationCodeGrantRequest(as, client, clientAuthentication, callbackParameters, redirectUri, codeVerifier, options) {
1858
+ assertAs(as);
1859
+ assertClient(client);
1860
+ if (!branded.has(callbackParameters)) {
1861
+ throw CodedTypeError('"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()', ERR_INVALID_ARG_VALUE);
1862
+ }
1863
+ assertString(redirectUri, '"redirectUri"');
1864
+ const code = getURLSearchParameter(callbackParameters, 'code');
1865
+ if (!code) {
1866
+ throw OPE('no authorization code in "callbackParameters"', INVALID_RESPONSE);
1867
+ }
1868
+ const parameters = new URLSearchParams(options?.additionalParameters);
1869
+ parameters.set('redirect_uri', redirectUri);
1870
+ parameters.set('code', code);
1871
+ if (codeVerifier !== nopkce) {
1872
+ assertString(codeVerifier, '"codeVerifier"');
1873
+ parameters.set('code_verifier', codeVerifier);
1874
+ }
1875
+ return tokenEndpointRequest(as, client, clientAuthentication, 'authorization_code', parameters, options);
1876
+ }
1877
+ const jwtClaimNames = {
1878
+ aud: 'audience',
1879
+ c_hash: 'code hash',
1880
+ client_id: 'client id',
1881
+ exp: 'expiration time',
1882
+ iat: 'issued at',
1883
+ iss: 'issuer',
1884
+ jti: 'jwt id',
1885
+ nonce: 'nonce',
1886
+ s_hash: 'state hash',
1887
+ sub: 'subject',
1888
+ ath: 'access token hash',
1889
+ htm: 'http method',
1890
+ htu: 'http uri',
1891
+ cnf: 'confirmation',
1892
+ auth_time: 'authentication time'
1893
+ };
1894
+ function validatePresence(required, result) {
1895
+ for (const claim of required) {
1896
+ if (result.claims[claim] === undefined) {
1897
+ throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, {
1898
+ claims: result.claims
1899
+ });
1900
+ }
1901
+ }
1902
+ return result;
1903
+ }
1904
+ const expectNoNonce = Symbol();
1905
+ const skipAuthTimeCheck = Symbol();
1906
+ async function processAuthorizationCodeResponse(as, client, response, options) {
1907
+ if (typeof options?.expectedNonce === 'string' || typeof options?.maxAge === 'number' || options?.requireIdToken) {
1908
+ return processAuthorizationCodeOpenIDResponse(as, client, response, options.expectedNonce, options.maxAge, options[jweDecrypt], options.recognizedTokenTypes);
1909
+ }
1910
+ return processAuthorizationCodeOAuth2Response(as, client, response, options?.[jweDecrypt], options?.recognizedTokenTypes);
1911
+ }
1912
+ async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge, decryptFn, recognizedTokenTypes) {
1913
+ const additionalRequiredClaims = [];
1914
+ switch (expectedNonce) {
1915
+ case undefined:
1916
+ expectedNonce = expectNoNonce;
1917
+ break;
1918
+ case expectNoNonce:
1919
+ break;
1920
+ default:
1921
+ assertString(expectedNonce, '"expectedNonce" argument');
1922
+ additionalRequiredClaims.push('nonce');
1923
+ }
1924
+ maxAge ??= client.default_max_age;
1925
+ switch (maxAge) {
1926
+ case undefined:
1927
+ maxAge = skipAuthTimeCheck;
1928
+ break;
1929
+ case skipAuthTimeCheck:
1930
+ break;
1931
+ default:
1932
+ assertNumber(maxAge, true, '"maxAge" argument');
1933
+ additionalRequiredClaims.push('auth_time');
1934
+ }
1935
+ const result = await processGenericAccessTokenResponse(as, client, response, additionalRequiredClaims, decryptFn, recognizedTokenTypes);
1936
+ assertString(result.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
1937
+ body: result
1938
+ });
1939
+ const claims = getValidatedIdTokenClaims(result);
1940
+ if (maxAge !== skipAuthTimeCheck) {
1941
+ const now = epochTime() + getClockSkew(client);
1942
+ const tolerance = getClockTolerance(client);
1943
+ if (claims.auth_time + maxAge < now - tolerance) {
1944
+ throw OPE('too much time has elapsed since the last End-User authentication', JWT_TIMESTAMP_CHECK, {
1945
+ claims,
1946
+ now,
1947
+ tolerance,
1948
+ claim: 'auth_time'
1949
+ });
1950
+ }
1951
+ }
1952
+ if (expectedNonce === expectNoNonce) {
1953
+ if (claims.nonce !== undefined) {
1954
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1955
+ expected: undefined,
1956
+ claims,
1957
+ claim: 'nonce'
1958
+ });
1959
+ }
1960
+ } else if (claims.nonce !== expectedNonce) {
1961
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1962
+ expected: expectedNonce,
1963
+ claims,
1964
+ claim: 'nonce'
1965
+ });
1966
+ }
1967
+ return result;
1968
+ }
1969
+ async function processAuthorizationCodeOAuth2Response(as, client, response, decryptFn, recognizedTokenTypes) {
1970
+ const result = await processGenericAccessTokenResponse(as, client, response, undefined, decryptFn, recognizedTokenTypes);
1971
+ const claims = getValidatedIdTokenClaims(result);
1972
+ if (claims) {
1973
+ if (client.default_max_age !== undefined) {
1974
+ assertNumber(client.default_max_age, true, '"client.default_max_age"');
1975
+ const now = epochTime() + getClockSkew(client);
1976
+ const tolerance = getClockTolerance(client);
1977
+ if (claims.auth_time + client.default_max_age < now - tolerance) {
1978
+ throw OPE('too much time has elapsed since the last End-User authentication', JWT_TIMESTAMP_CHECK, {
1979
+ claims,
1980
+ now,
1981
+ tolerance,
1982
+ claim: 'auth_time'
1983
+ });
1984
+ }
1985
+ }
1986
+ if (claims.nonce !== undefined) {
1987
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
1988
+ expected: undefined,
1989
+ claims,
1990
+ claim: 'nonce'
1991
+ });
1992
+ }
1993
+ }
1994
+ return result;
1995
+ }
1996
+ const WWW_AUTHENTICATE_CHALLENGE = 'OAUTH_WWW_AUTHENTICATE_CHALLENGE';
1997
+ const RESPONSE_BODY_ERROR = 'OAUTH_RESPONSE_BODY_ERROR';
1998
+ const UNSUPPORTED_OPERATION = 'OAUTH_UNSUPPORTED_OPERATION';
1999
+ const PARSE_ERROR = 'OAUTH_PARSE_ERROR';
2000
+ const INVALID_RESPONSE = 'OAUTH_INVALID_RESPONSE';
2001
+ const RESPONSE_IS_NOT_JSON = 'OAUTH_RESPONSE_IS_NOT_JSON';
2002
+ const RESPONSE_IS_NOT_CONFORM = 'OAUTH_RESPONSE_IS_NOT_CONFORM';
2003
+ const HTTP_REQUEST_FORBIDDEN = 'OAUTH_HTTP_REQUEST_FORBIDDEN';
2004
+ const REQUEST_PROTOCOL_FORBIDDEN = 'OAUTH_REQUEST_PROTOCOL_FORBIDDEN';
2005
+ const JWT_TIMESTAMP_CHECK = 'OAUTH_JWT_TIMESTAMP_CHECK_FAILED';
2006
+ const JWT_CLAIM_COMPARISON = 'OAUTH_JWT_CLAIM_COMPARISON_FAILED';
2007
+ const MISSING_SERVER_METADATA = 'OAUTH_MISSING_SERVER_METADATA';
2008
+ const INVALID_SERVER_METADATA = 'OAUTH_INVALID_SERVER_METADATA';
2009
+ function assertReadableResponse(response) {
2010
+ if (response.bodyUsed) {
2011
+ throw CodedTypeError('"response" body has been used already', ERR_INVALID_ARG_VALUE);
2012
+ }
2013
+ }
2014
+ async function validateJwt(jws, checkAlg, clockSkew, clockTolerance, decryptJwt) {
2015
+ let {
2016
+ 0: protectedHeader,
2017
+ 1: payload,
2018
+ length
2019
+ } = jws.split('.');
2020
+ if (length === 5) {
2021
+ if (decryptJwt !== undefined) {
2022
+ jws = await decryptJwt(jws);
2023
+ ({
2024
+ 0: protectedHeader,
2025
+ 1: payload,
2026
+ length
2027
+ } = jws.split('.'));
2028
+ } else {
2029
+ throw new UnsupportedOperationError('JWE decryption is not configured', {
2030
+ cause: jws
2031
+ });
2032
+ }
2033
+ }
2034
+ if (length !== 3) {
2035
+ throw OPE('Invalid JWT', INVALID_RESPONSE, jws);
2036
+ }
2037
+ let header;
2038
+ try {
2039
+ header = JSON.parse(buf(b64u(protectedHeader)));
2040
+ } catch (cause) {
2041
+ throw OPE('failed to parse JWT Header body as base64url encoded JSON', PARSE_ERROR, cause);
2042
+ }
2043
+ if (!isJsonObject(header)) {
2044
+ throw OPE('JWT Header must be a top level object', INVALID_RESPONSE, jws);
2045
+ }
2046
+ checkAlg(header);
2047
+ if (header.crit !== undefined) {
2048
+ throw new UnsupportedOperationError('no JWT "crit" header parameter extensions are supported', {
2049
+ cause: {
2050
+ header
2051
+ }
2052
+ });
2053
+ }
2054
+ let claims;
2055
+ try {
2056
+ claims = JSON.parse(buf(b64u(payload)));
2057
+ } catch (cause) {
2058
+ throw OPE('failed to parse JWT Payload body as base64url encoded JSON', PARSE_ERROR, cause);
2059
+ }
2060
+ if (!isJsonObject(claims)) {
2061
+ throw OPE('JWT Payload must be a top level object', INVALID_RESPONSE, jws);
2062
+ }
2063
+ const now = epochTime() + clockSkew;
2064
+ if (claims.exp !== undefined) {
2065
+ if (typeof claims.exp !== 'number') {
2066
+ throw OPE('unexpected JWT "exp" (expiration time) claim type', INVALID_RESPONSE, {
2067
+ claims
2068
+ });
2069
+ }
2070
+ if (claims.exp <= now - clockTolerance) {
2071
+ throw OPE('unexpected JWT "exp" (expiration time) claim value, expiration is past current timestamp', JWT_TIMESTAMP_CHECK, {
2072
+ claims,
2073
+ now,
2074
+ tolerance: clockTolerance,
2075
+ claim: 'exp'
2076
+ });
2077
+ }
2078
+ }
2079
+ if (claims.iat !== undefined) {
2080
+ if (typeof claims.iat !== 'number') {
2081
+ throw OPE('unexpected JWT "iat" (issued at) claim type', INVALID_RESPONSE, {
2082
+ claims
2083
+ });
2084
+ }
2085
+ }
2086
+ if (claims.iss !== undefined) {
2087
+ if (typeof claims.iss !== 'string') {
2088
+ throw OPE('unexpected JWT "iss" (issuer) claim type', INVALID_RESPONSE, {
2089
+ claims
2090
+ });
2091
+ }
2092
+ }
2093
+ if (claims.nbf !== undefined) {
2094
+ if (typeof claims.nbf !== 'number') {
2095
+ throw OPE('unexpected JWT "nbf" (not before) claim type', INVALID_RESPONSE, {
2096
+ claims
2097
+ });
2098
+ }
2099
+ if (claims.nbf > now + clockTolerance) {
2100
+ throw OPE('unexpected JWT "nbf" (not before) claim value', JWT_TIMESTAMP_CHECK, {
2101
+ claims,
2102
+ now,
2103
+ tolerance: clockTolerance,
2104
+ claim: 'nbf'
2105
+ });
2106
+ }
2107
+ }
2108
+ if (claims.aud !== undefined) {
2109
+ if (typeof claims.aud !== 'string' && !Array.isArray(claims.aud)) {
2110
+ throw OPE('unexpected JWT "aud" (audience) claim type', INVALID_RESPONSE, {
2111
+ claims
2112
+ });
2113
+ }
2114
+ }
2115
+ return {
2116
+ header,
2117
+ claims,
2118
+ jwt: jws
2119
+ };
2120
+ }
2121
+ function checkSigningAlgorithm(client, issuer, fallback, header) {
2122
+ if (client !== undefined) {
2123
+ if (typeof client === 'string' ? header.alg !== client : !client.includes(header.alg)) {
2124
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
2125
+ header,
2126
+ expected: client,
2127
+ reason: 'client configuration'
2128
+ });
2129
+ }
2130
+ return;
2131
+ }
2132
+ if (Array.isArray(issuer)) {
2133
+ if (!issuer.includes(header.alg)) {
2134
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
2135
+ header,
2136
+ expected: issuer,
2137
+ reason: 'authorization server metadata'
2138
+ });
2139
+ }
2140
+ return;
2141
+ }
2142
+ if (fallback !== undefined) {
2143
+ if (typeof fallback === 'string' ? header.alg !== fallback : typeof fallback === 'function' ? !fallback(header.alg) : !fallback.includes(header.alg)) {
2144
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
2145
+ header,
2146
+ expected: fallback,
2147
+ reason: 'default value'
2148
+ });
2149
+ }
2150
+ return;
2151
+ }
2152
+ throw OPE('missing client or server configuration to verify used JWT "alg" header parameter', undefined, {
2153
+ client,
2154
+ issuer,
2155
+ fallback
2156
+ });
2157
+ }
2158
+ function getURLSearchParameter(parameters, name) {
2159
+ const {
2160
+ 0: value,
2161
+ length
2162
+ } = parameters.getAll(name);
2163
+ if (length > 1) {
2164
+ throw OPE(`"${name}" parameter must be provided only once`, INVALID_RESPONSE);
2165
+ }
2166
+ return value;
2167
+ }
2168
+ async function getResponseJsonBody(response, check = assertApplicationJson) {
2169
+ let json;
2170
+ try {
2171
+ json = await response.json();
2172
+ } catch (cause) {
2173
+ check(response);
2174
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
2175
+ }
2176
+ if (!isJsonObject(json)) {
2177
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, {
2178
+ body: json
2179
+ });
2180
+ }
2181
+ return json;
2182
+ }
2183
+ const _expectedIssuer = Symbol();
1361
2184
 
1362
2185
  // cspell:ignore pkce
1363
2186
 
@@ -1652,11 +2475,12 @@ const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirec
1652
2475
  codeChallenge,
1653
2476
  codeVerifier
1654
2477
  } = await createPkceValuesFn();
2478
+ const nonce = createRandomUuid();
1655
2479
  const loginUrl = new URL(getBackendAuthUrl(backendUrl, '/oidc/auth'));
1656
2480
  loginUrl.searchParams.set('client_id', oidcClientId);
1657
2481
  loginUrl.searchParams.set('code_challenge', codeChallenge);
1658
2482
  loginUrl.searchParams.set('code_challenge_method', 'S256');
1659
- loginUrl.searchParams.set('nonce', createRandomUuid());
2483
+ loginUrl.searchParams.set('nonce', nonce);
1660
2484
  loginUrl.searchParams.set('prompt', 'consent');
1661
2485
  loginUrl.searchParams.set('response_type', 'code');
1662
2486
  loginUrl.searchParams.set('scope', oidcScope);
@@ -1667,16 +2491,19 @@ const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirec
1667
2491
  return {
1668
2492
  codeVerifier,
1669
2493
  loginUrl: loginUrl.toString(),
2494
+ nonce,
1670
2495
  redirectUri: effectiveRedirectUri
1671
2496
  };
1672
2497
  };
1673
2498
 
1674
2499
  const getLoggedInState = (state, response) => {
1675
2500
  const accessToken = typeof response.accessToken === 'string' ? response.accessToken : '';
2501
+ const refreshToken = typeof response.refreshToken === 'string' ? response.refreshToken : '';
1676
2502
  return {
1677
2503
  ...state,
1678
2504
  authAccessToken: accessToken,
1679
2505
  authErrorMessage: '',
2506
+ authRefreshToken: refreshToken,
1680
2507
  userName: typeof response.userName === 'string' ? response.userName : state.userName,
1681
2508
  userState: accessToken ? 'loggedIn' : 'loggedOut',
1682
2509
  userSubscriptionPlan: typeof response.subscriptionPlan === 'string' ? response.subscriptionPlan : state.userSubscriptionPlan,
@@ -1691,22 +2518,54 @@ const isLoginResponse = value => {
1691
2518
  return true;
1692
2519
  };
1693
2520
 
2521
+ const getBackendOidcTokenUrl = backendUrl => {
2522
+ return getBackendAuthUrl(backendUrl, '/oidc/token');
2523
+ };
2524
+
2525
+ const getAuthorizationServer = backendUrl => {
2526
+ return {
2527
+ issuer: getBackendAuthUrl(backendUrl, '/oidc'),
2528
+ jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
2529
+ token_endpoint: getBackendOidcTokenUrl(backendUrl)
2530
+ };
2531
+ };
2532
+ const getClient = () => {
2533
+ return {
2534
+ client_id: oidcClientId
2535
+ };
2536
+ };
2537
+ const exchangeElectronAuthorizationCode = async (backendUrl, code, redirectUri, codeVerifier, nonce, requestAuthorizationCodeGrant = authorizationCodeGrantRequest, processAuthorizationCodeGrantResponse = processAuthorizationCodeResponse) => {
2538
+ const authorizationServer = getAuthorizationServer(backendUrl);
2539
+ const client = getClient();
2540
+ const response = await requestAuthorizationCodeGrant(authorizationServer, client, None(), new URLSearchParams({
2541
+ code
2542
+ }), redirectUri, codeVerifier);
2543
+ const tokenResponse = await processAuthorizationCodeGrantResponse(authorizationServer, client, response, {
2544
+ expectedNonce: nonce
2545
+ });
2546
+ return {
2547
+ accessToken: tokenResponse.access_token,
2548
+ refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : ''
2549
+ };
2550
+ };
2551
+
1694
2552
  const hasAuthorizationCode = value => {
1695
2553
  return typeof value === 'string' && value.length > 0;
1696
2554
  };
1697
2555
  const getElectronAuthorizationCode = async uid => {
1698
2556
  return invoke$2('OAuthServer.getCode', String(uid));
1699
2557
  };
1700
- const waitForElectronBackendLogin = async (uid, codeVerifier, timeoutMs = 30_000, pollIntervalMs = 1000, getAuthorizationCode = getElectronAuthorizationCode) => {
2558
+ const waitForElectronBackendLogin = async (backendUrl, uid, redirectUri, nonce, codeVerifier, timeoutMs = 30_000, pollIntervalMs = 1000, getAuthorizationCode = getElectronAuthorizationCode, exchangeAuthorizationCode = exchangeElectronAuthorizationCode) => {
1701
2559
  const deadline = Date.now() + timeoutMs;
1702
2560
  while (Date.now() < deadline) {
1703
2561
  const authorizationCode = await getAuthorizationCode(uid);
1704
2562
  if (hasAuthorizationCode(authorizationCode)) {
2563
+ const tokenResponse = await exchangeAuthorizationCode(backendUrl, authorizationCode, redirectUri, codeVerifier, nonce);
1705
2564
  return {
1706
- authCode: authorizationCode,
1707
- authCodeVerifier: codeVerifier,
2565
+ authAccessToken: tokenResponse.accessToken,
1708
2566
  authErrorMessage: '',
1709
- userState: 'loggedOut'
2567
+ authRefreshToken: tokenResponse.refreshToken,
2568
+ userState: tokenResponse.accessToken ? 'loggedIn' : 'loggedOut'
1710
2569
  };
1711
2570
  }
1712
2571
  await delay(pollIntervalMs);
@@ -1750,10 +2609,12 @@ const handleClickLogin = async options => {
1750
2609
  const uid = 0;
1751
2610
  const {
1752
2611
  codeVerifier,
1753
- loginUrl
2612
+ loginUrl,
2613
+ nonce,
2614
+ redirectUri
1754
2615
  } = await getBackendLoginRequest(backendUrl, platform, uid);
1755
2616
  await invoke$1('Open.openUrl', loginUrl, platform, authUseRedirect);
1756
- const authState = platform === Electron ? await waitForElectronBackendLogin(uid, codeVerifier) : await waitForBackendLogin(backendUrl);
2617
+ const authState = platform === Electron ? await waitForElectronBackendLogin(backendUrl, uid, redirectUri, nonce, codeVerifier) : await waitForBackendLogin(backendUrl);
1757
2618
  return {
1758
2619
  ...authState
1759
2620
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/auth-worker",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Auth Worker",
5
5
  "repository": {
6
6
  "type": "git",