@lvce-editor/auth-worker 1.17.0 → 1.18.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.
Files changed (2) hide show
  1. package/dist/authWorkerMain.js +616 -331
  2. package/package.json +1 -1
@@ -1113,164 +1113,6 @@ const logoutFromBackend = async backendUrl => {
1113
1113
  }
1114
1114
  };
1115
1115
 
1116
- const getBackendRefreshUrl = backendUrl => {
1117
- return getBackendAuthUrl(backendUrl, '/auth/refresh');
1118
- };
1119
-
1120
- const delay = async ms => {
1121
- await new Promise(resolve => setTimeout(resolve, ms));
1122
- };
1123
-
1124
- let nextLoginResponse;
1125
- let nextRefreshResponse;
1126
- const setNextLoginResponse = response => {
1127
- nextLoginResponse = response;
1128
- };
1129
- const setNextRefreshResponse = response => {
1130
- nextRefreshResponse = response;
1131
- };
1132
- const clear = () => {
1133
- nextLoginResponse = undefined;
1134
- nextRefreshResponse = undefined;
1135
- };
1136
- const hasPendingMockLoginResponse = () => {
1137
- return !!nextLoginResponse;
1138
- };
1139
- const hasPendingMockRefreshResponse = () => {
1140
- return !!nextRefreshResponse;
1141
- };
1142
- const consumeNextLoginResponse = async () => {
1143
- if (!nextLoginResponse) {
1144
- return undefined;
1145
- }
1146
- const response = nextLoginResponse;
1147
- nextLoginResponse = undefined;
1148
- if (response.delay > 0) {
1149
- await delay(response.delay);
1150
- }
1151
- if (response.type === 'error') {
1152
- throw new Error(response.message);
1153
- }
1154
- return response.response;
1155
- };
1156
- const consumeNextRefreshResponse = async () => {
1157
- if (!nextRefreshResponse) {
1158
- return undefined;
1159
- }
1160
- const response = nextRefreshResponse;
1161
- nextRefreshResponse = undefined;
1162
- if (response.delay > 0) {
1163
- await new Promise(resolve => setTimeout(resolve, response.delay));
1164
- }
1165
- if (response.type === 'error') {
1166
- throw new Error(response.message);
1167
- }
1168
- return response.response;
1169
- };
1170
-
1171
- const isObject = value => {
1172
- return !!value && typeof value === 'object';
1173
- };
1174
-
1175
- const isBackendAuthResponse = value => {
1176
- return isObject(value);
1177
- };
1178
-
1179
- const getNumber = (value, fallback = 0) => {
1180
- return typeof value === 'number' ? value : fallback;
1181
- };
1182
-
1183
- const getString = (value, fallback = '') => {
1184
- return typeof value === 'string' ? value : fallback;
1185
- };
1186
-
1187
- const getUserName = value => {
1188
- if (typeof value.userName === 'string') {
1189
- return value.userName;
1190
- }
1191
- return getString(value.user?.displayName);
1192
- };
1193
- const toBackendAuthState = value => {
1194
- return {
1195
- authAccessToken: getString(value.accessToken),
1196
- authErrorMessage: getString(value.error),
1197
- authRefreshToken: getString(value.refreshToken),
1198
- userName: getUserName(value),
1199
- userState: value.accessToken ? 'loggedIn' : 'loggedOut',
1200
- userSubscriptionPlan: getString(value.subscriptionPlan),
1201
- userSubscriptionStatus: getString(value.subscriptionStatus),
1202
- userUsedTokens: getNumber(value.usedTokens)
1203
- };
1204
- };
1205
-
1206
- const parseBackendAuthResponse = value => {
1207
- if (!isBackendAuthResponse(value)) {
1208
- return getLoggedOutBackendAuthState('Backend returned an invalid authentication response.');
1209
- }
1210
- return toBackendAuthState(value);
1211
- };
1212
-
1213
- const getPayload = async response => {
1214
- try {
1215
- return await response.json();
1216
- } catch {
1217
- return undefined;
1218
- }
1219
- };
1220
- const syncBackendAuth = async backendUrl => {
1221
- if (!backendUrl) {
1222
- return getLoggedOutBackendAuthState('Backend URL is missing.');
1223
- }
1224
- try {
1225
- if (hasPendingMockRefreshResponse()) {
1226
- const mockResponse = await consumeNextRefreshResponse();
1227
- return parseBackendAuthResponse(mockResponse);
1228
- }
1229
- const response = await fetch(getBackendRefreshUrl(backendUrl), {
1230
- credentials: 'include',
1231
- headers: {
1232
- Accept: 'application/json'
1233
- },
1234
- method: 'POST'
1235
- });
1236
- if (response.status === 401 || response.status === 403) {
1237
- return getLoggedOutBackendAuthState();
1238
- }
1239
- const payload = await getPayload(response);
1240
- if (!response.ok) {
1241
- const parsed = parseBackendAuthResponse(payload);
1242
- return getLoggedOutBackendAuthState(parsed.authErrorMessage || 'Backend authentication failed.');
1243
- }
1244
- const parsed = parseBackendAuthResponse(payload);
1245
- if (parsed.authErrorMessage) {
1246
- return getLoggedOutBackendAuthState(parsed.authErrorMessage);
1247
- }
1248
- if (!parsed.authAccessToken) {
1249
- return getLoggedOutBackendAuthState();
1250
- }
1251
- return parsed;
1252
- } catch (error) {
1253
- const authErrorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
1254
- return getLoggedOutBackendAuthState(authErrorMessage);
1255
- }
1256
- };
1257
-
1258
- const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalMs = 1000) => {
1259
- const deadline = Date.now() + timeoutMs;
1260
- let lastErrorMessage = '';
1261
- while (Date.now() < deadline) {
1262
- const authState = await syncBackendAuth(backendUrl);
1263
- if (authState.userState === 'loggedIn') {
1264
- return authState;
1265
- }
1266
- if (authState.authErrorMessage) {
1267
- lastErrorMessage = authState.authErrorMessage;
1268
- }
1269
- await delay(pollIntervalMs);
1270
- }
1271
- return getLoggedOutBackendAuthState(lastErrorMessage);
1272
- };
1273
-
1274
1116
  let USER_AGENT;
