diff --git a/package-lock.json b/package-lock.json index 7615c7d0..d54d6ee6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "express": "^4.21.2", "is-stream": "^4.0.1", "make-dir": "^5.0.0", + "mitt": "^3.0.1", "mongoose": "^8.12.1", "node-ssh": "^13.2.0", "prop-types": "^15.8.1", @@ -6341,6 +6342,12 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", diff --git a/package.json b/package.json index 0ab6013a..5a14a389 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "express": "^4.21.2", "is-stream": "^4.0.1", "make-dir": "^5.0.0", + "mitt": "^3.0.1", "mongoose": "^8.12.1", "node-ssh": "^13.2.0", "prop-types": "^15.8.1", diff --git a/src/App.jsx b/src/App.jsx index 2a7ed548..6d3b3b2b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -16,6 +16,7 @@ import ProfileModal from "./modals/ProfileModal.jsx"; import ErrorModal from "./modals/ErrorModal.jsx"; import EditHostModal from "./modals/EditHostModal.jsx"; import NoAuthenticationModal from "./modals/NoAuthenticationModal.jsx"; +import eventBus from "./other/eventBus.jsx"; function App() { const [isAddHostHidden, setIsAddHostHidden] = useState(true); @@ -33,6 +34,7 @@ function App() { ip: "", user: "", password: "", + sshKey: "", port: 22, authMethod: "Select Auth", rememberHost: true, @@ -44,6 +46,7 @@ function App() { ip: "", user: "", password: "", + sshKey: "", port: 22, authMethod: "Select Auth", rememberHost: true, @@ -51,9 +54,16 @@ function App() { }); const [isNoAuthHidden, setIsNoAuthHidden] = useState(true); const [authForm, setAuthForm] = useState({ - username: "", - password: "", + username: '', + password: '', + confirmPassword: '' }); + const [noAuthenticationForm, setNoAuthenticationForm] = useState({ + authMethod: 'Select Auth', + password: '', + sshKey: '', + keyType: '', + }) const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false); const [splitTabIds, setSplitTabIds] = useState([]); const [isEditHostHidden, setIsEditHostHidden] = useState(true); @@ -137,7 +147,7 @@ function App() { let loginAttempts = 0; const maxAttempts = 50; let attemptLoginInterval; - + const loginTimeout = setTimeout(() => { if (isComponentMounted) { clearInterval(attemptLoginInterval); @@ -153,11 +163,11 @@ function App() { const attemptLogin = () => { if (!isComponentMounted || isLoginInProgress) return; - + if (loginAttempts >= maxAttempts || userRef.current?.getUser()) { clearTimeout(loginTimeout); clearInterval(attemptLoginInterval); - + if (!userRef.current?.getUser()) { localStorage.removeItem('sessionToken'); setIsAuthModalHidden(false); @@ -212,12 +222,13 @@ function App() { }, []); const handleAddHost = () => { - if (!addHostForm.ip?.trim() || !addHostForm.user?.trim() || !addHostForm.port) { - alert("Please fill out all required fields (IP, User, Port)."); - return; - } + if (addHostForm.ip && addHostForm.user && addHostForm.port) { + if (!addHostForm.rememberHost) { + connectToHost(); + setIsAddHostHidden(true); + return; + } - if (addHostForm.rememberHost) { if (addHostForm.authMethod === 'Select Auth') { alert("Please select an authentication method."); return; @@ -226,42 +237,33 @@ function App() { setIsNoAuthHidden(false); return; } - if (addHostForm.authMethod === 'key' && !addHostForm.privateKey) { + if (addHostForm.authMethod === 'sshKey' && !addHostForm.sshKey) { setIsNoAuthHidden(false); return; } - } - connectToHost(); - if (addHostForm.rememberHost) { + connectToHost(); if (!addHostForm.storePassword) { addHostForm.password = ''; - addHostForm.privateKey = ''; } handleSaveHost(); + setIsAddHostHidden(true); + } else { + alert("Please fill out all required fields (IP, User, Port)."); } - setIsAddHostHidden(true); }; const connectToHost = () => { const hostConfig = { name: addHostForm.name || '', folder: addHostForm.folder || '', - ip: addHostForm.ip.trim(), - user: addHostForm.user.trim(), + ip: addHostForm.ip, + user: addHostForm.user, port: String(addHostForm.port), + password: addHostForm.rememberHost && addHostForm.authMethod === 'password' ? addHostForm.password : undefined, + sshKey: addHostForm.rememberHost && addHostForm.authMethod === 'sshKey' ? addHostForm.sshKey : undefined, }; - if (addHostForm.rememberHost && addHostForm.storePassword) { - if (addHostForm.authMethod === 'password') { - hostConfig.password = addHostForm.password; - } else if (addHostForm.authMethod === 'key') { - hostConfig.privateKey = addHostForm.privateKey; - hostConfig.keyType = addHostForm.keyType; - hostConfig.passphrase = addHostForm.passphrase; - } - } - const newTerminal = { id: nextId, title: hostConfig.name || hostConfig.ip, @@ -271,21 +273,9 @@ function App() { setTerminals([...terminals, newTerminal]); setActiveTab(nextId); setNextId(nextId + 1); - setAddHostForm({ - name: "", - folder: "", - ip: "", - user: "", - password: "", - privateKey: "", - keyType: "", - passphrase: "", - port: 22, - authMethod: "Select Auth", - rememberHost: true, - storePassword: true - }); - }; + setIsAddHostHidden(true); + setAddHostForm({ name: "", folder: "", ip: "", user: "", password: "", sshKey: "", port: 22, authMethod: "Select Auth", rememberHost: true, storePassword: true }); + } const handleAuthSubmit = (form) => { const updatedTerminals = terminals.map((terminal) => { @@ -295,7 +285,7 @@ function App() { hostConfig: { ...terminal.hostConfig, password: form.password, - rsaKey: form.rsaKey + sshKey: form.sshKey } }; } @@ -321,7 +311,7 @@ function App() { user: hostConfig.user.trim(), port: hostConfig.port || '22', password: hostConfig.password?.trim(), - rsaKey: hostConfig.rsaKey?.trim(), + sshKey: hostConfig.sshKey?.trim(), }; const newTerminal = { @@ -337,74 +327,71 @@ function App() { } const handleSaveHost = () => { - const hostConfig = { + let hostConfig = { name: addHostForm.name || addHostForm.ip, folder: addHostForm.folder, - ip: addHostForm.ip.trim(), - user: addHostForm.user.trim(), + ip: addHostForm.ip, + user: addHostForm.user, + password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined, + sshKey: addHostForm.authMethod === 'sshKey' ? addHostForm.sshKey : undefined, port: String(addHostForm.port), - }; - - if (addHostForm.storePassword) { - if (addHostForm.authMethod === 'password') { - hostConfig.password = addHostForm.password; - } else if (addHostForm.authMethod === 'key') { - hostConfig.privateKey = addHostForm.privateKey; - hostConfig.keyType = addHostForm.keyType; - hostConfig.passphrase = addHostForm.passphrase; - } } - if (userRef.current) { userRef.current.saveHost({ hostConfig, }); } - }; + } - const handleLoginUser = ({ username, password, onSuccess, onFailure }) => { + const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => { if (userRef.current) { - userRef.current.loginUser({ - username, - password, - onSuccess: () => { - setIsAuthModalHidden(true); - if (onSuccess) onSuccess(); - }, - onFailure: (error) => { - setIsAuthModalHidden(false); - if (onFailure) onFailure(error); - }, - }); - } - }; - - const handleGuestLogin = async ({ onSuccess, onFailure }) => { - if (userRef.current) { - try { - await userRef.current.loginAsGuest(); - setIsAuthModalHidden(true); - if (onSuccess) onSuccess(); - } catch (error) { - setIsAuthModalHidden(false); - if (onFailure) onFailure(error); + if (sessionToken) { + userRef.current.loginUser({ + sessionToken, + onSuccess: () => { + setIsAuthModalHidden(true); + setIsLoggingIn(false); + if (onSuccess) onSuccess(); + }, + onFailure: (error) => { + localStorage.removeItem('sessionToken'); + setIsAuthModalHidden(false); + setIsLoggingIn(false); + if (onFailure) onFailure(error); + }, + }); + } else { + userRef.current.loginUser({ + username, + password, + onSuccess: () => { + setIsAuthModalHidden(true); + setIsLoggingIn(false); + if (onSuccess) onSuccess(); + }, + onFailure: (error) => { + setIsAuthModalHidden(false); + setIsLoggingIn(false); + if (onFailure) onFailure(error); + }, + }); } } }; + const handleGuestLogin = () => { + if (userRef.current) { + userRef.current.loginAsGuest(); + } + } + const handleCreateUser = ({ username, password, onSuccess, onFailure }) => { if (userRef.current) { userRef.current.createUser({ username, password, - onSuccess: () => { - setIsAuthModalHidden(true); - if (onSuccess) onSuccess(); - }, - onFailure: (error) => { - setIsAuthModalHidden(false); - if (onFailure) onFailure(error); - }, + onSuccess, + onFailure, }); } }; @@ -459,7 +446,7 @@ function App() { if (newConfig) { if (isEditing) return; setIsEditing(true); - + try { await userRef.current.editHost({ oldHostConfig: oldConfig, @@ -476,6 +463,7 @@ function App() { updateEditHostForm(oldConfig); } catch (error) { + console.error('Edit failed:', error); setErrorMessage(`Edit failed: ${error}`); setIsErrorHidden(false); setIsEditing(false); @@ -661,10 +649,10 @@ function App() { )} @@ -684,7 +672,7 @@ function App() { setForm={setEditHostForm} handleEditHost={handleEditHost} setIsEditHostHidden={setIsEditHostHidden} - hostConfig={currentHostConfig || {}} + hostConfig={currentHostConfig} /> {/* User component */} @@ -737,8 +725,8 @@ function App() { }} onCreateSuccess={() => { setIsAuthModalHidden(true); - handleLoginUser({ - username: authForm.username, + handleLoginUser({ + username: authForm.username, password: authForm.password, onSuccess: () => { setIsAuthModalHidden(true); @@ -759,6 +747,7 @@ function App() { setErrorMessage(`Action failed: ${error}`); setIsErrorHidden(false); setIsLoggingIn(false); + eventBus.emit('failedLoginUser'); }} /> diff --git a/src/apps/ssh/Terminal.jsx b/src/apps/ssh/Terminal.jsx index b50f90f3..e96ae1df 100644 --- a/src/apps/ssh/Terminal.jsx +++ b/src/apps/ssh/Terminal.jsx @@ -76,7 +76,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde socket.on("error", (err) => { const isAuthError = err.toLowerCase().includes("authentication") || err.toLowerCase().includes("auth"); - if (isAuthError && !hostConfig.password?.trim() && !hostConfig.rsaKey?.trim() && !authModalShown) { + if (isAuthError && !hostConfig.password?.trim() && !hostConfig.sshKey?.trim() && !authModalShown) { authModalShown = true; setIsNoAuthHidden(false); } @@ -88,7 +88,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde resizeTerminal(); const { cols, rows } = terminalInstance.current; - if (!hostConfig.password?.trim() && !hostConfig.rsaKey?.trim()) { + if (!hostConfig.password?.trim() && !hostConfig.sshKey?.trim()) { setIsNoAuthHidden(false); return; } @@ -98,7 +98,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde user: hostConfig.user, port: Number(hostConfig.port) || 22, password: hostConfig.password?.trim(), - rsaKey: hostConfig.rsaKey?.trim() + sshKey: hostConfig.sshKey?.trim() }; socket.emit("connectToHost", cols, rows, sshConfig); @@ -172,7 +172,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde let authModalShown = false; socket.on("noAuthRequired", () => { - if (!hostConfig.password?.trim() && !hostConfig.rsaKey?.trim() && !authModalShown) { + if (!hostConfig.password?.trim() && !hostConfig.sshKey?.trim() && !authModalShown) { authModalShown = true; setIsNoAuthHidden(false); } @@ -235,7 +235,7 @@ NewTerminal.propTypes = { ip: PropTypes.string.isRequired, user: PropTypes.string.isRequired, password: PropTypes.string, - rsaKey: PropTypes.string, + sshKey: PropTypes.string, port: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, }).isRequired, isVisible: PropTypes.bool.isRequired, diff --git a/src/apps/user/User.jsx b/src/apps/user/User.jsx index bc8b88d8..ab22ec4f 100644 --- a/src/apps/user/User.jsx +++ b/src/apps/user/User.jsx @@ -186,7 +186,7 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce user: host.config.user || '', port: host.config.port || '22', password: host.config.password || '', - rsaKey: host.config.rsaKey || '', + sshKey: host.config.sshKey || '', } : {} })).filter(host => host.config && host.config.ip && host.config.user); } else { diff --git a/src/backend/database.cjs b/src/backend/database.cjs index 1e453db3..6d99d81a 100644 --- a/src/backend/database.cjs +++ b/src/backend/database.cjs @@ -185,7 +185,7 @@ io.of('/database.io').on('connection', (socket) => { user: hostConfig.user.trim(), port: hostConfig.port || 22, password: hostConfig.password?.trim() || undefined, - rsaKey: hostConfig.rsaKey?.trim() || undefined + sshKey: hostConfig.sshKey?.trim() || undefined, }; const finalName = cleanConfig.name || cleanConfig.ip; @@ -415,7 +415,7 @@ io.of('/database.io').on('connection', (socket) => { user: newHostConfig.user.trim(), port: newHostConfig.port || 22, password: newHostConfig.password?.trim() || undefined, - rsaKey: newHostConfig.rsaKey?.trim() || undefined + sshKey: newHostConfig.sshKey?.trim() || undefined, }; const encryptedConfig = encryptData(cleanConfig, userId, sessionToken); diff --git a/src/backend/ssh.cjs b/src/backend/ssh.cjs index af6b8c86..427e9edb 100644 --- a/src/backend/ssh.cjs +++ b/src/backend/ssh.cjs @@ -47,7 +47,7 @@ io.on("connection", (socket) => { }; logger.info("Connecting with config:", safeHostConfig); - const { ip, port, user, password, privateKey, passphrase } = hostConfig; + const { ip, port, user, password, sshKey, } = hostConfig; const conn = new SSHClient(); conn @@ -99,8 +99,7 @@ io.on("connection", (socket) => { port: port, username: user, password: password, - privateKey: privateKey ? Buffer.from(privateKey) : undefined, - passphrase: passphrase, + sshKey: sshKey ? Buffer.from(sshKey) : undefined, tryKeyboard: true, algorithms: { kex: [ diff --git a/src/modals/AddHostModal.jsx b/src/modals/AddHostModal.jsx index dd322a4b..d8bdf8c7 100644 --- a/src/modals/AddHostModal.jsx +++ b/src/modals/AddHostModal.jsx @@ -24,7 +24,6 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff'; const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => { const [showPassword, setShowPassword] = useState(false); - const [showPassphrase, setShowPassphrase] = useState(false); const [activeTab, setActiveTab] = useState(0); const handleFileChange = (e) => { @@ -39,7 +38,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd '.ppk': 'PPK' }; - const isValidKeyFile = Object.keys(supportedKeyTypes).some(ext => + const isValidKeyFile = Object.keys(supportedKeyTypes).some(ext => file.name.toLowerCase().includes(ext) || file.name.endsWith('.pub') ); @@ -48,8 +47,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd reader.onload = (event) => { const keyContent = event.target.result; let keyType = 'UNKNOWN'; - - // Detect key type from content + if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) { keyType = 'RSA'; } else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) { @@ -60,11 +58,11 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd keyType = 'DSA'; } - setForm(prev => ({ - ...prev, - privateKey: keyContent, + setForm(prev => ({ + ...prev, + sshKey: keyContent, keyType: keyType, - authMethod: 'key' + authMethod: 'sshKey' })); }; reader.readAsText(file); @@ -78,30 +76,25 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd ...prev, authMethod: newMethod, password: "", - privateKey: "", + sshKey: "", keyType: "", - passphrase: "" })); }; const isFormValid = () => { - const { ip, user, port, authMethod, password, privateKey } = form; - - // Basic validation for required fields + const { ip, user, port, authMethod, password, sshKey } = form; + if (!ip?.trim() || !user?.trim() || !port) return false; - - // Port validation + const portNum = Number(port); if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false; - // If not remembering host, only basic fields are required if (!form.rememberHost) return true; - // Auth method validation only if remembering host if (form.rememberHost) { if (authMethod === 'Select Auth') return false; if (authMethod === 'password' && !password?.trim()) return false; - if (authMethod === 'key' && !privateKey?.trim()) return false; + if (authMethod === 'sshKey' && !sshKey?.trim()) return false; } return true; @@ -114,6 +107,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd return; } handleAddHost(); + setActiveTab(0); }; return ( @@ -144,10 +138,10 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd mx: 2, }} > - setActiveTab(val)} - sx={{ + sx={{ width: '100%', mb: 0, backgroundColor: theme.palette.general.tertiary, @@ -211,8 +205,8 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd Remember Host setForm({ - ...form, + onChange={(e) => setForm({ + ...form, rememberHost: e.target.checked, })} sx={{ @@ -284,7 +278,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd > - + @@ -315,9 +309,9 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd )} - {form.authMethod === 'key' && ( + {form.authMethod === 'sshKey' && ( - + SSH Key - {form.privateKey && ( - - Key Passphrase (optional) -
- setForm(prev => ({ ...prev, passphrase: e.target.value }))} - sx={{ - backgroundColor: theme.palette.general.primary, - color: theme.palette.text.primary, - flex: 1 - }} - /> - setShowPassphrase(!showPassphrase)} - sx={{ - color: theme.palette.text.primary, - marginLeft: 1 - }} - > - {showPassphrase ? : } - -
-
- )}
)} diff --git a/src/modals/AuthModal.jsx b/src/modals/AuthModal.jsx index 47350958..93cb46ae 100644 --- a/src/modals/AuthModal.jsx +++ b/src/modals/AuthModal.jsx @@ -1,159 +1,267 @@ import PropTypes from 'prop-types'; import { CssVarsProvider } from '@mui/joy/styles'; -import { Modal, Button, FormControl, FormLabel, Input, Stack, ModalDialog, IconButton, Tabs, TabList, Tab, TabPanel } from '@mui/joy'; +import { + Modal, + Button, + FormControl, + FormLabel, + Input, + Stack, + DialogContent, + ModalDialog, + IconButton, + Tabs, + TabList, + Tab, + TabPanel +} from '@mui/joy'; import theme from '/src/theme'; import { useEffect, useState } from 'react'; import Visibility from '@mui/icons-material/Visibility'; import VisibilityOff from '@mui/icons-material/VisibilityOff'; +import eventBus from '/src/other/eventBus'; -const AuthModal = ({ isHidden, form, setForm, handleLoginUser, handleGuestLogin, handleCreateUser, setIsLoginUserHidden }) => { - const [showPassword, setShowPassword] = useState(false); - const [confirmPassword, setConfirmPassword] = useState(''); +const AuthModal = ({ + isHidden, + form, + setForm, + handleLoginUser, + handleCreateUser, + handleGuestLogin, + setIsAuthModalHidden + }) => { const [activeTab, setActiveTab] = useState(0); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); - const isLoginFormValid = () => { - return form.username?.trim() && form.password?.trim(); + useEffect(() => { + const loginErrorHandler = () => setIsLoading(false); + eventBus.on('failedLoginUser', loginErrorHandler); + return () => eventBus.off('failedLoginUser', loginErrorHandler); + }, []); + + const resetForm = () => { + setForm({ username: '', password: '' }); + setShowPassword(false); + setShowConfirmPassword(false); + setIsLoading(false); }; - const isCreateFormValid = () => { - return form.username?.trim() && form.password?.trim() && confirmPassword?.trim() && form.password === confirmPassword; - }; - - const handleLogin = () => { - if (!isLoginFormValid()) { - alert("Please fill out all fields"); - return; - } - + const handleLogin = async () => { setIsLoading(true); - handleLoginUser({ - username: form.username.trim(), - password: form.password.trim(), - onSuccess: () => { - setIsLoading(false); - setIsLoginUserHidden(true); - }, - onFailure: (error) => { - setIsLoading(false); - alert(error); - } - }); + try { + await handleLoginUser({ + ...form, + onSuccess: () => { + setIsLoading(false); + setIsAuthModalHidden(true); + }, + onFailure: () => setIsLoading(false), + }); + } catch (error) { + setIsLoading(false); + } }; - const handleCreate = () => { - if (!isCreateFormValid()) { - alert("Please fill out all fields and ensure passwords match"); - return; - } - + const handleCreate = async () => { setIsLoading(true); - handleCreateUser({ - username: form.username.trim(), - password: form.password.trim(), - onSuccess: () => { - setIsLoading(false); - setIsLoginUserHidden(true); - }, - onFailure: (error) => { - setIsLoading(false); - alert(error); - } - }); + try { + await handleCreateUser({ + ...form, + onSuccess: () => { + setIsLoading(false); + setActiveTab(0); + setIsAuthModalHidden(true); + }, + onFailure: () => setIsLoading(false), + }); + } catch (error) { + setIsLoading(false); + } + }; + + const handleGuest = async () => { + setIsLoading(true); + try { + await handleGuestLogin({ + onSuccess: () => { + setIsLoading(false); + setIsAuthModalHidden(true); + }, + onFailure: () => setIsLoading(false) + }); + } catch (error) { + setIsLoading(false); + } }; useEffect(() => { - if (isHidden) { - setForm({ username: '', password: '' }); - setConfirmPassword(''); - setIsLoading(false); - setActiveTab(0); - } + if (isHidden) resetForm(); }, [isHidden]); + const isLoginValid = !!form.username && !!form.password; + const isCreateValid = isLoginValid && form.password === form.confirmPassword; + return ( - setIsLoginUserHidden(true)}> - - setActiveTab(val)}> - - Login - Create Account + setIsAuthModalHidden(true)}> + + setActiveTab(val)} + sx={{ + width: '100%', + backgroundColor: theme.palette.general.tertiary, + }} + > + + Login + Create -
- - + + + { e.preventDefault(); handleLogin(); }}> Username setForm({ ...form, username: event.target.value })} disabled={isLoading} + value={form.username} + onChange={(e) => setForm({ ...form, username: e.target.value })} + sx={inputStyle} /> Password
setForm({ ...form, password: event.target.value })} - disabled={isLoading} + onChange={(e) => setForm({ ...form, password: e.target.value })} + sx={{ ...inputStyle, flex: 1 }} /> - setShowPassword(!showPassword)}> + setShowPassword(!showPassword)} + sx={iconButtonStyle} + > {showPassword ? : }
- -
- - + + { e.preventDefault(); handleCreate(); }}> Username setForm({ ...form, username: event.target.value })} disabled={isLoading} + value={form.username} + onChange={(e) => setForm({ ...form, username: e.target.value })} + sx={inputStyle} /> Password
setForm({ ...form, password: event.target.value })} - disabled={isLoading} + onChange={(e) => setForm({ ...form, password: e.target.value })} + sx={{ ...inputStyle, flex: 1 }} /> - setShowPassword(!showPassword)}> + setShowPassword(!showPassword)} + sx={iconButtonStyle} + > {showPassword ? : }
Confirm Password - setConfirmPassword(event.target.value)} - disabled={isLoading} - /> +
+ setForm({ ...form, confirmPassword: e.target.value })} + sx={{ ...inputStyle, flex: 1 }} + /> + setShowConfirmPassword(!showConfirmPassword)} + sx={iconButtonStyle} + > + {showConfirmPassword ? : } + +
-
-
+
@@ -161,14 +269,38 @@ const AuthModal = ({ isHidden, form, setForm, handleLoginUser, handleGuestLogin, ); }; +const inputStyle = { + backgroundColor: theme.palette.general.primary, + color: theme.palette.text.primary, + '&:disabled': { + opacity: 0.5, + backgroundColor: theme.palette.general.primary, + }, +}; + +const iconButtonStyle = { + color: theme.palette.text.primary, + marginLeft: 1, + '&:disabled': { opacity: 0.5 }, +}; + +const buttonStyle = { + backgroundColor: theme.palette.general.primary, + '&:hover': { backgroundColor: theme.palette.general.disabled }, + '&:disabled': { + opacity: 0.5, + backgroundColor: theme.palette.general.primary, + }, +}; + AuthModal.propTypes = { isHidden: PropTypes.bool.isRequired, form: PropTypes.object.isRequired, setForm: PropTypes.func.isRequired, handleLoginUser: PropTypes.func.isRequired, - handleGuestLogin: PropTypes.func.isRequired, handleCreateUser: PropTypes.func.isRequired, - setIsLoginUserHidden: PropTypes.func.isRequired, + handleGuestLogin: PropTypes.func.isRequired, + setIsAuthModalHidden: PropTypes.func.isRequired, }; export default AuthModal; \ No newline at end of file diff --git a/src/modals/CreateUserModal.jsx b/src/modals/CreateUserModal.jsx deleted file mode 100644 index cb65a5aa..00000000 --- a/src/modals/CreateUserModal.jsx +++ /dev/null @@ -1,207 +0,0 @@ -import PropTypes from 'prop-types'; -import { CssVarsProvider } from '@mui/joy/styles'; -import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, IconButton } from '@mui/joy'; -import theme from '/src/theme'; -import { useEffect, useState } from 'react'; -import Visibility from '@mui/icons-material/Visibility'; -import VisibilityOff from '@mui/icons-material/VisibilityOff'; - -const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreateUserHidden, setIsLoginUserHidden }) => { - const [confirmPassword, setConfirmPassword] = useState(''); - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [isLoading, setIsLoading] = useState(false); - - const isFormValid = () => { - if (!form.username || !form.password || form.password !== confirmPassword) return false; - return true; - }; - - const handleCreate = async () => { - setIsLoading(true); - try { - await handleCreateUser({ - ...form, - onSuccess: () => setIsLoading(false), - onFailure: () => setIsLoading(false) - }); - } catch (error) { - setIsLoading(false); - } - }; - - useEffect(() => { - if (isHidden) { - setForm({ username: '', password: '' }); - setConfirmPassword(''); - setIsLoading(false); - } - }, [isHidden]); - - return ( - - {}}> - - Create - -
{ - event.preventDefault(); - if (isFormValid() && !isLoading) handleCreate(); - }} - > - - - Username - setForm({ ...form, username: event.target.value })} - sx={{ - backgroundColor: theme.palette.general.primary, - color: theme.palette.text.primary, - '&:disabled': { - opacity: 0.5, - backgroundColor: theme.palette.general.primary, - }, - }} - /> - - - Password -
- setForm({ ...form, password: event.target.value })} - sx={{ - backgroundColor: theme.palette.general.primary, - color: theme.palette.text.primary, - flex: 1, - '&:disabled': { - opacity: 0.5, - backgroundColor: theme.palette.general.primary, - }, - }} - /> - setShowPassword(!showPassword)} - sx={{ - color: theme.palette.text.primary, - marginLeft: 1, - '&:disabled': { - opacity: 0.5, - }, - }} - > - {showPassword ? : } - -
-
- - Confirm Password -
- setConfirmPassword(event.target.value)} - sx={{ - backgroundColor: theme.palette.general.primary, - color: theme.palette.text.primary, - flex: 1, - '&:disabled': { - opacity: 0.5, - backgroundColor: theme.palette.general.primary, - }, - }} - /> - setShowConfirmPassword(!showConfirmPassword)} - sx={{ - color: theme.palette.text.primary, - marginLeft: 1, - '&:disabled': { - opacity: 0.5, - }, - }} - > - {showConfirmPassword ? : } - -
-
- - -
-
-
-
-
-
- ); -}; - -CreateUserModal.propTypes = { - isHidden: PropTypes.bool.isRequired, - form: PropTypes.object.isRequired, - setForm: PropTypes.func.isRequired, - handleCreateUser: PropTypes.func.isRequired, - setIsCreateUserHidden: PropTypes.func.isRequired, - setIsLoginUserHidden: PropTypes.func.isRequired, -}; - -export default CreateUserModal; \ No newline at end of file diff --git a/src/modals/EditHostModal.jsx b/src/modals/EditHostModal.jsx index 9608fbe4..bd0a2e33 100644 --- a/src/modals/EditHostModal.jsx +++ b/src/modals/EditHostModal.jsx @@ -8,8 +8,6 @@ import { FormLabel, Input, Stack, - DialogTitle, - DialogContent, ModalDialog, Select, Option, @@ -32,15 +30,13 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo user: hostConfig?.user || '', port: hostConfig?.port || '', password: '', - privateKey: hostConfig?.privateKey || '', + sshKey: hostConfig?.sshKey || '', keyType: hostConfig?.keyType || '', - passphrase: '', authMethod: hostConfig?.authMethod || 'Select Auth', storePassword: true, rememberHost: true }); const [showPassword, setShowPassword] = useState(false); - const [showPassphrase, setShowPassphrase] = useState(false); const [activeTab, setActiveTab] = useState(0); const [isLoading, setIsLoading] = useState(false); @@ -52,13 +48,12 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo ip: hostConfig.ip || '', user: hostConfig.user || '', password: hostConfig.password || '', - privateKey: hostConfig.privateKey || '', + sshKey: hostConfig.sshKey || '', keyType: hostConfig.keyType || '', - passphrase: hostConfig.passphrase || '', port: hostConfig.port || 22, - authMethod: hostConfig.password ? 'password' : hostConfig.privateKey ? 'key' : 'Select Auth', + authMethod: hostConfig.password ? 'password' : hostConfig.sshKey ? 'key' : 'Select Auth', rememberHost: true, - storePassword: !!(hostConfig.password || hostConfig.privateKey), + storePassword: !!(hostConfig.password || hostConfig.sshKey), }); } }, [isHidden, hostConfig]); @@ -84,8 +79,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo reader.onload = (evt) => { const keyContent = evt.target.result; let keyType = 'UNKNOWN'; - - // Detect key type from content + if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) { keyType = 'RSA'; } else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) { @@ -98,7 +92,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo setForm((prev) => ({ ...prev, - privateKey: keyContent, + sshKey: keyContent, keyType: keyType, authMethod: 'key' })); @@ -121,27 +115,23 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo ...prev, storePassword: Boolean(checked), password: checked ? prev.password : "", - privateKey: checked ? prev.privateKey : "", - passphrase: checked ? prev.passphrase : "", + sshKey: checked ? prev.sshKey : "", authMethod: checked ? prev.authMethod : "Select Auth" })); }; const isFormValid = () => { - const { ip, user, port, authMethod, password, privateKey } = form; - - // Basic validation for required fields + const { ip, user, port, authMethod, password, sshKey } = form; + if (!ip?.trim() || !user?.trim() || !port) return false; - - // Port validation + const portNum = Number(port); if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false; - // Auth method validation only if storing password if (form.storePassword) { if (authMethod === 'Select Auth') return false; if (authMethod === 'password' && !password?.trim()) return false; - if (authMethod === 'key' && !privateKey?.trim()) return false; + if (authMethod === 'sshKey' && !sshKey?.trim()) return false; } return true; @@ -150,7 +140,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo const handleSubmit = async (event) => { event.preventDefault(); if (isLoading) return; - + setIsLoading(true); try { const newConfig = { @@ -165,9 +155,8 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo if (form.authMethod === 'password') { newConfig.password = form.password; } else if (form.authMethod === 'key') { - newConfig.privateKey = form.privateKey; + newConfig.sshKey = form.sshKey; newConfig.keyType = form.keyType; - newConfig.passphrase = form.passphrase; } } @@ -378,7 +367,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo {form.authMethod === 'key' && ( - + SSH Key - {hostConfig?.privateKey && !form.privateKey && ( + {hostConfig?.sshKey && !form.sshKey && ( )} - {form.privateKey && ( - - Key Passphrase (optional) -
- setForm(prev => ({ ...prev, passphrase: e.target.value }))} - sx={{ - backgroundColor: theme.palette.general.primary, - color: theme.palette.text.primary, - flex: 1 - }} - /> - setShowPassphrase(!showPassphrase)} - sx={{ - color: theme.palette.text.primary, - marginLeft: 1 - }} - > - {showPassphrase ? : } - -
-
- )}
)} @@ -452,7 +415,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo - - - - - -
-
-
- ); -}; - -LoginUserModal.propTypes = { - isHidden: PropTypes.bool.isRequired, - form: PropTypes.object.isRequired, - setForm: PropTypes.func.isRequired, - handleLoginUser: PropTypes.func.isRequired, - handleGuestLogin: PropTypes.func.isRequired, - setIsLoginUserHidden: PropTypes.func.isRequired, - setIsCreateUserHidden: PropTypes.func.isRequired, -}; - -export default LoginUserModal; \ No newline at end of file diff --git a/src/modals/NoAuthenticationModal.jsx b/src/modals/NoAuthenticationModal.jsx index 813f1e02..c930a7a2 100644 --- a/src/modals/NoAuthenticationModal.jsx +++ b/src/modals/NoAuthenticationModal.jsx @@ -15,31 +15,44 @@ import { Option, } from '@mui/joy'; import theme from '/src/theme'; -import { useState, useEffect } from 'react'; +import {useEffect, useState} from 'react'; import Visibility from '@mui/icons-material/Visibility'; import VisibilityOff from '@mui/icons-material/VisibilityOff'; -const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => { - const [form, setForm] = useState({ - authMethod: 'Select Auth', - password: '', - privateKey: '', - keyType: '', - passphrase: '' - }); +const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, handleAuthSubmit }) => { const [showPassword, setShowPassword] = useState(false); - const [showPassphrase, setShowPassphrase] = useState(false); + + useEffect(() => { + if (!form.authMethod) { + setForm(prev => ({ + ...prev, + authMethod: 'Select Auth', + password: '', + sshKey: '', + keyType: '', + })); + } + }, []); + + const isFormValid = () => { + if (!form.authMethod || form.authMethod === 'Select Auth') return false; + if (form.authMethod === 'sshKey' && !form.sshKey) return false; + if (form.authMethod === 'password' && !form.password) return false; + return true; + }; const handleSubmit = (e) => { e.preventDefault(); - onAuthenticate({ - authMethod: form.authMethod, - password: form.password, - privateKey: form.privateKey, - keyType: form.keyType, - passphrase: form.passphrase - }); - setIsHidden(true); + if(isFormValid()) { + handleAuthSubmit(form); + setForm (prev => ({ + ...prev, + authMethod: 'Select Auth', + password: '', + sshKey: '', + keyType: '', + })) + } }; const handleFileChange = (e) => { @@ -77,9 +90,9 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => { setForm({ ...form, - privateKey: keyContent, + sshKey: keyContent, keyType: keyType, - authMethod: 'key' + authMethod: 'sshKey' }); }; reader.readAsText(file); @@ -92,12 +105,31 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => { setIsHidden(true)} + onClose={(e, reason) => { + if (reason !== 'backdropClick') { + setIsNoAuthHidden(true); + } + }} + sx={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }} > Authentication Required @@ -107,14 +139,13 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => { Authentication Method {form.authMethod === 'password' && ( Password - setForm({ ...form, password: e.target.value })} - endDecorator={ - setShowPassword(!showPassword)}> - {showPassword ? : } - - } - /> +
+ setForm({...form, password: e.target.value})} + sx={{ + backgroundColor: theme.palette.general.primary, + color: theme.palette.text.primary, + flex: 1 + }} + /> + setShowPassword(!showPassword)} + sx={{ + color: theme.palette.text.primary, + marginLeft: 1, + '&:disabled': { + opacity: 0.5, + }, + }} + > + {showPassword ? : } + +
)} - {form.authMethod === 'key' && ( + {form.authMethod === 'sshKey' && ( - + SSH Key - {form.privateKey && ( - - Key Passphrase (optional) - setForm(prev => ({ ...prev, passphrase: e.target.value }))} - endDecorator={ - setShowPassphrase(!showPassphrase)}> - {showPassphrase ? : } - - } - /> - - )} )}