@lvce-editor/auth-worker 3.14.0 → 3.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 +788 -482
- package/package.json +1 -1
package/dist/authWorkerMain.js
CHANGED
|
@@ -543,6 +543,9 @@ const restoreExistingError = (error, currentStack) => {
|
|
|
543
543
|
};
|
|
544
544
|
const restoreMethodNotFoundError = (error, currentStack) => {
|
|
545
545
|
const restoredError = new JsonRpcError(error.message);
|
|
546
|
+
Object.assign(restoredError, {
|
|
547
|
+
code: error.code
|
|
548
|
+
});
|
|
546
549
|
const parentStack = getParentStack(error);
|
|
547
550
|
setStack(restoredError, `${parentStack}${NewLine}${currentStack}`);
|
|
548
551
|
return restoredError;
|
|
@@ -562,9 +565,13 @@ const applyDataProperties = (restoredError, error) => {
|
|
|
562
565
|
// @ts-ignore
|
|
563
566
|
restoredError.codeFrame = error.data.codeFrame;
|
|
564
567
|
}
|
|
565
|
-
if (error.data.code) {
|
|
568
|
+
if (typeof error.data.code === 'string' || typeof error.data.code === 'number') {
|
|
566
569
|
// @ts-ignore
|
|
567
|
-
restoredError
|
|
570
|
+
Object.defineProperty(restoredError, 'code', {
|
|
571
|
+
configurable: true,
|
|
572
|
+
value: error.data.code,
|
|
573
|
+
writable: true
|
|
574
|
+
});
|
|
568
575
|
}
|
|
569
576
|
if (error.data.type) {
|
|
570
577
|
// @ts-ignore
|
|
@@ -585,7 +592,14 @@ const applyDirectProperties = (restoredError, error) => {
|
|
|
585
592
|
}
|
|
586
593
|
};
|
|
587
594
|
const restoreMessageError = (error, _currentStack) => {
|
|
588
|
-
const restoredError = constructError(error.message, error.type, error.name);
|
|
595
|
+
const restoredError = constructError(error.message, error.type || error.data?.type, error.name);
|
|
596
|
+
if (typeof error.code === 'string' || typeof error.code === 'number') {
|
|
597
|
+
Object.defineProperty(restoredError, 'code', {
|
|
598
|
+
configurable: true,
|
|
599
|
+
value: error.code,
|
|
600
|
+
writable: true
|
|
601
|
+
});
|
|
602
|
+
}
|
|
589
603
|
if (error.data) {
|
|
590
604
|
applyDataProperties(restoredError, error);
|
|
591
605
|
} else {
|
|
@@ -593,6 +607,23 @@ const restoreMessageError = (error, _currentStack) => {
|
|
|
593
607
|
}
|
|
594
608
|
return restoredError;
|
|
595
609
|
};
|
|
610
|
+
const getFallbackMessage = error => {
|
|
611
|
+
if (error && typeof error.message === 'string' && error.message) {
|
|
612
|
+
return error.message;
|
|
613
|
+
}
|
|
614
|
+
if (error && error.data) {
|
|
615
|
+
return getFallbackMessage(error.data);
|
|
616
|
+
}
|
|
617
|
+
const stringified = String(error);
|
|
618
|
+
if (stringified !== '[object Object]') {
|
|
619
|
+
return stringified;
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
return JSON.stringify(error);
|
|
623
|
+
} catch {
|
|
624
|
+
return stringified;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
596
627
|
const restoreJsonRpcError = error => {
|
|
597
628
|
const currentStack = getCurrentStack();
|
|
598
629
|
if (error && error instanceof Error) {
|
|
@@ -601,13 +632,13 @@ const restoreJsonRpcError = error => {
|
|
|
601
632
|
if (error && error.code && error.code === MethodNotFound) {
|
|
602
633
|
return restoreMethodNotFoundError(error, currentStack);
|
|
603
634
|
}
|
|
635
|
+
if (error && error.error) {
|
|
636
|
+
return restoreJsonRpcError(error.error);
|
|
637
|
+
}
|
|
604
638
|
if (error && error.message) {
|
|
605
639
|
return restoreMessageError(error);
|
|
606
640
|
}
|
|
607
|
-
|
|
608
|
-
return new Error(`JsonRpc Error: ${error}`);
|
|
609
|
-
}
|
|
610
|
-
return new Error(`JsonRpc Error: ${error}`);
|
|
641
|
+
return new Error(`JsonRpc Error: ${getFallbackMessage(error)}`);
|
|
611
642
|
};
|
|
612
643
|
const unwrapJsonRpcResult = responseMessage => {
|
|
613
644
|
if ('error' in responseMessage) {
|
|
@@ -664,9 +695,9 @@ const getErrorProperty = (error, prettyError) => {
|
|
|
664
695
|
return {
|
|
665
696
|
code: Custom,
|
|
666
697
|
data: {
|
|
667
|
-
code: prettyError.code,
|
|
698
|
+
code: prettyError.code ?? error?.code,
|
|
668
699
|
codeFrame: prettyError.codeFrame,
|
|
669
|
-
name: prettyError.name,
|
|
700
|
+
name: prettyError.name ?? error?.name,
|
|
670
701
|
stack: getStack(prettyError),
|
|
671
702
|
type: getErrorType(prettyError)
|
|
672
703
|
},
|
|
@@ -701,7 +732,12 @@ const getErrorResponseSimple = (id, error) => {
|
|
|
701
732
|
return {
|
|
702
733
|
error: {
|
|
703
734
|
code: Custom,
|
|
704
|
-
data: error
|
|
735
|
+
data: error instanceof Error ? {
|
|
736
|
+
...error,
|
|
737
|
+
code: 'code' in error ? error.code : undefined,
|
|
738
|
+
stack: error.stack,
|
|
739
|
+
type: error.name
|
|
740
|
+
} : error,
|
|
705
741
|
// @ts-ignore
|
|
706
742
|
message: error.message
|
|
707
743
|
},
|
|
@@ -883,6 +919,9 @@ const logError = () => {
|
|
|
883
919
|
// handled by renderer worker
|
|
884
920
|
};
|
|
885
921
|
const handleMessage = event => {
|
|
922
|
+
if (event.data === 'ready') {
|
|
923
|
+
return Promise.resolve();
|
|
924
|
+
}
|
|
886
925
|
const actualRequiresSocket = event?.target?.requiresSocket || requiresSocket;
|
|
887
926
|
const actualExecute = event?.target?.execute || execute;
|
|
888
927
|
return handleJsonRpcMessage(event.target, event.data, actualExecute, event.target._resolve, preparePrettyError, logError, actualRequiresSocket);
|
|
@@ -1134,31 +1173,6 @@ const initializeOpenerWorker = async () => {
|
|
|
1134
1173
|
set(rpc);
|
|
1135
1174
|
};
|
|
1136
1175
|
|
|
1137
|
-
const state$3 = {
|
|
1138
|
-
backendUrl: ''
|
|
1139
|
-
};
|
|
1140
|
-
const getAuthBackendUrl = () => {
|
|
1141
|
-
return state$3.backendUrl;
|
|
1142
|
-
};
|
|
1143
|
-
const setAuthBackendUrl = backendUrl => {
|
|
1144
|
-
state$3.backendUrl = backendUrl;
|
|
1145
|
-
};
|
|
1146
|
-
|
|
1147
|
-
const state$2 = {
|
|
1148
|
-
platform: Web
|
|
1149
|
-
};
|
|
1150
|
-
const getAuthPlatform = () => {
|
|
1151
|
-
return state$2.platform;
|
|
1152
|
-
};
|
|
1153
|
-
const setAuthPlatform = value => {
|
|
1154
|
-
state$2.platform = value;
|
|
1155
|
-
};
|
|
1156
|
-
|
|
1157
|
-
const configure = options => {
|
|
1158
|
-
setAuthBackendUrl(options.backendUrl);
|
|
1159
|
-
setAuthPlatform(options.platform);
|
|
1160
|
-
};
|
|
1161
|
-
|
|
1162
1176
|
const millisecondsPerSecond = 1000;
|
|
1163
1177
|
const getAccessTokenExpiresAt = (expiresIn, now = Date.now()) => {
|
|
1164
1178
|
if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
@@ -1174,190 +1188,106 @@ const isAccessTokenValid = (accessToken, expiresAt, now = Date.now()) => {
|
|
|
1174
1188
|
return Number.isFinite(expiresAtNumber) && expiresAtNumber > now;
|
|
1175
1189
|
};
|
|
1176
1190
|
|
|
1177
|
-
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1191
|
+
// One auth worker owns the registry. Serialize its storage transactions, not network requests.
|
|
1192
|
+
const createAccountSessions = storage => {
|
|
1193
|
+
const state = {
|
|
1194
|
+
tail: Promise.resolve()
|
|
1195
|
+
};
|
|
1196
|
+
const transact = operation => {
|
|
1197
|
+
const previous = state.tail;
|
|
1198
|
+
const result = (async () => {
|
|
1199
|
+
await previous;
|
|
1200
|
+
const value = await storage.read();
|
|
1201
|
+
const registry = value ? JSON.parse(value) : {
|
|
1202
|
+
accounts: [],
|
|
1203
|
+
activeId: '',
|
|
1204
|
+
revision: 0
|
|
1205
|
+
};
|
|
1206
|
+
return operation(registry);
|
|
1207
|
+
})();
|
|
1208
|
+
state.tail = (async () => {
|
|
1209
|
+
try {
|
|
1210
|
+
await result;
|
|
1211
|
+
} catch {
|
|
1212
|
+
// A rejected transaction must not block subsequent requests.
|
|
1213
|
+
}
|
|
1214
|
+
})();
|
|
1215
|
+
return result;
|
|
1216
|
+
};
|
|
1217
|
+
const save = async registry => storage.write(JSON.stringify(registry));
|
|
1218
|
+
const read = () => transact(async registry => registry);
|
|
1219
|
+
const add = (account, revision) => transact(async registry => {
|
|
1220
|
+
const accounts = [...registry.accounts.filter(item => item.id !== account.id), account];
|
|
1221
|
+
const activeId = registry.revision === revision || !registry.activeId ? account.id : registry.activeId;
|
|
1222
|
+
await save({
|
|
1223
|
+
accounts,
|
|
1224
|
+
activeId,
|
|
1225
|
+
revision: registry.revision + 1
|
|
1205
1226
|
});
|
|
1206
1227
|
});
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
transaction.addEventListener('abort', () => {
|
|
1216
|
-
reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
|
|
1228
|
+
const select = id => transact(async registry => {
|
|
1229
|
+
if (registry.accounts.every(account => account.id !== id)) {
|
|
1230
|
+
throw new Error('Account is no longer available.');
|
|
1231
|
+
}
|
|
1232
|
+
await save({
|
|
1233
|
+
...registry,
|
|
1234
|
+
activeId: id,
|
|
1235
|
+
revision: registry.revision + 1
|
|
1217
1236
|
});
|
|
1218
|
-
|
|
1219
|
-
|
|
1237
|
+
});
|
|
1238
|
+
const remove = id => transact(async registry => {
|
|
1239
|
+
const accounts = registry.accounts.filter(account => account.id !== id);
|
|
1240
|
+
const activeId = registry.activeId === id ? accounts[0]?.id || '' : registry.activeId;
|
|
1241
|
+
await save({
|
|
1242
|
+
accounts,
|
|
1243
|
+
activeId,
|
|
1244
|
+
revision: registry.revision + 1
|
|
1220
1245
|
});
|
|
1221
1246
|
});
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const database = request.result;
|
|
1232
|
-
if (!database.objectStoreNames.contains(objectStoreName)) {
|
|
1233
|
-
database.createObjectStore(objectStoreName);
|
|
1234
|
-
}
|
|
1235
|
-
});
|
|
1236
|
-
request.addEventListener('success', () => {
|
|
1237
|
-
resolve(request.result);
|
|
1238
|
-
});
|
|
1239
|
-
request.addEventListener('error', () => {
|
|
1240
|
-
reject(request.error ?? new Error('Failed to open persistent auth storage.'));
|
|
1241
|
-
});
|
|
1247
|
+
const update = (account, session) => transact(async registry => {
|
|
1248
|
+
// A logout or another login for this identity invalidates an old refresh result.
|
|
1249
|
+
const accounts = registry.accounts.map(item => item.id === account.id && item.session.authRefreshToken === account.session.authRefreshToken ? {
|
|
1250
|
+
...item,
|
|
1251
|
+
session
|
|
1252
|
+
} : item);
|
|
1253
|
+
await save({
|
|
1254
|
+
...registry,
|
|
1255
|
+
accounts
|
|
1242
1256
|
});
|
|
1243
|
-
}
|
|
1244
|
-
return
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}
|
|
1251
|
-
const transaction = database.transaction(objectStoreName, 'readonly');
|
|
1252
|
-
const objectStore = transaction.objectStore(objectStoreName);
|
|
1253
|
-
const value = await requestToPromise(objectStore.get(key));
|
|
1254
|
-
return typeof value === 'string' ? value : '';
|
|
1255
|
-
};
|
|
1256
|
-
const setIndexedDbValue = async (key, value) => {
|
|
1257
|
-
memoryStorage.set(key, value);
|
|
1258
|
-
const database = await getDatabase();
|
|
1259
|
-
if (!database) {
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
const transaction = database.transaction(objectStoreName, 'readwrite');
|
|
1263
|
-
const objectStore = transaction.objectStore(objectStoreName);
|
|
1264
|
-
objectStore.put(value, key);
|
|
1265
|
-
await transactionToPromise(transaction);
|
|
1266
|
-
};
|
|
1267
|
-
const clearIndexedDbValue = async key => {
|
|
1268
|
-
memoryStorage.delete(key);
|
|
1269
|
-
const database = await getDatabase();
|
|
1270
|
-
if (!database) {
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
const transaction = database.transaction(objectStoreName, 'readwrite');
|
|
1274
|
-
const objectStore = transaction.objectStore(objectStoreName);
|
|
1275
|
-
objectStore.delete(key);
|
|
1276
|
-
await transactionToPromise(transaction);
|
|
1277
|
-
};
|
|
1278
|
-
const shouldUseSecretStorage = key => {
|
|
1279
|
-
return getAuthPlatform() === Electron && secretKeys.has(key);
|
|
1280
|
-
};
|
|
1281
|
-
const getPersistentAuthValue = async key => {
|
|
1282
|
-
if (!shouldUseSecretStorage(key)) {
|
|
1283
|
-
return getIndexedDbValue(key);
|
|
1284
|
-
}
|
|
1285
|
-
const secret = await getSecret(key);
|
|
1286
|
-
if (secret) {
|
|
1287
|
-
return secret;
|
|
1288
|
-
}
|
|
1289
|
-
const legacyValue = await getIndexedDbValue(key);
|
|
1290
|
-
if (!legacyValue) {
|
|
1291
|
-
return '';
|
|
1292
|
-
}
|
|
1293
|
-
await storeSecret(key, legacyValue);
|
|
1294
|
-
await clearIndexedDbValue(key);
|
|
1295
|
-
return legacyValue;
|
|
1296
|
-
};
|
|
1297
|
-
const setPersistentAuthValue = async (key, value) => {
|
|
1298
|
-
if (!shouldUseSecretStorage(key)) {
|
|
1299
|
-
await setIndexedDbValue(key, value);
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
if (value) {
|
|
1303
|
-
await storeSecret(key, value);
|
|
1304
|
-
} else {
|
|
1305
|
-
await deleteSecret(key);
|
|
1306
|
-
}
|
|
1307
|
-
await clearIndexedDbValue(key);
|
|
1308
|
-
};
|
|
1309
|
-
const clearPersistentAuthValue = async key => {
|
|
1310
|
-
if (shouldUseSecretStorage(key)) {
|
|
1311
|
-
await Promise.all([deleteSecret(key), clearIndexedDbValue(key)]);
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
await clearIndexedDbValue(key);
|
|
1257
|
+
});
|
|
1258
|
+
return {
|
|
1259
|
+
add,
|
|
1260
|
+
read,
|
|
1261
|
+
remove,
|
|
1262
|
+
select,
|
|
1263
|
+
update
|
|
1264
|
+
};
|
|
1315
1265
|
};
|
|
1316
1266
|
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
const pendingClientIdKey = 'pendingOidcClientId';
|
|
1320
|
-
const pendingCodeVerifierKey = 'pendingOidcCodeVerifier';
|
|
1321
|
-
const pendingRedirectUriKey = 'pendingOidcRedirectUri';
|
|
1322
|
-
const pendingStateKey = 'pendingOidcState';
|
|
1323
|
-
const clearOidcCallbackUrl = async () => {
|
|
1324
|
-
await clearPersistentAuthValue(callbackUrlKey);
|
|
1325
|
-
};
|
|
1326
|
-
const clearStoredOidcClientId = async () => {
|
|
1327
|
-
await clearPersistentAuthValue(clientIdKey);
|
|
1328
|
-
};
|
|
1329
|
-
const clearPendingOidcAuthState = async () => {
|
|
1330
|
-
await Promise.all([clearPersistentAuthValue(pendingClientIdKey), clearPersistentAuthValue(pendingCodeVerifierKey), clearPersistentAuthValue(pendingRedirectUriKey), clearPersistentAuthValue(pendingStateKey)]);
|
|
1267
|
+
const state$4 = {
|
|
1268
|
+
backendUrl: ''
|
|
1331
1269
|
};
|
|
1332
|
-
const
|
|
1333
|
-
return
|
|
1270
|
+
const getAuthBackendUrl = () => {
|
|
1271
|
+
return state$4.backendUrl;
|
|
1334
1272
|
};
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1273
|
+
const setAuthBackendUrl = backendUrl => {
|
|
1274
|
+
state$4.backendUrl = backendUrl;
|
|
1337
1275
|
};
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
return undefined;
|
|
1342
|
-
}
|
|
1343
|
-
return {
|
|
1344
|
-
clientId,
|
|
1345
|
-
codeVerifier,
|
|
1346
|
-
redirectUri,
|
|
1347
|
-
state
|
|
1348
|
-
};
|
|
1276
|
+
|
|
1277
|
+
const state$3 = {
|
|
1278
|
+
platform: Web
|
|
1349
1279
|
};
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1280
|
+
const getAuthPlatform = () => {
|
|
1281
|
+
return state$3.platform;
|
|
1352
1282
|
};
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1283
|
+
const setAuthPlatform = value => {
|
|
1284
|
+
state$3.platform = value;
|
|
1355
1285
|
};
|
|
1356
1286
|
|
|
1357
1287
|
let USER_AGENT;
|
|
1358
1288
|
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
|
|
1359
1289
|
const NAME = 'oauth4webapi';
|
|
1360
|
-
const VERSION = 'v3.8.
|
|
1290
|
+
const VERSION = 'v3.8.8';
|
|
1361
1291
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
1362
1292
|
}
|
|
1363
1293
|
function looseInstanceOf(input, expected) {
|
|
@@ -1711,9 +1641,7 @@ function parseWwwAuthenticateChallenges(response) {
|
|
|
1711
1641
|
if (match = rest.match(quotedParamRE)) {
|
|
1712
1642
|
[, key, value, rest] = match;
|
|
1713
1643
|
if (value.includes('\\')) {
|
|
1714
|
-
|
|
1715
|
-
value = JSON.parse(`"${value}"`);
|
|
1716
|
-
} catch {}
|
|
1644
|
+
value = value.replace(/\\([\s\S])/g, '$1');
|
|
1717
1645
|
}
|
|
1718
1646
|
parameters[key.toLowerCase()] = value;
|
|
1719
1647
|
continue;
|
|
@@ -1847,7 +1775,7 @@ async function processGenericAccessTokenResponse(as, client, response, additiona
|
|
|
1847
1775
|
assertString(json.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
|
|
1848
1776
|
body: json
|
|
1849
1777
|
});
|
|
1850
|
-
const requiredClaims = [
|
|
1778
|
+
const requiredClaims = [];
|
|
1851
1779
|
if (client.require_auth_time === true) {
|
|
1852
1780
|
requiredClaims.push('auth_time');
|
|
1853
1781
|
}
|
|
@@ -1858,27 +1786,9 @@ async function processGenericAccessTokenResponse(as, client, response, additiona
|
|
|
1858
1786
|
const {
|
|
1859
1787
|
claims,
|
|
1860
1788
|
jwt
|
|
1861
|
-
} = await
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, {
|
|
1865
|
-
claims,
|
|
1866
|
-
claim: 'aud'
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
if (claims.azp !== client.client_id) {
|
|
1870
|
-
throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, {
|
|
1871
|
-
expected: client.client_id,
|
|
1872
|
-
claims,
|
|
1873
|
-
claim: 'azp'
|
|
1874
|
-
});
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
if (claims.auth_time !== undefined) {
|
|
1878
|
-
assertNumber(claims.auth_time, true, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, {
|
|
1879
|
-
claims
|
|
1880
|
-
});
|
|
1881
|
-
}
|
|
1789
|
+
} = await validateIdTokenClaims(as, client, json.id_token, requiredClaims, decryptFn);
|
|
1790
|
+
validateIdTokenAuthorizedParty(client, claims);
|
|
1791
|
+
validateIdTokenAuthTimeClaim(claims);
|
|
1882
1792
|
jwtRefs.set(response, jwt);
|
|
1883
1793
|
idTokenClaims.set(json, claims);
|
|
1884
1794
|
}
|
|
@@ -1959,6 +1869,41 @@ function validatePresence(required, result) {
|
|
|
1959
1869
|
}
|
|
1960
1870
|
return result;
|
|
1961
1871
|
}
|
|
1872
|
+
function validateStringClaim(claim, result) {
|
|
1873
|
+
if (typeof result.claims[claim] !== 'string') {
|
|
1874
|
+
throw OPE(`unexpected JWT "${claim}" (${jwtClaimNames[claim]}) claim type`, INVALID_RESPONSE, {
|
|
1875
|
+
claims: result.claims
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
return result;
|
|
1879
|
+
}
|
|
1880
|
+
function validateIdTokenClaims(as, client, idToken, requiredClaims, decryptFn) {
|
|
1881
|
+
return validateJwt(idToken, 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, ['aud', 'exp', 'iat', 'iss', 'sub', ...requiredClaims])).then(validateIssuer.bind(undefined, as)).then(validateAudience.bind(undefined, client.client_id)).then(validateStringClaim.bind(undefined, 'sub'));
|
|
1882
|
+
}
|
|
1883
|
+
function validateIdTokenAuthTimeClaim(claims) {
|
|
1884
|
+
if (claims.auth_time !== undefined) {
|
|
1885
|
+
assertNumber(claims.auth_time, true, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, {
|
|
1886
|
+
claims
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
function validateIdTokenAuthorizedParty(client, claims) {
|
|
1891
|
+
if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
|
|
1892
|
+
if (claims.azp === undefined) {
|
|
1893
|
+
throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, {
|
|
1894
|
+
claims,
|
|
1895
|
+
claim: 'aud'
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
if (claims.azp !== client.client_id) {
|
|
1899
|
+
throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, {
|
|
1900
|
+
expected: client.client_id,
|
|
1901
|
+
claims,
|
|
1902
|
+
claim: 'azp'
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1962
1907
|
const WWW_AUTHENTICATE_CHALLENGE = 'OAUTH_WWW_AUTHENTICATE_CHALLENGE';
|
|
1963
1908
|
const RESPONSE_BODY_ERROR = 'OAUTH_RESPONSE_BODY_ERROR';
|
|
1964
1909
|
const UNSUPPORTED_OPERATION = 'OAUTH_UNSUPPORTED_OPERATION';
|
|
@@ -2175,86 +2120,32 @@ const getClient$1 = clientId => {
|
|
|
2175
2120
|
client_id: clientId
|
|
2176
2121
|
};
|
|
2177
2122
|
};
|
|
2178
|
-
const
|
|
2123
|
+
const exchangeAuthorizationCode = async (backendUrl, clientId, code, redirectUri, codeVerifier, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
|
|
2179
2124
|
const authorizationServer = getAuthorizationServer$1(backendUrl);
|
|
2180
2125
|
const client = getClient$1(clientId);
|
|
2181
|
-
const response = await requestTokenEndpoint(authorizationServer, client, None(), '
|
|
2182
|
-
|
|
2126
|
+
const response = await requestTokenEndpoint(authorizationServer, client, None(), 'authorization_code', new URLSearchParams({
|
|
2127
|
+
code,
|
|
2128
|
+
code_verifier: codeVerifier,
|
|
2129
|
+
redirect_uri: redirectUri
|
|
2183
2130
|
}));
|
|
2184
2131
|
const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
|
|
2185
2132
|
return {
|
|
2186
2133
|
accessToken: tokenResponse.access_token,
|
|
2187
2134
|
expiresIn: typeof tokenResponse.expires_in === 'number' ? tokenResponse.expires_in : undefined,
|
|
2188
|
-
refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token :
|
|
2135
|
+
refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : ''
|
|
2189
2136
|
};
|
|
2190
2137
|
};
|
|
2191
2138
|
|
|
2192
|
-
const
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2139
|
+
const getCurrentHref = async () => {
|
|
2140
|
+
try {
|
|
2141
|
+
return await invoke('Layout.getHref');
|
|
2142
|
+
} catch {
|
|
2143
|
+
// ignore
|
|
2196
2144
|
}
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
if (!backendUrl || !clientId || !refreshToken) {
|
|
2200
|
-
const expiresAt = Number(accessTokenExpiresAt);
|
|
2201
|
-
return accessTokenExpiresAt && Number.isFinite(expiresAt) && expiresAt <= now ? '' : accessToken;
|
|
2145
|
+
if (!globalThis.location || typeof globalThis.location.href !== 'string' || !globalThis.location.href) {
|
|
2146
|
+
return '';
|
|
2202
2147
|
}
|
|
2203
|
-
|
|
2204
|
-
const expiresAt = getAccessTokenExpiresAt(refreshedTokens.expiresIn, now);
|
|
2205
|
-
await Promise.all([setPersistentAuthValue('accessToken', refreshedTokens.accessToken), setPersistentAuthValue('accessTokenExpiresAt', typeof expiresAt === 'number' ? String(expiresAt) : ''), setPersistentAuthValue('refreshToken', refreshedTokens.refreshToken)]);
|
|
2206
|
-
return refreshedTokens.accessToken;
|
|
2207
|
-
};
|
|
2208
|
-
|
|
2209
|
-
const handleMessagePort = async (port, rpcId) => {
|
|
2210
|
-
const rpc = await create$4({
|
|
2211
|
-
commandMap: commandMap,
|
|
2212
|
-
isMessagePortOpen: true,
|
|
2213
|
-
messagePort: port
|
|
2214
|
-
});
|
|
2215
|
-
if (rpcId) {
|
|
2216
|
-
set$3(rpcId, rpc);
|
|
2217
|
-
}
|
|
2218
|
-
};
|
|
2219
|
-
|
|
2220
|
-
const getAuthorizationServer = backendUrl => {
|
|
2221
|
-
return {
|
|
2222
|
-
issuer: getBackendAuthUrl(backendUrl, '/oidc'),
|
|
2223
|
-
jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
|
|
2224
|
-
token_endpoint: getBackendOidcTokenUrl(backendUrl)
|
|
2225
|
-
};
|
|
2226
|
-
};
|
|
2227
|
-
const getClient = clientId => {
|
|
2228
|
-
return {
|
|
2229
|
-
client_id: clientId
|
|
2230
|
-
};
|
|
2231
|
-
};
|
|
2232
|
-
const exchangeAuthorizationCode = async (backendUrl, clientId, code, redirectUri, codeVerifier, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
|
|
2233
|
-
const authorizationServer = getAuthorizationServer(backendUrl);
|
|
2234
|
-
const client = getClient(clientId);
|
|
2235
|
-
const response = await requestTokenEndpoint(authorizationServer, client, None(), 'authorization_code', new URLSearchParams({
|
|
2236
|
-
code,
|
|
2237
|
-
code_verifier: codeVerifier,
|
|
2238
|
-
redirect_uri: redirectUri
|
|
2239
|
-
}));
|
|
2240
|
-
const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
|
|
2241
|
-
return {
|
|
2242
|
-
accessToken: tokenResponse.access_token,
|
|
2243
|
-
expiresIn: typeof tokenResponse.expires_in === 'number' ? tokenResponse.expires_in : undefined,
|
|
2244
|
-
refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : ''
|
|
2245
|
-
};
|
|
2246
|
-
};
|
|
2247
|
-
|
|
2248
|
-
const getCurrentHref = async () => {
|
|
2249
|
-
try {
|
|
2250
|
-
return await invoke('Layout.getHref');
|
|
2251
|
-
} catch {
|
|
2252
|
-
// ignore
|
|
2253
|
-
}
|
|
2254
|
-
if (!globalThis.location || typeof globalThis.location.href !== 'string' || !globalThis.location.href) {
|
|
2255
|
-
return '';
|
|
2256
|
-
}
|
|
2257
|
-
return globalThis.location.href;
|
|
2148
|
+
return globalThis.location.href;
|
|
2258
2149
|
};
|
|
2259
2150
|
|
|
2260
2151
|
const getLoggedOutBackendAuthState = (authErrorMessage = '') => {
|
|
@@ -2295,6 +2186,186 @@ const getOidcUserName = async (backendUrl, accessToken, fetchFn = fetch) => {
|
|
|
2295
2186
|
return typeof displayName === 'string' ? displayName : '';
|
|
2296
2187
|
};
|
|
2297
2188
|
|
|
2189
|
+
const authStorageId = 'lvce-editor.auth';
|
|
2190
|
+
const deleteSecret = async key => {
|
|
2191
|
+
await deleteSecret$1(authStorageId, key);
|
|
2192
|
+
};
|
|
2193
|
+
const getSecret = async key => {
|
|
2194
|
+
return (await getSecret$1(authStorageId, key)) ?? '';
|
|
2195
|
+
};
|
|
2196
|
+
const storeSecret = async (key, value) => {
|
|
2197
|
+
await storeSecret$1(authStorageId, key, value);
|
|
2198
|
+
};
|
|
2199
|
+
|
|
2200
|
+
const databaseName = 'auth-worker';
|
|
2201
|
+
const objectStoreName = 'auth';
|
|
2202
|
+
const memoryStorage = new Map();
|
|
2203
|
+
const secretKeys = new Set(['accessToken', 'refreshToken', 'accountSessions']);
|
|
2204
|
+
const state$2 = {
|
|
2205
|
+
databasePromise: undefined
|
|
2206
|
+
};
|
|
2207
|
+
|
|
2208
|
+
// IndexedDB request objects are mutable browser primitives and cannot satisfy the readonly rule structurally.
|
|
2209
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
2210
|
+
const requestToPromise = request => {
|
|
2211
|
+
return new Promise((resolve, reject) => {
|
|
2212
|
+
request.addEventListener('success', () => {
|
|
2213
|
+
resolve(request.result);
|
|
2214
|
+
});
|
|
2215
|
+
request.addEventListener('error', () => {
|
|
2216
|
+
reject(request.error ?? new Error('Persistent storage request failed.'));
|
|
2217
|
+
});
|
|
2218
|
+
});
|
|
2219
|
+
};
|
|
2220
|
+
|
|
2221
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
2222
|
+
const transactionToPromise = transaction => {
|
|
2223
|
+
return new Promise((resolve, reject) => {
|
|
2224
|
+
transaction.addEventListener('complete', () => {
|
|
2225
|
+
resolve();
|
|
2226
|
+
});
|
|
2227
|
+
transaction.addEventListener('abort', () => {
|
|
2228
|
+
reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
|
|
2229
|
+
});
|
|
2230
|
+
transaction.addEventListener('error', () => {
|
|
2231
|
+
reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
|
|
2232
|
+
});
|
|
2233
|
+
});
|
|
2234
|
+
};
|
|
2235
|
+
const getDatabase = async () => {
|
|
2236
|
+
if (typeof indexedDB === 'undefined') {
|
|
2237
|
+
return undefined;
|
|
2238
|
+
}
|
|
2239
|
+
if (!state$2.databasePromise) {
|
|
2240
|
+
state$2.databasePromise = new Promise((resolve, reject) => {
|
|
2241
|
+
const request = indexedDB.open(databaseName, 1);
|
|
2242
|
+
request.addEventListener('upgradeneeded', () => {
|
|
2243
|
+
const database = request.result;
|
|
2244
|
+
if (!database.objectStoreNames.contains(objectStoreName)) {
|
|
2245
|
+
database.createObjectStore(objectStoreName);
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
request.addEventListener('success', () => {
|
|
2249
|
+
resolve(request.result);
|
|
2250
|
+
});
|
|
2251
|
+
request.addEventListener('error', () => {
|
|
2252
|
+
reject(request.error ?? new Error('Failed to open persistent auth storage.'));
|
|
2253
|
+
});
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
return state$2.databasePromise;
|
|
2257
|
+
};
|
|
2258
|
+
const getIndexedDbValue = async key => {
|
|
2259
|
+
const database = await getDatabase();
|
|
2260
|
+
if (!database) {
|
|
2261
|
+
return memoryStorage.get(key) ?? '';
|
|
2262
|
+
}
|
|
2263
|
+
const transaction = database.transaction(objectStoreName, 'readonly');
|
|
2264
|
+
const objectStore = transaction.objectStore(objectStoreName);
|
|
2265
|
+
const value = await requestToPromise(objectStore.get(key));
|
|
2266
|
+
return typeof value === 'string' ? value : '';
|
|
2267
|
+
};
|
|
2268
|
+
const setIndexedDbValue = async (key, value) => {
|
|
2269
|
+
memoryStorage.set(key, value);
|
|
2270
|
+
const database = await getDatabase();
|
|
2271
|
+
if (!database) {
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const transaction = database.transaction(objectStoreName, 'readwrite');
|
|
2275
|
+
const objectStore = transaction.objectStore(objectStoreName);
|
|
2276
|
+
objectStore.put(value, key);
|
|
2277
|
+
await transactionToPromise(transaction);
|
|
2278
|
+
};
|
|
2279
|
+
const clearIndexedDbValue = async key => {
|
|
2280
|
+
memoryStorage.delete(key);
|
|
2281
|
+
const database = await getDatabase();
|
|
2282
|
+
if (!database) {
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
const transaction = database.transaction(objectStoreName, 'readwrite');
|
|
2286
|
+
const objectStore = transaction.objectStore(objectStoreName);
|
|
2287
|
+
objectStore.delete(key);
|
|
2288
|
+
await transactionToPromise(transaction);
|
|
2289
|
+
};
|
|
2290
|
+
const shouldUseSecretStorage = key => {
|
|
2291
|
+
return getAuthPlatform() === Electron && secretKeys.has(key);
|
|
2292
|
+
};
|
|
2293
|
+
const getPersistentAuthValue = async key => {
|
|
2294
|
+
if (!shouldUseSecretStorage(key)) {
|
|
2295
|
+
return getIndexedDbValue(key);
|
|
2296
|
+
}
|
|
2297
|
+
const secret = await getSecret(key);
|
|
2298
|
+
if (secret) {
|
|
2299
|
+
return secret;
|
|
2300
|
+
}
|
|
2301
|
+
const legacyValue = await getIndexedDbValue(key);
|
|
2302
|
+
if (!legacyValue) {
|
|
2303
|
+
return '';
|
|
2304
|
+
}
|
|
2305
|
+
await storeSecret(key, legacyValue);
|
|
2306
|
+
await clearIndexedDbValue(key);
|
|
2307
|
+
return legacyValue;
|
|
2308
|
+
};
|
|
2309
|
+
const setPersistentAuthValue = async (key, value) => {
|
|
2310
|
+
if (!shouldUseSecretStorage(key)) {
|
|
2311
|
+
await setIndexedDbValue(key, value);
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
if (value) {
|
|
2315
|
+
await storeSecret(key, value);
|
|
2316
|
+
} else {
|
|
2317
|
+
await deleteSecret(key);
|
|
2318
|
+
}
|
|
2319
|
+
await clearIndexedDbValue(key);
|
|
2320
|
+
};
|
|
2321
|
+
const clearPersistentAuthValue = async key => {
|
|
2322
|
+
if (shouldUseSecretStorage(key)) {
|
|
2323
|
+
await Promise.all([deleteSecret(key), clearIndexedDbValue(key)]);
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
await clearIndexedDbValue(key);
|
|
2327
|
+
};
|
|
2328
|
+
|
|
2329
|
+
const callbackUrlKey = 'oidcCallbackUrl';
|
|
2330
|
+
const clientIdKey = 'oidcClientId';
|
|
2331
|
+
const pendingClientIdKey = 'pendingOidcClientId';
|
|
2332
|
+
const pendingCodeVerifierKey = 'pendingOidcCodeVerifier';
|
|
2333
|
+
const pendingRedirectUriKey = 'pendingOidcRedirectUri';
|
|
2334
|
+
const pendingStateKey = 'pendingOidcState';
|
|
2335
|
+
const clearOidcCallbackUrl = async () => {
|
|
2336
|
+
await clearPersistentAuthValue(callbackUrlKey);
|
|
2337
|
+
};
|
|
2338
|
+
const clearStoredOidcClientId = async () => {
|
|
2339
|
+
await clearPersistentAuthValue(clientIdKey);
|
|
2340
|
+
};
|
|
2341
|
+
const clearPendingOidcAuthState = async () => {
|
|
2342
|
+
await Promise.all([clearPersistentAuthValue(pendingClientIdKey), clearPersistentAuthValue(pendingCodeVerifierKey), clearPersistentAuthValue(pendingRedirectUriKey), clearPersistentAuthValue(pendingStateKey)]);
|
|
2343
|
+
};
|
|
2344
|
+
const getOidcCallbackUrl = async () => {
|
|
2345
|
+
return getPersistentAuthValue(callbackUrlKey);
|
|
2346
|
+
};
|
|
2347
|
+
const getStoredOidcClientId = async () => {
|
|
2348
|
+
return getPersistentAuthValue(clientIdKey);
|
|
2349
|
+
};
|
|
2350
|
+
const loadPendingOidcAuthState = async () => {
|
|
2351
|
+
const [clientId, codeVerifier, redirectUri, state] = await Promise.all([getPersistentAuthValue(pendingClientIdKey), getPersistentAuthValue(pendingCodeVerifierKey), getPersistentAuthValue(pendingRedirectUriKey), getPersistentAuthValue(pendingStateKey)]);
|
|
2352
|
+
if (!clientId || !codeVerifier || !redirectUri || !state) {
|
|
2353
|
+
return undefined;
|
|
2354
|
+
}
|
|
2355
|
+
return {
|
|
2356
|
+
clientId,
|
|
2357
|
+
codeVerifier,
|
|
2358
|
+
redirectUri,
|
|
2359
|
+
state
|
|
2360
|
+
};
|
|
2361
|
+
};
|
|
2362
|
+
const saveOidcClientId = async clientId => {
|
|
2363
|
+
await setPersistentAuthValue(clientIdKey, clientId);
|
|
2364
|
+
};
|
|
2365
|
+
const savePendingOidcAuthState = async value => {
|
|
2366
|
+
await Promise.all([setPersistentAuthValue(pendingClientIdKey, value.clientId), setPersistentAuthValue(pendingCodeVerifierKey, value.codeVerifier), setPersistentAuthValue(pendingRedirectUriKey, value.redirectUri), setPersistentAuthValue(pendingStateKey, value.state)]);
|
|
2367
|
+
};
|
|
2368
|
+
|
|
2298
2369
|
const normalizeRedirectUri = value => {
|
|
2299
2370
|
const url = new URL(value);
|
|
2300
2371
|
url.hash = '';
|
|
@@ -2349,18 +2420,139 @@ const completeBrowserOidcLogin = async backendUrl => {
|
|
|
2349
2420
|
const authAccessTokenExpiresAt = getAccessTokenExpiresAt(exchanged.expiresIn);
|
|
2350
2421
|
await clearPendingOidcAuthState();
|
|
2351
2422
|
return {
|
|
2352
|
-
authAccessToken: exchanged.accessToken,
|
|
2353
|
-
...(authAccessTokenExpiresAt && {
|
|
2354
|
-
authAccessTokenExpiresAt
|
|
2355
|
-
}),
|
|
2356
|
-
authClientId: pendingAuthState.clientId,
|
|
2357
|
-
authErrorMessage: '',
|
|
2358
|
-
authRefreshToken: exchanged.refreshToken,
|
|
2359
|
-
userName,
|
|
2360
|
-
userState: exchanged.accessToken ? 'loggedIn' : 'loggedOut'
|
|
2423
|
+
authAccessToken: exchanged.accessToken,
|
|
2424
|
+
...(authAccessTokenExpiresAt && {
|
|
2425
|
+
authAccessTokenExpiresAt
|
|
2426
|
+
}),
|
|
2427
|
+
authClientId: pendingAuthState.clientId,
|
|
2428
|
+
authErrorMessage: '',
|
|
2429
|
+
authRefreshToken: exchanged.refreshToken,
|
|
2430
|
+
userName,
|
|
2431
|
+
userState: exchanged.accessToken ? 'loggedIn' : 'loggedOut'
|
|
2432
|
+
};
|
|
2433
|
+
};
|
|
2434
|
+
|
|
2435
|
+
const getAccountProfile = async (backendUrl, accessToken, fetchFn = fetch) => {
|
|
2436
|
+
const response = await fetchFn(new URL('/account/me', backendUrl), {
|
|
2437
|
+
headers: {
|
|
2438
|
+
Accept: 'application/json',
|
|
2439
|
+
Authorization: `Bearer ${accessToken}`
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
if (!response.ok) {
|
|
2443
|
+
throw new Error('Unable to load the signed in account.');
|
|
2444
|
+
}
|
|
2445
|
+
const value = await response.json();
|
|
2446
|
+
if (!value || typeof value.id !== 'string' || !value.id) {
|
|
2447
|
+
throw new Error('The backend did not return an account identity.');
|
|
2448
|
+
}
|
|
2449
|
+
return {
|
|
2450
|
+
displayName: typeof value.displayName === 'string' ? value.displayName : '',
|
|
2451
|
+
email: typeof value.email === 'string' ? value.email : '',
|
|
2452
|
+
id: value.id,
|
|
2453
|
+
provider: typeof value.provider === 'string' ? value.provider : 'LVCE Editor'
|
|
2454
|
+
};
|
|
2455
|
+
};
|
|
2456
|
+
|
|
2457
|
+
const getBackendRefreshUrl = backendUrl => {
|
|
2458
|
+
return getBackendAuthUrl(backendUrl, '/auth/refresh');
|
|
2459
|
+
};
|
|
2460
|
+
|
|
2461
|
+
const delay = async ms => {
|
|
2462
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
2463
|
+
};
|
|
2464
|
+
|
|
2465
|
+
const state$1 = {
|
|
2466
|
+
nextLoginResponse: undefined,
|
|
2467
|
+
nextRefreshResponse: undefined
|
|
2468
|
+
};
|
|
2469
|
+
const setNextLoginResponse = response => {
|
|
2470
|
+
state$1.nextLoginResponse = response;
|
|
2471
|
+
};
|
|
2472
|
+
const setNextRefreshResponse = response => {
|
|
2473
|
+
state$1.nextRefreshResponse = response;
|
|
2474
|
+
};
|
|
2475
|
+
const clear = () => {
|
|
2476
|
+
state$1.nextLoginResponse = undefined;
|
|
2477
|
+
state$1.nextRefreshResponse = undefined;
|
|
2478
|
+
};
|
|
2479
|
+
const hasPendingMockLoginResponse = () => {
|
|
2480
|
+
return !!state$1.nextLoginResponse;
|
|
2481
|
+
};
|
|
2482
|
+
const hasPendingMockRefreshResponse = () => {
|
|
2483
|
+
return !!state$1.nextRefreshResponse;
|
|
2484
|
+
};
|
|
2485
|
+
const consumeNextLoginResponse = async () => {
|
|
2486
|
+
if (!state$1.nextLoginResponse) {
|
|
2487
|
+
return undefined;
|
|
2488
|
+
}
|
|
2489
|
+
const response = state$1.nextLoginResponse;
|
|
2490
|
+
state$1.nextLoginResponse = undefined;
|
|
2491
|
+
if (response.delay > 0) {
|
|
2492
|
+
await delay(response.delay);
|
|
2493
|
+
}
|
|
2494
|
+
if (response.type === 'error') {
|
|
2495
|
+
throw new Error(response.message);
|
|
2496
|
+
}
|
|
2497
|
+
return response.response;
|
|
2498
|
+
};
|
|
2499
|
+
const consumeNextRefreshResponse = async () => {
|
|
2500
|
+
if (!state$1.nextRefreshResponse) {
|
|
2501
|
+
return undefined;
|
|
2502
|
+
}
|
|
2503
|
+
const response = state$1.nextRefreshResponse;
|
|
2504
|
+
state$1.nextRefreshResponse = undefined;
|
|
2505
|
+
if (response.delay > 0) {
|
|
2506
|
+
await new Promise(resolve => setTimeout(resolve, response.delay));
|
|
2507
|
+
}
|
|
2508
|
+
if (response.type === 'error') {
|
|
2509
|
+
throw new Error(response.message);
|
|
2510
|
+
}
|
|
2511
|
+
return response.response;
|
|
2512
|
+
};
|
|
2513
|
+
|
|
2514
|
+
const isObject = value => {
|
|
2515
|
+
return !!value && typeof value === 'object';
|
|
2516
|
+
};
|
|
2517
|
+
|
|
2518
|
+
const isBackendAuthResponse = value => {
|
|
2519
|
+
return isObject(value);
|
|
2520
|
+
};
|
|
2521
|
+
|
|
2522
|
+
const getNumber = (value, fallback = 0) => {
|
|
2523
|
+
return typeof value === 'number' ? value : fallback;
|
|
2524
|
+
};
|
|
2525
|
+
|
|
2526
|
+
const getString = (value, fallback = '') => {
|
|
2527
|
+
return typeof value === 'string' ? value : fallback;
|
|
2528
|
+
};
|
|
2529
|
+
|
|
2530
|
+
const getUserName = value => {
|
|
2531
|
+
if (typeof value.userName === 'string') {
|
|
2532
|
+
return value.userName;
|
|
2533
|
+
}
|
|
2534
|
+
return getString(value.user?.displayName);
|
|
2535
|
+
};
|
|
2536
|
+
const toBackendAuthState = value => {
|
|
2537
|
+
return {
|
|
2538
|
+
authAccessToken: getString(value.accessToken),
|
|
2539
|
+
authErrorMessage: getString(value.error),
|
|
2540
|
+
authRefreshToken: getString(value.refreshToken),
|
|
2541
|
+
userName: getUserName(value),
|
|
2542
|
+
userState: value.accessToken ? 'loggedIn' : 'loggedOut',
|
|
2543
|
+
userSubscriptionPlan: getString(value.subscriptionPlan),
|
|
2544
|
+
userSubscriptionStatus: getString(value.subscriptionStatus),
|
|
2545
|
+
userUsedTokens: getNumber(value.usedTokens)
|
|
2361
2546
|
};
|
|
2362
2547
|
};
|
|
2363
2548
|
|
|
2549
|
+
const parseBackendAuthResponse = value => {
|
|
2550
|
+
if (!isBackendAuthResponse(value)) {
|
|
2551
|
+
return getLoggedOutBackendAuthState('Backend returned an invalid authentication response.');
|
|
2552
|
+
}
|
|
2553
|
+
return toBackendAuthState(value);
|
|
2554
|
+
};
|
|
2555
|
+
|
|
2364
2556
|
const accessTokenKey = 'accessToken';
|
|
2365
2557
|
const accessTokenExpiresAtKey = 'accessTokenExpiresAt';
|
|
2366
2558
|
const refreshTokenKey = 'refreshToken';
|
|
@@ -2433,6 +2625,32 @@ const persistLoginResult = async loginResult => {
|
|
|
2433
2625
|
return loginResult;
|
|
2434
2626
|
};
|
|
2435
2627
|
|
|
2628
|
+
const getAuthorizationServer = backendUrl => {
|
|
2629
|
+
return {
|
|
2630
|
+
issuer: getBackendAuthUrl(backendUrl, '/oidc'),
|
|
2631
|
+
jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
|
|
2632
|
+
token_endpoint: getBackendOidcTokenUrl(backendUrl)
|
|
2633
|
+
};
|
|
2634
|
+
};
|
|
2635
|
+
const getClient = clientId => {
|
|
2636
|
+
return {
|
|
2637
|
+
client_id: clientId
|
|
2638
|
+
};
|
|
2639
|
+
};
|
|
2640
|
+
const refreshOidcTokens = async (backendUrl, clientId, refreshToken, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
|
|
2641
|
+
const authorizationServer = getAuthorizationServer(backendUrl);
|
|
2642
|
+
const client = getClient(clientId);
|
|
2643
|
+
const response = await requestTokenEndpoint(authorizationServer, client, None(), 'refresh_token', new URLSearchParams({
|
|
2644
|
+
refresh_token: refreshToken
|
|
2645
|
+
}));
|
|
2646
|
+
const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
|
|
2647
|
+
return {
|
|
2648
|
+
accessToken: tokenResponse.access_token,
|
|
2649
|
+
expiresIn: typeof tokenResponse.expires_in === 'number' ? tokenResponse.expires_in : undefined,
|
|
2650
|
+
refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : refreshToken
|
|
2651
|
+
};
|
|
2652
|
+
};
|
|
2653
|
+
|
|
2436
2654
|
const clearStoredOidcAuth = async () => {
|
|
2437
2655
|
await clearPersistedAuthSession();
|
|
2438
2656
|
};
|
|
@@ -2471,163 +2689,6 @@ const restoreOidcAuth = async backendUrl => {
|
|
|
2471
2689
|
}
|
|
2472
2690
|
};
|
|
2473
2691
|
|
|
2474
|
-
const getBackendUrl = options => {
|
|
2475
|
-
if (typeof options === 'number') {
|
|
2476
|
-
return '';
|
|
2477
|
-
}
|
|
2478
|
-
return options.backendUrl || '';
|
|
2479
|
-
};
|
|
2480
|
-
const getPlatform = options => {
|
|
2481
|
-
return typeof options === 'number' ? options : options.platform ?? Web;
|
|
2482
|
-
};
|
|
2483
|
-
const initialize = async options => {
|
|
2484
|
-
const backendUrl = getBackendUrl(options);
|
|
2485
|
-
configure({
|
|
2486
|
-
backendUrl,
|
|
2487
|
-
platform: getPlatform(options)
|
|
2488
|
-
});
|
|
2489
|
-
try {
|
|
2490
|
-
if (backendUrl) {
|
|
2491
|
-
const completedBrowserLogin = await completeBrowserOidcLogin(backendUrl);
|
|
2492
|
-
if (completedBrowserLogin) {
|
|
2493
|
-
return persistLoginResult(completedBrowserLogin);
|
|
2494
|
-
}
|
|
2495
|
-
const restoredOidcAuth = await restoreOidcAuth(backendUrl);
|
|
2496
|
-
if (restoredOidcAuth) {
|
|
2497
|
-
return persistLoginResult(restoredOidcAuth);
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
const persistedAuthSession = await getPersistedAuthSession();
|
|
2501
|
-
if (persistedAuthSession) {
|
|
2502
|
-
return persistedAuthSession;
|
|
2503
|
-
}
|
|
2504
|
-
return getLoggedOutBackendAuthState();
|
|
2505
|
-
} catch (error) {
|
|
2506
|
-
const authErrorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
|
|
2507
|
-
return getLoggedOutBackendAuthState(authErrorMessage);
|
|
2508
|
-
}
|
|
2509
|
-
};
|
|
2510
|
-
|
|
2511
|
-
const getBackendLogoutUrl = backendUrl => {
|
|
2512
|
-
return getBackendAuthUrl(backendUrl, '/auth/logout');
|
|
2513
|
-
};
|
|
2514
|
-
|
|
2515
|
-
const logoutFromBackend = async backendUrl => {
|
|
2516
|
-
if (!backendUrl) {
|
|
2517
|
-
return;
|
|
2518
|
-
}
|
|
2519
|
-
try {
|
|
2520
|
-
await fetch(getBackendLogoutUrl(backendUrl), {
|
|
2521
|
-
credentials: 'include',
|
|
2522
|
-
headers: {
|
|
2523
|
-
Accept: 'application/json'
|
|
2524
|
-
},
|
|
2525
|
-
method: 'POST'
|
|
2526
|
-
});
|
|
2527
|
-
} catch {
|
|
2528
|
-
// Ignore logout failures and still clear local auth state.
|
|
2529
|
-
}
|
|
2530
|
-
};
|
|
2531
|
-
|
|
2532
|
-
const getBackendRefreshUrl = backendUrl => {
|
|
2533
|
-
return getBackendAuthUrl(backendUrl, '/auth/refresh');
|
|
2534
|
-
};
|
|
2535
|
-
|
|
2536
|
-
const delay = async ms => {
|
|
2537
|
-
await new Promise(resolve => setTimeout(resolve, ms));
|
|
2538
|
-
};
|
|
2539
|
-
|
|
2540
|
-
const state = {
|
|
2541
|
-
nextLoginResponse: undefined,
|
|
2542
|
-
nextRefreshResponse: undefined
|
|
2543
|
-
};
|
|
2544
|
-
const setNextLoginResponse = response => {
|
|
2545
|
-
state.nextLoginResponse = response;
|
|
2546
|
-
};
|
|
2547
|
-
const setNextRefreshResponse = response => {
|
|
2548
|
-
state.nextRefreshResponse = response;
|
|
2549
|
-
};
|
|
2550
|
-
const clear = () => {
|
|
2551
|
-
state.nextLoginResponse = undefined;
|
|
2552
|
-
state.nextRefreshResponse = undefined;
|
|
2553
|
-
};
|
|
2554
|
-
const hasPendingMockLoginResponse = () => {
|
|
2555
|
-
return !!state.nextLoginResponse;
|
|
2556
|
-
};
|
|
2557
|
-
const hasPendingMockRefreshResponse = () => {
|
|
2558
|
-
return !!state.nextRefreshResponse;
|
|
2559
|
-
};
|
|
2560
|
-
const consumeNextLoginResponse = async () => {
|
|
2561
|
-
if (!state.nextLoginResponse) {
|
|
2562
|
-
return undefined;
|
|
2563
|
-
}
|
|
2564
|
-
const response = state.nextLoginResponse;
|
|
2565
|
-
state.nextLoginResponse = undefined;
|
|
2566
|
-
if (response.delay > 0) {
|
|
2567
|
-
await delay(response.delay);
|
|
2568
|
-
}
|
|
2569
|
-
if (response.type === 'error') {
|
|
2570
|
-
throw new Error(response.message);
|
|
2571
|
-
}
|
|
2572
|
-
return response.response;
|
|
2573
|
-
};
|
|
2574
|
-
const consumeNextRefreshResponse = async () => {
|
|
2575
|
-
if (!state.nextRefreshResponse) {
|
|
2576
|
-
return undefined;
|
|
2577
|
-
}
|
|
2578
|
-
const response = state.nextRefreshResponse;
|
|
2579
|
-
state.nextRefreshResponse = undefined;
|
|
2580
|
-
if (response.delay > 0) {
|
|
2581
|
-
await new Promise(resolve => setTimeout(resolve, response.delay));
|
|
2582
|
-
}
|
|
2583
|
-
if (response.type === 'error') {
|
|
2584
|
-
throw new Error(response.message);
|
|
2585
|
-
}
|
|
2586
|
-
return response.response;
|
|
2587
|
-
};
|
|
2588
|
-
|
|
2589
|
-
const isObject = value => {
|
|
2590
|
-
return !!value && typeof value === 'object';
|
|
2591
|
-
};
|
|
2592
|
-
|
|
2593
|
-
const isBackendAuthResponse = value => {
|
|
2594
|
-
return isObject(value);
|
|
2595
|
-
};
|
|
2596
|
-
|
|
2597
|
-
const getNumber = (value, fallback = 0) => {
|
|
2598
|
-
return typeof value === 'number' ? value : fallback;
|
|
2599
|
-
};
|
|
2600
|
-
|
|
2601
|
-
const getString = (value, fallback = '') => {
|
|
2602
|
-
return typeof value === 'string' ? value : fallback;
|
|
2603
|
-
};
|
|
2604
|
-
|
|
2605
|
-
const getUserName = value => {
|
|
2606
|
-
if (typeof value.userName === 'string') {
|
|
2607
|
-
return value.userName;
|
|
2608
|
-
}
|
|
2609
|
-
return getString(value.user?.displayName);
|
|
2610
|
-
};
|
|
2611
|
-
const toBackendAuthState = value => {
|
|
2612
|
-
return {
|
|
2613
|
-
authAccessToken: getString(value.accessToken),
|
|
2614
|
-
authErrorMessage: getString(value.error),
|
|
2615
|
-
authRefreshToken: getString(value.refreshToken),
|
|
2616
|
-
userName: getUserName(value),
|
|
2617
|
-
userState: value.accessToken ? 'loggedIn' : 'loggedOut',
|
|
2618
|
-
userSubscriptionPlan: getString(value.subscriptionPlan),
|
|
2619
|
-
userSubscriptionStatus: getString(value.subscriptionStatus),
|
|
2620
|
-
userUsedTokens: getNumber(value.usedTokens)
|
|
2621
|
-
};
|
|
2622
|
-
};
|
|
2623
|
-
|
|
2624
|
-
const parseBackendAuthResponse = value => {
|
|
2625
|
-
if (!isBackendAuthResponse(value)) {
|
|
2626
|
-
return getLoggedOutBackendAuthState('Backend returned an invalid authentication response.');
|
|
2627
|
-
}
|
|
2628
|
-
return toBackendAuthState(value);
|
|
2629
|
-
};
|
|
2630
|
-
|
|
2631
2692
|
const getPayload = async response => {
|
|
2632
2693
|
try {
|
|
2633
2694
|
return await response.json();
|
|
@@ -2635,7 +2696,7 @@ const getPayload = async response => {
|
|
|
2635
2696
|
return undefined;
|
|
2636
2697
|
}
|
|
2637
2698
|
};
|
|
2638
|
-
const syncBackendAuth = async backendUrl => {
|
|
2699
|
+
const syncBackendAuth$1 = async backendUrl => {
|
|
2639
2700
|
if (!backendUrl) {
|
|
2640
2701
|
return getLoggedOutBackendAuthState('Backend URL is missing.');
|
|
2641
2702
|
}
|
|
@@ -2685,7 +2746,7 @@ const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalM
|
|
|
2685
2746
|
const deadline = Date.now() + timeoutMs;
|
|
2686
2747
|
let lastErrorMessage = '';
|
|
2687
2748
|
while (Date.now() < deadline) {
|
|
2688
|
-
const authState = await syncBackendAuth(backendUrl);
|
|
2749
|
+
const authState = await syncBackendAuth$1(backendUrl);
|
|
2689
2750
|
if (authState.userState === 'loggedIn') {
|
|
2690
2751
|
return authState;
|
|
2691
2752
|
}
|
|
@@ -2697,6 +2758,13 @@ const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalM
|
|
|
2697
2758
|
return getLoggedOutBackendAuthState(lastErrorMessage);
|
|
2698
2759
|
};
|
|
2699
2760
|
|
|
2761
|
+
const getAccountLoginUrl = (backendUrl, authorizationUrl) => {
|
|
2762
|
+
const url = new URL('/login', backendUrl);
|
|
2763
|
+
url.searchParams.set('selectAccount', 'true');
|
|
2764
|
+
url.searchParams.set('returnTo', authorizationUrl);
|
|
2765
|
+
return url.href;
|
|
2766
|
+
};
|
|
2767
|
+
|
|
2700
2768
|
const getAuthUseRedirect = platform => {
|
|
2701
2769
|
return platform !== Electron;
|
|
2702
2770
|
};
|
|
@@ -3088,7 +3156,7 @@ const waitForElectronBackendLogin = async (backendUrl, uid, redirectUri, codeVer
|
|
|
3088
3156
|
return getLoggedOutBackendAuthState('Timed out waiting for backend login.');
|
|
3089
3157
|
};
|
|
3090
3158
|
|
|
3091
|
-
const getMockLoginResult = async signingInState => {
|
|
3159
|
+
const getMockLoginResult = async (signingInState, persistResult) => {
|
|
3092
3160
|
if (!hasPendingMockLoginResponse()) {
|
|
3093
3161
|
return undefined;
|
|
3094
3162
|
}
|
|
@@ -3105,9 +3173,9 @@ const getMockLoginResult = async signingInState => {
|
|
|
3105
3173
|
userState: 'loggedOut'
|
|
3106
3174
|
};
|
|
3107
3175
|
}
|
|
3108
|
-
return
|
|
3176
|
+
return persistResult(getLoggedInState(signingInState, response));
|
|
3109
3177
|
};
|
|
3110
|
-
const getInteractiveLoginResult = async (backendUrl, platform, signingInState) => {
|
|
3178
|
+
const getInteractiveLoginResult = async (backendUrl, platform, signingInState, persistResult, selectAccount) => {
|
|
3111
3179
|
const authUseRedirect = getAuthUseRedirect(platform);
|
|
3112
3180
|
const uid = 0;
|
|
3113
3181
|
const {
|
|
@@ -3125,7 +3193,7 @@ const getInteractiveLoginResult = async (backendUrl, platform, signingInState) =
|
|
|
3125
3193
|
state
|
|
3126
3194
|
});
|
|
3127
3195
|
}
|
|
3128
|
-
await invoke$1('Open.openUrl', loginUrl, platform, authUseRedirect);
|
|
3196
|
+
await invoke$1('Open.openUrl', selectAccount ? getAccountLoginUrl(backendUrl, loginUrl) : loginUrl, platform, authUseRedirect);
|
|
3129
3197
|
if (platform !== Electron && authUseRedirect) {
|
|
3130
3198
|
return signingInState;
|
|
3131
3199
|
}
|
|
@@ -3133,12 +3201,12 @@ const getInteractiveLoginResult = async (backendUrl, platform, signingInState) =
|
|
|
3133
3201
|
if (platform !== Electron) {
|
|
3134
3202
|
await clearPendingOidcAuthState();
|
|
3135
3203
|
}
|
|
3136
|
-
return
|
|
3204
|
+
return persistResult({
|
|
3137
3205
|
...authState,
|
|
3138
3206
|
authClientId: clientId
|
|
3139
3207
|
});
|
|
3140
3208
|
};
|
|
3141
|
-
const handleClickLogin = async options => {
|
|
3209
|
+
const handleClickLogin = async (options, persistResult = persistLoginResult) => {
|
|
3142
3210
|
const {
|
|
3143
3211
|
backendUrl,
|
|
3144
3212
|
platform
|
|
@@ -3155,11 +3223,11 @@ const handleClickLogin = async options => {
|
|
|
3155
3223
|
userState: 'loggingIn'
|
|
3156
3224
|
};
|
|
3157
3225
|
try {
|
|
3158
|
-
const mockLoginResult = await getMockLoginResult(signingInState);
|
|
3226
|
+
const mockLoginResult = await getMockLoginResult(signingInState, persistResult);
|
|
3159
3227
|
if (mockLoginResult) {
|
|
3160
3228
|
return mockLoginResult;
|
|
3161
3229
|
}
|
|
3162
|
-
return getInteractiveLoginResult(backendUrl, platform, signingInState);
|
|
3230
|
+
return getInteractiveLoginResult(backendUrl, platform, signingInState, persistResult, options.selectAccount === true);
|
|
3163
3231
|
} catch (error) {
|
|
3164
3232
|
await clearPendingOidcAuthState();
|
|
3165
3233
|
const errorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
|
|
@@ -3170,17 +3238,252 @@ const handleClickLogin = async options => {
|
|
|
3170
3238
|
}
|
|
3171
3239
|
};
|
|
3172
3240
|
|
|
3173
|
-
const
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3241
|
+
const storageKey = 'accountSessions';
|
|
3242
|
+
const accounts = createAccountSessions({
|
|
3243
|
+
read: () => getPersistentAuthValue(storageKey),
|
|
3244
|
+
write: value => setPersistentAuthValue(storageKey, value)
|
|
3245
|
+
});
|
|
3246
|
+
const refreshes = new Map();
|
|
3247
|
+
const state = {
|
|
3248
|
+
login: undefined,
|
|
3249
|
+
migration: undefined
|
|
3250
|
+
};
|
|
3251
|
+
const normalizeBackendUrl = backendUrl => new URL(backendUrl).origin;
|
|
3252
|
+
const addSession = async (backendUrl, session, revision) => {
|
|
3253
|
+
if (session.userState !== 'loggedIn' || !session.authAccessToken) {
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
const profile = await getAccountProfile(backendUrl, session.authAccessToken);
|
|
3257
|
+
await accounts.add({
|
|
3258
|
+
backendUrl,
|
|
3259
|
+
id: `${backendUrl}/${profile.id}`,
|
|
3260
|
+
profile,
|
|
3261
|
+
session: {
|
|
3262
|
+
...session,
|
|
3263
|
+
userName: profile.displayName
|
|
3264
|
+
}
|
|
3265
|
+
}, revision);
|
|
3266
|
+
};
|
|
3267
|
+
const migrate = async backendUrl => {
|
|
3268
|
+
if (await getPersistentAuthValue(storageKey)) {
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
const legacy = await getPersistedAuthSession();
|
|
3272
|
+
if (legacy) {
|
|
3273
|
+
if (legacy.authClientId && legacy.authRefreshToken) {
|
|
3274
|
+
const tokens = await refreshOidcTokens(backendUrl, legacy.authClientId, legacy.authRefreshToken);
|
|
3275
|
+
const authAccessTokenExpiresAt = getAccessTokenExpiresAt(tokens.expiresIn);
|
|
3276
|
+
const {
|
|
3277
|
+
authAccessTokenExpiresAt: _legacyExpiry,
|
|
3278
|
+
...legacySession
|
|
3279
|
+
} = legacy;
|
|
3280
|
+
await addSession(backendUrl, {
|
|
3281
|
+
...legacySession,
|
|
3282
|
+
authAccessToken: tokens.accessToken,
|
|
3283
|
+
...(authAccessTokenExpiresAt && {
|
|
3284
|
+
authAccessTokenExpiresAt
|
|
3285
|
+
}),
|
|
3286
|
+
authRefreshToken: tokens.refreshToken
|
|
3287
|
+
}, 0);
|
|
3288
|
+
} else {
|
|
3289
|
+
await addSession(backendUrl, legacy, 0);
|
|
3290
|
+
}
|
|
3291
|
+
} else {
|
|
3292
|
+
await setPersistentAuthValue(storageKey, JSON.stringify({
|
|
3293
|
+
accounts: [],
|
|
3294
|
+
activeId: '',
|
|
3295
|
+
revision: 0
|
|
3296
|
+
}));
|
|
3297
|
+
}
|
|
3298
|
+
// Clear legacy credentials only after the registry is durably committed.
|
|
3299
|
+
await clearPersistedAuthSession();
|
|
3300
|
+
};
|
|
3301
|
+
const ensureMigrated = async (backendUrl = getAuthBackendUrl()) => {
|
|
3302
|
+
const url = normalizeBackendUrl(backendUrl);
|
|
3303
|
+
if (!state.migration) {
|
|
3304
|
+
state.migration = (async () => {
|
|
3305
|
+
try {
|
|
3306
|
+
await migrate(url);
|
|
3307
|
+
} catch (error) {
|
|
3308
|
+
state.migration = undefined;
|
|
3309
|
+
throw error;
|
|
3310
|
+
}
|
|
3311
|
+
})();
|
|
3312
|
+
}
|
|
3313
|
+
await state.migration;
|
|
3314
|
+
return url;
|
|
3315
|
+
};
|
|
3316
|
+
const getActive = async backendUrl => {
|
|
3317
|
+
const registry = await accounts.read();
|
|
3318
|
+
return registry.accounts.find(account => account.id === registry.activeId && account.backendUrl === backendUrl);
|
|
3319
|
+
};
|
|
3320
|
+
const getCurrentState = async (backendUrl, authErrorMessage = '') => {
|
|
3321
|
+
const account = await getActive(backendUrl);
|
|
3322
|
+
return account ? {
|
|
3323
|
+
...account.session,
|
|
3324
|
+
authErrorMessage
|
|
3325
|
+
} : getLoggedOutBackendAuthState(authErrorMessage);
|
|
3326
|
+
};
|
|
3327
|
+
const refresh = async account => {
|
|
3328
|
+
const {
|
|
3329
|
+
backendUrl,
|
|
3330
|
+
session
|
|
3331
|
+
} = account;
|
|
3332
|
+
if (!session.authClientId || !session.authRefreshToken) {
|
|
3333
|
+
if (session.authAccessTokenExpiresAt && session.authAccessTokenExpiresAt <= Date.now()) {
|
|
3334
|
+
await accounts.update(account, getLoggedOutBackendAuthState('Please sign in to this account again.'));
|
|
3335
|
+
}
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
const key = `${account.id}/${session.authRefreshToken}`;
|
|
3339
|
+
const pending = refreshes.get(key);
|
|
3340
|
+
if (pending) {
|
|
3341
|
+
return pending;
|
|
3342
|
+
}
|
|
3343
|
+
const operation = (async () => {
|
|
3344
|
+
const tokens = await refreshOidcTokens(backendUrl, session.authClientId, session.authRefreshToken);
|
|
3345
|
+
const authAccessTokenExpiresAt = getAccessTokenExpiresAt(tokens.expiresIn);
|
|
3346
|
+
const {
|
|
3347
|
+
authAccessTokenExpiresAt: _previousExpiry,
|
|
3348
|
+
...previousSession
|
|
3349
|
+
} = session;
|
|
3350
|
+
await accounts.update(account, {
|
|
3351
|
+
...previousSession,
|
|
3352
|
+
authAccessToken: tokens.accessToken,
|
|
3353
|
+
...(authAccessTokenExpiresAt && {
|
|
3354
|
+
authAccessTokenExpiresAt
|
|
3355
|
+
}),
|
|
3356
|
+
authRefreshToken: tokens.refreshToken
|
|
3357
|
+
});
|
|
3358
|
+
})();
|
|
3359
|
+
refreshes.set(key, operation);
|
|
3360
|
+
try {
|
|
3361
|
+
await operation;
|
|
3362
|
+
} catch (error) {
|
|
3363
|
+
// Network errors must not destroy a retained session. A rejected grant needs another login.
|
|
3364
|
+
if (error && typeof error === 'object' && 'error' in error && error.error === 'invalid_grant') {
|
|
3365
|
+
await accounts.update(account, getLoggedOutBackendAuthState('Please sign in to this account again.'));
|
|
3366
|
+
}
|
|
3367
|
+
throw error;
|
|
3368
|
+
} finally {
|
|
3369
|
+
refreshes.delete(key);
|
|
3370
|
+
}
|
|
3371
|
+
};
|
|
3372
|
+
const getAccounts = async () => {
|
|
3373
|
+
const backendUrl = await ensureMigrated();
|
|
3374
|
+
const registry = await accounts.read();
|
|
3375
|
+
return registry.accounts.filter(account => account.backendUrl === backendUrl).map(account => ({
|
|
3376
|
+
...account.profile,
|
|
3377
|
+
active: account.id === registry.activeId,
|
|
3378
|
+
color: 'blue',
|
|
3379
|
+
id: account.id,
|
|
3380
|
+
signedIn: account.session.userState === 'loggedIn'
|
|
3381
|
+
}));
|
|
3382
|
+
};
|
|
3383
|
+
const getAccessToken = async (options = {}) => {
|
|
3384
|
+
const backendUrl = await ensureMigrated();
|
|
3385
|
+
const account = await getActive(backendUrl);
|
|
3386
|
+
if (!account) {
|
|
3387
|
+
return '';
|
|
3388
|
+
}
|
|
3389
|
+
if (options.refresh === 'always' || options.refresh === 'if-needed' && !isAccessTokenValid(account.session.authAccessToken || '', String(account.session.authAccessTokenExpiresAt || ''))) {
|
|
3390
|
+
try {
|
|
3391
|
+
await refresh(account);
|
|
3392
|
+
} catch (error) {
|
|
3393
|
+
const selected = await getActive(backendUrl);
|
|
3394
|
+
if (selected?.id === account.id) {
|
|
3395
|
+
throw error;
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
// A refresh belongs to the identity it started with; selection can change while it is in flight.
|
|
3400
|
+
const current = await getActive(backendUrl);
|
|
3401
|
+
if (current?.id !== account.id) {
|
|
3402
|
+
return getAccessToken(options);
|
|
3403
|
+
}
|
|
3404
|
+
return current?.session.authAccessToken || '';
|
|
3405
|
+
};
|
|
3406
|
+
const initialize = async (options = {}) => {
|
|
3407
|
+
if (typeof options.platform === 'number') {
|
|
3408
|
+
setAuthPlatform(options.platform);
|
|
3409
|
+
}
|
|
3410
|
+
const backendUrl = await ensureMigrated(options.backendUrl);
|
|
3411
|
+
const registry = await accounts.read();
|
|
3412
|
+
const completed = await completeBrowserOidcLogin(backendUrl);
|
|
3413
|
+
if (completed) {
|
|
3414
|
+
await addSession(backendUrl, completed, registry.revision);
|
|
3415
|
+
return getCurrentState(backendUrl, completed.authErrorMessage);
|
|
3416
|
+
}
|
|
3417
|
+
try {
|
|
3418
|
+
await getAccessToken({
|
|
3419
|
+
refresh: 'if-needed'
|
|
3420
|
+
});
|
|
3421
|
+
return getCurrentState(backendUrl);
|
|
3422
|
+
} catch (error) {
|
|
3423
|
+
return getCurrentState(backendUrl, error instanceof Error ? error.message : 'Unable to refresh the account.');
|
|
3424
|
+
}
|
|
3425
|
+
};
|
|
3426
|
+
const login = async options => {
|
|
3427
|
+
if (state.login) {
|
|
3428
|
+
return state.login;
|
|
3429
|
+
}
|
|
3430
|
+
const operation = (async () => {
|
|
3431
|
+
const backendUrl = await ensureMigrated(options.backendUrl);
|
|
3432
|
+
const registry = await accounts.read();
|
|
3433
|
+
const result = await handleClickLogin({
|
|
3434
|
+
...options,
|
|
3435
|
+
selectAccount: registry.accounts.length > 0
|
|
3436
|
+
}, async value => value);
|
|
3437
|
+
await addSession(backendUrl, result, registry.revision);
|
|
3438
|
+
return getCurrentState(backendUrl, result.authErrorMessage);
|
|
3439
|
+
})();
|
|
3440
|
+
state.login = operation;
|
|
3441
|
+
try {
|
|
3442
|
+
return await operation;
|
|
3443
|
+
} finally {
|
|
3444
|
+
state.login = undefined;
|
|
3445
|
+
}
|
|
3446
|
+
};
|
|
3447
|
+
const useAccount = async id => {
|
|
3448
|
+
const backendUrl = await ensureMigrated();
|
|
3449
|
+
const registry = await accounts.read();
|
|
3450
|
+
if (registry.accounts.every(account => !(account.id === id && account.backendUrl === backendUrl))) {
|
|
3451
|
+
throw new Error('Account is no longer available.');
|
|
3452
|
+
}
|
|
3453
|
+
await accounts.select(id);
|
|
3454
|
+
return getCurrentState(backendUrl);
|
|
3455
|
+
};
|
|
3456
|
+
const removeAccount = async id => {
|
|
3457
|
+
const backendUrl = await ensureMigrated();
|
|
3458
|
+
const registry = await accounts.read();
|
|
3459
|
+
if (registry.accounts.some(account => account.id === id && account.backendUrl === backendUrl)) {
|
|
3460
|
+
await accounts.remove(id);
|
|
3461
|
+
}
|
|
3462
|
+
return getCurrentState(backendUrl);
|
|
3463
|
+
};
|
|
3464
|
+
const logout = async () => {
|
|
3465
|
+
const backendUrl = await ensureMigrated();
|
|
3466
|
+
const account = await getActive(backendUrl);
|
|
3467
|
+
return account ? removeAccount(account.id) : getLoggedOutBackendAuthState();
|
|
3468
|
+
};
|
|
3469
|
+
const syncBackendAuth = async backendUrl => initialize({
|
|
3470
|
+
backendUrl
|
|
3471
|
+
});
|
|
3472
|
+
|
|
3473
|
+
const configure = options => {
|
|
3474
|
+
setAuthBackendUrl(options.backendUrl);
|
|
3475
|
+
setAuthPlatform(options.platform);
|
|
3476
|
+
};
|
|
3477
|
+
|
|
3478
|
+
const handleMessagePort = async (port, rpcId) => {
|
|
3479
|
+
const rpc = await create$4({
|
|
3480
|
+
commandMap: commandMap,
|
|
3481
|
+
isMessagePortOpen: true,
|
|
3482
|
+
messagePort: port
|
|
3483
|
+
});
|
|
3484
|
+
if (rpcId) {
|
|
3485
|
+
set$3(rpcId, rpc);
|
|
3486
|
+
}
|
|
3184
3487
|
};
|
|
3185
3488
|
|
|
3186
3489
|
const commandMap = {
|
|
@@ -3189,14 +3492,17 @@ const commandMap = {
|
|
|
3189
3492
|
'Auth.consumeNextLoginResponse': consumeNextLoginResponse,
|
|
3190
3493
|
'Auth.consumeNextRefreshResponse': consumeNextRefreshResponse,
|
|
3191
3494
|
'Auth.getAccessToken': getAccessToken,
|
|
3495
|
+
'Auth.getAccounts': getAccounts,
|
|
3192
3496
|
'Auth.hasPendingMockLoginResponse': hasPendingMockLoginResponse,
|
|
3193
3497
|
'Auth.hasPendingMockRefreshResponse': hasPendingMockRefreshResponse,
|
|
3194
3498
|
'Auth.initialize': initialize,
|
|
3195
|
-
'Auth.login':
|
|
3499
|
+
'Auth.login': login,
|
|
3196
3500
|
'Auth.logout': logout,
|
|
3501
|
+
'Auth.removeAccount': removeAccount,
|
|
3197
3502
|
'Auth.setNextLoginResponse': setNextLoginResponse,
|
|
3198
3503
|
'Auth.setNextRefreshResponse': setNextRefreshResponse,
|
|
3199
3504
|
'Auth.syncBackendAuth': syncBackendAuth,
|
|
3505
|
+
'Auth.useAccount': useAccount,
|
|
3200
3506
|
'HandleMessagePort.handleMessagePort': handleMessagePort,
|
|
3201
3507
|
initialize: (_, port) => handleMessagePort(port, RendererWorker)
|
|
3202
3508
|
};
|