1275
1117
  if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
1276
1118
  const NAME = 'oauth4webapi';
@@ -2064,14 +1906,34 @@ async function getResponseJsonBody(response, check = assertApplicationJson) {
2064
1906
  }
2065
1907
  const _expectedIssuer = Symbol();
2066
1908
 
2067
- // cspell:ignore pkce
1909
+ const getBackendOidcTokenUrl = backendUrl => {
1910
+ return getBackendAuthUrl(backendUrl, '/oidc/token');
1911
+ };
2068
1912
 
2069
- const createPkceValues = async () => {
2070
- const codeVerifier = generateRandomCodeVerifier();
2071
- const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
1913
+ const getAuthorizationServer$1 = backendUrl => {
2072
1914
  return {
2073
- codeChallenge,
2074
- codeVerifier
1915
+ issuer: getBackendAuthUrl(backendUrl, '/oidc'),
1916
+ jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
1917
+ token_endpoint: getBackendOidcTokenUrl(backendUrl)
1918
+ };
1919
+ };
1920
+ const getClient$1 = clientId => {
1921
+ return {
1922
+ client_id: clientId
1923
+ };
1924
+ };
1925
+ const exchangeAuthorizationCode = async (backendUrl, clientId, code, redirectUri, codeVerifier, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
1926
+ const authorizationServer = getAuthorizationServer$1(backendUrl);
1927
+ const client = getClient$1(clientId);
1928
+ const response = await requestTokenEndpoint(authorizationServer, client, None(), 'authorization_code', new URLSearchParams({
1929
+ code,
1930
+ code_verifier: codeVerifier,
1931
+ redirect_uri: redirectUri
1932
+ }));
1933
+ const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
1934
+ return {
1935
+ accessToken: tokenResponse.access_token,
1936
+ refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : ''
2075
1937
  };
2076
1938
  };
2077
1939
 
@@ -2087,55 +1949,517 @@ const getCurrentHref = async () => {
2087
1949
  return globalThis.location.href;
2088
1950
  };
2089
1951
 
2090
- const successHtml = `<!doctype html>
2091
- <html lang="en">
2092
- <head>
2093
- <meta charset="utf-8">
2094
- <meta name="viewport" content="width=device-width, initial-scale=1">
2095
- <title>Authentication Complete</title>
2096
- <style>
2097
- :root {
2098
- color-scheme: light;
2099
- --background: linear-gradient(180deg, #f4f7fb 0%, #e9eef8 100%);
2100
- --panel: rgba(255, 255, 255, 0.92);
2101
- --panel-border: rgba(33, 52, 88, 0.08);
2102
- --text: #132238;
2103
- --muted: #5f6f86;
2104
- --accent: #1f7a5a;
2105
- --accent-soft: #e7f6ef;
2106
- --shadow: 0 24px 60px rgba(44, 65, 98, 0.16);
2107
- }
2108
-
2109
- * {
2110
- box-sizing: border-box;
2111
- }
2112
-
2113
- html,
2114
- body {
2115
- margin: 0;
2116
- min-height: 100%;
2117
- font-family: Inter, sans-serif;
2118
- background: var(--background);
2119
- color: var(--text);
2120
- }
1952
+ const getPayload$1 = async response => {
1953
+ try {
1954
+ return await response.json();
1955
+ } catch {
1956
+ return undefined;
1957
+ }
1958
+ };
1959
+ const getOidcUserName = async (backendUrl, accessToken, fetchFn = fetch) => {
1960
+ if (!backendUrl || !accessToken) {
1961
+ return '';
1962
+ }
1963
+ const response = await fetchFn(new URL('/account/me', backendUrl), {
1964
+ headers: {
1965
+ Accept: 'application/json',
1966
+ Authorization: `Bearer ${accessToken}`
1967
+ }
1968
+ });
1969
+ if (!response.ok) {
1970
+ return '';
1971
+ }
1972
+ const payload = await getPayload$1(response);
1973
+ if (!payload || typeof payload !== 'object') {
1974
+ return '';
1975
+ }
1976
+ const displayName = Reflect.get(payload, 'displayName');
1977
+ return typeof displayName === 'string' ? displayName : '';
1978
+ };
2121
1979
 
2122
- body {
2123
- display: flex;
2124
- align-items: center;
2125
- justify-content: center;
2126
- padding: 24px;
2127
- }
1980
+ const databaseName = 'auth-worker';
1981
+ const objectStoreName = 'auth';
1982
+ const memoryStorage = new Map();
1983
+ let databasePromise;
2128
1984
 
2129
- .card {
2130
- width: min(100%, 460px);
2131
- padding: 32px 28px;
2132
- border: 1px solid var(--panel-border);
2133
- border-radius: 20px;
2134
- background: var(--panel);
2135
- box-shadow: var(--shadow);
2136
- text-align: center;
2137
- backdrop-filter: blur(12px);
2138
- }
1985
+ // IndexedDB request objects are mutable browser primitives and cannot satisfy the readonly rule structurally.
1986
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
1987
+ const requestToPromise = request => {
1988
+ return new Promise((resolve, reject) => {
1989
+ request.addEventListener('success', () => {
1990
+ resolve(request.result);
1991
+ });
1992
+ request.addEventListener('error', () => {
1993
+ reject(request.error ?? new Error('Persistent storage request failed.'));
1994
+ });
1995
+ });
1996
+ };
1997
+
1998
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
1999
+ const transactionToPromise = transaction => {
2000
+ return new Promise((resolve, reject) => {
2001
+ transaction.addEventListener('complete', () => {
2002
+ resolve();
2003
+ });
2004
+ transaction.addEventListener('abort', () => {
2005
+ reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
2006
+ });
2007
+ transaction.addEventListener('error', () => {
2008
+ reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
2009
+ });
2010
+ });
2011
+ };
2012
+ const getDatabase = async () => {
2013
+ if (typeof indexedDB === 'undefined') {
2014
+ return undefined;
2015
+ }
2016
+ if (!databasePromise) {
2017
+ databasePromise = new Promise((resolve, reject) => {
2018
+ const request = indexedDB.open(databaseName, 1);
2019
+ request.addEventListener('upgradeneeded', () => {
2020
+ const database = request.result;
2021
+ if (!database.objectStoreNames.contains(objectStoreName)) {
2022
+ database.createObjectStore(objectStoreName);
2023
+ }
2024
+ });
2025
+ request.addEventListener('success', () => {
2026
+ resolve(request.result);
2027
+ });
2028
+ request.addEventListener('error', () => {
2029
+ reject(request.error ?? new Error('Failed to open persistent auth storage.'));
2030
+ });
2031
+ });
2032
+ }
2033
+ return databasePromise;
2034
+ };
2035
+ const getPersistentAuthValue = async key => {
2036
+ const database = await getDatabase();
2037
+ if (!database) {
2038
+ return memoryStorage.get(key) ?? '';
2039
+ }
2040
+ const transaction = database.transaction(objectStoreName, 'readonly');
2041
+ const objectStore = transaction.objectStore(objectStoreName);
2042
+ const value = await requestToPromise(objectStore.get(key));
2043
+ return typeof value === 'string' ? value : '';
2044
+ };
2045
+ const setPersistentAuthValue = async (key, value) => {
2046
+ memoryStorage.set(key, value);
2047
+ const database = await getDatabase();
2048
+ if (!database) {
2049
+ return;
2050
+ }
2051
+ const transaction = database.transaction(objectStoreName, 'readwrite');
2052
+ const objectStore = transaction.objectStore(objectStoreName);
2053
+ objectStore.put(value, key);
2054
+ await transactionToPromise(transaction);
2055
+ };
2056
+ const clearPersistentAuthValue = async key => {
2057
+ memoryStorage.delete(key);
2058
+ const database = await getDatabase();
2059
+ if (!database) {
2060
+ return;
2061
+ }
2062
+ const transaction = database.transaction(objectStoreName, 'readwrite');
2063
+ const objectStore = transaction.objectStore(objectStoreName);
2064
+ objectStore.delete(key);
2065
+ await transactionToPromise(transaction);
2066
+ };
2067
+
2068
+ const callbackUrlKey = 'oidcCallbackUrl';
2069
+ const clientIdKey = 'oidcClientId';
2070
+ const pendingClientIdKey = 'pendingOidcClientId';
2071
+ const pendingCodeVerifierKey = 'pendingOidcCodeVerifier';
2072
+ const pendingRedirectUriKey = 'pendingOidcRedirectUri';
2073
+ const pendingStateKey = 'pendingOidcState';
2074
+ const clearOidcCallbackUrl = async () => {
2075
+ await clearPersistentAuthValue(callbackUrlKey);
2076
+ };
2077
+ const clearStoredOidcClientId = async () => {
2078
+ await clearPersistentAuthValue(clientIdKey);
2079
+ };
2080
+ const clearPendingOidcAuthState = async () => {
2081
+ await Promise.all([clearPersistentAuthValue(pendingClientIdKey), clearPersistentAuthValue(pendingCodeVerifierKey), clearPersistentAuthValue(pendingRedirectUriKey), clearPersistentAuthValue(pendingStateKey)]);
2082
+ };
2083
+ const getOidcCallbackUrl = async () => {
2084
+ return getPersistentAuthValue(callbackUrlKey);
2085
+ };
2086
+ const getStoredOidcClientId = async () => {
2087
+ return getPersistentAuthValue(clientIdKey);
2088
+ };
2089
+ const loadPendingOidcAuthState = async () => {
2090
+ const [clientId, codeVerifier, redirectUri, state] = await Promise.all([getPersistentAuthValue(pendingClientIdKey), getPersistentAuthValue(pendingCodeVerifierKey), getPersistentAuthValue(pendingRedirectUriKey), getPersistentAuthValue(pendingStateKey)]);
2091
+ if (!clientId || !codeVerifier || !redirectUri || !state) {
2092
+ return undefined;
2093
+ }
2094
+ return {
2095
+ clientId,
2096
+ codeVerifier,
2097
+ redirectUri,
2098
+ state
2099
+ };
2100
+ };
2101
+ const saveOidcClientId = async clientId => {
2102
+ await setPersistentAuthValue(clientIdKey, clientId);
2103
+ };
2104
+ const savePendingOidcAuthState = async value => {
2105
+ await Promise.all([setPersistentAuthValue(pendingClientIdKey, value.clientId), setPersistentAuthValue(pendingCodeVerifierKey, value.codeVerifier), setPersistentAuthValue(pendingRedirectUriKey, value.redirectUri), setPersistentAuthValue(pendingStateKey, value.state)]);
2106
+ };
2107
+
2108
+ const normalizeRedirectUri = value => {
2109
+ const url = new URL(value);
2110
+ url.hash = '';
2111
+ url.search = '';
2112
+ return url.toString();
2113
+ };
2114
+ const getCallbackHref = async () => {
2115
+ const storedCallbackUrl = await getOidcCallbackUrl();
2116
+ if (storedCallbackUrl) {
2117
+ await clearOidcCallbackUrl();
2118
+ return storedCallbackUrl;
2119
+ }
2120
+ return getCurrentHref();
2121
+ };
2122
+ const completeBrowserOidcLogin = async backendUrl => {
2123
+ const href = await getCallbackHref();
2124
+ if (!href) {
2125
+ return undefined;
2126
+ }
2127
+ let url;
2128
+ try {
2129
+ url = new URL(href);
2130
+ } catch {
2131
+ return undefined;
2132
+ }
2133
+ const code = url.searchParams.get('code') || '';
2134
+ const error = url.searchParams.get('error') || '';
2135
+ const errorDescription = url.searchParams.get('error_description') || '';
2136
+ if (!code && !error) {
2137
+ return undefined;
2138
+ }
2139
+ const pendingAuthState = await loadPendingOidcAuthState();
2140
+ if (!pendingAuthState) {
2141
+ await clearPendingOidcAuthState();
2142
+ return getLoggedOutBackendAuthState('Authentication state is missing.');
2143
+ }
2144
+ if (normalizeRedirectUri(href) !== normalizeRedirectUri(pendingAuthState.redirectUri)) {
2145
+ await clearPendingOidcAuthState();
2146
+ return getLoggedOutBackendAuthState('Authentication returned to an unexpected redirect URI.');
2147
+ }
2148
+ if (error) {
2149
+ await clearPendingOidcAuthState();
2150
+ return getLoggedOutBackendAuthState(errorDescription || error);
2151
+ }
2152
+ const returnedState = url.searchParams.get('state') || '';
2153
+ if (!returnedState || returnedState !== pendingAuthState.state) {
2154
+ await clearPendingOidcAuthState();
2155
+ return getLoggedOutBackendAuthState('Authentication state mismatch.');
2156
+ }
2157
+ const exchanged = await exchangeAuthorizationCode(backendUrl, pendingAuthState.clientId, code, pendingAuthState.redirectUri, pendingAuthState.codeVerifier);
2158
+ const userName = await getOidcUserName(backendUrl, exchanged.accessToken);
2159
+ await clearPendingOidcAuthState();
2160
+ return {
2161
+ authAccessToken: exchanged.accessToken,
2162
+ authClientId: pendingAuthState.clientId,
2163
+ authErrorMessage: '',
2164
+ authRefreshToken: exchanged.refreshToken,
2165
+ userName,
2166
+ userState: exchanged.accessToken ? 'loggedIn' : 'loggedOut'
2167
+ };
2168
+ };
2169
+
2170
+ const getBackendRefreshUrl = backendUrl => {
2171
+ return getBackendAuthUrl(backendUrl, '/auth/refresh');
2172
+ };
2173
+
2174
+ const delay = async ms => {
2175
+ await new Promise(resolve => setTimeout(resolve, ms));
2176
+ };
2177
+
2178
+ let nextLoginResponse;
2179
+ let nextRefreshResponse;
2180
+ const setNextLoginResponse = response => {
2181
+ nextLoginResponse = response;
2182
+ };
2183
+ const setNextRefreshResponse = response => {
2184
+ nextRefreshResponse = response;
2185
+ };
2186
+ const clear = () => {
2187
+ nextLoginResponse = undefined;
2188
+ nextRefreshResponse = undefined;
2189
+ };
2190
+ const hasPendingMockLoginResponse = () => {
2191
+ return !!nextLoginResponse;
2192
+ };
2193
+ const hasPendingMockRefreshResponse = () => {
2194
+ return !!nextRefreshResponse;
2195
+ };
2196
+ const consumeNextLoginResponse = async () => {
2197
+ if (!nextLoginResponse) {
2198
+ return undefined;
2199
+ }
2200
+ const response = nextLoginResponse;
2201
+ nextLoginResponse = undefined;
2202
+ if (response.delay > 0) {
2203
+ await delay(response.delay);
2204
+ }
2205
+ if (response.type === 'error') {
2206
+ throw new Error(response.message);
2207
+ }
2208
+ return response.response;
2209
+ };
2210
+ const consumeNextRefreshResponse = async () => {
2211
+ if (!nextRefreshResponse) {
2212
+ return undefined;
2213
+ }
2214
+ const response = nextRefreshResponse;
2215
+ nextRefreshResponse = undefined;
2216
+ if (response.delay > 0) {
2217
+ await new Promise(resolve => setTimeout(resolve, response.delay));
2218
+ }
2219
+ if (response.type === 'error') {
2220
+ throw new Error(response.message);
2221
+ }
2222
+ return response.response;
2223
+ };
2224
+
2225
+ const isObject = value => {
2226
+ return !!value && typeof value === 'object';
2227
+ };
2228
+
2229
+ const isBackendAuthResponse = value => {
2230
+ return isObject(value);
2231
+ };
2232
+
2233
+ const getNumber = (value, fallback = 0) => {
2234
+ return typeof value === 'number' ? value : fallback;
2235
+ };
2236
+
2237
+ const getString = (value, fallback = '') => {
2238
+ return typeof value === 'string' ? value : fallback;
2239
+ };
2240
+
2241
+ const getUserName = value => {
2242
+ if (typeof value.userName === 'string') {
2243
+ return value.userName;
2244
+ }
2245
+ return getString(value.user?.displayName);
2246
+ };
2247
+ const toBackendAuthState = value => {
2248
+ return {
2249
+ authAccessToken: getString(value.accessToken),
2250
+ authErrorMessage: getString(value.error),
2251
+ authRefreshToken: getString(value.refreshToken),
2252
+ userName: getUserName(value),
2253
+ userState: value.accessToken ? 'loggedIn' : 'loggedOut',
2254
+ userSubscriptionPlan: getString(value.subscriptionPlan),
2255
+ userSubscriptionStatus: getString(value.subscriptionStatus),
2256
+ userUsedTokens: getNumber(value.usedTokens)
2257
+ };
2258
+ };
2259
+
2260
+ const parseBackendAuthResponse = value => {
2261
+ if (!isBackendAuthResponse(value)) {
2262
+ return getLoggedOutBackendAuthState('Backend returned an invalid authentication response.');
2263
+ }
2264
+ return toBackendAuthState(value);
2265
+ };
2266
+
2267
+ const persistLoginResult = async loginResult => {
2268
+ if (loginResult.userState !== 'loggedIn') {
2269
+ return loginResult;
2270
+ }
2271
+ await Promise.all([setPersistentAuthValue('accessToken', loginResult.authAccessToken ?? ''), setPersistentAuthValue('refreshToken', loginResult.authRefreshToken ?? ''), loginResult.authClientId ? saveOidcClientId(loginResult.authClientId) : Promise.resolve()]);
2272
+ return loginResult;
2273
+ };
2274
+
2275
+ const getAuthorizationServer = backendUrl => {
2276
+ return {
2277
+ issuer: getBackendAuthUrl(backendUrl, '/oidc'),
2278
+ jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
2279
+ token_endpoint: getBackendOidcTokenUrl(backendUrl)
2280
+ };
2281
+ };
2282
+ const getClient = clientId => {
2283
+ return {
2284
+ client_id: clientId
2285
+ };
2286
+ };
2287
+ const refreshOidcTokens = async (backendUrl, clientId, refreshToken, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
2288
+ const authorizationServer = getAuthorizationServer(backendUrl);
2289
+ const client = getClient(clientId);
2290
+ const response = await requestTokenEndpoint(authorizationServer, client, None(), 'refresh_token', new URLSearchParams({
2291
+ refresh_token: refreshToken
2292
+ }));
2293
+ const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
2294
+ return {
2295
+ accessToken: tokenResponse.access_token,
2296
+ refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : refreshToken
2297
+ };
2298
+ };
2299
+
2300
+ const clearStoredOidcAuth = async () => {
2301
+ await Promise.all([clearPersistentAuthValue('accessToken'), clearPersistentAuthValue('refreshToken'), clearStoredOidcClientId()]);
2302
+ };
2303
+ const toLoginResult = (accessToken, refreshToken, clientId, userName) => {
2304
+ return {
2305
+ authAccessToken: accessToken,
2306
+ authClientId: clientId,
2307
+ authErrorMessage: '',
2308
+ authRefreshToken: refreshToken,
2309
+ userName,
2310
+ userState: accessToken ? 'loggedIn' : 'loggedOut'
2311
+ };
2312
+ };
2313
+ const restoreOidcAuth = async backendUrl => {
2314
+ const [accessToken, refreshToken, clientId] = await Promise.all([getPersistentAuthValue('accessToken'), getPersistentAuthValue('refreshToken'), getStoredOidcClientId()]);
2315
+ if (!refreshToken || !clientId) {
2316
+ return undefined;
2317
+ }
2318
+ try {
2319
+ const refreshedTokens = await refreshOidcTokens(backendUrl, clientId, refreshToken);
2320
+ const userName = await getOidcUserName(backendUrl, refreshedTokens.accessToken);
2321
+ return toLoginResult(refreshedTokens.accessToken, refreshedTokens.refreshToken, clientId, userName);
2322
+ } catch {
2323
+ if (accessToken) {
2324
+ const userName = await getOidcUserName(backendUrl, accessToken);
2325
+ if (userName) {
2326
+ return toLoginResult(accessToken, refreshToken, clientId, userName);
2327
+ }
2328
+ }
2329
+ await clearStoredOidcAuth();
2330
+ return undefined;
2331
+ }
2332
+ };
2333
+
2334
+ const getPayload = async response => {
2335
+ try {
2336
+ return await response.json();
2337
+ } catch {
2338
+ return undefined;
2339
+ }
2340
+ };
2341
+ const syncBackendAuth = async backendUrl => {
2342
+ if (!backendUrl) {
2343
+ return getLoggedOutBackendAuthState('Backend URL is missing.');
2344
+ }
2345
+ try {
2346
+ const completedBrowserLogin = await completeBrowserOidcLogin(backendUrl);
2347
+ if (completedBrowserLogin) {
2348
+ return persistLoginResult(completedBrowserLogin);
2349
+ }
2350
+ const restoredOidcAuth = await restoreOidcAuth(backendUrl);
2351
+ if (restoredOidcAuth) {
2352
+ return persistLoginResult(restoredOidcAuth);
2353
+ }
2354
+ if (hasPendingMockRefreshResponse()) {
2355
+ const mockResponse = await consumeNextRefreshResponse();
2356
+ return parseBackendAuthResponse(mockResponse);
2357
+ }
2358
+ const response = await fetch(getBackendRefreshUrl(backendUrl), {
2359
+ credentials: 'include',
2360
+ headers: {
2361
+ Accept: 'application/json'
2362
+ },
2363
+ method: 'POST'
2364
+ });
2365
+ if (response.status === 401 || response.status === 403) {
2366
+ return getLoggedOutBackendAuthState();
2367
+ }
2368
+ const payload = await getPayload(response);
2369
+ if (!response.ok) {
2370
+ const parsed = parseBackendAuthResponse(payload);
2371
+ return getLoggedOutBackendAuthState(parsed.authErrorMessage || 'Backend authentication failed.');
2372
+ }
2373
+ const parsed = parseBackendAuthResponse(payload);
2374
+ if (parsed.authErrorMessage) {
2375
+ return getLoggedOutBackendAuthState(parsed.authErrorMessage);
2376
+ }
2377
+ if (!parsed.authAccessToken) {
2378
+ return getLoggedOutBackendAuthState();
2379
+ }
2380
+ return parsed;
2381
+ } catch (error) {
2382
+ const authErrorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
2383
+ return getLoggedOutBackendAuthState(authErrorMessage);
2384
+ }
2385
+ };
2386
+
2387
+ const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalMs = 1000) => {
2388
+ const deadline = Date.now() + timeoutMs;
2389
+ let lastErrorMessage = '';
2390
+ while (Date.now() < deadline) {
2391
+ const authState = await syncBackendAuth(backendUrl);
2392
+ if (authState.userState === 'loggedIn') {
2393
+ return authState;
2394
+ }
2395
+ if (authState.authErrorMessage) {
2396
+ lastErrorMessage = authState.authErrorMessage;
2397
+ }
2398
+ await delay(pollIntervalMs);
2399
+ }
2400
+ return getLoggedOutBackendAuthState(lastErrorMessage);
2401
+ };
2402
+
2403
+ // cspell:ignore pkce
2404
+
2405
+ const createPkceValues = async () => {
2406
+ const codeVerifier = generateRandomCodeVerifier();
2407
+ const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
2408
+ return {
2409
+ codeChallenge,
2410
+ codeVerifier
2411
+ };
2412
+ };
2413
+
2414
+ const successHtml = `<!doctype html>
2415
+ <html lang="en">
2416
+ <head>
2417
+ <meta charset="utf-8">
2418
+ <meta name="viewport" content="width=device-width, initial-scale=1">
2419
+ <title>Authentication Complete</title>
2420
+ <style>
2421
+ :root {
2422
+ color-scheme: light;
2423
+ --background: linear-gradient(180deg, #f4f7fb 0%, #e9eef8 100%);
2424
+ --panel: rgba(255, 255, 255, 0.92);
2425
+ --panel-border: rgba(33, 52, 88, 0.08);
2426
+ --text: #132238;
2427
+ --muted: #5f6f86;
2428
+ --accent: #1f7a5a;
2429
+ --accent-soft: #e7f6ef;
2430
+ --shadow: 0 24px 60px rgba(44, 65, 98, 0.16);
2431
+ }
2432
+
2433
+ * {
2434
+ box-sizing: border-box;
2435
+ }
2436
+
2437
+ html,
2438
+ body {
2439
+ margin: 0;
2440
+ min-height: 100%;
2441
+ font-family: Inter, sans-serif;
2442
+ background: var(--background);
2443
+ color: var(--text);
2444
+ }
2445
+
2446
+ body {
2447
+ display: flex;
2448
+ align-items: center;
2449
+ justify-content: center;
2450
+ padding: 24px;
2451
+ }
2452
+
2453
+ .card {
2454
+ width: min(100%, 460px);
2455
+ padding: 32px 28px;
2456
+ border: 1px solid var(--panel-border);
2457
+ border-radius: 20px;
2458
+ background: var(--panel);
2459
+ box-shadow: var(--shadow);
2460
+ text-align: center;
2461
+ backdrop-filter: blur(12px);
2462
+ }
2139
2463
 
2140
2464
  .badge {
2141
2465
  display: inline-flex;
@@ -2338,6 +2662,18 @@ const getElectronRedirectUri = async uid => {
2338
2662
  return `http://localhost:${localOauthServerPort}/callback`;
2339
2663
  };
2340
2664
 
2665
+ const getWebRedirectUri = async () => {
2666
+ const href = await getCurrentHref();
2667
+ if (!href) {
2668
+ return '';
2669
+ }
2670
+ try {
2671
+ const url = new URL(href);
2672
+ return `${url.origin}/auth/callback`;
2673
+ } catch {
2674
+ return '';
2675
+ }
2676
+ };
2341
2677
  const getEffectiveRedirectUri = async (platform, uid, redirectUri) => {
2342
2678
  if (redirectUri) {
2343
2679
  return redirectUri;
@@ -2345,11 +2681,15 @@ const getEffectiveRedirectUri = async (platform, uid, redirectUri) => {
2345
2681
  if (platform === Electron) {
2346
2682
  return getElectronRedirectUri(uid);
2347
2683
  }
2348
- return getCurrentHref();
2684
+ return getWebRedirectUri();
2349
2685
  };
2350
2686
 
2351
- const oidcClientId = 'lvce-editor-native';
2687
+ const nativeOidcClientId = 'lvce-editor-native';
2688
+ const webOidcClientId = 'lvce-editor-web';
2352
2689
  const oidcScope = 'openid offline_access profile email';
2690
+ const getOidcClientId = platform => {
2691
+ return platform === Electron ? nativeOidcClientId : webOidcClientId;
2692
+ };
2353
2693
 
2354
2694
  const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirectUri = '', createPkceValuesFn = createPkceValues, createRandomUuid = () => globalThis.crypto.randomUUID()) => {
2355
2695
  const effectiveRedirectUri = await getEffectiveRedirectUri(platform, uid, redirectUri);
@@ -2357,24 +2697,28 @@ const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirec
2357
2697
  codeChallenge,
2358
2698
  codeVerifier
2359
2699
  } = await createPkceValuesFn();
2700
+ const clientId = getOidcClientId(platform);
2360
2701
  const nonce = createRandomUuid();
2702
+ const state = createRandomUuid();
2361
2703
  const loginUrl = new URL(getBackendAuthUrl(backendUrl, '/oidc/auth'));
2362
- loginUrl.searchParams.set('client_id', oidcClientId);
2704
+ loginUrl.searchParams.set('client_id', clientId);
2363
2705
  loginUrl.searchParams.set('code_challenge', codeChallenge);
2364
2706
  loginUrl.searchParams.set('code_challenge_method', 'S256');
2365
2707
  loginUrl.searchParams.set('nonce', nonce);
2366
2708
  loginUrl.searchParams.set('prompt', 'consent');
2367
2709
  loginUrl.searchParams.set('response_type', 'code');
2368
2710
  loginUrl.searchParams.set('scope', oidcScope);
2369
- loginUrl.searchParams.set('state', createRandomUuid());
2711
+ loginUrl.searchParams.set('state', state);
2370
2712
  if (effectiveRedirectUri) {
2371
2713
  loginUrl.searchParams.set('redirect_uri', effectiveRedirectUri);
2372
2714
  }
2373
2715
  return {
2716
+ clientId,
2374
2717
  codeVerifier,
2375
2718
  loginUrl: loginUrl.toString(),
2376
2719
  nonce,
2377
- redirectUri: effectiveRedirectUri
2720
+ redirectUri: effectiveRedirectUri,
2721
+ state
2378
2722
  };
2379
2723
  };
2380
2724
 
@@ -2398,98 +2742,8 @@ const isLoginResponse = value => {
2398
2742
  return !!value && typeof value === 'object';
2399
2743
  };
2400
2744
 
2401
- const databaseName = 'auth-worker';
2402
- const objectStoreName = 'auth';
2403
- const memoryStorage = new Map();
2404
- let databasePromise;
2405
-
2406
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
2407
- const transactionToPromise = transaction => {
2408
- return new Promise((resolve, reject) => {
2409
- transaction.addEventListener('complete', () => {
2410
- resolve();
2411
- });
2412
- transaction.addEventListener('abort', () => {
2413
- reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
2414
- });
2415
- transaction.addEventListener('error', () => {
2416
- reject(transaction.error ?? new Error('Persistent storage transaction failed.'));
2417
- });
2418
- });
2419
- };
2420
- const getDatabase = async () => {
2421
- if (typeof indexedDB === 'undefined') {
2422
- return undefined;
2423
- }
2424
- if (!databasePromise) {
2425
- databasePromise = new Promise((resolve, reject) => {
2426
- const request = indexedDB.open(databaseName, 1);
2427
- request.addEventListener('upgradeneeded', () => {
2428
- const database = request.result;
2429
- if (!database.objectStoreNames.contains(objectStoreName)) {
2430
- database.createObjectStore(objectStoreName);
2431
- }
2432
- });
2433
- request.addEventListener('success', () => {
2434
- resolve(request.result);
2435
- });
2436
- request.addEventListener('error', () => {
2437
- reject(request.error ?? new Error('Failed to open persistent auth storage.'));
2438
- });
2439
- });
2440
- }
2441
- return databasePromise;
2442
- };
2443
- const setPersistentAuthValue = async (key, value) => {
2444
- memoryStorage.set(key, value);
2445
- const database = await getDatabase();
2446
- if (!database) {
2447
- return;
2448
- }
2449
- const transaction = database.transaction(objectStoreName, 'readwrite');
2450
- const objectStore = transaction.objectStore(objectStoreName);
2451
- objectStore.put(value, key);
2452
- await transactionToPromise(transaction);
2453
- };
2454
-
2455
- const persistLoginResult = async loginResult => {
2456
- if (loginResult.userState !== 'loggedIn') {
2457
- return loginResult;
2458
- }
2459
- await setPersistentAuthValue('accessToken', loginResult.authAccessToken ?? '');
2460
- await setPersistentAuthValue('refreshToken', loginResult.authRefreshToken ?? '');
2461
- return loginResult;
2462
- };
2463
-
2464
- const getBackendOidcTokenUrl = backendUrl => {
2465
- return getBackendAuthUrl(backendUrl, '/oidc/token');
2466
- };
2467
-
2468
- const getAuthorizationServer = backendUrl => {
2469
- return {
2470
- issuer: getBackendAuthUrl(backendUrl, '/oidc'),
2471
- jwks_uri: getBackendAuthUrl(backendUrl, '/oidc/jwks'),
2472
- token_endpoint: getBackendOidcTokenUrl(backendUrl)
2473
- };
2474
- };
2475
- const getClient = () => {
2476
- return {
2477
- client_id: oidcClientId
2478
- };
2479
- };
2480
2745
  const exchangeElectronAuthorizationCode = async (backendUrl, code, redirectUri, codeVerifier, requestTokenEndpoint = genericTokenEndpointRequest, processTokenEndpointResponse = processGenericTokenEndpointResponse) => {
2481
- const authorizationServer = getAuthorizationServer(backendUrl);
2482
- const client = getClient();
2483
- const response = await requestTokenEndpoint(authorizationServer, client, None(), 'authorization_code', new URLSearchParams({
2484
- code,
2485
- code_verifier: codeVerifier,
2486
- redirect_uri: redirectUri
2487
- }));
2488
- const tokenResponse = await processTokenEndpointResponse(authorizationServer, client, response);
2489
- return {
2490
- accessToken: tokenResponse.access_token,
2491
- refreshToken: typeof tokenResponse.refresh_token === 'string' ? tokenResponse.refresh_token : ''
2492
- };
2746
+ return exchangeAuthorizationCode(backendUrl, nativeOidcClientId, code, redirectUri, codeVerifier, requestTokenEndpoint, processTokenEndpointResponse);
2493
2747
  };
2494
2748
 
2495
2749
  const hasAuthorizationCode = value => {
@@ -2516,6 +2770,55 @@ const waitForElectronBackendLogin = async (backendUrl, uid, redirectUri, codeVer
2516
2770
  return getLoggedOutBackendAuthState('Timed out waiting for backend login.');
2517
2771
  };
2518
2772
 
2773
+ const getMockLoginResult = async signingInState => {
2774
+ if (!hasPendingMockLoginResponse()) {
2775
+ return undefined;
2776
+ }
2777
+ const response = await consumeNextLoginResponse();
2778
+ if (!isLoginResponse(response)) {
2779
+ return {
2780
+ authErrorMessage: 'Backend returned an invalid login response.',
2781
+ userState: 'loggedOut'
2782
+ };
2783
+ }
2784
+ if (typeof response.error === 'string' && response.error) {
2785
+ return {
2786
+ authErrorMessage: response.error,
2787
+ userState: 'loggedOut'
2788
+ };
2789
+ }
2790
+ return persistLoginResult(getLoggedInState(signingInState, response));
2791
+ };
2792
+ const getInteractiveLoginResult = async (backendUrl, platform, authUseRedirect, signingInState) => {
2793
+ const uid = 0;
2794
+ const {
2795
+ clientId,
2796
+ codeVerifier,
2797
+ loginUrl,
2798
+ redirectUri,
2799
+ state
2800
+ } = await getBackendLoginRequest(backendUrl, platform, uid);
2801
+ if (platform !== Electron) {
2802
+ await savePendingOidcAuthState({
2803
+ clientId,
2804
+ codeVerifier,
2805
+ redirectUri,
2806
+ state
2807
+ });
2808
+ }
2809
+ await invoke$1('Open.openUrl', loginUrl, platform, authUseRedirect);
2810
+ if (platform !== Electron && authUseRedirect) {
2811
+ return signingInState;
2812
+ }
2813
+ const authState = platform === Electron ? await waitForElectronBackendLogin(backendUrl, uid, redirectUri, codeVerifier) : await waitForBackendLogin(backendUrl);
2814
+ if (platform !== Electron) {
2815
+ await clearPendingOidcAuthState();
2816
+ }
2817
+ return persistLoginResult({
2818
+ ...authState,
2819
+ authClientId: clientId
2820
+ });
2821
+ };
2519
2822
  const handleClickLogin = async options => {
2520
2823
  const {
2521
2824
  authUseRedirect,
@@ -2533,32 +2836,13 @@ const handleClickLogin = async options => {
2533
2836
  userState: 'loggingIn'
2534
2837
  };
2535
2838
  try {
2536
- if (hasPendingMockLoginResponse()) {
2537
- const response = await consumeNextLoginResponse();
2538
- if (!isLoginResponse(response)) {
2539
- return {
2540
- authErrorMessage: 'Backend returned an invalid login response.',
2541
- userState: 'loggedOut'
2542
- };
2543
- }
2544
- if (typeof response.error === 'string' && response.error) {
2545
- return {
2546
- authErrorMessage: response.error,
2547
- userState: 'loggedOut'
2548
- };
2549
- }
2550
- return persistLoginResult(getLoggedInState(signingInState, response));
2839
+ const mockLoginResult = await getMockLoginResult(signingInState);
2840
+ if (mockLoginResult) {
2841
+ return mockLoginResult;
2551
2842
  }
2552
- const uid = 0;
2553
- const {
2554
- codeVerifier,
2555
- loginUrl,
2556
- redirectUri
2557
- } = await getBackendLoginRequest(backendUrl, platform, uid);
2558
- await invoke$1('Open.openUrl', loginUrl, platform, authUseRedirect);
2559
- const authState = platform === Electron ? await waitForElectronBackendLogin(backendUrl, uid, redirectUri, codeVerifier) : await waitForBackendLogin(backendUrl);
2560
- return persistLoginResult(authState);
2843
+ return getInteractiveLoginResult(backendUrl, platform, authUseRedirect, signingInState);
2561
2844
  } catch (error) {
2845
+ await clearPendingOidcAuthState();
2562
2846
  const errorMessage = error instanceof Error && error.message ? error.message : 'Backend authentication failed.';
2563
2847
  return {
2564
2848
  ...signingInState,
@@ -2573,6 +2857,7 @@ const logout = async state => {
2573
2857
  userState: 'loggingOut'
2574
2858
  };
2575
2859
  await logoutFromBackend(state.backendUrl);
2860
+ await Promise.all([clearOidcCallbackUrl(), clearPendingOidcAuthState(), clearStoredOidcClientId(), clearPersistentAuthValue('accessToken'), clearPersistentAuthValue('refreshToken')]);
2576
2861
  return {
2577
2862
  ...loggingOutState,
2578
2863
  ...getLoggedOutBackendAuthState()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/auth-worker",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "Auth Worker",
5
5
  "repository": {
6
6
  "type": "git",