diff --git a/Dockerfile.saas b/Dockerfile.saas index fcf6521f..20514c4b 100644 --- a/Dockerfile.saas +++ b/Dockerfile.saas @@ -1,4 +1,4 @@ -FROM node:20-alpine3.20 as build +FROM node:20-alpine3.20 AS build # LABELS LABEL \ diff --git a/Dockerfile.standalone b/Dockerfile.standalone index 793df169..87d42bf8 100644 --- a/Dockerfile.standalone +++ b/Dockerfile.standalone @@ -1,4 +1,4 @@ -FROM node:20-alpine3.20 as build +FROM node:20-alpine3.20 AS build # LABELS LABEL \ diff --git a/package.json b/package.json index e0e56dca..0e9bdcdc 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,9 @@ "vite": "^5.3.5", "vitest": "^2.0.5" }, + "overrides": { + "rollup": "npm:@rollup/wasm-node" + }, "lint-staged": { "*.{js,jsx,ts,tsx}": "eslint --cache --fix" } diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx index 4474947c..7c9a494e 100644 --- a/src/pages/auth/LoginPage.tsx +++ b/src/pages/auth/LoginPage.tsx @@ -7,7 +7,13 @@ import { Alert, Button, Checkbox, Col, Divider, Form, Image, Input, Layout, noti import { useState, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; -import { AMUI_URL, isSaasBuild } from '../../services/BaseService'; +import { + AMUI_URL, + NMUI_ACCESS_TOKEN_LOCALSTORAGE_KEY, + NMUI_USERNAME_LOCALSTORAGE_KEY, + NMUI_USER_LOCALSTORAGE_KEY, + isSaasBuild, +} from '../../services/BaseService'; import { extractErrorMsg } from '@/utils/ServiceUtils'; import { UsersService } from '@/services/UsersService'; import { User, UserRole } from '@/models/User'; @@ -57,26 +63,22 @@ export default function LoginPage(props: LoginPageProps) { setIsUserDeniedFromDashboard(true); return; } - store.setStore({ user, userPlatformRole }); + + store.setStore({ user }); + window?.localStorage?.setItem(NMUI_USER_LOCALSTORAGE_KEY, JSON.stringify(user)); } catch (err) { notify.error({ message: 'Failed to get user details', description: extractErrorMsg(err as any) }); } }; - // const checkLoginErrorMessage = (err: string) => { - // if (err.toLowerCase() === USER_ERROR_MESSAGE.toLowerCase()) { - // setIsUserLoggingIn(true); - // return; - // } - // setIsUserLoggingIn(false); - // }; - const onLogin = async () => { try { const formData = await form.validateFields(); setIsBasicAuthLoading(true); const data = await (await AuthService.login(formData)).data; store.setStore({ jwt: data.Response.AuthToken, username: data.Response.UserName }); + window?.localStorage?.setItem(NMUI_ACCESS_TOKEN_LOCALSTORAGE_KEY, data.Response.AuthToken); + window?.localStorage?.setItem(NMUI_USERNAME_LOCALSTORAGE_KEY, data.Response.UserName); await storeFetchServerConfig(); await getUserAndUpdateInStore(data.Response.UserName); } catch (err) { diff --git a/src/services/BaseService.ts b/src/services/BaseService.ts index b4e8a90c..d166f898 100644 --- a/src/services/BaseService.ts +++ b/src/services/BaseService.ts @@ -13,6 +13,14 @@ export const AMUI_URL = isSaasBuild ? (window as any).NMUI_AMUI_URL : ''; export const INTERCOM_APP_ID = isSaasBuild ? (window as any).NMUI_INTERCOM_APP_ID : ''; +export const NMUI_ACCESS_TOKEN_LOCALSTORAGE_KEY = 'nmui-at-lsk'; +export const NMUI_USERNAME_LOCALSTORAGE_KEY = 'nmui-un-lsk'; +export const NMUI_BASE_URL_LOCALSTORAGE_KEY = 'nmui-burl-lsk'; +export const NMUI_TENANT_ID_LOCALSTORAGE_KEY = 'nmui-tid-lsk'; +export const NMUI_TENANT_NAME_LOCALSTORAGE_KEY = 'nmui-tn-lsk'; +export const NMUI_AMUI_USER_ID_LOCALSTORAGE_KEY = 'nmui-amuiuid-lsk'; +export const NMUI_USER_LOCALSTORAGE_KEY = 'nmui-u-lsk'; + // function to resolve the particular SaaS tenant's backend URL, ... export async function setupTenantConfig(): Promise { if (!isSaasBuild) { @@ -20,6 +28,9 @@ export async function setupTenantConfig(): Promise { const resolvedBaseUrl = dynamicBaseUrl ? `${dynamicBaseUrl}/api` : `${import.meta.env.VITE_BASE_URL}/api`; useStore.getState().setStore({ baseUrl: resolvedBaseUrl, + jwt: window?.localStorage?.getItem(NMUI_ACCESS_TOKEN_LOCALSTORAGE_KEY) ?? '', + username: window?.localStorage?.getItem(NMUI_USERNAME_LOCALSTORAGE_KEY) ?? '', + user: JSON.parse(window?.localStorage?.getItem(NMUI_USER_LOCALSTORAGE_KEY) ?? 'null'), }); axiosService.defaults.baseURL = resolvedBaseUrl; return; @@ -35,13 +46,6 @@ export async function setupTenantConfig(): Promise { const amuiUserId = url.searchParams.get('userId') ?? ''; const isNewTenant = url.searchParams.get('isNewTenant') === 'true'; - const resolvedBaseUrl = baseUrl - ? baseUrl?.startsWith('https') - ? `${baseUrl}/api` - : `https://${baseUrl}/api` - : useStore.getState().baseUrl; - axiosService.defaults.baseURL = resolvedBaseUrl; - truncateQueryParamsFromCurrentUrl(); // let user: User | undefined; @@ -57,14 +61,39 @@ export async function setupTenantConfig(): Promise { // return; // } + let resolvedBaseUrl; + if (baseUrl) { + resolvedBaseUrl = baseUrl?.startsWith('https') ? `${baseUrl}/api` : `https://${baseUrl}/api`; + window?.localStorage?.setItem(NMUI_BASE_URL_LOCALSTORAGE_KEY, resolvedBaseUrl); + } else { + resolvedBaseUrl = window?.localStorage?.getItem(NMUI_BASE_URL_LOCALSTORAGE_KEY) ?? ''; + } + axiosService.defaults.baseURL = resolvedBaseUrl; + + if (accessToken) { + window?.localStorage?.setItem(NMUI_ACCESS_TOKEN_LOCALSTORAGE_KEY, accessToken); + } + if (username) { + window?.localStorage?.setItem(NMUI_USERNAME_LOCALSTORAGE_KEY, username); + } + if (tenantId) { + window?.localStorage?.setItem(NMUI_TENANT_ID_LOCALSTORAGE_KEY, tenantId); + } + if (tenantName) { + window?.localStorage?.setItem(NMUI_TENANT_NAME_LOCALSTORAGE_KEY, tenantName); + } + if (amuiUserId) { + window?.localStorage?.setItem(NMUI_AMUI_USER_ID_LOCALSTORAGE_KEY, amuiUserId); + } + useStore.getState().setStore({ baseUrl: resolvedBaseUrl, - jwt: accessToken || useStore.getState().jwt, - tenantId: tenantId || useStore.getState().tenantId, - tenantName: tenantName || useStore.getState().tenantName, + jwt: accessToken || (window?.localStorage?.getItem(NMUI_ACCESS_TOKEN_LOCALSTORAGE_KEY) ?? ''), + tenantId: tenantId || (window?.localStorage?.getItem(NMUI_TENANT_ID_LOCALSTORAGE_KEY) ?? ''), + tenantName: tenantName || (window?.localStorage?.getItem(NMUI_TENANT_NAME_LOCALSTORAGE_KEY) ?? ''), amuiAuthToken, - username: username || useStore.getState().username, - amuiUserId: amuiUserId || useStore.getState().amuiUserId, + username: username || (window?.localStorage?.getItem(NMUI_USERNAME_LOCALSTORAGE_KEY) ?? ''), + amuiUserId: amuiUserId || (window?.localStorage?.getItem(NMUI_AMUI_USER_ID_LOCALSTORAGE_KEY) ?? ''), isNewTenant: isNewTenant, // user, }); diff --git a/src/store/auth.ts b/src/store/auth.ts index ce9dd0bd..6ceeca3c 100644 --- a/src/store/auth.ts +++ b/src/store/auth.ts @@ -1,7 +1,16 @@ import { StateCreator } from 'zustand'; import { TenantConfig } from '../models/ServerConfig'; import { User, UserRole } from '@/models/User'; -import { isSaasBuild } from '@/services/BaseService'; +import { + NMUI_ACCESS_TOKEN_LOCALSTORAGE_KEY, + NMUI_AMUI_USER_ID_LOCALSTORAGE_KEY, + NMUI_BASE_URL_LOCALSTORAGE_KEY, + NMUI_TENANT_ID_LOCALSTORAGE_KEY, + NMUI_TENANT_NAME_LOCALSTORAGE_KEY, + NMUI_USERNAME_LOCALSTORAGE_KEY, + NMUI_USER_LOCALSTORAGE_KEY, + isSaasBuild, +} from '@/services/BaseService'; import { isValidJwt } from '@/utils/Utils'; export interface IAuthSlice { @@ -39,7 +48,10 @@ const createAuthSlice: StateCreator = (set, get) isLoggedIn() { // TODO: fix username retrieval for SaaS return ( - !!get().jwt && isValidJwt(get().jwt || '') && (!isSaasBuild ? !!get().user && !!get().userPlatformRole : true) + !!get().baseUrl && + !!get().jwt && + isValidJwt(get().jwt || '') && + (!isSaasBuild ? !!get().user && !!get().userPlatformRole : true) ); }, setStore(config) { @@ -57,6 +69,13 @@ const createAuthSlice: StateCreator = (set, get) user: null, userPlatformRole: null, }); + window?.localStorage?.removeItem(NMUI_ACCESS_TOKEN_LOCALSTORAGE_KEY); + window?.localStorage?.removeItem(NMUI_USERNAME_LOCALSTORAGE_KEY); + window?.localStorage?.removeItem(NMUI_BASE_URL_LOCALSTORAGE_KEY); + window?.localStorage?.removeItem(NMUI_TENANT_ID_LOCALSTORAGE_KEY); + window?.localStorage?.removeItem(NMUI_TENANT_NAME_LOCALSTORAGE_KEY); + window?.localStorage?.removeItem(NMUI_AMUI_USER_ID_LOCALSTORAGE_KEY); + window?.localStorage?.removeItem(NMUI_USER_LOCALSTORAGE_KEY); }, }); diff --git a/src/store/store.ts b/src/store/store.ts index 9edfce7d..dc869d5d 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { devtools, persist } from 'zustand/middleware'; +import { devtools } from 'zustand/middleware'; import { AppSlice, IAppSlice } from './app'; import { AuthSlice, IAuthSlice } from './auth'; import { HostSlice, IHostSlice } from './hosts'; @@ -10,21 +10,14 @@ import { INetworksUsecaseSlice, NetworksUsecaseSlice } from './networkusecase'; export const useStore = create< INodeSlice & IAppSlice & INetworkSlice & IAuthSlice & IHostSlice & INetworksUsecaseSlice >()( - devtools( - persist( - (...a) => ({ - ...NodeSlice.createNodeSlice(...a), - ...AppSlice.createAppSlice(...a), - ...AuthSlice.createAuthSlice(...a), - ...NetworkSlice.createNetworkSlice(...a), - ...HostSlice.createHostSlice(...a), - ...NetworksUsecaseSlice.createNetworkUsecaseSlice(...a), - }), - { - name: 'netmaker-storage', - }, - ), - ), + devtools((...a) => ({ + ...NodeSlice.createNodeSlice(...a), + ...AppSlice.createAppSlice(...a), + ...AuthSlice.createAuthSlice(...a), + ...NetworkSlice.createNetworkSlice(...a), + ...HostSlice.createHostSlice(...a), + ...NetworksUsecaseSlice.createNetworkUsecaseSlice(...a), + })), ); export const BrowserStore = {