@lvce-editor/auth-worker 3.18.1 → 3.20.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/README.md +4 -0
- package/dist/authWorkerMain.js +96 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
# Auth Worker
|
|
2
2
|
|
|
3
3
|
WebWorker for the Auth functionality in Lvce Editor.
|
|
4
|
+
|
|
5
|
+
## Cloudflare account connections
|
|
6
|
+
|
|
7
|
+
`Auth.getCloudflareConnectionsUrl` returns `/cloudflare` on the configured authentication backend. `Auth.getCloudflareStatus`, `Auth.getCloudflareZones`, and `Auth.approveCloudflareMachine(id, code, zoneId)` use the current LVCE access token. Cloudflare OAuth credentials stay in backend-2. Signed-out requests fail locally. The backend needs a separately registered Cloudflare OAuth client; see [deployment setup](https://github.com/lvce-editor/backend-2/blob/main/docs/cloudflare.md).
|
package/dist/authWorkerMain.js
CHANGED
|
@@ -1253,7 +1253,8 @@ const setAuthBackendUrl = backendUrl => {
|
|
|
1253
1253
|
};
|
|
1254
1254
|
|
|
1255
1255
|
const state$3 = {
|
|
1256
|
-
platform: Web
|
|
1256
|
+
platform: Web,
|
|
1257
|
+
useNativeAuth: false
|
|
1257
1258
|
};
|
|
1258
1259
|
const getAuthPlatform = () => {
|
|
1259
1260
|
return state$3.platform;
|
|
@@ -1262,6 +1263,12 @@ const setAuthPlatform = value => {
|
|
|
1262
1263
|
state$3.platform = value;
|
|
1263
1264
|
};
|
|
1264
1265
|
|
|
1266
|
+
// Native shells can use loopback OAuth without Electron's process or secret storage.
|
|
1267
|
+
const setUseNativeAuth = value => {
|
|
1268
|
+
state$3.useNativeAuth = value;
|
|
1269
|
+
};
|
|
1270
|
+
const getUseNativeAuth = () => state$3.useNativeAuth;
|
|
1271
|
+
|
|
1265
1272
|
let USER_AGENT;
|
|
1266
1273
|
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
|
|
1267
1274
|
const NAME = 'oauth4webapi';
|
|
@@ -3048,7 +3055,7 @@ const getEffectiveRedirectUri = async (platform, uid, redirectUri) => {
|
|
|
3048
3055
|
|
|
3049
3056
|
const nativeOidcClientId = 'lvce-editor-native';
|
|
3050
3057
|
const webOidcClientId = 'lvce-editor-web';
|
|
3051
|
-
const oidcScope = 'openid offline_access profile email';
|
|
3058
|
+
const oidcScope = 'openid offline_access profile email user remote';
|
|
3052
3059
|
const getOidcClientId = platform => {
|
|
3053
3060
|
return platform === Electron ? nativeOidcClientId : webOidcClientId;
|
|
3054
3061
|
};
|
|
@@ -3118,7 +3125,16 @@ const getElectronAuthorizationCode = async uid => {
|
|
|
3118
3125
|
const waitForElectronBackendLogin = async (backendUrl, uid, redirectUri, codeVerifier, timeoutMs = 30_000, pollIntervalMs = 1000, getAuthorizationCode = getElectronAuthorizationCode, exchangeAuthorizationCode = exchangeElectronAuthorizationCode, getUserName = getOidcUserName) => {
|
|
3119
3126
|
const deadline = Date.now() + timeoutMs;
|
|
3120
3127
|
while (Date.now() < deadline) {
|
|
3121
|
-
|
|
3128
|
+
let timer;
|
|
3129
|
+
const timeout = new Promise(resolve => {
|
|
3130
|
+
timer = setTimeout(resolve, Math.max(0, deadline - Date.now()));
|
|
3131
|
+
});
|
|
3132
|
+
let authorizationCode;
|
|
3133
|
+
try {
|
|
3134
|
+
authorizationCode = await Promise.race([getAuthorizationCode(uid), timeout]);
|
|
3135
|
+
} finally {
|
|
3136
|
+
clearTimeout(timer);
|
|
3137
|
+
}
|
|
3122
3138
|
if (hasAuthorizationCode(authorizationCode)) {
|
|
3123
3139
|
const tokenResponse = await exchangeAuthorizationCode(backendUrl, authorizationCode, redirectUri, codeVerifier);
|
|
3124
3140
|
const authAccessTokenExpiresAt = getAccessTokenExpiresAt(tokenResponse.expiresIn);
|
|
@@ -3159,37 +3175,45 @@ const getMockLoginResult = async (signingInState, persistResult) => {
|
|
|
3159
3175
|
return persistResult(getLoggedInState(signingInState, response));
|
|
3160
3176
|
};
|
|
3161
3177
|
const getInteractiveLoginResult = async (backendUrl, platform, signingInState, persistResult, selectAccount) => {
|
|
3162
|
-
const
|
|
3163
|
-
const
|
|
3164
|
-
const
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
redirectUri,
|
|
3169
|
-
state
|
|
3170
|
-
} = await getBackendLoginRequest(backendUrl, platform, uid);
|
|
3171
|
-
if (platform !== Electron) {
|
|
3172
|
-
const returnUrl = await getCurrentHref();
|
|
3173
|
-
await savePendingOidcAuthState({
|
|
3178
|
+
const nativeAuth = getUseNativeAuth();
|
|
3179
|
+
const transportPlatform = nativeAuth ? Electron : platform;
|
|
3180
|
+
const authUseRedirect = getAuthUseRedirect(transportPlatform);
|
|
3181
|
+
const uid = nativeAuth ? globalThis.crypto.randomUUID() : 0;
|
|
3182
|
+
try {
|
|
3183
|
+
const {
|
|
3174
3184
|
clientId,
|
|
3175
3185
|
codeVerifier,
|
|
3186
|
+
loginUrl,
|
|
3176
3187
|
redirectUri,
|
|
3177
|
-
returnUrl,
|
|
3178
3188
|
state
|
|
3189
|
+
} = await getBackendLoginRequest(backendUrl, transportPlatform, uid);
|
|
3190
|
+
if (transportPlatform !== Electron) {
|
|
3191
|
+
const returnUrl = await getCurrentHref();
|
|
3192
|
+
await savePendingOidcAuthState({
|
|
3193
|
+
clientId,
|
|
3194
|
+
codeVerifier,
|
|
3195
|
+
redirectUri,
|
|
3196
|
+
returnUrl,
|
|
3197
|
+
state
|
|
3198
|
+
});
|
|
3199
|
+
}
|
|
3200
|
+
await invoke$1('Open.openUrl', selectAccount ? getAccountLoginUrl(backendUrl, loginUrl) : loginUrl, platform, authUseRedirect);
|
|
3201
|
+
if (transportPlatform !== Electron && authUseRedirect) {
|
|
3202
|
+
return signingInState;
|
|
3203
|
+
}
|
|
3204
|
+
const authState = transportPlatform === Electron ? await waitForElectronBackendLogin(backendUrl, uid, redirectUri, codeVerifier) : await waitForBackendLogin(backendUrl);
|
|
3205
|
+
if (transportPlatform !== Electron) {
|
|
3206
|
+
await clearPendingOidcAuthState();
|
|
3207
|
+
}
|
|
3208
|
+
return persistResult({
|
|
3209
|
+
...authState,
|
|
3210
|
+
authClientId: clientId
|
|
3179
3211
|
});
|
|
3212
|
+
} finally {
|
|
3213
|
+
if (nativeAuth) {
|
|
3214
|
+
await invoke$3('OAuthServer.dispose', String(uid));
|
|
3215
|
+
}
|
|
3180
3216
|
}
|
|
3181
|
-
await invoke$1('Open.openUrl', selectAccount ? getAccountLoginUrl(backendUrl, loginUrl) : loginUrl, platform, authUseRedirect);
|
|
3182
|
-
if (platform !== Electron && authUseRedirect) {
|
|
3183
|
-
return signingInState;
|
|
3184
|
-
}
|
|
3185
|
-
const authState = platform === Electron ? await waitForElectronBackendLogin(backendUrl, uid, redirectUri, codeVerifier) : await waitForBackendLogin(backendUrl);
|
|
3186
|
-
if (platform !== Electron) {
|
|
3187
|
-
await clearPendingOidcAuthState();
|
|
3188
|
-
}
|
|
3189
|
-
return persistResult({
|
|
3190
|
-
...authState,
|
|
3191
|
-
authClientId: clientId
|
|
3192
|
-
});
|
|
3193
3217
|
};
|
|
3194
3218
|
const handleClickLogin = async (options, persistResult = persistLoginResult) => {
|
|
3195
3219
|
const {
|
|
@@ -3212,7 +3236,7 @@ const handleClickLogin = async (options, persistResult = persistLoginResult) =>
|
|
|
3212
3236
|
if (mockLoginResult) {
|
|
3213
3237
|
return mockLoginResult;
|
|
3214
3238
|
}
|
|
3215
|
-
return getInteractiveLoginResult(backendUrl, platform, signingInState, persistResult, options.selectAccount === true);
|
|
3239
|
+
return await getInteractiveLoginResult(backendUrl, platform, signingInState, persistResult, options.selectAccount === true);
|
|
3216
3240
|
} catch (error) {
|
|
3217
3241
|
await clearPendingOidcAuthState();
|
|
3218
3242
|
const errorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
|
|
@@ -3499,6 +3523,42 @@ const syncBackendAuth = async backendUrl => initialize({
|
|
|
3499
3523
|
backendUrl
|
|
3500
3524
|
});
|
|
3501
3525
|
|
|
3526
|
+
const identifier = /^[a-f0-9]{32}$/;
|
|
3527
|
+
const confirmationCode = /^[A-F0-9]{8}$/;
|
|
3528
|
+
const getCloudflareConnectionsUrl = () => new URL('/cloudflare', getAuthBackendUrl()).href;
|
|
3529
|
+
const request = async (pathname, method = 'GET', body) => {
|
|
3530
|
+
const backendUrl = getAuthBackendUrl();
|
|
3531
|
+
const accessToken = await getAccessToken({
|
|
3532
|
+
refresh: 'if-needed'
|
|
3533
|
+
});
|
|
3534
|
+
if (backendUrl !== getAuthBackendUrl()) throw new Error('Account backend changed. Retry Cloudflare setup.');
|
|
3535
|
+
if (!accessToken) throw new Error('Sign in to LVCE to manage Cloudflare connections.');
|
|
3536
|
+
const response = await fetch(new URL(pathname, backendUrl), {
|
|
3537
|
+
headers: {
|
|
3538
|
+
Accept: 'application/json',
|
|
3539
|
+
Authorization: `Bearer ${accessToken}`,
|
|
3540
|
+
'Content-Type': 'application/json'
|
|
3541
|
+
},
|
|
3542
|
+
method,
|
|
3543
|
+
redirect: 'error',
|
|
3544
|
+
signal: AbortSignal.timeout(30_000),
|
|
3545
|
+
...(body !== undefined && {
|
|
3546
|
+
body: JSON.stringify(body)
|
|
3547
|
+
})
|
|
3548
|
+
});
|
|
3549
|
+
if (!response.ok) throw new Error(`Cloudflare connection request failed (${response.status}). Open account connections to check authorization and domains.`);
|
|
3550
|
+
return response.json();
|
|
3551
|
+
};
|
|
3552
|
+
const getCloudflareStatus = () => request('/cloudflare/status');
|
|
3553
|
+
const getCloudflareZones = () => request('/cloudflare/zones');
|
|
3554
|
+
const approveCloudflareMachine = (id, code, zoneId) => {
|
|
3555
|
+
if (!identifier.test(id) || !identifier.test(zoneId) || !confirmationCode.test(code)) throw new Error('Invalid Cloudflare setup code or domain.');
|
|
3556
|
+
return request(`/cloudflare/setup/${id}/approve`, 'POST', {
|
|
3557
|
+
code,
|
|
3558
|
+
zoneId
|
|
3559
|
+
});
|
|
3560
|
+
};
|
|
3561
|
+
|
|
3502
3562
|
const sendMessagePortToAuthProcess = async port => {
|
|
3503
3563
|
await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToSharedProcess', port, 'HandleMessagePortForAuthProcess.handleMessagePortForAuthProcess');
|
|
3504
3564
|
};
|
|
@@ -3524,8 +3584,11 @@ const initializeMainProcess = async () => {
|
|
|
3524
3584
|
const configure = async options => {
|
|
3525
3585
|
setAuthBackendUrl(options.backendUrl);
|
|
3526
3586
|
setAuthPlatform(options.platform);
|
|
3587
|
+
setUseNativeAuth(options.useNativeAuth === true);
|
|
3527
3588
|
if (options.platform === Electron) {
|
|
3528
3589
|
await Promise.all([initializeAuthProcess(), initializeMainProcess()]);
|
|
3590
|
+
} else if (options.useNativeAuth) {
|
|
3591
|
+
await initializeAuthProcess();
|
|
3529
3592
|
}
|
|
3530
3593
|
};
|
|
3531
3594
|
|
|
@@ -3541,6 +3604,7 @@ const handleMessagePort = async (port, rpcId) => {
|
|
|
3541
3604
|
};
|
|
3542
3605
|
|
|
3543
3606
|
const commandMap = {
|
|
3607
|
+
'Auth.approveCloudflareMachine': approveCloudflareMachine,
|
|
3544
3608
|
'Auth.clearMocks': clear,
|
|
3545
3609
|
'Auth.configure': configure,
|
|
3546
3610
|
'Auth.consumeNextLoginResponse': consumeNextLoginResponse,
|
|
@@ -3548,6 +3612,9 @@ const commandMap = {
|
|
|
3548
3612
|
'Auth.disconnectConnectedAccount': disconnectConnectedAccount,
|
|
3549
3613
|
'Auth.getAccessToken': getAccessToken,
|
|
3550
3614
|
'Auth.getAccounts': getAccounts,
|
|
3615
|
+
'Auth.getCloudflareConnectionsUrl': getCloudflareConnectionsUrl,
|
|
3616
|
+
'Auth.getCloudflareStatus': getCloudflareStatus,
|
|
3617
|
+
'Auth.getCloudflareZones': getCloudflareZones,
|
|
3551
3618
|
'Auth.getConnectedAccounts': getConnectedAccounts,
|
|
3552
3619
|
'Auth.hasPendingMockLoginResponse': hasPendingMockLoginResponse,
|
|
3553
3620
|
'Auth.hasPendingMockRefreshResponse': hasPendingMockRefreshResponse,
|