diff --git a/Dockerfile b/Dockerfile index fb50d79048..96289a669e 100755 --- a/Dockerfile +++ b/Dockerfile @@ -62,6 +62,7 @@ ARG VITE_READY_PLAYER_ME_URL ARG VITE_DISABLE_LOG ARG VITE_AVATURN_URL ARG VITE_AVATURN_API +ARG VITE_FEATHERS_STORE_KEY ENV MYSQL_HOST=$MYSQL_HOST ENV MYSQL_PORT=$MYSQL_PORT ENV MYSQL_USER=$MYSQL_USER @@ -85,6 +86,7 @@ ENV VITE_READY_PLAYER_ME_URL=$VITE_READY_PLAYER_ME_URL ENV VITE_DISABLE_LOG=$VITE_DISABLE_LOG ENV VITE_AVATURN_URL=$VITE_AVATURN_URL ENV VITE_AVATURN_API=$VITE_AVATURN_API +ENV VITE_FEATHERS_STORE_KEY=$VITE_FEATHERS_STORE_KEY ARG CACHE_DATE RUN npx cross-env ts-node --swc scripts/check-db-exists.ts diff --git a/packages/client-core/src/API.ts b/packages/client-core/src/API.ts index 88332748ec..84e292954e 100755 --- a/packages/client-core/src/API.ts +++ b/packages/client-core/src/API.ts @@ -61,10 +61,7 @@ export class API { }) ) - primus.on('reconnected', () => API.instance.client.reAuthenticate(true)) - - API.instance = new API() - API.instance.client = feathersClient as any + primus.on('reconnected', () => feathersClient.reAuthenticate(true)) Engine.instance.api = feathersClient } diff --git a/packages/client-core/src/social/services/LocationService.ts b/packages/client-core/src/social/services/LocationService.ts index 3ceaddae07..2e02fa675a 100755 --- a/packages/client-core/src/social/services/LocationService.ts +++ b/packages/client-core/src/social/services/LocationService.ts @@ -30,7 +30,6 @@ import { Engine } from '@etherealengine/ecs/src/Engine' import { defineState, getMutableState } from '@etherealengine/hyperflux' import { locationBanPath, SceneID, UserID } from '@etherealengine/common/src/schema.type.module' -import { API } from '../../API' import { NotificationService } from '../../common/services/NotificationService' export const LocationSeed: LocationType = { @@ -129,7 +128,7 @@ export const LocationService = { getLocation: async (locationId: LocationID) => { try { LocationState.fetchingCurrentSocialLocation() - const location = await API.instance.client.service(locationPath).get(locationId) + const location = await Engine.instance.api.service(locationPath).get(locationId) LocationState.socialLocationRetrieved(location) } catch (err) { NotificationService.dispatchNotify(err.message, { variant: 'error' }) @@ -137,7 +136,7 @@ export const LocationService = { }, getLocationByName: async (locationName: string) => { LocationState.fetchingCurrentSocialLocation() - const locationResult = (await API.instance.client.service(locationPath).find({ + const locationResult = (await Engine.instance.api.service(locationPath).find({ query: { slugifiedName: locationName } @@ -155,7 +154,7 @@ export const LocationService = { } }, getLobby: async () => { - const lobbyResult = (await API.instance.client.service(locationPath).find({ + const lobbyResult = (await Engine.instance.api.service(locationPath).find({ query: { isLobby: true, $limit: 1 @@ -170,7 +169,7 @@ export const LocationService = { }, banUserFromLocation: async (userId: UserID, locationId: LocationID) => { try { - await API.instance.client.service(locationBanPath).create({ + await Engine.instance.api.service(locationBanPath).create({ userId: userId, locationId: locationId }) diff --git a/packages/client-core/src/user/components/UserMenu/menus/LocationMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/LocationMenu.tsx index 7550119c55..2117a4013e 100755 --- a/packages/client-core/src/user/components/UserMenu/menus/LocationMenu.tsx +++ b/packages/client-core/src/user/components/UserMenu/menus/LocationMenu.tsx @@ -41,7 +41,7 @@ import TextField from '@etherealengine/ui/src/primitives/mui/TextField' import Typography from '@etherealengine/ui/src/primitives/mui/Typography' import { SceneID } from '@etherealengine/common/src/schema.type.module' -import { API } from '../../../../API' +import { Engine } from '@etherealengine/ecs/src/Engine' import { LocationSeed } from '../../../../social/services/LocationService' import styles from '../index.module.scss' @@ -69,7 +69,7 @@ const LocationMenu = (props: Props) => { }, []) const fetchLocations = (page: number, rows: number, search?: string) => { - API.instance.client + Engine.instance.api .service(locationPath) .find({ query: { diff --git a/packages/client-core/src/user/services/AuthService.ts b/packages/client-core/src/user/services/AuthService.ts index d16d0bbf22..5571a1f6c3 100755 --- a/packages/client-core/src/user/services/AuthService.ts +++ b/packages/client-core/src/user/services/AuthService.ts @@ -32,7 +32,15 @@ import config, { validateEmail, validatePhoneNumber } from '@etherealengine/comm import { AuthUserSeed, resolveAuthUser } from '@etherealengine/common/src/interfaces/AuthUser' import multiLogger from '@etherealengine/common/src/logger' import { AuthStrategiesType } from '@etherealengine/common/src/schema.type.module' -import { defineState, getMutableState, getState, syncStateWithLocalStorage } from '@etherealengine/hyperflux' +import { + defineState, + getMutableState, + getState, + stateNamespaceKey, + syncStateWithLocalStorage +} from '@etherealengine/hyperflux' + +import { Static, Type } from '@feathersjs/typebox' import { AvatarID, @@ -61,13 +69,15 @@ import { } from '@etherealengine/common/src/schema.type.module' import { Engine } from '@etherealengine/ecs/src/Engine' import { AuthenticationResult } from '@feathersjs/authentication' -import { API } from '../../API' +import { FeathersClient } from '../../API' import { NotificationService } from '../../common/services/NotificationService' import { LocationState } from '../../social/services/LocationService' export const logger = multiLogger.child({ component: 'client-core:AuthService' }) export const TIMEOUT_INTERVAL = 50 // ms per interval of waiting for authToken to be updated +const api = Engine.instance.api as FeathersClient + export const UserSeed: UserType = { id: '' as UserID, name: '' as UserName, @@ -109,6 +119,13 @@ export const UserSeed: UserType = { lastLogin: null } +export const hasAccessSchema = Type.Object({ + hasStorageAccess: Type.Boolean(), + cookieSet: Type.Boolean() +}) + +export interface HasAccessType extends Static {} + const resolveWalletUser = (credentials: any): UserType => { return { ...UserSeed, @@ -120,6 +137,114 @@ const resolveWalletUser = (credentials: any): UserType => { } } +const waitForToken = async (win, clientUrl): Promise => { + return new Promise((resolve) => { + win.postMessage( + JSON.stringify({ + key: `${stateNamespaceKey}.AuthState.authUser`, + method: 'get' + }), + clientUrl + ) + const getIframeResponse = function (e) { + if (e.origin !== clientUrl) return + if (e?.data) { + try { + const value = JSON.parse(e.data) + if (value?.accessToken != null) { + window.removeEventListener('message', getIframeResponse) + resolve(value?.accessToken) + } + } catch { + resolve('') + } + } else resolve(e) + } + window.addEventListener('message', getIframeResponse) + }) +} + +const getToken = async (): Promise => { + let gotResponse = false + const iframe = document.getElementById('root-cookie-accessor') as HTMLFrameElement + let win + try { + win = iframe!.contentWindow + } catch (e) { + win = iframe!.contentWindow + } + + window.addEventListener('message', (e) => { + if (e?.data) { + try { + const value = JSON.parse(e.data) + if (value?.invalidDomain != null) { + localStorage.setItem('invalidCrossOriginDomain', 'true') + } + } catch (err) { + // + } + } + }) + + const clientUrl = config.client.clientUrl + let iteration = 0 + const hasAccess = (await new Promise((resolve) => { + const checkAccessInterval = setInterval(() => { + if (iteration > 4) { + clearInterval(checkAccessInterval) + resolve({ cookieSet: false, hasStorageAccess: false }) + } + if (!gotResponse) { + iteration++ + win.postMessage(JSON.stringify({ method: 'checkAccess' }), clientUrl) + } else clearInterval(checkAccessInterval) + }, 100) + const hasAccessListener = async function (e) { + gotResponse = true + window.removeEventListener('message', hasAccessListener) + if (!e.data) resolve({ hasStorageAccess: false, cookieSet: false }) + const data = JSON.parse(e.data) + resolve(data) + } + window.addEventListener('message', hasAccessListener) + })) as HasAccessType + + if (!hasAccess.cookieSet || !hasAccess.hasStorageAccess) { + const skipCheck = localStorage.getItem('skipCrossOriginCookieCheck') + const invalidCrossOriginDomain = localStorage.getItem('invalidCrossOriginDomain') + if (skipCheck === 'true' || invalidCrossOriginDomain === 'true') { + const authState = getMutableState(AuthState) + const accessToken = authState?.authUser?.accessToken?.value + return Promise.resolve(accessToken?.length > 0 ? accessToken : '') + } else { + iframe.style.display = 'block' + return await new Promise((resolve) => { + const clickResponseListener = async function (e) { + try { + window.removeEventListener('message', clickResponseListener) + const parsed = !e.data ? {} : JSON.parse(e.data) + if (parsed.skipCrossOriginCookieCheck != null) { + localStorage.setItem('skipCrossOriginCookieCheck', parsed.skipCrossOriginCookieCheck) + iframe.style.display = 'none' + resolve('') + } else { + const token = await waitForToken(win, clientUrl) + iframe.style.display = 'none' + resolve(token) + } + } catch (err) { + //Do nothing + } + } + window.addEventListener('message', clickResponseListener) + }) + } + } else { + return waitForToken(win, clientUrl) + } +} + export const AuthState = defineState({ name: 'AuthState', initial: () => ({ @@ -152,21 +277,40 @@ export interface LinkedInLoginForm { email: string } +export const writeAuthUserToIframe = () => { + const iframe = document.getElementById('root-cookie-accessor') as HTMLFrameElement + let win + try { + win = iframe!.contentWindow + } catch (e) { + win = iframe!.contentWindow + } + + win.postMessage( + JSON.stringify({ + key: `${stateNamespaceKey}.${AuthState.name}.authUser`, + method: 'set', + data: getState(AuthState).authUser + }), + config.client.clientUrl + ) +} + /** * Resets the current user's accessToken to a new random guest token. */ async function _resetToGuestToken(options = { reset: true }) { if (options.reset) { - await API.instance.client.authentication.reset() + await api.authentication.reset() } - const newProvider = await Engine.instance.api.service(identityProviderPath).create({ + const newProvider = await api.service(identityProviderPath).create({ type: 'guest', token: v1(), userId: '' as UserID }) const accessToken = newProvider.accessToken! - console.log(`Created new guest accessToken: ${accessToken}`) - await API.instance.client.authentication.setAccessToken(accessToken as string) + await api.authentication.setAccessToken(accessToken as string) + writeAuthUserToIframe() return accessToken } @@ -175,29 +319,25 @@ export const AuthService = { // Oauth callbacks may be running when a guest identity-provider has been deleted. // This would normally cause doLoginAuto to make a guest user, which we do not want. // Instead, just skip it on oauth callbacks, and the callback handler will log them in. - // The client and auth settigns will not be needed on these routes + // The client and auth settings will not be needed on these routes if (/auth\/oauth/.test(location.pathname)) return const authState = getMutableState(AuthState) try { - const accessToken = !forceClientAuthReset && authState?.authUser?.accessToken?.value + const rootDomainToken = await getToken() - if (forceClientAuthReset) { - await API.instance.client.authentication.reset() - } - if (accessToken) { - await API.instance.client.authentication.setAccessToken(accessToken as string) - } else { - await _resetToGuestToken({ reset: false }) - } + if (forceClientAuthReset) await api.authentication.reset() + + if (rootDomainToken?.length > 0) await api.authentication.setAccessToken(rootDomainToken as string) + else await _resetToGuestToken({ reset: false }) let res: AuthenticationResult try { - res = await API.instance.client.reAuthenticate() + res = await api.reAuthenticate() } catch (err) { if (err.className === 'not-found' || (err.className === 'not-authenticated' && err.message === 'jwt expired')) { authState.merge({ isLoggedIn: false, user: UserSeed, authUser: AuthUserSeed }) await _resetToGuestToken() - res = await API.instance.client.reAuthenticate() + res = await api.reAuthenticate() } else { logger.error(err, 'Error re-authenticating') throw err @@ -209,18 +349,20 @@ export const AuthService = { if (!identityProvider?.id) { authState.merge({ isLoggedIn: false, user: UserSeed, authUser: AuthUserSeed }) await _resetToGuestToken() - res = await API.instance.client.reAuthenticate() + res = await api.reAuthenticate() } const authUser = resolveAuthUser(res) // authUser is now { accessToken, authentication, identityProvider } authState.merge({ authUser }) - await AuthService.loadUserData(authUser.identityProvider?.userId) + writeAuthUserToIframe() + await AuthService.loadUserData(authUser.identityProvider.userId) } else { logger.warn('No response received from reAuthenticate()!') } } catch (err) { logger.error(err, 'Error on resolving auth user in doLoginAuto, logging out') authState.merge({ isLoggedIn: false, user: UserSeed, authUser: AuthUserSeed }) + writeAuthUserToIframe() // if (window.location.pathname !== '/') { // window.location.href = '/'; @@ -230,15 +372,14 @@ export const AuthService = { async loadUserData(userId: UserID) { try { - const client = API.instance.client - const user = await client.service(userPath).get(userId) + const user = await api.service(userPath).get(userId) if (!user.userSetting) { - const settingsRes = (await client + const settingsRes = (await api .service(userSettingPath) .find({ query: { userId: userId } })) as Paginated if (settingsRes.total === 0) { - user.userSetting = await client.service(userSettingPath).create({ userId: userId }) + user.userSetting = await api.service(userSettingPath).create({ userId: userId }) } else { user.userSetting = settingsRes.data[0] } @@ -262,7 +403,7 @@ export const AuthService = { authState.merge({ isProcessing: true, error: '' }) try { - const authenticationResult = await API.instance.client.authenticate({ + const authenticationResult = await api.authenticate({ strategy: 'local', email: form.email, password: form.password @@ -307,7 +448,6 @@ export const AuthService = { authState.merge({ isProcessing: true, error: '' }) const credentials: any = parseUserWalletCredentials(vprResult) - console.log(credentials) const walletUser = resolveWalletUser(credentials) const authUser = { @@ -324,8 +464,7 @@ export const AuthService = { } // TODO: This is temp until we move completely to XR wallet #6453 - const oldId = authState.user.id.value - walletUser.id = oldId + walletUser.id = authState.user.id.value // loadXRAvatarForUpdatedUser(walletUser) authState.merge({ isLoggedIn: true, user: walletUser, authUser }) @@ -362,24 +501,24 @@ export const AuthService = { }, async removeUserOAuth(service: string) { - const ipResult = (await Engine.instance.api.service(identityProviderPath).find()) as Paginated + const ipResult = (await api.service(identityProviderPath).find()) as Paginated const ipToRemove = ipResult.data.find((ip) => ip.type === service) if (ipToRemove) { if (ipResult.total === 1) { NotificationService.dispatchNotify('You can not remove your last login method.', { variant: 'warning' }) } else { const otherIp = ipResult.data.find((ip) => ip.type !== service) - const newTokenResult = await Engine.instance.api.service(generateTokenPath).create({ + const newTokenResult = await api.service(generateTokenPath).create({ type: otherIp!.type, token: otherIp!.token }) if (newTokenResult?.token) { getMutableState(AuthState).merge({ isProcessing: true, error: '' }) - await API.instance.client.authentication.setAccessToken(newTokenResult.token) - const res = await API.instance.client.reAuthenticate(true) + await api.authentication.setAccessToken(newTokenResult.token) + const res = await api.reAuthenticate(true) const authUser = resolveAuthUser(res) - await Engine.instance.api.service(identityProviderPath).remove(ipToRemove.id) + await api.service(identityProviderPath).remove(ipToRemove.id) const authState = getMutableState(AuthState) authState.merge({ authUser }) await AuthService.loadUserData(authUser.identityProvider.userId) @@ -393,14 +532,15 @@ export const AuthService = { const authState = getMutableState(AuthState) authState.merge({ isProcessing: true, error: '' }) try { - await API.instance.client.authentication.setAccessToken(accessToken as string) - const res = await API.instance.client.authenticate({ + await api.authentication.setAccessToken(accessToken as string) + const res = await api.authenticate({ strategy: 'jwt', accessToken }) const authUser = resolveAuthUser(res) authState.merge({ authUser }) + writeAuthUserToIframe() await AuthService.loadUserData(authUser.identityProvider?.userId) authState.merge({ isProcessing: false, error: '' }) let timeoutTimer = 0 @@ -409,8 +549,7 @@ export const AuthService = { // in properly. This interval waits to make sure the token has been updated before redirecting const waitForTokenStored = setInterval(() => { timeoutTimer += TIMEOUT_INTERVAL - const authData = authState - const storedToken = authData.authUser?.accessToken?.value + const storedToken = authState.authUser?.accessToken?.value if (storedToken === accessToken) { clearInterval(waitForTokenStored) window.location.href = redirectSuccess @@ -430,7 +569,7 @@ export const AuthService = { async loginUserMagicLink(token, redirectSuccess, redirectError) { try { - const res = await Engine.instance.api.service(loginPath).get(token) + const res = await api.service(loginPath).get(token) await AuthService.loginUserByJwt(res.token!, '/', '/') } catch (err) { NotificationService.dispatchNotify(err.message, { variant: 'error' }) @@ -443,12 +582,13 @@ export const AuthService = { const authState = getMutableState(AuthState) authState.merge({ isProcessing: true, error: '' }) try { - await API.instance.client.logout() + await api.logout() authState.merge({ isLoggedIn: false, user: UserSeed, authUser: AuthUserSeed }) } catch (_) { authState.merge({ isLoggedIn: false, user: UserSeed, authUser: AuthUserSeed }) } finally { authState.merge({ isProcessing: false, error: '' }) + writeAuthUserToIframe() AuthService.doLoginAuto(true) } }, @@ -457,7 +597,7 @@ export const AuthService = { const authState = getMutableState(AuthState) authState.merge({ isProcessing: true, error: '' }) try { - const identityProvider: any = await Engine.instance.api.service(identityProviderPath).create({ + const identityProvider: any = await api.service(identityProviderPath).create({ token: form.email, type: 'password', userId: '' as UserID @@ -516,7 +656,7 @@ export const AuthService = { } try { - await Engine.instance.api.service(magicLinkPath).create({ type, [paramName]: emailPhone }) + await api.service(magicLinkPath).create({ type, [paramName]: emailPhone }) NotificationService.dispatchNotify(i18n.t('user:auth.magiklink.success-msg'), { variant: 'success' }) } catch (err) { NotificationService.dispatchNotify(err.message, { variant: 'error' }) @@ -530,7 +670,7 @@ export const AuthService = { authState.merge({ isProcessing: true, error: '' }) try { - const identityProvider = await Engine.instance.api.service(identityProviderPath).create({ + const identityProvider = await api.service(identityProviderPath).create({ token: form.email, type: 'password', userId: '' as UserID @@ -548,7 +688,7 @@ export const AuthService = { const authState = getMutableState(AuthState) authState.merge({ isProcessing: true, error: '' }) try { - const identityProvider = (await Engine.instance.api.service(magicLinkPath).create({ + const identityProvider = (await api.service(magicLinkPath).create({ email, type: 'email', userId @@ -574,7 +714,7 @@ export const AuthService = { } try { - const identityProvider = (await Engine.instance.api.service(magicLinkPath).create({ + const identityProvider = (await api.service(magicLinkPath).create({ mobile: sendPhone, type: 'sms', userId @@ -600,7 +740,7 @@ export const AuthService = { async removeConnection(identityProviderId: number, userId: UserID) { getMutableState(AuthState).merge({ isProcessing: true, error: '' }) try { - await Engine.instance.api.service(identityProviderPath).remove(identityProviderId) + await api.service(identityProviderPath).remove(identityProviderId) return AuthService.loadUserData(userId) } catch (err) { NotificationService.dispatchNotify(err.message, { variant: 'error' }) @@ -614,45 +754,41 @@ export const AuthService = { }, async updateUserSettings(id: UserSettingID, data: UserSettingPatch) { - const response = await Engine.instance.api.service(userSettingPath).patch(id, data) + const response = await api.service(userSettingPath).patch(id, data) getMutableState(AuthState).user.userSetting.merge(response) }, async removeUser(userId: UserID) { - await Engine.instance.api.service(userPath).remove(userId) + await api.service(userPath).remove(userId) AuthService.logoutUser() }, async updateApiKey() { - const userApiKey = (await Engine.instance.api.service(userApiKeyPath).find()) as Paginated + const userApiKey = (await api.service(userApiKeyPath).find()) as Paginated let apiKey: UserApiKeyType | undefined if (userApiKey.data.length > 0) { - apiKey = await Engine.instance.api.service(userApiKeyPath).patch(userApiKey.data[0].id, {}) + apiKey = await api.service(userApiKeyPath).patch(userApiKey.data[0].id, {}) } else { - apiKey = await Engine.instance.api.service(userApiKeyPath).create({}) + apiKey = await api.service(userApiKeyPath).create({}) } getMutableState(AuthState).user.merge({ apiKey }) }, async updateUsername(userId: UserID, name: UserName) { - const { name: updatedName } = (await Engine.instance.api - .service(userPath) - .patch(userId, { name: name })) as UserType + const { name: updatedName } = (await api.service(userPath).patch(userId, { name: name })) as UserType NotificationService.dispatchNotify(i18n.t('user:usermenu.profile.update-msg'), { variant: 'success' }) getMutableState(AuthState).user.merge({ name: updatedName }) }, async createLoginToken() { - return Engine.instance.api.service(loginTokenPath).create({}) + return api.service(loginTokenPath).create({}) }, useAPIListeners: () => { useEffect(() => { const userPatchedListener = (user: UserPublicPatch | UserPatch) => { - console.log('USER PATCHED %o', user) - if (!user.id) return const selfUser = getMutableState(AuthState).user @@ -663,14 +799,12 @@ export const AuthService = { } const userAvatarPatchedListener = async (userAvatar: UserAvatarPatch) => { - console.log('USER AVATAR PATCHED %o', userAvatar) - if (!userAvatar.userId) return const selfUser = getMutableState(AuthState).user if (selfUser.id.value === userAvatar.userId) { - const user = await Engine.instance.api.service(userPath).get(userAvatar.userId) + const user = await api.service(userPath).get(userAvatar.userId) getMutableState(AuthState).user.merge(user) } } @@ -681,19 +815,19 @@ export const AuthService = { const locationBan = params.locationBan if (selfUser.id === locationBan.userId && currentLocation.id === locationBan.locationId) { const userId = selfUser.id ?? '' - const user = await Engine.instance.api.service(userPath).get(userId) + const user = await api.service(userPath).get(userId) getMutableState(AuthState).merge({ user }) } } - Engine.instance.api.service(userPath).on('patched', userPatchedListener) - Engine.instance.api.service(userAvatarPath).on('patched', userAvatarPatchedListener) - Engine.instance.api.service(locationBanPath).on('created', locationBanCreatedListener) + api.service(userPath).on('patched', userPatchedListener) + api.service(userAvatarPath).on('patched', userAvatarPatchedListener) + api.service(locationBanPath).on('created', locationBanCreatedListener) return () => { - Engine.instance.api.service(userPath).off('patched', userPatchedListener) - Engine.instance.api.service(userAvatarPath).off('patched', userAvatarPatchedListener) - Engine.instance.api.service(locationBanPath).off('created', locationBanCreatedListener) + api.service(userPath).off('patched', userPatchedListener) + api.service(userAvatarPath).off('patched', userAvatarPatchedListener) + api.service(locationBanPath).off('created', locationBanCreatedListener) } }, []) } @@ -703,8 +837,6 @@ export const AuthService = { * @param vprResult {any} See `loginUserByXRWallet()`'s docstring. */ function parseUserWalletCredentials(vprResult: any) { - console.log('PARSING:', vprResult) - const { data: { presentation: vp } } = vprResult diff --git a/packages/client-core/src/util/wait-for-client-authenticated.ts b/packages/client-core/src/util/wait-for-client-authenticated.ts index b52082e8dc..832fb96011 100644 --- a/packages/client-core/src/util/wait-for-client-authenticated.ts +++ b/packages/client-core/src/util/wait-for-client-authenticated.ts @@ -24,7 +24,6 @@ Ethereal Engine. All Rights Reserved. */ import { Engine } from '@etherealengine/ecs/src/Engine' - import { FeathersClient } from '../API' async function waitForClientAuthenticated(): Promise { diff --git a/packages/client/index.html b/packages/client/index.html index aa77cae2c1..da3379d4bd 100755 --- a/packages/client/index.html +++ b/packages/client/index.html @@ -47,10 +47,30 @@ }) }) } + + //Base height and width of cross-origin iframe + const w = 400 + const h = 400 + + let width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; + let height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; + + const left = (width - w) / 2 + const topSetting = (height - h) / 2 + + document.addEventListener('DOMContentLoaded', async (event) => { + const rootIframe = document.getElementById('root-cookie-accessor') + rootIframe.style.width = `${w}px` + rootIframe.style.height = `${h}px` + rootIframe.style.top = `${topSetting}px` + rootIframe.style.left = `${left}px` + })
+ + diff --git a/packages/client/public/cookie-accessor.css b/packages/client/public/cookie-accessor.css new file mode 100644 index 0000000000..d3276fabcf --- /dev/null +++ b/packages/client/public/cookie-accessor.css @@ -0,0 +1,33 @@ +body { + display: flex; + flex-direction: column; + margin: auto; + text-align: center; + background-image: linear-gradient(150deg, #00022e, rgba(255, 0, 125, 0.2901960784)); + color: white; +} + +.button-container { + display: flex; + justify-content: space-around; +} + +button { + width: 150px; + border-radius: 5px; + border: 2px solid transparent; + color: white; + cursor: pointer; +} + +button:hover { + border-color: black; +} + +#confirmButton { + background-color: darkgreen; +} + +#cancelButton { + background-color: darkred; +} \ No newline at end of file diff --git a/packages/client/public/root-cookie-accessor-template.html b/packages/client/public/root-cookie-accessor-template.html new file mode 100644 index 0000000000..1ee4c1ef81 --- /dev/null +++ b/packages/client/public/root-cookie-accessor-template.html @@ -0,0 +1,133 @@ + + + + Local Storage Accessor + + + + +

This site runs on Ethereal Engine. To automatically use your login across all Ethereal Engine-powered sites, please confirm below. If you decline, then each Ethereal Engine-powered site will log in separately, and you will receive this prompt on each site.

+
+ + +
+ + \ No newline at end of file diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts index d2f6e9c570..b8c0995592 100755 --- a/packages/client/vite.config.ts +++ b/packages/client/vite.config.ts @@ -26,7 +26,7 @@ Ethereal Engine. All Rights Reserved. import { viteCommonjs } from '@originjs/vite-plugin-commonjs' import packageRoot from 'app-root-path' import dotenv from 'dotenv' -import fs from 'fs' +import fs, { readFileSync, writeFileSync } from 'fs' import lodash from 'lodash' import path from 'path' import { UserConfig, defineConfig } from 'vite' @@ -36,6 +36,7 @@ import { nodePolyfills } from 'vite-plugin-node-polyfills' import svgr from 'vite-plugin-svgr' const { isArray, mergeWith } = lodash +import appRootPath from 'app-root-path' import manifest from './manifest.default.json' import PWA from './pwa.config' import { getClientSetting } from './scripts/getClientSettings' @@ -230,6 +231,23 @@ const resetSWFiles = () => { deleteDirFilesUsingPattern(/workbox-/, './public/') } +const updateRootCookieAccessorDomain = (isDevOrLocal) => { + const localStorageAccessor = readFileSync( + path.join(appRootPath.path, 'packages', 'client', 'public', 'root-cookie-accessor-template.html') + ).toString() + + const apiUrl = + isDevOrLocal && process.env.VITE_LOCAL_NGINX !== 'true' + ? `https://${process.env.VITE_SERVER_HOST}:${process.env.VITE_SERVER_PORT}` + : `https://${process.env.VITE_SERVER_HOST}` + const updated = localStorageAccessor.replace(//g, apiUrl) + + writeFileSync( + path.join(appRootPath.path, 'packages', 'client', 'public', 'root-cookie-accessor.html'), + Buffer.from(updated) + ) +} + export default defineConfig(async () => { dotenv.config({ path: packageRoot.path + '/.env.local' @@ -241,6 +259,8 @@ export default defineConfig(async () => { const isDevOrLocal = process.env.APP_ENV === 'development' || process.env.VITE_LOCAL_BUILD === 'true' + updateRootCookieAccessorDomain(isDevOrLocal) + let base = `https://${process.env['APP_HOST'] ? process.env['APP_HOST'] : process.env['VITE_APP_HOST']}/` if (process.env.SERVE_CLIENT_FROM_STORAGE_PROVIDER === 'true') { @@ -260,7 +280,7 @@ export default defineConfig(async () => { define: define, server: { proxy: {}, - cors: isDevOrLocal ? false : true, + cors: !isDevOrLocal, hmr: process.env.VITE_HMR === 'true' ? { @@ -316,7 +336,8 @@ export default defineConfig(async () => { ? 'dev-sw.js?dev-sw' : 'service-worker.js' : '', - paymentPointer: coilSetting?.paymentPointer || '' + paymentPointer: coilSetting?.paymentPointer || '', + rootCookieAccessor: `${clientSetting.url}/root-cookie-accessor.html` }), viteCompression({ filter: /\.(js|mjs|json|css)$/i, @@ -334,7 +355,7 @@ export default defineConfig(async () => { }, build: { target: 'esnext', - sourcemap: process.env.VITE_SOURCEMAPS === 'true' ? true : false, + sourcemap: process.env.VITE_SOURCEMAPS === 'true', minify: 'terser', dynamicImportVarsOptions: { warnOnError: true diff --git a/packages/common/src/schemas/networking/allowed-domains.schema.ts b/packages/common/src/schemas/networking/allowed-domains.schema.ts new file mode 100644 index 0000000000..5c4d68d684 --- /dev/null +++ b/packages/common/src/schemas/networking/allowed-domains.schema.ts @@ -0,0 +1,35 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Type } from '@feathersjs/typebox' + +// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html + +export const allowedDomainsPath = 'allowed-domains' + +export const allowedDomainsMethods = ['find'] as const + +export const allowedDomainsSchema = Type.Boolean() +export interface AllowedDomainsType {} diff --git a/packages/ecs/src/Engine.ts b/packages/ecs/src/Engine.ts index 9afe49ced4..5c1aafb17c 100755 --- a/packages/ecs/src/Engine.ts +++ b/packages/ecs/src/Engine.ts @@ -29,10 +29,8 @@ import { ReactorReconciler, createHyperStore, getState } from '@etherealengine/h import { HyperFlux, HyperStore, disposeStore } from '@etherealengine/hyperflux/functions/StoreFunctions' import * as bitECS from 'bitecs' -import type { FeathersApplication } from '@feathersjs/feathers' - -import type { ServiceTypes } from '@etherealengine/common/declarations' - +import { ServiceTypes } from '@etherealengine/common/declarations' +import { FeathersApplication } from '@feathersjs/feathers' import { getAllEntities } from 'bitecs' import { Group, Scene } from 'three' import { ECSState } from './ECSState' diff --git a/packages/editor/src/components/assets/ImageConvertPanel.tsx b/packages/editor/src/components/assets/ImageConvertPanel.tsx index 03cba074e8..24f58a750b 100644 --- a/packages/editor/src/components/assets/ImageConvertPanel.tsx +++ b/packages/editor/src/components/assets/ImageConvertPanel.tsx @@ -25,7 +25,7 @@ Ethereal Engine. All Rights Reserved. import React from 'react' -import { API } from '@etherealengine/client-core/src/API' +import { Engine } from '@etherealengine/ecs/src/Engine' import { ImageConvertParms } from '@etherealengine/engine/src/assets/constants/ImageConvertParms' import { State } from '@etherealengine/hyperflux' @@ -51,7 +51,7 @@ export default function ImageConvertPanel({ function convertImage() { const props = fileProperties.value convertProperties.src.set(props.type === 'folder' ? `${props.url}/${props.key}` : props.url) - API.instance.client + Engine.instance.api .service('image-convert') .create(convertProperties.value) .then(() => { diff --git a/packages/editor/src/components/properties/PortalNodeEditor.tsx b/packages/editor/src/components/properties/PortalNodeEditor.tsx index e4c0e9c126..3a25c54c80 100755 --- a/packages/editor/src/components/properties/PortalNodeEditor.tsx +++ b/packages/editor/src/components/properties/PortalNodeEditor.tsx @@ -27,9 +27,9 @@ import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Euler, Quaternion, Vector3 } from 'three' -import { API } from '@etherealengine/client-core/src/API' import { UUIDComponent } from '@etherealengine/ecs' import { getComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions' +import { Engine } from '@etherealengine/ecs/src/Engine' import { PortalComponent, PortalEffects, @@ -95,7 +95,7 @@ export const PortalNodeEditor: EditorComponentType = (props) => { const portalsDetail: PortalType[] = [] try { portalsDetail.push( - ...((await API.instance.client.service(portalPath).find({ query: { paginate: false } })) as PortalType[]) + ...((await Engine.instance.api.service(portalPath).find({ query: { paginate: false } })) as PortalType[]) ) console.log('portalsDetail', portalsDetail, getComponent(props.entity, UUIDComponent)) } catch (error) { diff --git a/packages/editor/src/functions/assetFunctions.ts b/packages/editor/src/functions/assetFunctions.ts index 4dc6ecf9df..ae6f9c109a 100644 --- a/packages/editor/src/functions/assetFunctions.ts +++ b/packages/editor/src/functions/assetFunctions.ts @@ -23,7 +23,6 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { API } from '@etherealengine/client-core/src/API' import { FileBrowserService } from '@etherealengine/client-core/src/common/services/FileBrowserService' import { CancelableUploadPromiseArrayReturnType, @@ -123,7 +122,7 @@ export const uploadProjectFiles = (projectName: string, files: File[], isAsset = export async function clearModelResources(projectName: string, modelName: string) { const resourcePath = `projects/${projectName}/assets/${modelResourcesPath(modelName)}` - const exists = await API.instance.client.service(fileBrowserPath).get(resourcePath) + const exists = await Engine.instance.api.service(fileBrowserPath).get(resourcePath) if (exists) { await FileBrowserService.deleteContent(resourcePath) } diff --git a/packages/hyperflux/functions/StateFunctions.ts b/packages/hyperflux/functions/StateFunctions.ts index 8b1888c329..cdc47aad80 100644 --- a/packages/hyperflux/functions/StateFunctions.ts +++ b/packages/hyperflux/functions/StateFunctions.ts @@ -27,7 +27,6 @@ import { createState, SetInitialStateAction, State, useHookstate } from '@hookst import type { Object as _Object, Function, String } from 'ts-toolbelt' import { DeepReadonly } from '@etherealengine/common/src/DeepReadonly' -import multiLogger from '@etherealengine/common/src/logger' import { isClient } from '@etherealengine/common/src/utils/getEnvironment' import { resolveObject } from '@etherealengine/common/src/utils/resolveObject' @@ -37,8 +36,6 @@ import { HyperFlux, HyperStore } from './StoreFunctions' export * from '@hookstate/core' -const logger = multiLogger.child({ component: 'hyperflux:State' }) - export const NO_PROXY = { noproxy: true } export const NO_PROXY_STEALTH = { noproxy: true, stealth: true } @@ -103,7 +100,7 @@ export function useMutableState( return useHookstate(resolvedState) as any } -const stateNamespaceKey = 'ee.hyperflux' +export const stateNamespaceKey = 'ee.hyperflux' /** * Automatically synchronises specific root paths of a hyperflux state definition with the localStorage. diff --git a/packages/server-core/src/appconfig.ts b/packages/server-core/src/appconfig.ts index e0b0eb5715..90145919f8 100755 --- a/packages/server-core/src/appconfig.ts +++ b/packages/server-core/src/appconfig.ts @@ -30,6 +30,7 @@ import path from 'path' import url from 'url' import { oembedPath } from '@etherealengine/common/src/schemas/media/oembed.schema' +import { allowedDomainsPath } from '@etherealengine/common/src/schemas/networking/allowed-domains.schema' import { routePath } from '@etherealengine/common/src/schemas/route/route.schema' import { acceptInvitePath } from '@etherealengine/common/src/schemas/user/accept-invite.schema' import { discordBotAuthPath } from '@etherealengine/common/src/schemas/user/discord-bot-auth.schema' @@ -260,6 +261,7 @@ const authentication = { 'auth', 'oauth/:provider', 'authentication', + allowedDomainsPath, oembedPath, githubRepoAccessWebhookPath, { path: identityProviderPath, methods: ['create'] }, diff --git a/packages/server-core/src/hooks/authenticate.ts b/packages/server-core/src/hooks/authenticate.ts index 49c8e3c068..1509283270 100644 --- a/packages/server-core/src/hooks/authenticate.ts +++ b/packages/server-core/src/hooks/authenticate.ts @@ -31,8 +31,8 @@ import { UserType, userPath } from '@etherealengine/common/src/schemas/user/user import { toDateTimeSql } from '@etherealengine/common/src/utils/datetime-sql' import { AsyncLocalStorage } from 'async_hooks' import { isProvider } from 'feathers-hooks-common' +import { Application } from '../../declarations' import config from '../appconfig' -import { Application } from './../../declarations' const { authenticate } = authentication.hooks @@ -112,7 +112,7 @@ export default async (context: HookContext, next: NextFunction): Pr /** * A method to check if the service requesting is whitelisted. - * In that scenario we dont need to perform authentication check. + * In that scenario we don't need to perform authentication check. */ const checkWhitelist = (context: HookContext): boolean => { for (const item of config.authentication.whiteList) { diff --git a/packages/server-core/src/networking/allowed-domains/allowed-domains.class.ts b/packages/server-core/src/networking/allowed-domains/allowed-domains.class.ts new file mode 100755 index 0000000000..8b51b58854 --- /dev/null +++ b/packages/server-core/src/networking/allowed-domains/allowed-domains.class.ts @@ -0,0 +1,38 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { AllowedDomainsType } from '@etherealengine/common/src/schemas/networking/allowed-domains.schema' +import { Params } from '@feathersjs/feathers' +import { KnexAdapterParams } from '@feathersjs/knex' +import { BaseService } from '../../BaseService' +export interface AllowedDomainParams extends KnexAdapterParams { + additionalDomains?: string[] + isAllowed?: boolean +} + +export class AllowedDomainsService< + T = AllowedDomainsType, + ServiceParams extends Params = AllowedDomainParams +> extends BaseService {} diff --git a/packages/server-core/src/networking/allowed-domains/allowed-domains.docs.ts b/packages/server-core/src/networking/allowed-domains/allowed-domains.docs.ts new file mode 100755 index 0000000000..9ed46f24ea --- /dev/null +++ b/packages/server-core/src/networking/allowed-domains/allowed-domains.docs.ts @@ -0,0 +1,37 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { allowedDomainsSchema } from '@etherealengine/common/src/schemas/networking/allowed-domains.schema' +import { createSwaggerServiceOptions } from 'feathers-swagger' + +export default createSwaggerServiceOptions({ + schemas: { + allowedDomainsSchema + }, + docs: { + description: 'Allowed-domain service description', + securities: ['all'] + } +}) diff --git a/packages/server-core/src/networking/allowed-domains/allowed-domains.hooks.ts b/packages/server-core/src/networking/allowed-domains/allowed-domains.hooks.ts new file mode 100755 index 0000000000..cddd633541 --- /dev/null +++ b/packages/server-core/src/networking/allowed-domains/allowed-domains.hooks.ts @@ -0,0 +1,79 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { disallow } from 'feathers-hooks-common' + +import { createSkippableHooks } from '../../hooks/createSkippableHooks' + +import logRequest from '@etherealengine/server-core/src/hooks/log-request' +import { HookContext } from '../../../declarations' +import appConfig from '../../appconfig' +import { AllowedDomainsService } from './allowed-domains.class' + +// Don't remove this comment. It's needed to format import lines nicely. + +const checkDomain = async (context: HookContext) => { + const { params } = context + const domainToCheck = params?.query?.domainToCheck + const additionalDomains = params.additionalDomains + const isAllowed = params.isAllowed + let allowedDomains = [`https://${appConfig.server.clientHost}`] + + if (additionalDomains && Array.isArray(additionalDomains)) allowedDomains = allowedDomains.concat(additionalDomains) + + context.result = isAllowed || allowedDomains.indexOf(domainToCheck) > -1 +} + +export default createSkippableHooks({ + before: { + all: [logRequest()], + find: [checkDomain], + get: [disallow()], + create: [disallow()], + update: [disallow()], + patch: [disallow()], + remove: [disallow()] + }, + + after: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + error: { + all: [logRequest()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } +}) diff --git a/packages/server-core/src/networking/allowed-domains/allowed-domains.ts b/packages/server-core/src/networking/allowed-domains/allowed-domains.ts new file mode 100755 index 0000000000..186f974609 --- /dev/null +++ b/packages/server-core/src/networking/allowed-domains/allowed-domains.ts @@ -0,0 +1,52 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { + allowedDomainsMethods, + allowedDomainsPath +} from '@etherealengine/common/src/schemas/networking/allowed-domains.schema' +import { Application } from '../../../declarations' +import { AllowedDomainsService } from './allowed-domains.class' +import allowedDomainsDocs from './allowed-domains.docs' +import hooks from './allowed-domains.hooks' + +declare module '@etherealengine/common/declarations' { + interface ServiceTypes { + [allowedDomainsPath]: AllowedDomainsService + } +} + +export default (app: Application): void => { + app.use(allowedDomainsPath, new AllowedDomainsService(), { + // A list of all methods this service exposes externally + methods: allowedDomainsMethods, + // You can add additional custom events to be sent to clients here + events: [], + docs: allowedDomainsDocs + }) + + const service = app.service(allowedDomainsPath) + service.hooks(hooks) +} diff --git a/packages/server-core/src/networking/services.ts b/packages/server-core/src/networking/services.ts index 016126ee55..16f98974c4 100755 --- a/packages/server-core/src/networking/services.ts +++ b/packages/server-core/src/networking/services.ts @@ -23,6 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import AllowedDomains from './allowed-domains/allowed-domains' import InstanceActive from './instance-active/instance-active' import InstanceAttendance from './instance-attendance/instance-attendance' import InstanceAuthorizedUser from './instance-authorized-user/instance-authorized-user' @@ -32,6 +33,7 @@ import InstanceServerLoad from './instanceserver-load/instanceserver-load.servic import InstanceServerProvision from './instanceserver-provision/instanceserver-provision.service' export default [ + AllowedDomains, Instance, InstanceServerLoad, InstanceServerProvision, diff --git a/packages/ui/src/pages/Capture/index.stories.tsx b/packages/ui/src/pages/Capture/index.stories.tsx index f1f987b848..5886603686 100644 --- a/packages/ui/src/pages/Capture/index.stories.tsx +++ b/packages/ui/src/pages/Capture/index.stories.tsx @@ -51,7 +51,7 @@ import 'tailwindcss/tailwind.css' // import { useLocation } from 'react-router-dom' const initializeEngineForRecorder = async () => { - // const projects = API.instance.client.service(projectsPath).find() + // const projects = Engine.instance.api.service(projectsPath).find() // await loadEngineInjection(await projects) getMutableState(SceneState).sceneLoaded.set(true) } diff --git a/scripts/build_minikube.sh b/scripts/build_minikube.sh index ed4792905e..de1f8deb46 100755 --- a/scripts/build_minikube.sh +++ b/scripts/build_minikube.sh @@ -121,6 +121,14 @@ else NODE_ENV=$NODE_ENV fi +if [ -z "$VITE_FEATHERS_STORE_KEY" ] +then + VITE_FEATHERS_STORE_KEY=EtherealEngine-Auth-Store +else + VITE_FEATHERS_STORE_KEY=VITE_FEATHERS_STORE_KEY +fi + +echo $VITE_APP_HOST # ./generate-certs.sh @@ -152,6 +160,7 @@ docker buildx build \ --build-arg VITE_8TH_WALL=$VITE_8TH_WALL \ --build-arg VITE_LOGIN_WITH_WALLET=$VITE_LOGIN_WITH_WALLET \ --build-arg VITE_AVATURN_URL=$VITE_AVATURN_URL \ - --build-arg VITE_AVATURN_API=$VITE_AVATURN_API . + --build-arg VITE_AVATURN_API=$VITE_AVATURN_API \ + --build-arg VITE_FEATHERS_STORE_KEY=$VITE_FEATHERS_STORE_KEY . #DOCKER_BUILDKIT=1 docker build -t etherealengine-testbot -f ./dockerfiles/testbot/Dockerfile-testbot . \ No newline at end of file