@lvce-editor/auth-worker 1.12.0 → 1.14.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.
@@ -1261,6 +1261,115 @@ const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalM
1261
1261
  return getLoggedOutBackendAuthState(lastErrorMessage);
1262
1262
  };
1263
1263
 
1264
+ if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) ;
1265
+ const ERR_INVALID_ARG_VALUE = 'ERR_INVALID_ARG_VALUE';
1266
+ const ERR_INVALID_ARG_TYPE = 'ERR_INVALID_ARG_TYPE';
1267
+ function CodedTypeError(message, code, cause) {
1268
+ const err = new TypeError(message, {
1269
+ cause
1270
+ });
1271
+ Object.assign(err, {
1272
+ code
1273
+ });
1274
+ return err;
1275
+ }
1276
+ const encoder = new TextEncoder();
1277
+ const decoder = new TextDecoder();
1278
+ function buf(input) {
1279
+ if (typeof input === 'string') {
1280
+ return encoder.encode(input);
1281
+ }
1282
+ return decoder.decode(input);
1283
+ }
1284
+ let encodeBase64Url;
1285
+ if (Uint8Array.prototype.toBase64) {
1286
+ encodeBase64Url = input => {
1287
+ if (input instanceof ArrayBuffer) {
1288
+ input = new Uint8Array(input);
1289
+ }
1290
+ return input.toBase64({
1291
+ alphabet: 'base64url',
1292
+ omitPadding: true
1293
+ });
1294
+ };
1295
+ } else {
1296
+ const CHUNK_SIZE = 0x8000;
1297
+ encodeBase64Url = input => {
1298
+ if (input instanceof ArrayBuffer) {
1299
+ input = new Uint8Array(input);
1300
+ }
1301
+ const arr = [];
1302
+ for (let i = 0; i < input.byteLength; i += CHUNK_SIZE) {
1303
+ arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
1304
+ }
1305
+ return btoa(arr.join('')).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
1306
+ };
1307
+ }
1308
+ let decodeBase64Url;
1309
+ if (Uint8Array.fromBase64) {
1310
+ decodeBase64Url = input => {
1311
+ try {
1312
+ return Uint8Array.fromBase64(input, {
1313
+ alphabet: 'base64url'
1314
+ });
1315
+ } catch (cause) {
1316
+ throw CodedTypeError('The input to be decoded is not correctly encoded.', ERR_INVALID_ARG_VALUE, cause);
1317
+ }
1318
+ };
1319
+ } else {
1320
+ decodeBase64Url = input => {
1321
+ try {
1322
+ const binary = atob(input.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, ''));
1323
+ const bytes = new Uint8Array(binary.length);
1324
+ for (let i = 0; i < binary.length; i++) {
1325
+ bytes[i] = binary.charCodeAt(i);
1326
+ }
1327
+ return bytes;
1328
+ } catch (cause) {
1329
+ throw CodedTypeError('The input to be decoded is not correctly encoded.', ERR_INVALID_ARG_VALUE, cause);
1330
+ }
1331
+ };
1332
+ }
1333
+ function b64u(input) {
1334
+ if (typeof input === 'string') {
1335
+ return decodeBase64Url(input);
1336
+ }
1337
+ return encodeBase64Url(input);
1338
+ }
1339
+ function assertString(input, it, code, cause) {
1340
+ try {
1341
+ if (typeof input !== 'string') {
1342
+ throw CodedTypeError(`${it} must be a string`, ERR_INVALID_ARG_TYPE, cause);
1343
+ }
1344
+ if (input.length === 0) {
1345
+ throw CodedTypeError(`${it} must not be empty`, ERR_INVALID_ARG_VALUE, cause);
1346
+ }
1347
+ } catch (err) {
1348
+ throw err;
1349
+ }
1350
+ }
1351
+ function randomBytes() {
1352
+ return b64u(crypto.getRandomValues(new Uint8Array(32)));
1353
+ }
1354
+ function generateRandomCodeVerifier() {
1355
+ return randomBytes();
1356
+ }
1357
+ async function calculatePKCECodeChallenge(codeVerifier) {
1358
+ assertString(codeVerifier, 'codeVerifier');
1359
+ return b64u(await crypto.subtle.digest('SHA-256', buf(codeVerifier)));
1360
+ }
1361
+
1362
+ // cspell:ignore pkce
1363
+
1364
+ const createPkceValues = async () => {
1365
+ const codeVerifier = generateRandomCodeVerifier();
1366
+ const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
1367
+ return {
1368
+ codeChallenge,
1369
+ codeVerifier
1370
+ };
1371
+ };
1372
+
1264
1373
  const getCurrentHref = async () => {
1265
1374
  try {
1266
1375
  return await invoke('Layout.getHref');
@@ -1521,7 +1630,7 @@ const errorHtml = `<!doctype html>
1521
1630
 
1522
1631
  const getElectronRedirectUri = async uid => {
1523
1632
  const localOauthServerPort = await invoke$2('OAuthServer.create', String(uid), successHtml, errorHtml);
1524
- return `http://localhost:${localOauthServerPort}`;
1633
+ return `http://localhost:${localOauthServerPort}/callback`;
1525
1634
  };
1526
1635
 
1527
1636
  const getEffectiveRedirectUri = async (platform, uid, redirectUri) => {
@@ -1534,13 +1643,29 @@ const getEffectiveRedirectUri = async (platform, uid, redirectUri) => {
1534
1643
  return getCurrentHref();
1535
1644
  };
1536
1645
 
1537
- const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirectUri = '') => {
1538
- const loginUrl = new URL(getBackendAuthUrl(backendUrl, '/login'));
1646
+ const oidcClientId = 'lvce-editor-native';
1647
+ const oidcScope = 'openid offline_access profile email';
1648
+
1649
+ const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirectUri = '', createPkceValuesFn = createPkceValues, createRandomUuid = () => globalThis.crypto.randomUUID()) => {
1539
1650
  const effectiveRedirectUri = await getEffectiveRedirectUri(platform, uid, redirectUri);
1651
+ const {
1652
+ codeChallenge,
1653
+ codeVerifier
1654
+ } = await createPkceValuesFn();
1655
+ const loginUrl = new URL(getBackendAuthUrl(backendUrl, '/oidc/auth'));
1656
+ loginUrl.searchParams.set('client_id', oidcClientId);
1657
+ loginUrl.searchParams.set('code_challenge', codeChallenge);
1658
+ loginUrl.searchParams.set('code_challenge_method', 'S256');
1659
+ loginUrl.searchParams.set('nonce', createRandomUuid());
1660
+ loginUrl.searchParams.set('prompt', 'consent');
1661
+ loginUrl.searchParams.set('response_type', 'code');
1662
+ loginUrl.searchParams.set('scope', oidcScope);
1663
+ loginUrl.searchParams.set('state', createRandomUuid());
1540
1664
  if (effectiveRedirectUri) {
1541
1665
  loginUrl.searchParams.set('redirect_uri', effectiveRedirectUri);
1542
1666
  }
1543
1667
  return {
1668
+ codeVerifier,
1544
1669
  loginUrl: loginUrl.toString(),
1545
1670
  redirectUri: effectiveRedirectUri
1546
1671
  };
@@ -1566,52 +1691,23 @@ const isLoginResponse = value => {
1566
1691
  return true;
1567
1692
  };
1568
1693
 
1569
- const getBackendNativeExchangeUrl = backendUrl => {
1570
- return getBackendAuthUrl(backendUrl, '/auth/native/exchange');
1571
- };
1572
-
1573
- const getExchangeErrorMessage = async response => {
1574
- try {
1575
- const payload = await response.json();
1576
- if (payload && typeof payload === 'object' && typeof payload.error === 'string' && payload.error) {
1577
- return payload.error;
1578
- }
1579
- } catch {
1580
- // ignore
1581
- }
1582
- return 'Backend authentication failed.';
1583
- };
1584
- const exchangeElectronAuthorizationCode = async (backendUrl, code, redirectUri) => {
1585
- const response = await fetch(getBackendNativeExchangeUrl(backendUrl), {
1586
- body: JSON.stringify({
1587
- code,
1588
- redirectUri
1589
- }),
1590
- credentials: 'include',
1591
- headers: {
1592
- Accept: 'application/json',
1593
- 'Content-Type': 'application/json'
1594
- },
1595
- method: 'POST'
1596
- });
1597
- if (!response.ok) {
1598
- throw new Error(await getExchangeErrorMessage(response));
1599
- }
1600
- };
1601
-
1602
1694
  const hasAuthorizationCode = value => {
1603
1695
  return typeof value === 'string' && value.length > 0;
1604
1696
  };
1605
- const waitForElectronBackendLogin = async (backendUrl, uid, redirectUri, timeoutMs = 30_000, pollIntervalMs = 1000) => {
1606
- const started = Date.now();
1607
- const deadline = started + timeoutMs;
1697
+ const getElectronAuthorizationCode = async uid => {
1698
+ return invoke$2('OAuthServer.getCode', String(uid));
1699
+ };
1700
+ const waitForElectronBackendLogin = async (uid, codeVerifier, timeoutMs = 30_000, pollIntervalMs = 1000, getAuthorizationCode = getElectronAuthorizationCode) => {
1701
+ const deadline = Date.now() + timeoutMs;
1608
1702
  while (Date.now() < deadline) {
1609
- const authorizationCode = await invoke$2('OAuthServer.getCode', String(uid));
1703
+ const authorizationCode = await getAuthorizationCode(uid);
1610
1704
  if (hasAuthorizationCode(authorizationCode)) {
1611
- const elapsed = Date.now() - started;
1612
- const remainingTime = Math.max(0, timeoutMs - elapsed);
1613
- await exchangeElectronAuthorizationCode(backendUrl, authorizationCode, redirectUri);
1614
- return waitForBackendLogin(backendUrl, remainingTime, pollIntervalMs);
1705
+ return {
1706
+ authCode: authorizationCode,
1707
+ authCodeVerifier: codeVerifier,
1708
+ authErrorMessage: '',
1709
+ userState: 'loggedOut'
1710
+ };
1615
1711
  }
1616
1712
  await delay(pollIntervalMs);
1617
1713
  }
@@ -1653,11 +1749,11 @@ const handleClickLogin = async options => {
1653
1749
  }
1654
1750
  const uid = 0;
1655
1751
  const {
1656
- loginUrl,
1657
- redirectUri
1752
+ codeVerifier,
1753
+ loginUrl
1658
1754
  } = await getBackendLoginRequest(backendUrl, platform, uid);
1659
1755
  await invoke$1('Open.openUrl', loginUrl, platform, authUseRedirect);
1660
- const authState = platform === Electron ? await waitForElectronBackendLogin(backendUrl, uid, redirectUri) : await waitForBackendLogin(backendUrl);
1756
+ const authState = platform === Electron ? await waitForElectronBackendLogin(uid, codeVerifier) : await waitForBackendLogin(backendUrl);
1661
1757
  return {
1662
1758
  ...authState
1663
1759
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/auth-worker",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Auth Worker",
5
5
  "repository": {
6
6
  "type": "git",