@lvce-editor/auth-worker 1.14.0 → 1.16.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/authWorkerMain.js +805 -12
- package/package.json +1 -1
package/dist/authWorkerMain.js
CHANGED
|
@@ -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
|
-
|
|
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,581 @@ 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
|
+
async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, decryptFn, recognizedTokenTypes) {
|
|
1722
|
+
assertAs(as);
|
|
1723
|
+
assertClient(client);
|
|
1724
|
+
if (!looseInstanceOf(response, Response)) {
|
|
1725
|
+
throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
|
|
1726
|
+
}
|
|
1727
|
+
await checkOAuthBodyError(response, 200, 'Token Endpoint');
|
|
1728
|
+
assertReadableResponse(response);
|
|
1729
|
+
const json = await getResponseJsonBody(response);
|
|
1730
|
+
assertString(json.access_token, '"response" body "access_token" property', INVALID_RESPONSE, {
|
|
1731
|
+
body: json
|
|
1732
|
+
});
|
|
1733
|
+
assertString(json.token_type, '"response" body "token_type" property', INVALID_RESPONSE, {
|
|
1734
|
+
body: json
|
|
1735
|
+
});
|
|
1736
|
+
json.token_type = json.token_type.toLowerCase();
|
|
1737
|
+
if (json.expires_in !== undefined) {
|
|
1738
|
+
let expiresIn = typeof json.expires_in !== 'number' ? parseFloat(json.expires_in) : json.expires_in;
|
|
1739
|
+
assertNumber(expiresIn, true, '"response" body "expires_in" property', INVALID_RESPONSE, {
|
|
1740
|
+
body: json
|
|
1741
|
+
});
|
|
1742
|
+
json.expires_in = expiresIn;
|
|
1743
|
+
}
|
|
1744
|
+
if (json.refresh_token !== undefined) {
|
|
1745
|
+
assertString(json.refresh_token, '"response" body "refresh_token" property', INVALID_RESPONSE, {
|
|
1746
|
+
body: json
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
if (json.scope !== undefined && typeof json.scope !== 'string') {
|
|
1750
|
+
throw OPE('"response" body "scope" property must be a string', INVALID_RESPONSE, {
|
|
1751
|
+
body: json
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
if (json.id_token !== undefined) {
|
|
1755
|
+
assertString(json.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
|
|
1756
|
+
body: json
|
|
1757
|
+
});
|
|
1758
|
+
const requiredClaims = ['aud', 'exp', 'iat', 'iss', 'sub'];
|
|
1759
|
+
if (client.require_auth_time === true) {
|
|
1760
|
+
requiredClaims.push('auth_time');
|
|
1761
|
+
}
|
|
1762
|
+
if (client.default_max_age !== undefined) {
|
|
1763
|
+
assertNumber(client.default_max_age, true, '"client.default_max_age"');
|
|
1764
|
+
requiredClaims.push('auth_time');
|
|
1765
|
+
}
|
|
1766
|
+
const {
|
|
1767
|
+
claims,
|
|
1768
|
+
jwt
|
|
1769
|
+
} = 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));
|
|
1770
|
+
if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
|
|
1771
|
+
if (claims.azp === undefined) {
|
|
1772
|
+
throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, {
|
|
1773
|
+
claims,
|
|
1774
|
+
claim: 'aud'
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
if (claims.azp !== client.client_id) {
|
|
1778
|
+
throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, {
|
|
1779
|
+
expected: client.client_id,
|
|
1780
|
+
claims,
|
|
1781
|
+
claim: 'azp'
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
if (claims.auth_time !== undefined) {
|
|
1786
|
+
assertNumber(claims.auth_time, true, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, {
|
|
1787
|
+
claims
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
jwtRefs.set(response, jwt);
|
|
1791
|
+
idTokenClaims.set(json, claims);
|
|
1792
|
+
}
|
|
1793
|
+
if (recognizedTokenTypes?.[json.token_type] !== undefined) {
|
|
1794
|
+
recognizedTokenTypes[json.token_type](response, json);
|
|
1795
|
+
} else if (json.token_type !== 'dpop' && json.token_type !== 'bearer') {
|
|
1796
|
+
throw new UnsupportedOperationError('unsupported `token_type` value', {
|
|
1797
|
+
cause: {
|
|
1798
|
+
body: json
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
return json;
|
|
1803
|
+
}
|
|
1804
|
+
function checkAuthenticationChallenges(response) {
|
|
1805
|
+
let challenges;
|
|
1806
|
+
if (challenges = parseWwwAuthenticateChallenges(response)) {
|
|
1807
|
+
throw new WWWAuthenticateChallengeError('server responded with a challenge in the WWW-Authenticate HTTP Header', {
|
|
1808
|
+
cause: challenges,
|
|
1809
|
+
response
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
function validateAudience(expected, result) {
|
|
1814
|
+
if (Array.isArray(result.claims.aud)) {
|
|
1815
|
+
if (!result.claims.aud.includes(expected)) {
|
|
1816
|
+
throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
|
|
1817
|
+
expected,
|
|
1818
|
+
claims: result.claims,
|
|
1819
|
+
claim: 'aud'
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
} else if (result.claims.aud !== expected) {
|
|
1823
|
+
throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
|
|
1824
|
+
expected,
|
|
1825
|
+
claims: result.claims,
|
|
1826
|
+
claim: 'aud'
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
return result;
|
|
1830
|
+
}
|
|
1831
|
+
function validateIssuer(as, result) {
|
|
1832
|
+
const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
|
|
1833
|
+
if (result.claims.iss !== expected) {
|
|
1834
|
+
throw OPE('unexpected JWT "iss" (issuer) claim value', JWT_CLAIM_COMPARISON, {
|
|
1835
|
+
expected,
|
|
1836
|
+
claims: result.claims,
|
|
1837
|
+
claim: 'iss'
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
return result;
|
|
1841
|
+
}
|
|
1842
|
+
const branded = new WeakSet();
|
|
1843
|
+
const jwtClaimNames = {
|
|
1844
|
+
aud: 'audience',
|
|
1845
|
+
c_hash: 'code hash',
|
|
1846
|
+
client_id: 'client id',
|
|
1847
|
+
exp: 'expiration time',
|
|
1848
|
+
iat: 'issued at',
|
|
1849
|
+
iss: 'issuer',
|
|
1850
|
+
jti: 'jwt id',
|
|
1851
|
+
nonce: 'nonce',
|
|
1852
|
+
s_hash: 'state hash',
|
|
1853
|
+
sub: 'subject',
|
|
1854
|
+
ath: 'access token hash',
|
|
1855
|
+
htm: 'http method',
|
|
1856
|
+
htu: 'http uri',
|
|
1857
|
+
cnf: 'confirmation',
|
|
1858
|
+
auth_time: 'authentication time'
|
|
1859
|
+
};
|
|
1860
|
+
function validatePresence(required, result) {
|
|
1861
|
+
for (const claim of required) {
|
|
1862
|
+
if (result.claims[claim] === undefined) {
|
|
1863
|
+
throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, {
|
|
1864
|
+
claims: result.claims
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return result;
|
|
1869
|
+
}
|
|
1870
|
+
const WWW_AUTHENTICATE_CHALLENGE = 'OAUTH_WWW_AUTHENTICATE_CHALLENGE';
|
|
1871
|
+
const RESPONSE_BODY_ERROR = 'OAUTH_RESPONSE_BODY_ERROR';
|
|
1872
|
+
const UNSUPPORTED_OPERATION = 'OAUTH_UNSUPPORTED_OPERATION';
|
|
1873
|
+
const PARSE_ERROR = 'OAUTH_PARSE_ERROR';
|
|
1874
|
+
const INVALID_RESPONSE = 'OAUTH_INVALID_RESPONSE';
|
|
1875
|
+
const RESPONSE_IS_NOT_JSON = 'OAUTH_RESPONSE_IS_NOT_JSON';
|
|
1876
|
+
const RESPONSE_IS_NOT_CONFORM = 'OAUTH_RESPONSE_IS_NOT_CONFORM';
|
|
1877
|
+
const HTTP_REQUEST_FORBIDDEN = 'OAUTH_HTTP_REQUEST_FORBIDDEN';
|
|
1878
|
+
const REQUEST_PROTOCOL_FORBIDDEN = 'OAUTH_REQUEST_PROTOCOL_FORBIDDEN';
|
|
1879
|
+
const JWT_TIMESTAMP_CHECK = 'OAUTH_JWT_TIMESTAMP_CHECK_FAILED';
|
|
1880
|
+
const JWT_CLAIM_COMPARISON = 'OAUTH_JWT_CLAIM_COMPARISON_FAILED';
|
|
1881
|
+
const MISSING_SERVER_METADATA = 'OAUTH_MISSING_SERVER_METADATA';
|
|
1882
|
+
const INVALID_SERVER_METADATA = 'OAUTH_INVALID_SERVER_METADATA';
|
|
1883
|
+
async function genericTokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
|
|
1884
|
+
assertAs(as);
|
|
1885
|
+
assertClient(client);
|
|
1886
|
+
assertString(grantType, '"grantType"');
|
|
1887
|
+
return tokenEndpointRequest(as, client, clientAuthentication, grantType, new URLSearchParams(parameters), options);
|
|
1888
|
+
}
|
|
1889
|
+
async function processGenericTokenEndpointResponse(as, client, response, options) {
|
|
1890
|
+
return processGenericAccessTokenResponse(as, client, response, undefined, options?.[jweDecrypt], options?.recognizedTokenTypes);
|
|
1891
|
+
}
|
|
1892
|
+
function assertReadableResponse(response) {
|
|
1893
|
+
if (response.bodyUsed) {
|
|
1894
|
+
throw CodedTypeError('"response" body has been used already', ERR_INVALID_ARG_VALUE);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
async function validateJwt(jws, checkAlg, clockSkew, clockTolerance, decryptJwt) {
|
|
1898
|
+
let {
|
|
1899
|
+
0: protectedHeader,
|
|
1900
|
+
1: payload,
|
|
1901
|
+
length
|
|
1902
|
+
} = jws.split('.');
|
|
1903
|
+
if (length === 5) {
|
|
1904
|
+
if (decryptJwt !== undefined) {
|
|
1905
|
+
jws = await decryptJwt(jws);
|
|
1906
|
+
({
|
|
1907
|
+
0: protectedHeader,
|
|
1908
|
+
1: payload,
|
|
1909
|
+
length
|
|
1910
|
+
} = jws.split('.'));
|
|
1911
|
+
} else {
|
|
1912
|
+
throw new UnsupportedOperationError('JWE decryption is not configured', {
|
|
1913
|
+
cause: jws
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (length !== 3) {
|
|
1918
|
+
throw OPE('Invalid JWT', INVALID_RESPONSE, jws);
|
|
1919
|
+
}
|
|
1920
|
+
let header;
|
|
1921
|
+
try {
|
|
1922
|
+
header = JSON.parse(buf(b64u(protectedHeader)));
|
|
1923
|
+
} catch (cause) {
|
|
1924
|
+
throw OPE('failed to parse JWT Header body as base64url encoded JSON', PARSE_ERROR, cause);
|
|
1925
|
+
}
|
|
1926
|
+
if (!isJsonObject(header)) {
|
|
1927
|
+
throw OPE('JWT Header must be a top level object', INVALID_RESPONSE, jws);
|
|
1928
|
+
}
|
|
1929
|
+
checkAlg(header);
|
|
1930
|
+
if (header.crit !== undefined) {
|
|
1931
|
+
throw new UnsupportedOperationError('no JWT "crit" header parameter extensions are supported', {
|
|
1932
|
+
cause: {
|
|
1933
|
+
header
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
let claims;
|
|
1938
|
+
try {
|
|
1939
|
+
claims = JSON.parse(buf(b64u(payload)));
|
|
1940
|
+
} catch (cause) {
|
|
1941
|
+
throw OPE('failed to parse JWT Payload body as base64url encoded JSON', PARSE_ERROR, cause);
|
|
1942
|
+
}
|
|
1943
|
+
if (!isJsonObject(claims)) {
|
|
1944
|
+
throw OPE('JWT Payload must be a top level object', INVALID_RESPONSE, jws);
|
|
1945
|
+
}
|
|
1946
|
+
const now = epochTime() + clockSkew;
|
|
1947
|
+
if (claims.exp !== undefined) {
|
|
1948
|
+
if (typeof claims.exp !== 'number') {
|
|
1949
|
+
throw OPE('unexpected JWT "exp" (expiration time) claim type', INVALID_RESPONSE, {
|
|
1950
|
+
claims
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
if (claims.exp <= now - clockTolerance) {
|
|
1954
|
+
throw OPE('unexpected JWT "exp" (expiration time) claim value, expiration is past current timestamp', JWT_TIMESTAMP_CHECK, {
|
|
1955
|
+
claims,
|
|
1956
|
+
now,
|
|
1957
|
+
tolerance: clockTolerance,
|
|
1958
|
+
claim: 'exp'
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (claims.iat !== undefined) {
|
|
1963
|
+
if (typeof claims.iat !== 'number') {
|
|
1964
|
+
throw OPE('unexpected JWT "iat" (issued at) claim type', INVALID_RESPONSE, {
|
|
1965
|
+
claims
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
if (claims.iss !== undefined) {
|
|
1970
|
+
if (typeof claims.iss !== 'string') {
|
|
1971
|
+
throw OPE('unexpected JWT "iss" (issuer) claim type', INVALID_RESPONSE, {
|
|
1972
|
+
claims
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
if (claims.nbf !== undefined) {
|
|
1977
|
+
if (typeof claims.nbf !== 'number') {
|
|
1978
|
+
throw OPE('unexpected JWT "nbf" (not before) claim type', INVALID_RESPONSE, {
|
|
1979
|
+
claims
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
if (claims.nbf > now + clockTolerance) {
|
|
1983
|
+
throw OPE('unexpected JWT "nbf" (not before) claim value', JWT_TIMESTAMP_CHECK, {
|
|
1984
|
+
claims,
|
|
1985
|
+
now,
|
|
1986
|
+
tolerance: clockTolerance,
|
|
1987
|
+
claim: 'nbf'
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
if (claims.aud !== undefined) {
|
|
1992
|
+
if (typeof claims.aud !== 'string' && !Array.isArray(claims.aud)) {
|
|
1993
|
+
throw OPE('unexpected JWT "aud" (audience) claim type', INVALID_RESPONSE, {
|
|
1994
|
+
claims
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return {
|
|
1999
|
+
header,
|
|
2000
|
+
claims,
|
|
2001
|
+
jwt: jws
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
function checkSigningAlgorithm(client, issuer, fallback, header) {
|
|
2005
|
+
if (client !== undefined) {
|
|
2006
|
+
if (typeof client === 'string' ? header.alg !== client : !client.includes(header.alg)) {
|
|
2007
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
2008
|
+
header,
|
|
2009
|
+
expected: client,
|
|
2010
|
+
reason: 'client configuration'
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (Array.isArray(issuer)) {
|
|
2016
|
+
if (!issuer.includes(header.alg)) {
|
|
2017
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
2018
|
+
header,
|
|
2019
|
+
expected: issuer,
|
|
2020
|
+
reason: 'authorization server metadata'
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
if (fallback !== undefined) {
|
|
2026
|
+
if (typeof fallback === 'string' ? header.alg !== fallback : typeof fallback === 'function' ? !fallback(header.alg) : !fallback.includes(header.alg)) {
|
|
2027
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
2028
|
+
header,
|
|
2029
|
+
expected: fallback,
|
|
2030
|
+
reason: 'default value'
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
throw OPE('missing client or server configuration to verify used JWT "alg" header parameter', undefined, {
|
|
2036
|
+
client,
|
|
2037
|
+
issuer,
|
|
2038
|
+
fallback
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
async function getResponseJsonBody(response, check = assertApplicationJson) {
|
|
2042
|
+
let json;
|
|
2043
|
+
try {
|
|
2044
|
+
json = await response.json();
|
|
2045
|
+
} catch (cause) {
|
|
2046
|
+
check(response);
|
|
2047
|
+
throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
|
|
2048
|
+
}
|
|
2049
|
+
if (!isJsonObject(json)) {
|
|
2050
|
+
throw OPE('"response" body must be a top level object', INVALID_RESPONSE, {
|
|
2051
|
+
body: json
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
return json;
|
|
2055
|
+
}
|
|
2056
|
+
const _expectedIssuer = Symbol();
|
|
1361
2057
|
|
|
1362
2058
|
// cspell:ignore pkce
|
|
1363
2059
|
|
|
@@ -1652,11 +2348,12 @@ const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirec
|
|
|
1652
2348
|
codeChallenge,
|
|
1653
2349
|
codeVerifier
|
|
1654
2350
|
} = await createPkceValuesFn();
|
|
2351
|
+
const nonce = createRandomUuid();
|
|
1655
2352
|
const loginUrl = new URL(getBackendAuthUrl(backendUrl, '/oidc/auth'));
|
|
1656
2353
|
loginUrl.searchParams.set('client_id', oidcClientId);
|
|
1657
2354
|
loginUrl.searchParams.set('code_challenge', codeChallenge);
|
|
1658
2355
|
loginUrl.searchParams.set('code_challenge_method', 'S256');
|
|
1659
|
-
loginUrl.searchParams.set('nonce',
|
|
2356
|
+
loginUrl.searchParams.set('nonce', nonce);
|
|
1660
2357
|
loginUrl.searchParams.set('prompt', 'consent');
|
|
1661
2358
|
loginUrl.searchParams.set('response_type', 'code');
|
|
1662
2359
|
loginUrl.searchParams.set('scope', oidcScope);
|
|
@@ -1667,16 +2364,19 @@ const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirec
|
|
|
1667
2364
|
return {
|
|
1668
2365
|
codeVerifier,
|
|
1669
2366
|
loginUrl: loginUrl.toString(),
|
|
2367
|
+
nonce,
|
|
1670
2368
|
redirectUri: effectiveRedirectUri
|
|
1671
2369
|
};
|
|
1672
2370
|
};
|
|
1673
2371
|
|
|
1674
2372
|
const getLoggedInState = (state, response) => {
|
|
1675
2373
|
const accessToken = typeof response.accessToken === 'string' ? response.accessToken : '';
|
|
2374
|
+
const refreshToken = typeof response.refreshToken === 'string' ? response.refreshToken : '';
|
|
1676
2375
|
return {
|
|
1677
2376
|
...state,
|
|
1678
2377
|
authAccessToken: accessToken,
|
|
1679
2378
|
authErrorMessage: '',
|
|
2379
|
+
authRefreshToken: refreshToken,
|
|
1680
2380
|
userName: typeof response.userName === 'string' ? response.userName : state.userName,
|
|
1681
2381
|
userState: accessToken ? 'loggedIn' : 'loggedOut',
|
|
1682
2382
|
userSubscriptionPlan: typeof response.subscriptionPlan === 'string' ? response.subscriptionPlan : state.userSubscriptionPlan,
|
|
@@ -1691,22 +2391,108 @@ const isLoginResponse = value => {
|
|
|
1691
2391
|
return true;
|
|
1692
2392
|
};
|
|
1693
2393
|
|
|
2394
|
+
const databaseName = 'auth-worker';
|
|
2395
|
+
const objectStoreName = 'auth';
|
|
2396
|
+
const memoryStorage = new Map();
|
|
2397
|
+
let databasePromise;
|
|
2398
|
+
|
|
2399
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
2400
|
+
const transactionToPromise = transaction => {
|
|
2401
|
+
return new Promise((resolve, reject) => {
|
|
2402
|
+
transaction.addEventListener('complete', () => {
|
|
2403
|
+
resolve();
|
|
2404
|
+
});
|
|
2405
|
+
transaction.addEventListener('abort', () => {
|
|
2406
|
+
reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
|
|
2407
|
+
});
|
|
2408
|
+
transaction.addEventListener('error', () => {
|
|
2409
|
+
reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
|
|
2410
|
+
});
|
|
2411
|
+
});
|
|
2412
|
+
};
|
|
2413
|
+
const getDatabase = async () => {
|
|
2414
|
+
if (typeof indexedDB === 'undefined') {
|
|
2415
|
+
return undefined;
|
|
2416
|
+
}
|
|
2417
|
+
if (!databasePromise) {
|
|
2418
|
+
databasePromise = new Promise((resolve, reject) => {
|
|
2419
|
+
const request = indexedDB.open(databaseName, 1);
|
|
2420
|
+
request.addEventListener('upgradeneeded', () => {
|
|
2421
|
+
const database = request.result;
|
|
2422
|
+
if (!database.objectStoreNames.contains(objectStoreName)) {
|
|
2423
|
+
database.createObjectStore(objectStoreName);
|
|
2424
|
+
}
|
|
2425
|
+
});
|
|
2426
|
+
request.addEventListener('success', () => {
|
|
2427
|
+
resolve(request.result);
|
|
2428
|
+
});
|
|
2429
|
+
request.addEventListener('error', () => {
|
|
2430
|
+
reject(request.error ?? new Error('Failed to open persistent auth storage.'));
|
|
2431
|
+
});
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
return databasePromise;
|
|
2435
|
+
};
|
|
2436
|
+
const setPersistentAuthValue = async (key, value) => {
|
|
2437
|
+
memoryStorage.set(key, value);
|
|
2438
|
+
const database = await getDatabase();
|
|
2439
|
+
if (!database) {
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
const transaction = database.transaction(objectStoreName, 'readwrite');
|
|
2443
|
+
const objectStore = transaction.objectStore(objectStoreName);
|
|
2444
|
+
objectStore.put(value, key);
|
|
2445
|
+
await transactionToPromise(transaction);
|
|
2446
|
+
};
|
|
2447
|
+
|
|
2448
|
+
const getBackendOidcTokenUrl = backendUrl => {
|
|
2449
|
+
return getBackendAuthUrl(backendUrl, '/oidc/token');
|
|
2450
|
+
};
|
|
2451
|
+
|
|
2452
|
+
const getAuthorizationServer = backendUrl => {
|
|
2453
|
+
return {
|
|
2454
|
+
issuer: getBackendAuthUrl(backendUrl, '/oidc'),
|
|
2455
|
+
jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
|
|
2456
|
+
token_endpoint: getBackendOidcTokenUrl(backendUrl)
|
|
2457
|
+
};
|
|
2458
|
+
};
|
|
2459
|
+
const getClient = () => {
|
|
2460
|
+
return {
|
|
2461
|
+
client_id: oidcClientId
|
|
2462
|
+
};
|
|
2463
|
+
};
|
|
2464
|
+
const exchangeElectronAuthorizationCode = async (backendUrl, code, redirectUri, codeVerifier, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
|
|
2465
|
+
const authorizationServer = getAuthorizationServer(backendUrl);
|
|
2466
|
+
const client = getClient();
|
|
2467
|
+
const response = await requestTokenEndpoint(authorizationServer, client, None(), 'authorization_code', new URLSearchParams({
|
|
2468
|
+
code,
|
|
2469
|
+
code_verifier: codeVerifier,
|
|
2470
|
+
redirect_uri: redirectUri
|
|
2471
|
+
}));
|
|
2472
|
+
const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
|
|
2473
|
+
return {
|
|
2474
|
+
accessToken: tokenResponse.access_token,
|
|
2475
|
+
refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : ''
|
|
2476
|
+
};
|
|
2477
|
+
};
|
|
2478
|
+
|
|
1694
2479
|
const hasAuthorizationCode = value => {
|
|
1695
2480
|
return typeof value === 'string' && value.length > 0;
|
|
1696
2481
|
};
|
|
1697
2482
|
const getElectronAuthorizationCode = async uid => {
|
|
1698
2483
|
return invoke$2('OAuthServer.getCode', String(uid));
|
|
1699
2484
|
};
|
|
1700
|
-
const waitForElectronBackendLogin = async (uid, codeVerifier, timeoutMs = 30_000, pollIntervalMs = 1000, getAuthorizationCode = getElectronAuthorizationCode) => {
|
|
2485
|
+
const waitForElectronBackendLogin = async (backendUrl, uid, redirectUri, codeVerifier, timeoutMs = 30_000, pollIntervalMs = 1000, getAuthorizationCode = getElectronAuthorizationCode, exchangeAuthorizationCode = exchangeElectronAuthorizationCode) => {
|
|
1701
2486
|
const deadline = Date.now() + timeoutMs;
|
|
1702
2487
|
while (Date.now() < deadline) {
|
|
1703
2488
|
const authorizationCode = await getAuthorizationCode(uid);
|
|
1704
2489
|
if (hasAuthorizationCode(authorizationCode)) {
|
|
2490
|
+
const tokenResponse = await exchangeAuthorizationCode(backendUrl, authorizationCode, redirectUri, codeVerifier);
|
|
1705
2491
|
return {
|
|
1706
|
-
|
|
1707
|
-
authCodeVerifier: codeVerifier,
|
|
2492
|
+
authAccessToken: tokenResponse.accessToken,
|
|
1708
2493
|
authErrorMessage: '',
|
|
1709
|
-
|
|
2494
|
+
authRefreshToken: tokenResponse.refreshToken,
|
|
2495
|
+
userState: tokenResponse.accessToken ? 'loggedIn' : 'loggedOut'
|
|
1710
2496
|
};
|
|
1711
2497
|
}
|
|
1712
2498
|
await delay(pollIntervalMs);
|
|
@@ -1714,6 +2500,14 @@ const waitForElectronBackendLogin = async (uid, codeVerifier, timeoutMs = 30_000
|
|
|
1714
2500
|
return getLoggedOutBackendAuthState('Timed out waiting for backend login.');
|
|
1715
2501
|
};
|
|
1716
2502
|
|
|
2503
|
+
const persistLoginResult = async loginResult => {
|
|
2504
|
+
if (loginResult.userState !== 'loggedIn') {
|
|
2505
|
+
return loginResult;
|
|
2506
|
+
}
|
|
2507
|
+
await setPersistentAuthValue('accessToken', loginResult.authAccessToken ?? '');
|
|
2508
|
+
await setPersistentAuthValue('refreshToken', loginResult.authRefreshToken ?? '');
|
|
2509
|
+
return loginResult;
|
|
2510
|
+
};
|
|
1717
2511
|
const handleClickLogin = async options => {
|
|
1718
2512
|
const {
|
|
1719
2513
|
authUseRedirect,
|
|
@@ -1745,18 +2539,17 @@ const handleClickLogin = async options => {
|
|
|
1745
2539
|
userState: 'loggedOut'
|
|
1746
2540
|
};
|
|
1747
2541
|
}
|
|
1748
|
-
return getLoggedInState(signingInState, response);
|
|
2542
|
+
return persistLoginResult(getLoggedInState(signingInState, response));
|
|
1749
2543
|
}
|
|
1750
2544
|
const uid = 0;
|
|
1751
2545
|
const {
|
|
1752
2546
|
codeVerifier,
|
|
1753
|
-
loginUrl
|
|
2547
|
+
loginUrl,
|
|
2548
|
+
redirectUri
|
|
1754
2549
|
} = await getBackendLoginRequest(backendUrl, platform, uid);
|
|
1755
2550
|
await invoke$1('Open.openUrl', loginUrl, platform, authUseRedirect);
|
|
1756
|
-
const authState = platform === Electron ? await waitForElectronBackendLogin(uid, codeVerifier) : await waitForBackendLogin(backendUrl);
|
|
1757
|
-
return
|
|
1758
|
-
...authState
|
|
1759
|
-
};
|
|
2551
|
+
const authState = platform === Electron ? await waitForElectronBackendLogin(backendUrl, uid, redirectUri, codeVerifier) : await waitForBackendLogin(backendUrl);
|
|
2552
|
+
return persistLoginResult(authState);
|
|
1760
2553
|
} catch (error) {
|
|
1761
2554
|
const errorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
|
|
1762
2555
|
return {
|