@lvce-editor/auth-worker 3.15.0 → 3.16.1
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 +792 -485
- 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,89 +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
|
-
if (options.refresh === 'always') {
|
|
2201
|
-
return '';
|
|
2202
|
-
}
|
|
2203
|
-
const expiresAt = Number(accessTokenExpiresAt);
|
|
2204
|
-
return accessTokenExpiresAt && Number.isFinite(expiresAt) && expiresAt <= now ? '' : accessToken;
|
|
2145
|
+
if (!globalThis.location || typeof globalThis.location.href !== 'string' || !globalThis.location.href) {
|
|
2146
|
+
return '';
|
|
2205
2147
|
}
|
|
2206
|
-
|
|
2207
|
-
const expiresAt = getAccessTokenExpiresAt(refreshedTokens.expiresIn, now);
|
|
2208
|
-
await Promise.all([setPersistentAuthValue('accessToken', refreshedTokens.accessToken), setPersistentAuthValue('accessTokenExpiresAt', typeof expiresAt === 'number' ? String(expiresAt) : ''), setPersistentAuthValue('refreshToken', refreshedTokens.refreshToken)]);
|
|
2209
|
-
return refreshedTokens.accessToken;
|
|
2210
|
-
};
|
|
2211
|
-
|
|
2212
|
-
const handleMessagePort = async (port, rpcId) => {
|
|
2213
|
-
const rpc = await create$4({
|
|
2214
|
-
commandMap: commandMap,
|
|
2215
|
-
isMessagePortOpen: true,
|
|
2216
|
-
messagePort: port
|
|
2217
|
-
});
|
|
2218
|
-
if (rpcId) {
|
|
2219
|
-
set$3(rpcId, rpc);
|
|
2220
|
-
}
|
|
2221
|
-
};
|
|
2222
|
-
|
|
2223
|
-
const getAuthorizationServer = backendUrl => {
|
|
2224
|
-
return {
|
|
2225
|
-
issuer: getBackendAuthUrl(backendUrl, '/oidc'),
|
|
2226
|
-
jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
|
|
2227
|
-
token_endpoint: getBackendOidcTokenUrl(backendUrl)
|
|
2228
|
-
};
|
|
2229
|
-
};
|
|
2230
|
-
const getClient = clientId => {
|
|
2231
|
-
return {
|
|
2232
|
-
client_id: clientId
|
|
2233
|
-
};
|
|
2234
|
-
};
|
|
2235
|
-
const exchangeAuthorizationCode = async (backendUrl, clientId, code, redirectUri, codeVerifier, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
|
|
2236
|
-
const authorizationServer = getAuthorizationServer(backendUrl);
|
|
2237
|
-
const client = getClient(clientId);
|
|
2238
|
-
const response = await requestTokenEndpoint(authorizationServer, client, None(), 'authorization_code', new URLSearchParams({
|
|
2239
|
-
code,
|
|
2240
|
-
code_verifier: codeVerifier,
|
|
2241
|
-
redirect_uri: redirectUri
|
|
2242
|
-
}));
|
|
2243
|
-
const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
|
|
2244
|
-
return {
|
|
2245
|
-
accessToken: tokenResponse.access_token,
|
|
2246
|
-
expiresIn: typeof tokenResponse.expires_in === 'number' ? tokenResponse.expires_in : undefined,
|
|
2247
|
-
refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : ''
|
|
2248
|
-
};
|
|
2249
|
-
};
|
|
2250
|
-
|
|
2251
|
-
const getCurrentHref = async () => {
|
|
2252
|
-
try {
|
|
2253
|
-
return await invoke('Layout.getHref');
|
|
2254
|
-
} catch {
|
|
2255
|
-
// ignore
|
|
2256
|
-
}
|
|
2257
|
-
if (!globalThis.location || typeof globalThis.location.href !== 'string' || !globalThis.location.href) {
|
|
2258
|
-
return '';
|
|
2259
|
-
}
|
|
2260
|
-
return globalThis.location.href;
|
|
2148
|
+
return globalThis.location.href;
|
|
2261
2149
|
};
|
|
2262
2150
|
|
|
2263
2151
|
const getLoggedOutBackendAuthState = (authErrorMessage = '') => {
|
|
@@ -2298,6 +2186,188 @@ const getOidcUserName = async (backendUrl, accessToken, fetchFn = fetch) => {
|
|
|
2298
2186
|
return typeof displayName === 'string' ? displayName : '';
|
|
2299
2187
|
};
|
|
2300
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 pendingReturnUrlKey = 'pendingOidcReturnUrl';
|
|
2335
|
+
const pendingStateKey = 'pendingOidcState';
|
|
2336
|
+
const clearOidcCallbackUrl = async () => {
|
|
2337
|
+
await clearPersistentAuthValue(callbackUrlKey);
|
|
2338
|
+
};
|
|
2339
|
+
const clearStoredOidcClientId = async () => {
|
|
2340
|
+
await clearPersistentAuthValue(clientIdKey);
|
|
2341
|
+
};
|
|
2342
|
+
const clearPendingOidcAuthState = async () => {
|
|
2343
|
+
await Promise.all([clearPersistentAuthValue(pendingClientIdKey), clearPersistentAuthValue(pendingCodeVerifierKey), clearPersistentAuthValue(pendingRedirectUriKey), clearPersistentAuthValue(pendingReturnUrlKey), clearPersistentAuthValue(pendingStateKey)]);
|
|
2344
|
+
};
|
|
2345
|
+
const getOidcCallbackUrl = async () => {
|
|
2346
|
+
return getPersistentAuthValue(callbackUrlKey);
|
|
2347
|
+
};
|
|
2348
|
+
const getStoredOidcClientId = async () => {
|
|
2349
|
+
return getPersistentAuthValue(clientIdKey);
|
|
2350
|
+
};
|
|
2351
|
+
const loadPendingOidcAuthState = async () => {
|
|
2352
|
+
const [clientId, codeVerifier, redirectUri, returnUrl, state] = await Promise.all([getPersistentAuthValue(pendingClientIdKey), getPersistentAuthValue(pendingCodeVerifierKey), getPersistentAuthValue(pendingRedirectUriKey), getPersistentAuthValue(pendingReturnUrlKey), getPersistentAuthValue(pendingStateKey)]);
|
|
2353
|
+
if (!clientId || !codeVerifier || !redirectUri || !state) {
|
|
2354
|
+
return undefined;
|
|
2355
|
+
}
|
|
2356
|
+
return {
|
|
2357
|
+
clientId,
|
|
2358
|
+
codeVerifier,
|
|
2359
|
+
redirectUri,
|
|
2360
|
+
returnUrl,
|
|
2361
|
+
state
|
|
2362
|
+
};
|
|
2363
|
+
};
|
|
2364
|
+
const saveOidcClientId = async clientId => {
|
|
2365
|
+
await setPersistentAuthValue(clientIdKey, clientId);
|
|
2366
|
+
};
|
|
2367
|
+
const savePendingOidcAuthState = async value => {
|
|
2368
|
+
await Promise.all([setPersistentAuthValue(pendingClientIdKey, value.clientId), setPersistentAuthValue(pendingCodeVerifierKey, value.codeVerifier), setPersistentAuthValue(pendingRedirectUriKey, value.redirectUri), setPersistentAuthValue(pendingReturnUrlKey, value.returnUrl), setPersistentAuthValue(pendingStateKey, value.state)]);
|
|
2369
|
+
};
|
|
2370
|
+
|
|
2301
2371
|
const normalizeRedirectUri = value => {
|
|
2302
2372
|
const url = new URL(value);
|
|
2303
2373
|
url.hash = '';
|
|
@@ -2352,18 +2422,139 @@ const completeBrowserOidcLogin = async backendUrl => {
|
|
|
2352
2422
|
const authAccessTokenExpiresAt = getAccessTokenExpiresAt(exchanged.expiresIn);
|
|
2353
2423
|
await clearPendingOidcAuthState();
|
|
2354
2424
|
return {
|
|
2355
|
-
authAccessToken: exchanged.accessToken,
|
|
2356
|
-
...(authAccessTokenExpiresAt && {
|
|
2357
|
-
authAccessTokenExpiresAt
|
|
2358
|
-
}),
|
|
2359
|
-
authClientId: pendingAuthState.clientId,
|
|
2360
|
-
authErrorMessage: '',
|
|
2361
|
-
authRefreshToken: exchanged.refreshToken,
|
|
2362
|
-
userName,
|
|
2363
|
-
userState: exchanged.accessToken ? 'loggedIn' : 'loggedOut'
|
|
2425
|
+
authAccessToken: exchanged.accessToken,
|
|
2426
|
+
...(authAccessTokenExpiresAt && {
|
|
2427
|
+
authAccessTokenExpiresAt
|
|
2428
|
+
}),
|
|
2429
|
+
authClientId: pendingAuthState.clientId,
|
|
2430
|
+
authErrorMessage: '',
|
|
2431
|
+
authRefreshToken: exchanged.refreshToken,
|
|
2432
|
+
userName,
|
|
2433
|
+
userState: exchanged.accessToken ? 'loggedIn' : 'loggedOut'
|
|
2434
|
+
};
|
|
2435
|
+
};
|
|
2436
|
+
|
|
2437
|
+
const getAccountProfile = async (backendUrl, accessToken, fetchFn = fetch) => {
|
|
2438
|
+
const response = await fetchFn(new URL('/account/me', backendUrl), {
|
|
2439
|
+
headers: {
|
|
2440
|
+
Accept: 'application/json',
|
|
2441
|
+
Authorization: `Bearer ${accessToken}`
|
|
2442
|
+
}
|
|
2443
|
+
});
|
|
2444
|
+
if (!response.ok) {
|
|
2445
|
+
throw new Error('Unable to load the signed in account.');
|
|
2446
|
+
}
|
|
2447
|
+
const value = await response.json();
|
|
2448
|
+
if (!value || typeof value.id !== 'string' || !value.id) {
|
|
2449
|
+
throw new Error('The backend did not return an account identity.');
|
|
2450
|
+
}
|
|
2451
|
+
return {
|
|
2452
|
+
displayName: typeof value.displayName === 'string' ? value.displayName : '',
|
|
2453
|
+
email: typeof value.email === 'string' ? value.email : '',
|
|
2454
|
+
id: value.id,
|
|
2455
|
+
provider: typeof value.provider === 'string' ? value.provider : 'LVCE Editor'
|
|
2456
|
+
};
|
|
2457
|
+
};
|
|
2458
|
+
|
|
2459
|
+
const getBackendRefreshUrl = backendUrl => {
|
|
2460
|
+
return getBackendAuthUrl(backendUrl, '/auth/refresh');
|
|
2461
|
+
};
|
|
2462
|
+
|
|
2463
|
+
const delay = async ms => {
|
|
2464
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
2465
|
+
};
|
|
2466
|
+
|
|
2467
|
+
const state$1 = {
|
|
2468
|
+
nextLoginResponse: undefined,
|
|
2469
|
+
nextRefreshResponse: undefined
|
|
2470
|
+
};
|
|
2471
|
+
const setNextLoginResponse = response => {
|
|
2472
|
+
state$1.nextLoginResponse = response;
|
|
2473
|
+
};
|
|
2474
|
+
const setNextRefreshResponse = response => {
|
|
2475
|
+
state$1.nextRefreshResponse = response;
|
|
2476
|
+
};
|
|
2477
|
+
const clear = () => {
|
|
2478
|
+
state$1.nextLoginResponse = undefined;
|
|
2479
|
+
state$1.nextRefreshResponse = undefined;
|
|
2480
|
+
};
|
|
2481
|
+
const hasPendingMockLoginResponse = () => {
|
|
2482
|
+
return !!state$1.nextLoginResponse;
|
|
2483
|
+
};
|
|
2484
|
+
const hasPendingMockRefreshResponse = () => {
|
|
2485
|
+
return !!state$1.nextRefreshResponse;
|
|
2486
|
+
};
|
|
2487
|
+
const consumeNextLoginResponse = async () => {
|
|
2488
|
+
if (!state$1.nextLoginResponse) {
|
|
2489
|
+
return undefined;
|
|
2490
|
+
}
|
|
2491
|
+
const response = state$1.nextLoginResponse;
|
|
2492
|
+
state$1.nextLoginResponse = undefined;
|
|
2493
|
+
if (response.delay > 0) {
|
|
2494
|
+
await delay(response.delay);
|
|
2495
|
+
}
|
|
2496
|
+
if (response.type === 'error') {
|
|
2497
|
+
throw new Error(response.message);
|
|
2498
|
+
}
|
|
2499
|
+
return response.response;
|
|
2500
|
+
};
|
|
2501
|
+
const consumeNextRefreshResponse = async () => {
|
|
2502
|
+
if (!state$1.nextRefreshResponse) {
|
|
2503
|
+
return undefined;
|
|
2504
|
+
}
|
|
2505
|
+
const response = state$1.nextRefreshResponse;
|
|
2506
|
+
state$1.nextRefreshResponse = undefined;
|
|
2507
|
+
if (response.delay > 0) {
|
|
2508
|
+
await new Promise(resolve => setTimeout(resolve, response.delay));
|
|
2509
|
+
}
|
|
2510
|
+
if (response.type === 'error') {
|
|
2511
|
+
throw new Error(response.message);
|
|
2512
|
+
}
|
|
2513
|
+
return response.response;
|
|
2514
|
+
};
|
|
2515
|
+
|
|
2516
|
+
const isObject = value => {
|
|
2517
|
+
return !!value && typeof value === 'object';
|
|
2518
|
+
};
|
|
2519
|
+
|
|
2520
|
+
const isBackendAuthResponse = value => {
|
|
2521
|
+
return isObject(value);
|
|
2522
|
+
};
|
|
2523
|
+
|
|
2524
|
+
const getNumber = (value, fallback = 0) => {
|
|
2525
|
+
return typeof value === 'number' ? value : fallback;
|
|
2526
|
+
};
|
|
2527
|
+
|
|
2528
|
+
const getString = (value, fallback = '') => {
|
|
2529
|
+
return typeof value === 'string' ? value : fallback;
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2532
|
+
const getUserName = value => {
|
|
2533
|
+
if (typeof value.userName === 'string') {
|
|
2534
|
+
return value.userName;
|
|
2535
|
+
}
|
|
2536
|
+
return getString(value.user?.displayName);
|
|
2537
|
+
};
|
|
2538
|
+
const toBackendAuthState = value => {
|
|
2539
|
+
return {
|
|
2540
|
+
authAccessToken: getString(value.accessToken),
|
|
2541
|
+
authErrorMessage: getString(value.error),
|
|
2542
|
+
authRefreshToken: getString(value.refreshToken),
|
|
2543
|
+
userName: getUserName(value),
|
|
2544
|
+
userState: value.accessToken ? 'loggedIn' : 'loggedOut',
|
|
2545
|
+
userSubscriptionPlan: getString(value.subscriptionPlan),
|
|
2546
|
+
userSubscriptionStatus: getString(value.subscriptionStatus),
|
|
2547
|
+
userUsedTokens: getNumber(value.usedTokens)
|
|
2364
2548
|
};
|
|
2365
2549
|
};
|
|
2366
2550
|
|
|
2551
|
+
const parseBackendAuthResponse = value => {
|
|
2552
|
+
if (!isBackendAuthResponse(value)) {
|
|
2553
|
+
return getLoggedOutBackendAuthState('Backend returned an invalid authentication response.');
|
|
2554
|
+
}
|
|
2555
|
+
return toBackendAuthState(value);
|
|
2556
|
+
};
|
|
2557
|
+
|
|
2367
2558
|
const accessTokenKey = 'accessToken';
|
|
2368
2559
|
const accessTokenExpiresAtKey = 'accessTokenExpiresAt';
|
|
2369
2560
|
const refreshTokenKey = 'refreshToken';
|
|
@@ -2436,6 +2627,32 @@ const persistLoginResult = async loginResult => {
|
|
|
2436
2627
|
return loginResult;
|
|
2437
2628
|
};
|
|
2438
2629
|
|
|
2630
|
+
const getAuthorizationServer = backendUrl => {
|
|
2631
|
+
return {
|
|
2632
|
+
issuer: getBackendAuthUrl(backendUrl, '/oidc'),
|
|
2633
|
+
jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
|
|
2634
|
+
token_endpoint: getBackendOidcTokenUrl(backendUrl)
|
|
2635
|
+
};
|
|
2636
|
+
};
|
|
2637
|
+
const getClient = clientId => {
|
|
2638
|
+
return {
|
|
2639
|
+
client_id: clientId
|
|
2640
|
+
};
|
|
2641
|
+
};
|
|
2642
|
+
const refreshOidcTokens = async (backendUrl, clientId, refreshToken, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
|
|
2643
|
+
const authorizationServer = getAuthorizationServer(backendUrl);
|
|
2644
|
+
const client = getClient(clientId);
|
|
2645
|
+
const response = await requestTokenEndpoint(authorizationServer, client, None(), 'refresh_token', new URLSearchParams({
|
|
2646
|
+
refresh_token: refreshToken
|
|
2647
|
+
}));
|
|
2648
|
+
const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
|
|
2649
|
+
return {
|
|
2650
|
+
accessToken: tokenResponse.access_token,
|
|
2651
|
+
expiresIn: typeof tokenResponse.expires_in === 'number' ? tokenResponse.expires_in : undefined,
|
|
2652
|
+
refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : refreshToken
|
|
2653
|
+
};
|
|
2654
|
+
};
|
|
2655
|
+
|
|
2439
2656
|
const clearStoredOidcAuth = async () => {
|
|
2440
2657
|
await clearPersistedAuthSession();
|
|
2441
2658
|
};
|
|
@@ -2474,163 +2691,6 @@ const restoreOidcAuth = async backendUrl => {
|
|
|
2474
2691
|
}
|
|
2475
2692
|
};
|
|
2476
2693
|
|
|
2477
|
-
const getBackendUrl = options => {
|
|
2478
|
-
if (typeof options === 'number') {
|
|
2479
|
-
return '';
|
|
2480
|
-
}
|
|
2481
|
-
return options.backendUrl || '';
|
|
2482
|
-
};
|
|
2483
|
-
const getPlatform = options => {
|
|
2484
|
-
return typeof options === 'number' ? options : options.platform ?? Web;
|
|
2485
|
-
};
|
|
2486
|
-
const initialize = async options => {
|
|
2487
|
-
const backendUrl = getBackendUrl(options);
|
|
2488
|
-
configure({
|
|
2489
|
-
backendUrl,
|
|
2490
|
-
platform: getPlatform(options)
|
|
2491
|
-
});
|
|
2492
|
-
try {
|
|
2493
|
-
if (backendUrl) {
|
|
2494
|
-
const completedBrowserLogin = await completeBrowserOidcLogin(backendUrl);
|
|
2495
|
-
if (completedBrowserLogin) {
|
|
2496
|
-
return persistLoginResult(completedBrowserLogin);
|
|
2497
|
-
}
|
|
2498
|
-
const restoredOidcAuth = await restoreOidcAuth(backendUrl);
|
|
2499
|
-
if (restoredOidcAuth) {
|
|
2500
|
-
return persistLoginResult(restoredOidcAuth);
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
const persistedAuthSession = await getPersistedAuthSession();
|
|
2504
|
-
if (persistedAuthSession) {
|
|
2505
|
-
return persistedAuthSession;
|
|
2506
|
-
}
|
|
2507
|
-
return getLoggedOutBackendAuthState();
|
|
2508
|
-
} catch (error) {
|
|
2509
|
-
const authErrorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
|
|
2510
|
-
return getLoggedOutBackendAuthState(authErrorMessage);
|
|
2511
|
-
}
|
|
2512
|
-
};
|
|
2513
|
-
|
|
2514
|
-
const getBackendLogoutUrl = backendUrl => {
|
|
2515
|
-
return getBackendAuthUrl(backendUrl, '/auth/logout');
|
|
2516
|
-
};
|
|
2517
|
-
|
|
2518
|
-
const logoutFromBackend = async backendUrl => {
|
|
2519
|
-
if (!backendUrl) {
|
|
2520
|
-
return;
|
|
2521
|
-
}
|
|
2522
|
-
try {
|
|
2523
|
-
await fetch(getBackendLogoutUrl(backendUrl), {
|
|
2524
|
-
credentials: 'include',
|
|
2525
|
-
headers: {
|
|
2526
|
-
Accept: 'application/json'
|
|
2527
|
-
},
|
|
2528
|
-
method: 'POST'
|
|
2529
|
-
});
|
|
2530
|
-
} catch {
|
|
2531
|
-
// Ignore logout failures and still clear local auth state.
|
|
2532
|
-
}
|
|
2533
|
-
};
|
|
2534
|
-
|
|
2535
|
-
const getBackendRefreshUrl = backendUrl => {
|
|
2536
|
-
return getBackendAuthUrl(backendUrl, '/auth/refresh');
|
|
2537
|
-
};
|
|
2538
|
-
|
|
2539
|
-
const delay = async ms => {
|
|
2540
|
-
await new Promise(resolve => setTimeout(resolve, ms));
|
|
2541
|
-
};
|
|
2542
|
-
|
|
2543
|
-
const state = {
|
|
2544
|
-
nextLoginResponse: undefined,
|
|
2545
|
-
nextRefreshResponse: undefined
|
|
2546
|
-
};
|
|
2547
|
-
const setNextLoginResponse = response => {
|
|
2548
|
-
state.nextLoginResponse = response;
|
|
2549
|
-
};
|
|
2550
|
-
const setNextRefreshResponse = response => {
|
|
2551
|
-
state.nextRefreshResponse = response;
|
|
2552
|
-
};
|
|
2553
|
-
const clear = () => {
|
|
2554
|
-
state.nextLoginResponse = undefined;
|
|
2555
|
-
state.nextRefreshResponse = undefined;
|
|
2556
|
-
};
|
|
2557
|
-
const hasPendingMockLoginResponse = () => {
|
|
2558
|
-
return !!state.nextLoginResponse;
|
|
2559
|
-
};
|
|
2560
|
-
const hasPendingMockRefreshResponse = () => {
|
|
2561
|
-
return !!state.nextRefreshResponse;
|
|
2562
|
-
};
|
|
2563
|
-
const consumeNextLoginResponse = async () => {
|
|
2564
|
-
if (!state.nextLoginResponse) {
|
|
2565
|
-
return undefined;
|
|
2566
|
-
}
|
|
2567
|
-
const response = state.nextLoginResponse;
|
|
2568
|
-
state.nextLoginResponse = undefined;
|
|
2569
|
-
if (response.delay > 0) {
|
|
2570
|
-
await delay(response.delay);
|
|
2571
|
-
}
|
|
2572
|
-
if (response.type === 'error') {
|
|
2573
|
-
throw new Error(response.message);
|
|
2574
|
-
}
|
|
2575
|
-
return response.response;
|
|
2576
|
-
};
|
|
2577
|
-
const consumeNextRefreshResponse = async () => {
|
|
2578
|
-
if (!state.nextRefreshResponse) {
|
|
2579
|
-
return undefined;
|
|
2580
|
-
}
|
|
2581
|
-
const response = state.nextRefreshResponse;
|
|
2582
|
-
state.nextRefreshResponse = undefined;
|
|
2583
|
-
if (response.delay > 0) {
|
|
2584
|
-
await new Promise(resolve => setTimeout(resolve, response.delay));
|
|
2585
|
-
}
|
|
2586
|
-
if (response.type === 'error') {
|
|
2587
|
-
throw new Error(response.message);
|
|
2588
|
-
}
|
|
2589
|
-
return response.response;
|
|
2590
|
-
};
|
|
2591
|
-
|
|
2592
|
-
const isObject = value => {
|
|
2593
|
-
return !!value && typeof value === 'object';
|
|
2594
|
-
};
|
|
2595
|
-
|
|
2596
|
-
const isBackendAuthResponse = value => {
|
|
2597
|
-
return isObject(value);
|
|
2598
|
-
};
|
|
2599
|
-
|
|
2600
|
-
const getNumber = (value, fallback = 0) => {
|
|
2601
|
-
return typeof value === 'number' ? value : fallback;
|
|
2602
|
-
};
|
|
2603
|
-
|
|
2604
|
-
const getString = (value, fallback = '') => {
|
|
2605
|
-
return typeof value === 'string' ? value : fallback;
|
|
2606
|
-
};
|
|
2607
|
-
|
|
2608
|
-
const getUserName = value => {
|
|
2609
|
-
if (typeof value.userName === 'string') {
|
|
2610
|
-
return value.userName;
|
|
2611
|
-
}
|
|
2612
|
-
return getString(value.user?.displayName);
|
|
2613
|
-
};
|
|
2614
|
-
const toBackendAuthState = value => {
|
|
2615
|
-
return {
|
|
2616
|
-
authAccessToken: getString(value.accessToken),
|
|
2617
|
-
authErrorMessage: getString(value.error),
|
|
2618
|
-
authRefreshToken: getString(value.refreshToken),
|
|
2619
|
-
userName: getUserName(value),
|
|
2620
|
-
userState: value.accessToken ? 'loggedIn' : 'loggedOut',
|
|
2621
|
-
userSubscriptionPlan: getString(value.subscriptionPlan),
|
|
2622
|
-
userSubscriptionStatus: getString(value.subscriptionStatus),
|
|
2623
|
-
userUsedTokens: getNumber(value.usedTokens)
|
|
2624
|
-
};
|
|
2625
|
-
};
|
|
2626
|
-
|
|
2627
|
-
const parseBackendAuthResponse = value => {
|
|
2628
|
-
if (!isBackendAuthResponse(value)) {
|
|
2629
|
-
return getLoggedOutBackendAuthState('Backend returned an invalid authentication response.');
|
|
2630
|
-
}
|
|
2631
|
-
return toBackendAuthState(value);
|
|
2632
|
-
};
|
|
2633
|
-
|
|
2634
2694
|
const getPayload = async response => {
|
|
2635
2695
|
try {
|
|
2636
2696
|
return await response.json();
|
|
@@ -2638,7 +2698,7 @@ const getPayload = async response => {
|
|
|
2638
2698
|
return undefined;
|
|
2639
2699
|
}
|
|
2640
2700
|
};
|
|
2641
|
-
const syncBackendAuth = async backendUrl => {
|
|
2701
|
+
const syncBackendAuth$1 = async backendUrl => {
|
|
2642
2702
|
if (!backendUrl) {
|
|
2643
2703
|
return getLoggedOutBackendAuthState('Backend URL is missing.');
|
|
2644
2704
|
}
|
|
@@ -2688,7 +2748,7 @@ const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalM
|
|
|
2688
2748
|
const deadline = Date.now() + timeoutMs;
|
|
2689
2749
|
let lastErrorMessage = '';
|
|
2690
2750
|
while (Date.now() < deadline) {
|
|
2691
|
-
const authState = await syncBackendAuth(backendUrl);
|
|
2751
|
+
const authState = await syncBackendAuth$1(backendUrl);
|
|
2692
2752
|
if (authState.userState === 'loggedIn') {
|
|
2693
2753
|
return authState;
|
|
2694
2754
|
}
|
|
@@ -2700,6 +2760,13 @@ const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalM
|
|
|
2700
2760
|
return getLoggedOutBackendAuthState(lastErrorMessage);
|
|
2701
2761
|
};
|
|
2702
2762
|
|
|
2763
|
+
const getAccountLoginUrl = (backendUrl, authorizationUrl) => {
|
|
2764
|
+
const url = new URL('/login', backendUrl);
|
|
2765
|
+
url.searchParams.set('selectAccount', 'true');
|
|
2766
|
+
url.searchParams.set('returnTo', authorizationUrl);
|
|
2767
|
+
return url.href;
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2703
2770
|
const getAuthUseRedirect = platform => {
|
|
2704
2771
|
return platform !== Electron;
|
|
2705
2772
|
};
|
|
@@ -3091,7 +3158,7 @@ const waitForElectronBackendLogin = async (backendUrl, uid, redirectUri, codeVer
|
|
|
3091
3158
|
return getLoggedOutBackendAuthState('Timed out waiting for backend login.');
|
|
3092
3159
|
};
|
|
3093
3160
|
|
|
3094
|
-
const getMockLoginResult = async signingInState => {
|
|
3161
|
+
const getMockLoginResult = async (signingInState, persistResult) => {
|
|
3095
3162
|
if (!hasPendingMockLoginResponse()) {
|
|
3096
3163
|
return undefined;
|
|
3097
3164
|
}
|
|
@@ -3108,9 +3175,9 @@ const getMockLoginResult = async signingInState => {
|
|
|
3108
3175
|
userState: 'loggedOut'
|
|
3109
3176
|
};
|
|
3110
3177
|
}
|
|
3111
|
-
return
|
|
3178
|
+
return persistResult(getLoggedInState(signingInState, response));
|
|
3112
3179
|
};
|
|
3113
|
-
const getInteractiveLoginResult = async (backendUrl, platform, signingInState) => {
|
|
3180
|
+
const getInteractiveLoginResult = async (backendUrl, platform, signingInState, persistResult, selectAccount) => {
|
|
3114
3181
|
const authUseRedirect = getAuthUseRedirect(platform);
|
|
3115
3182
|
const uid = 0;
|
|
3116
3183
|
const {
|
|
@@ -3121,14 +3188,16 @@ const getInteractiveLoginResult = async (backendUrl, platform, signingInState) =
|
|
|
3121
3188
|
state
|
|
3122
3189
|
} = await getBackendLoginRequest(backendUrl, platform, uid);
|
|
3123
3190
|
if (platform !== Electron) {
|
|
3191
|
+
const returnUrl = await getCurrentHref();
|
|
3124
3192
|
await savePendingOidcAuthState({
|
|
3125
3193
|
clientId,
|
|
3126
3194
|
codeVerifier,
|
|
3127
3195
|
redirectUri,
|
|
3196
|
+
returnUrl,
|
|
3128
3197
|
state
|
|
3129
3198
|
});
|
|
3130
3199
|
}
|
|
3131
|
-
await invoke$1('Open.openUrl', loginUrl, platform, authUseRedirect);
|
|
3200
|
+
await invoke$1('Open.openUrl', selectAccount ? getAccountLoginUrl(backendUrl, loginUrl) : loginUrl, platform, authUseRedirect);
|
|
3132
3201
|
if (platform !== Electron && authUseRedirect) {
|
|
3133
3202
|
return signingInState;
|
|
3134
3203
|
}
|
|
@@ -3136,12 +3205,12 @@ const getInteractiveLoginResult = async (backendUrl, platform, signingInState) =
|
|
|
3136
3205
|
if (platform !== Electron) {
|
|
3137
3206
|
await clearPendingOidcAuthState();
|
|
3138
3207
|
}
|
|
3139
|
-
return
|
|
3208
|
+
return persistResult({
|
|
3140
3209
|
...authState,
|
|
3141
3210
|
authClientId: clientId
|
|
3142
3211
|
});
|
|
3143
3212
|
};
|
|
3144
|
-
const handleClickLogin = async options => {
|
|
3213
|
+
const handleClickLogin = async (options, persistResult = persistLoginResult) => {
|
|
3145
3214
|
const {
|
|
3146
3215
|
backendUrl,
|
|
3147
3216
|
platform
|
|
@@ -3158,11 +3227,11 @@ const handleClickLogin = async options => {
|
|
|
3158
3227
|
userState: 'loggingIn'
|
|
3159
3228
|
};
|
|
3160
3229
|
try {
|
|
3161
|
-
const mockLoginResult = await getMockLoginResult(signingInState);
|
|
3230
|
+
const mockLoginResult = await getMockLoginResult(signingInState, persistResult);
|
|
3162
3231
|
if (mockLoginResult) {
|
|
3163
3232
|
return mockLoginResult;
|
|
3164
3233
|
}
|
|
3165
|
-
return getInteractiveLoginResult(backendUrl, platform, signingInState);
|
|
3234
|
+
return getInteractiveLoginResult(backendUrl, platform, signingInState, persistResult, options.selectAccount === true);
|
|
3166
3235
|
} catch (error) {
|
|
3167
3236
|
await clearPendingOidcAuthState();
|
|
3168
3237
|
const errorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
|
|
@@ -3173,17 +3242,252 @@ const handleClickLogin = async options => {
|
|
|
3173
3242
|
}
|
|
3174
3243
|
};
|
|
3175
3244
|
|
|
3176
|
-
const
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3245
|
+
const storageKey = 'accountSessions';
|
|
3246
|
+
const accounts = createAccountSessions({
|
|
3247
|
+
read: () => getPersistentAuthValue(storageKey),
|
|
3248
|
+
write: value => setPersistentAuthValue(storageKey, value)
|
|
3249
|
+
});
|
|
3250
|
+
const refreshes = new Map();
|
|
3251
|
+
const state = {
|
|
3252
|
+
login: undefined,
|
|
3253
|
+
migration: undefined
|
|
3254
|
+
};
|
|
3255
|
+
const normalizeBackendUrl = backendUrl => new URL(backendUrl).origin;
|
|
3256
|
+
const addSession = async (backendUrl, session, revision) => {
|
|
3257
|
+
if (session.userState !== 'loggedIn' || !session.authAccessToken) {
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
const profile = await getAccountProfile(backendUrl, session.authAccessToken);
|
|
3261
|
+
await accounts.add({
|
|
3262
|
+
backendUrl,
|
|
3263
|
+
id: `${backendUrl}/${profile.id}`,
|
|
3264
|
+
profile,
|
|
3265
|
+
session: {
|
|
3266
|
+
...session,
|
|
3267
|
+
userName: profile.displayName
|
|
3268
|
+
}
|
|
3269
|
+
}, revision);
|
|
3270
|
+
};
|
|
3271
|
+
const migrate = async backendUrl => {
|
|
3272
|
+
if (await getPersistentAuthValue(storageKey)) {
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
const legacy = await getPersistedAuthSession();
|
|
3276
|
+
if (legacy) {
|
|
3277
|
+
if (legacy.authClientId && legacy.authRefreshToken) {
|
|
3278
|
+
const tokens = await refreshOidcTokens(backendUrl, legacy.authClientId, legacy.authRefreshToken);
|
|
3279
|
+
const authAccessTokenExpiresAt = getAccessTokenExpiresAt(tokens.expiresIn);
|
|
3280
|
+
const {
|
|
3281
|
+
authAccessTokenExpiresAt: _legacyExpiry,
|
|
3282
|
+
...legacySession
|
|
3283
|
+
} = legacy;
|
|
3284
|
+
await addSession(backendUrl, {
|
|
3285
|
+
...legacySession,
|
|
3286
|
+
authAccessToken: tokens.accessToken,
|
|
3287
|
+
...(authAccessTokenExpiresAt && {
|
|
3288
|
+
authAccessTokenExpiresAt
|
|
3289
|
+
}),
|
|
3290
|
+
authRefreshToken: tokens.refreshToken
|
|
3291
|
+
}, 0);
|
|
3292
|
+
} else {
|
|
3293
|
+
await addSession(backendUrl, legacy, 0);
|
|
3294
|
+
}
|
|
3295
|
+
} else {
|
|
3296
|
+
await setPersistentAuthValue(storageKey, JSON.stringify({
|
|
3297
|
+
accounts: [],
|
|
3298
|
+
activeId: '',
|
|
3299
|
+
revision: 0
|
|
3300
|
+
}));
|
|
3301
|
+
}
|
|
3302
|
+
// Clear legacy credentials only after the registry is durably committed.
|
|
3303
|
+
await clearPersistedAuthSession();
|
|
3304
|
+
};
|
|
3305
|
+
const ensureMigrated = async (backendUrl = getAuthBackendUrl()) => {
|
|
3306
|
+
const url = normalizeBackendUrl(backendUrl);
|
|
3307
|
+
if (!state.migration) {
|
|
3308
|
+
state.migration = (async () => {
|
|
3309
|
+
try {
|
|
3310
|
+
await migrate(url);
|
|
3311
|
+
} catch (error) {
|
|
3312
|
+
state.migration = undefined;
|
|
3313
|
+
throw error;
|
|
3314
|
+
}
|
|
3315
|
+
})();
|
|
3316
|
+
}
|
|
3317
|
+
await state.migration;
|
|
3318
|
+
return url;
|
|
3319
|
+
};
|
|
3320
|
+
const getActive = async backendUrl => {
|
|
3321
|
+
const registry = await accounts.read();
|
|
3322
|
+
return registry.accounts.find(account => account.id === registry.activeId && account.backendUrl === backendUrl);
|
|
3323
|
+
};
|
|
3324
|
+
const getCurrentState = async (backendUrl, authErrorMessage = '') => {
|
|
3325
|
+
const account = await getActive(backendUrl);
|
|
3326
|
+
return account ? {
|
|
3327
|
+
...account.session,
|
|
3328
|
+
authErrorMessage
|
|
3329
|
+
} : getLoggedOutBackendAuthState(authErrorMessage);
|
|
3330
|
+
};
|
|
3331
|
+
const refresh = async account => {
|
|
3332
|
+
const {
|
|
3333
|
+
backendUrl,
|
|
3334
|
+
session
|
|
3335
|
+
} = account;
|
|
3336
|
+
if (!session.authClientId || !session.authRefreshToken) {
|
|
3337
|
+
if (session.authAccessTokenExpiresAt && session.authAccessTokenExpiresAt <= Date.now()) {
|
|
3338
|
+
await accounts.update(account, getLoggedOutBackendAuthState('Please sign in to this account again.'));
|
|
3339
|
+
}
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
const key = `${account.id}/${session.authRefreshToken}`;
|
|
3343
|
+
const pending = refreshes.get(key);
|
|
3344
|
+
if (pending) {
|
|
3345
|
+
return pending;
|
|
3346
|
+
}
|
|
3347
|
+
const operation = (async () => {
|
|
3348
|
+
const tokens = await refreshOidcTokens(backendUrl, session.authClientId, session.authRefreshToken);
|
|
3349
|
+
const authAccessTokenExpiresAt = getAccessTokenExpiresAt(tokens.expiresIn);
|
|
3350
|
+
const {
|
|
3351
|
+
authAccessTokenExpiresAt: _previousExpiry,
|
|
3352
|
+
...previousSession
|
|
3353
|
+
} = session;
|
|
3354
|
+
await accounts.update(account, {
|
|
3355
|
+
...previousSession,
|
|
3356
|
+
authAccessToken: tokens.accessToken,
|
|
3357
|
+
...(authAccessTokenExpiresAt && {
|
|
3358
|
+
authAccessTokenExpiresAt
|
|
3359
|
+
}),
|
|
3360
|
+
authRefreshToken: tokens.refreshToken
|
|
3361
|
+
});
|
|
3362
|
+
})();
|
|
3363
|
+
refreshes.set(key, operation);
|
|
3364
|
+
try {
|
|
3365
|
+
await operation;
|
|
3366
|
+
} catch (error) {
|
|
3367
|
+
// Network errors must not destroy a retained session. A rejected grant needs another login.
|
|
3368
|
+
if (error && typeof error === 'object' && 'error' in error && error.error === 'invalid_grant') {
|
|
3369
|
+
await accounts.update(account, getLoggedOutBackendAuthState('Please sign in to this account again.'));
|
|
3370
|
+
}
|
|
3371
|
+
throw error;
|
|
3372
|
+
} finally {
|
|
3373
|
+
refreshes.delete(key);
|
|
3374
|
+
}
|
|
3375
|
+
};
|
|
3376
|
+
const getAccounts = async () => {
|
|
3377
|
+
const backendUrl = await ensureMigrated();
|
|
3378
|
+
const registry = await accounts.read();
|
|
3379
|
+
return registry.accounts.filter(account => account.backendUrl === backendUrl).map(account => ({
|
|
3380
|
+
...account.profile,
|
|
3381
|
+
active: account.id === registry.activeId,
|
|
3382
|
+
color: 'blue',
|
|
3383
|
+
id: account.id,
|
|
3384
|
+
signedIn: account.session.userState === 'loggedIn'
|
|
3385
|
+
}));
|
|
3386
|
+
};
|
|
3387
|
+
const getAccessToken = async (options = {}) => {
|
|
3388
|
+
const backendUrl = await ensureMigrated();
|
|
3389
|
+
const account = await getActive(backendUrl);
|
|
3390
|
+
if (!account) {
|
|
3391
|
+
return '';
|
|
3392
|
+
}
|
|
3393
|
+
if (options.refresh === 'always' || options.refresh === 'if-needed' && !isAccessTokenValid(account.session.authAccessToken || '', String(account.session.authAccessTokenExpiresAt || ''))) {
|
|
3394
|
+
try {
|
|
3395
|
+
await refresh(account);
|
|
3396
|
+
} catch (error) {
|
|
3397
|
+
const selected = await getActive(backendUrl);
|
|
3398
|
+
if (selected?.id === account.id) {
|
|
3399
|
+
throw error;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
// A refresh belongs to the identity it started with; selection can change while it is in flight.
|
|
3404
|
+
const current = await getActive(backendUrl);
|
|
3405
|
+
if (current?.id !== account.id) {
|
|
3406
|
+
return getAccessToken(options);
|
|
3407
|
+
}
|
|
3408
|
+
return current?.session.authAccessToken || '';
|
|
3409
|
+
};
|
|
3410
|
+
const initialize = async (options = {}) => {
|
|
3411
|
+
if (typeof options.platform === 'number') {
|
|
3412
|
+
setAuthPlatform(options.platform);
|
|
3413
|
+
}
|
|
3414
|
+
const backendUrl = await ensureMigrated(options.backendUrl);
|
|
3415
|
+
const registry = await accounts.read();
|
|
3416
|
+
const completed = await completeBrowserOidcLogin(backendUrl);
|
|
3417
|
+
if (completed) {
|
|
3418
|
+
await addSession(backendUrl, completed, registry.revision);
|
|
3419
|
+
return getCurrentState(backendUrl, completed.authErrorMessage);
|
|
3420
|
+
}
|
|
3421
|
+
try {
|
|
3422
|
+
await getAccessToken({
|
|
3423
|
+
refresh: 'if-needed'
|
|
3424
|
+
});
|
|
3425
|
+
return getCurrentState(backendUrl);
|
|
3426
|
+
} catch (error) {
|
|
3427
|
+
return getCurrentState(backendUrl, error instanceof Error ? error.message : 'Unable to refresh the account.');
|
|
3428
|
+
}
|
|
3429
|
+
};
|
|
3430
|
+
const login = async options => {
|
|
3431
|
+
if (state.login) {
|
|
3432
|
+
return state.login;
|
|
3433
|
+
}
|
|
3434
|
+
const operation = (async () => {
|
|
3435
|
+
const backendUrl = await ensureMigrated(options.backendUrl);
|
|
3436
|
+
const registry = await accounts.read();
|
|
3437
|
+
const result = await handleClickLogin({
|
|
3438
|
+
...options,
|
|
3439
|
+
selectAccount: registry.accounts.length > 0
|
|
3440
|
+
}, async value => value);
|
|
3441
|
+
await addSession(backendUrl, result, registry.revision);
|
|
3442
|
+
return getCurrentState(backendUrl, result.authErrorMessage);
|
|
3443
|
+
})();
|
|
3444
|
+
state.login = operation;
|
|
3445
|
+
try {
|
|
3446
|
+
return await operation;
|
|
3447
|
+
} finally {
|
|
3448
|
+
state.login = undefined;
|
|
3449
|
+
}
|
|
3450
|
+
};
|
|
3451
|
+
const useAccount = async id => {
|
|
3452
|
+
const backendUrl = await ensureMigrated();
|
|
3453
|
+
const registry = await accounts.read();
|
|
3454
|
+
if (registry.accounts.every(account => !(account.id === id && account.backendUrl === backendUrl))) {
|
|
3455
|
+
throw new Error('Account is no longer available.');
|
|
3456
|
+
}
|
|
3457
|
+
await accounts.select(id);
|
|
3458
|
+
return getCurrentState(backendUrl);
|
|
3459
|
+
};
|
|
3460
|
+
const removeAccount = async id => {
|
|
3461
|
+
const backendUrl = await ensureMigrated();
|
|
3462
|
+
const registry = await accounts.read();
|
|
3463
|
+
if (registry.accounts.some(account => account.id === id && account.backendUrl === backendUrl)) {
|
|
3464
|
+
await accounts.remove(id);
|
|
3465
|
+
}
|
|
3466
|
+
return getCurrentState(backendUrl);
|
|
3467
|
+
};
|
|
3468
|
+
const logout = async () => {
|
|
3469
|
+
const backendUrl = await ensureMigrated();
|
|
3470
|
+
const account = await getActive(backendUrl);
|
|
3471
|
+
return account ? removeAccount(account.id) : getLoggedOutBackendAuthState();
|
|
3472
|
+
};
|
|
3473
|
+
const syncBackendAuth = async backendUrl => initialize({
|
|
3474
|
+
backendUrl
|
|
3475
|
+
});
|
|
3476
|
+
|
|
3477
|
+
const configure = options => {
|
|
3478
|
+
setAuthBackendUrl(options.backendUrl);
|
|
3479
|
+
setAuthPlatform(options.platform);
|
|
3480
|
+
};
|
|
3481
|
+
|
|
3482
|
+
const handleMessagePort = async (port, rpcId) => {
|
|
3483
|
+
const rpc = await create$4({
|
|
3484
|
+
commandMap: commandMap,
|
|
3485
|
+
isMessagePortOpen: true,
|
|
3486
|
+
messagePort: port
|
|
3487
|
+
});
|
|
3488
|
+
if (rpcId) {
|
|
3489
|
+
set$3(rpcId, rpc);
|
|
3490
|
+
}
|
|
3187
3491
|
};
|
|
3188
3492
|
|
|
3189
3493
|
const commandMap = {
|
|
@@ -3192,14 +3496,17 @@ const commandMap = {
|
|
|
3192
3496
|
'Auth.consumeNextLoginResponse': consumeNextLoginResponse,
|
|
3193
3497
|
'Auth.consumeNextRefreshResponse': consumeNextRefreshResponse,
|
|
3194
3498
|
'Auth.getAccessToken': getAccessToken,
|
|
3499
|
+
'Auth.getAccounts': getAccounts,
|
|
3195
3500
|
'Auth.hasPendingMockLoginResponse': hasPendingMockLoginResponse,
|
|
3196
3501
|
'Auth.hasPendingMockRefreshResponse': hasPendingMockRefreshResponse,
|
|
3197
3502
|
'Auth.initialize': initialize,
|
|
3198
|
-
'Auth.login':
|
|
3503
|
+
'Auth.login': login,
|
|
3199
3504
|
'Auth.logout': logout,
|
|
3505
|
+
'Auth.removeAccount': removeAccount,
|
|
3200
3506
|
'Auth.setNextLoginResponse': setNextLoginResponse,
|
|
3201
3507
|
'Auth.setNextRefreshResponse': setNextRefreshResponse,
|
|
3202
3508
|
'Auth.syncBackendAuth': syncBackendAuth,
|
|
3509
|
+
'Auth.useAccount': useAccount,
|
|
3203
3510
|
'HandleMessagePort.handleMessagePort': handleMessagePort,
|
|
3204
3511
|
initialize: (_, port) => handleMessagePort(port, RendererWorker)
|
|
3205
3512
|
};
|