From dc21ade84e2da677eb838dc21153728dda38446f Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Mon, 29 Dec 2025 01:25:43 +0400 Subject: [PATCH] feat: implement mandatory login on startup and set roman password --- packages/electron-app/electron/main/main.ts | 2 + .../electron-app/electron/main/user-store.ts | 29 ++- packages/ui/src/App.tsx | 182 ++++++++++-------- packages/ui/src/components/auth/LoginView.tsx | 161 ++++++++++++++++ packages/ui/src/lib/user-context.ts | 24 +-- 5 files changed, 295 insertions(+), 103 deletions(-) create mode 100644 packages/ui/src/components/auth/LoginView.tsx diff --git a/packages/electron-app/electron/main/main.ts b/packages/electron-app/electron/main/main.ts index b412e3d..d9ef899 100644 --- a/packages/electron-app/electron/main/main.ts +++ b/packages/electron-app/electron/main/main.ts @@ -481,6 +481,8 @@ if (isMac) { } app.whenReady().then(() => { + clearGuestUsers() + logoutActiveUser() ensureDefaultUsers() applyUserEnvToCli() startCli() diff --git a/packages/electron-app/electron/main/user-store.ts b/packages/electron-app/electron/main/user-store.ts index 2831d79..68ea6a9 100644 --- a/packages/electron-app/electron/main/user-store.ts +++ b/packages/electron-app/electron/main/user-store.ts @@ -111,19 +111,30 @@ function migrateLegacyData(targetDir: string) { export function ensureDefaultUsers(): UserRecord { const store = readStore() - if (store.users.length > 0) { - const active = store.users.find((u) => u.id === store.activeUserId) ?? store.users[0] - if (!store.activeUserId) { - store.activeUserId = active.id + + // If roman exists, ensure his password is updated to the new required one if it matches the old default + const roman = store.users.find(u => u.name === "roman") + if (roman && roman.salt && roman.passwordHash) { + const oldDefaultHash = hashPassword("q1w2e3r4", roman.salt) + if (roman.passwordHash === oldDefaultHash) { + console.log("[UserStore] Updating roman's password to new default") + const newSalt = generateSalt() + roman.salt = newSalt + roman.passwordHash = hashPassword("!@#$q1w2e3r4", newSalt) + roman.updatedAt = nowIso() writeStore(store) } + } + + if (store.users.length > 0) { + const active = store.users.find((u) => u.id === store.activeUserId) ?? store.users[0] return active } const existingIds = new Set() const userId = ensureUniqueId("roman", existingIds) const salt = generateSalt() - const passwordHash = hashPassword("q1w2e3r4", salt) + const passwordHash = hashPassword("!@#$q1w2e3r4", salt) const record: UserRecord = { id: userId, name: "roman", @@ -134,7 +145,6 @@ export function ensureDefaultUsers(): UserRecord { } store.users.push(record) - store.activeUserId = record.id writeStore(store) const userDir = getUserDir(record.id) @@ -153,6 +163,13 @@ export function getActiveUser(): UserRecord | null { return store.users.find((user) => user.id === store.activeUserId) ?? null } +export function logoutActiveUser() { + const store = readStore() + store.activeUserId = undefined + writeStore(store) + console.log("[UserStore] Active user logged out") +} + export function setActiveUser(userId: string) { const store = readStore() const user = store.users.find((u) => u.id === userId) diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index bbcd360..48aa215 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -11,6 +11,8 @@ import { RemoteAccessOverlay } from "./components/remote-access-overlay" import { InstanceMetadataProvider } from "./lib/contexts/instance-metadata-context" import { initMarkdown } from "./lib/markdown" import QwenOAuthCallback from "./pages/QwenOAuthCallback" +import LoginView from "./components/auth/LoginView" +import { isLoggedIn, initializeUserContext } from "./lib/user-context" import { useTheme } from "./lib/theme" import { useCommands } from "./lib/hooks/use-commands" @@ -391,100 +393,110 @@ const App: Component = () => { -
- - setRemoteAccessOpen(true)} - /> + initializeUserContext()} />} + > +
+ + setRemoteAccessOpen(true)} + /> - - {(instance) => { - const isActiveInstance = () => activeInstanceId() === instance.id - const isVisible = () => isActiveInstance() && !showFolderSelection() - return ( -
- - handleCloseSession(instance.id, sessionId)} - onNewSession={() => handleNewSession(instance.id)} - handleSidebarAgentChange={(sessionId, agent) => handleSidebarAgentChange(instance.id, sessionId, agent)} - handleSidebarModelChange={(sessionId, model) => handleSidebarModelChange(instance.id, sessionId, model)} - onExecuteCommand={executeCommand} - tabBarOffset={instanceTabBarHeight()} - /> - + + {(instance) => { + const isActiveInstance = () => activeInstanceId() === instance.id + const isVisible = () => isActiveInstance() && !showFolderSelection() + return ( +
+ + handleCloseSession(instance.id, sessionId)} + onNewSession={() => handleNewSession(instance.id)} + handleSidebarAgentChange={(sessionId, agent) => handleSidebarAgentChange(instance.id, sessionId, agent)} + handleSidebarModelChange={(sessionId, model) => handleSidebarModelChange(instance.id, sessionId, model)} + onExecuteCommand={executeCommand} + tabBarOffset={instanceTabBarHeight()} + /> + -
- ) +
+ ) - }} -
+ }} + - - } - > - setIsAdvancedSettingsOpen(true)} - onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)} - onOpenRemoteAccess={() => setRemoteAccessOpen(true)} - /> -
+ + } + > + setIsAdvancedSettingsOpen(true)} + onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)} + onOpenRemoteAccess={() => setRemoteAccessOpen(true)} + /> + - -
-
- - setIsAdvancedSettingsOpen(true)} - onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)} - /> + +
+
+ + setIsAdvancedSettingsOpen(true)} + onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)} + /> +
-
- + - setRemoteAccessOpen(false)} /> + setRemoteAccessOpen(false)} /> - + - -
+ +
) } diff --git a/packages/ui/src/components/auth/LoginView.tsx b/packages/ui/src/components/auth/LoginView.tsx new file mode 100644 index 0000000..7ee48d5 --- /dev/null +++ b/packages/ui/src/components/auth/LoginView.tsx @@ -0,0 +1,161 @@ +import { Component, createSignal, onMount, For, Show } from "solid-js" +import { Lock, User, LogIn, ShieldCheck, Cpu } from "lucide-solid" +import toast from "solid-toast" + +interface UserRecord { + id: string + name: string + isGuest?: boolean +} + +interface LoginViewProps { + onLoginSuccess: (user: UserRecord) => void +} + +const LoginView: Component = (props) => { + const [users, setUsers] = createSignal([]) + const [selectedUserId, setSelectedUserId] = createSignal("") + const [password, setPassword] = createSignal("") + const [isLoggingIn, setIsLoggingIn] = createSignal(false) + const [isLoadingUsers, setIsLoadingUsers] = createSignal(true) + + onMount(async () => { + try { + const ipcRenderer = (window as any).electron?.ipcRenderer + if (ipcRenderer) { + const userList = await ipcRenderer.invoke("users:list") + setUsers(userList) + if (userList.length > 0) { + setSelectedUserId(userList[0].id) + } + } + } catch (error) { + console.error("Failed to fetch users:", error) + toast.error("Failed to load user list") + } finally { + setIsLoadingUsers(false) + } + }) + + const handleLogin = async (e: Event) => { + e.preventDefault() + if (!selectedUserId()) return + + setIsLoggingIn(true) + try { + const ipcRenderer = (window as any).electron?.ipcRenderer + if (ipcRenderer) { + const result = await ipcRenderer.invoke("users:login", { + id: selectedUserId(), + password: password(), + }) + + if (result.success) { + toast.success(`Welcome back, ${result.user.name}!`) + props.onLoginSuccess(result.user) + } else { + toast.error("Invalid password") + } + } + } catch (error) { + console.error("Login failed:", error) + toast.error("Login failed. Please try again.") + } finally { + setIsLoggingIn(false) + } + } + + return ( +
+ {/* Dynamic Background */} +
+
+
+
+ +
+ {/* Logo & Header */} +
+
+
+ +
+
+

NomadArch

+

Secure Neural Access Point

+
+ +
+ {/* User Selection */} +
+ +
+
+ +
+ +
+ +
+
+
+ + {/* Password Input */} +
+ +
+
+ +
+ setPassword(e.currentTarget.value)} + required + class="block w-full pl-12 pr-4 py-4 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white placeholder-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/50 transition-all font-mono" + /> +
+
+ + +
+ +
+ Powered by Antigravity OS v4.5 | Encrypted Connection +
+
+
+ ) +} + +export default LoginView diff --git a/packages/ui/src/lib/user-context.ts b/packages/ui/src/lib/user-context.ts index 78661d8..bcc4d52 100644 --- a/packages/ui/src/lib/user-context.ts +++ b/packages/ui/src/lib/user-context.ts @@ -1,20 +1,23 @@ -/** - * User Context utilities for frontend - * Handles active user ID and passes it in API requests - */ +import { createSignal } from "solid-js" // Storage key for active user const ACTIVE_USER_KEY = "codenomad_active_user_id" +const [isLoggedIn, setLoggedIn] = createSignal(false) + +export { isLoggedIn, setLoggedIn } + /** * Set the active user ID */ export function setActiveUserId(userId: string | null): void { if (userId) { localStorage.setItem(ACTIVE_USER_KEY, userId) + setLoggedIn(true) console.log(`[UserContext] Active user set to: ${userId}`) } else { localStorage.removeItem(ACTIVE_USER_KEY) + setLoggedIn(false) console.log(`[UserContext] Active user cleared`) } } @@ -72,25 +75,22 @@ export async function initializeUserContext(): Promise { setActiveUserId(activeUser.id) console.log(`[UserContext] Initialized with user: ${activeUser.id} (${activeUser.name})`) } else { + setLoggedIn(false) console.log(`[UserContext] No active user from IPC`) } } else { // Web mode - try to get from localStorage or use default const existingId = getActiveUserId() if (existingId) { + setLoggedIn(true) console.log(`[UserContext] Using cached user ID: ${existingId}`) } else { - // Set a default user ID for web mode - const defaultUserId = "default" - setActiveUserId(defaultUserId) - console.log(`[UserContext] Web mode - using default user ID`) + setLoggedIn(false) + console.log(`[UserContext] Web mode - no active user`) } } } catch (error) { console.error(`[UserContext] Failed to initialize:`, error) - // Fall back to default - if (!getActiveUserId()) { - setActiveUserId("default") - } + setLoggedIn(false) } }