Better auth key support and fix to dockerfile for mongoDB.
This commit is contained in:
@@ -24,14 +24,23 @@ RUN apk add --no-cache python3 make g++ \
|
|||||||
# Stage 4: Final production image
|
# Stage 4: Final production image
|
||||||
FROM mongo:4.4-focal
|
FROM mongo:4.4-focal
|
||||||
# Install Node.js and nginx, cleanup in the same layer
|
# Install Node.js and nginx, cleanup in the same layer
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN set -ex; \
|
||||||
|
# Add MongoDB repository key
|
||||||
|
curl -fsSL https://pgp.mongodb.com/server-4.4.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg; \
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] http://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list; \
|
||||||
|
# Update and install packages
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
nginx \
|
nginx \
|
||||||
curl \
|
curl \
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
gnupg && \
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
# Install Node.js
|
||||||
&& apt-get clean \
|
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
apt-get install -y --no-install-recommends nodejs && \
|
||||||
&& rm -rf /var/cache/apt/*
|
# Cleanup
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
rm -rf /var/cache/apt/*
|
||||||
|
|
||||||
# Configure nginx and copy frontend
|
# Configure nginx and copy frontend
|
||||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ io.on("connection", (socket) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hostConfig.password && !hostConfig.rsaKey) {
|
if (!hostConfig.password && !hostConfig.privateKey) {
|
||||||
logger.error("No authentication provided");
|
logger.error("No authentication provided");
|
||||||
socket.emit("error", "Authentication required");
|
socket.emit("error", "Authentication required");
|
||||||
return;
|
return;
|
||||||
@@ -42,11 +42,12 @@ io.on("connection", (socket) => {
|
|||||||
ip: hostConfig.ip,
|
ip: hostConfig.ip,
|
||||||
port: hostConfig.port,
|
port: hostConfig.port,
|
||||||
user: hostConfig.user,
|
user: hostConfig.user,
|
||||||
authType: hostConfig.password ? 'password' : 'public key',
|
authType: hostConfig.password ? 'password' : 'key',
|
||||||
|
keyType: hostConfig.keyType
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.info("Connecting with config:", safeHostConfig);
|
logger.info("Connecting with config:", safeHostConfig);
|
||||||
const { ip, port, user, password, rsaKey } = hostConfig;
|
const { ip, port, user, password, privateKey, passphrase } = hostConfig;
|
||||||
|
|
||||||
const conn = new SSHClient();
|
const conn = new SSHClient();
|
||||||
conn
|
conn
|
||||||
@@ -98,7 +99,30 @@ io.on("connection", (socket) => {
|
|||||||
port: port,
|
port: port,
|
||||||
username: user,
|
username: user,
|
||||||
password: password,
|
password: password,
|
||||||
privateKey: rsaKey ? Buffer.from(rsaKey) : undefined,
|
privateKey: privateKey ? Buffer.from(privateKey) : undefined,
|
||||||
|
passphrase: passphrase,
|
||||||
|
tryKeyboard: true,
|
||||||
|
algorithms: {
|
||||||
|
kex: [
|
||||||
|
'curve25519-sha256',
|
||||||
|
'curve25519-sha256@libssh.org',
|
||||||
|
'ecdh-sha2-nistp256',
|
||||||
|
'ecdh-sha2-nistp384',
|
||||||
|
'ecdh-sha2-nistp521',
|
||||||
|
'diffie-hellman-group-exchange-sha256',
|
||||||
|
'diffie-hellman-group14-sha256',
|
||||||
|
'diffie-hellman-group14-sha1'
|
||||||
|
],
|
||||||
|
serverHostKey: [
|
||||||
|
'ssh-ed25519',
|
||||||
|
'ecdsa-sha2-nistp256',
|
||||||
|
'ecdsa-sha2-nistp384',
|
||||||
|
'ecdsa-sha2-nistp521',
|
||||||
|
'rsa-sha2-512',
|
||||||
|
'rsa-sha2-256',
|
||||||
|
'ssh-rsa'
|
||||||
|
]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,20 +26,52 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
|||||||
|
|
||||||
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
|
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [showPassphrase, setShowPassphrase] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
|
||||||
const handleFileChange = (e) => {
|
const handleFileChange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
const supportedKeyTypes = {
|
||||||
if (file.name.endsWith('.key') || file.name.endsWith('.pem') || file.name.endsWith('.pub')) {
|
'id_rsa': 'RSA',
|
||||||
|
'id_ed25519': 'ED25519',
|
||||||
|
'id_ecdsa': 'ECDSA',
|
||||||
|
'id_dsa': 'DSA',
|
||||||
|
'.pem': 'PEM',
|
||||||
|
'.key': 'KEY',
|
||||||
|
'.ppk': 'PPK'
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValidKeyFile = Object.keys(supportedKeyTypes).some(ext =>
|
||||||
|
file.name.toLowerCase().includes(ext) || file.name.endsWith('.pub')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isValidKeyFile) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
setForm({ ...form, rsaKey: event.target.result });
|
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')) {
|
||||||
|
keyType = 'ED25519';
|
||||||
|
} else if (keyContent.includes('BEGIN EC PRIVATE KEY') || keyContent.includes('BEGIN EC PUBLIC KEY')) {
|
||||||
|
keyType = 'ECDSA';
|
||||||
|
} else if (keyContent.includes('BEGIN DSA PRIVATE KEY')) {
|
||||||
|
keyType = 'DSA';
|
||||||
|
}
|
||||||
|
|
||||||
|
setForm({
|
||||||
|
...form,
|
||||||
|
privateKey: keyContent,
|
||||||
|
keyType: keyType,
|
||||||
|
authMethod: 'key'
|
||||||
|
});
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
} else {
|
} else {
|
||||||
alert("Please upload a valid public key file.");
|
alert('Please upload a valid SSH key file (RSA, ED25519, ECDSA, DSA, PEM, or PPK format).');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,7 +80,9 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
...prev,
|
...prev,
|
||||||
authMethod: newMethod,
|
authMethod: newMethod,
|
||||||
password: "",
|
password: "",
|
||||||
rsaKey: ""
|
privateKey: "",
|
||||||
|
keyType: "",
|
||||||
|
passphrase: ""
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +93,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
|
|
||||||
if (form.rememberHost) {
|
if (form.rememberHost) {
|
||||||
if (form.authMethod === 'Select Auth') return false;
|
if (form.authMethod === 'Select Auth') return false;
|
||||||
if (form.authMethod === 'rsaKey' && !form.rsaKey) return false;
|
if (form.authMethod === 'key' && !form.privateKey) return false;
|
||||||
if (form.authMethod === 'password' && !form.password) return false;
|
if (form.authMethod === 'password' && !form.password) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +115,8 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
ip: '',
|
ip: '',
|
||||||
user: '',
|
user: '',
|
||||||
password: '',
|
password: '',
|
||||||
rsaKey: '',
|
privateKey: '',
|
||||||
|
keyType: '',
|
||||||
port: 22,
|
port: 22,
|
||||||
authMethod: 'Select Auth',
|
authMethod: 'Select Auth',
|
||||||
rememberHost: false,
|
rememberHost: false,
|
||||||
@@ -241,7 +276,9 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
...((!e.target.checked) && {
|
...((!e.target.checked) && {
|
||||||
authMethod: 'Select Auth',
|
authMethod: 'Select Auth',
|
||||||
password: '',
|
password: '',
|
||||||
rsaKey: '',
|
privateKey: '',
|
||||||
|
keyType: '',
|
||||||
|
passphrase: '',
|
||||||
storePassword: true
|
storePassword: true
|
||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
@@ -280,7 +317,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
>
|
>
|
||||||
<Option value="Select Auth" disabled>Select Auth</Option>
|
<Option value="Select Auth" disabled>Select Auth</Option>
|
||||||
<Option value="password">Password</Option>
|
<Option value="password">Password</Option>
|
||||||
<Option value="rsaKey">Public Key</Option>
|
<Option value="key">SSH Key</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
@@ -311,9 +348,10 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{form.authMethod === 'rsaKey' && (
|
{form.authMethod === 'key' && (
|
||||||
<FormControl error={!form.rsaKey}>
|
<Stack spacing={2}>
|
||||||
<FormLabel>Public Key</FormLabel>
|
<FormControl error={!form.privateKey}>
|
||||||
|
<FormLabel>SSH Key</FormLabel>
|
||||||
<Button
|
<Button
|
||||||
component="label"
|
component="label"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -329,7 +367,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{form.rsaKey ? 'Change Public Key File' : 'Upload Public Key File'}
|
{form.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
@@ -337,6 +375,22 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
{form.privateKey && (
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Key Passphrase (optional)</FormLabel>
|
||||||
|
<Input
|
||||||
|
type={showPassphrase ? "text" : "password"}
|
||||||
|
value={form.passphrase || ''}
|
||||||
|
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
|
||||||
|
endDecorator={
|
||||||
|
<IconButton onClick={() => setShowPassphrase(!showPassphrase)}>
|
||||||
|
{showPassphrase ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -380,7 +434,8 @@ AddHostModal.propTypes = {
|
|||||||
ip: PropTypes.string.isRequired,
|
ip: PropTypes.string.isRequired,
|
||||||
user: PropTypes.string.isRequired,
|
user: PropTypes.string.isRequired,
|
||||||
password: PropTypes.string,
|
password: PropTypes.string,
|
||||||
rsaKey: PropTypes.string,
|
privateKey: PropTypes.string,
|
||||||
|
keyType: PropTypes.string,
|
||||||
port: PropTypes.number.isRequired,
|
port: PropTypes.number.isRequired,
|
||||||
authMethod: PropTypes.string.isRequired,
|
authMethod: PropTypes.string.isRequired,
|
||||||
rememberHost: PropTypes.bool,
|
rememberHost: PropTypes.bool,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
|
|||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [showPassphrase, setShowPassphrase] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isHidden && hostConfig) {
|
if (!isHidden && hostConfig) {
|
||||||
@@ -48,14 +49,47 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
|
|||||||
|
|
||||||
const handleFileChange = (e) => {
|
const handleFileChange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file.name.endsWith('.rsa') || file.name.endsWith('.key') || file.name.endsWith('.pem') || file.name.endsWith('.der') || file.name.endsWith('.p8') || file.name.endsWith('.ssh') || file.name.endsWith('.pub')) {
|
const supportedKeyTypes = {
|
||||||
|
'id_rsa': 'RSA',
|
||||||
|
'id_ed25519': 'ED25519',
|
||||||
|
'id_ecdsa': 'ECDSA',
|
||||||
|
'id_dsa': 'DSA',
|
||||||
|
'.pem': 'PEM',
|
||||||
|
'.key': 'KEY',
|
||||||
|
'.ppk': 'PPK'
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValidKeyFile = Object.keys(supportedKeyTypes).some(ext =>
|
||||||
|
file.name.toLowerCase().includes(ext) || file.name.endsWith('.pub')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isValidKeyFile) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (evt) => {
|
reader.onload = (evt) => {
|
||||||
setForm((prev) => ({ ...prev, rsaKey: evt.target.result }));
|
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')) {
|
||||||
|
keyType = 'ED25519';
|
||||||
|
} else if (keyContent.includes('BEGIN EC PRIVATE KEY') || keyContent.includes('BEGIN EC PUBLIC KEY')) {
|
||||||
|
keyType = 'ECDSA';
|
||||||
|
} else if (keyContent.includes('BEGIN DSA PRIVATE KEY')) {
|
||||||
|
keyType = 'DSA';
|
||||||
|
}
|
||||||
|
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
privateKey: keyContent,
|
||||||
|
keyType: keyType,
|
||||||
|
authMethod: 'key'
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
} else {
|
} else {
|
||||||
alert('Please upload a valid RSA private key file.');
|
alert('Please upload a valid SSH key file (RSA, ED25519, ECDSA, DSA, PEM, or PPK format).');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -281,6 +315,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
|
|||||||
<Option value="Select Auth" disabled>Select Auth</Option>
|
<Option value="Select Auth" disabled>Select Auth</Option>
|
||||||
<Option value="password">Password</Option>
|
<Option value="password">Password</Option>
|
||||||
<Option value="rsaKey">Public Key</Option>
|
<Option value="rsaKey">Public Key</Option>
|
||||||
|
<Option value="key">SSH Key</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -352,6 +387,64 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
|
|||||||
)}
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{form.authMethod === 'key' && form.storePassword && (
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<FormControl error={!form.privateKey && !hostConfig?.privateKey}>
|
||||||
|
<FormLabel>SSH Key</FormLabel>
|
||||||
|
<Button
|
||||||
|
component="label"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.general.primary,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '40px',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.general.disabled,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{form.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
|
||||||
|
<Input
|
||||||
|
type="file"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
sx={{ display: 'none' }}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
{hostConfig?.privateKey && !form.privateKey && (
|
||||||
|
<FormLabel
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
mt: 1,
|
||||||
|
display: 'block',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Existing {hostConfig.keyType || 'SSH'} key detected. Upload to replace.
|
||||||
|
</FormLabel>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
{form.privateKey && (
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Key Passphrase (optional)</FormLabel>
|
||||||
|
<Input
|
||||||
|
type={showPassphrase ? "text" : "password"}
|
||||||
|
value={form.passphrase || ''}
|
||||||
|
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
|
||||||
|
endDecorator={
|
||||||
|
<IconButton onClick={() => setShowPassphrase(!showPassphrase)}>
|
||||||
|
{showPassphrase ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -19,30 +19,72 @@ import { useState, useEffect } from 'react';
|
|||||||
import Visibility from '@mui/icons-material/Visibility';
|
import Visibility from '@mui/icons-material/Visibility';
|
||||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||||
|
|
||||||
const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, handleAuthSubmit }) => {
|
const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
authMethod: 'Select Auth',
|
||||||
|
password: '',
|
||||||
|
privateKey: '',
|
||||||
|
keyType: '',
|
||||||
|
passphrase: ''
|
||||||
|
});
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [showPassphrase, setShowPassphrase] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleSubmit = (e) => {
|
||||||
if (!form.authMethod) {
|
e.preventDefault();
|
||||||
setForm(prev => ({
|
onAuthenticate({
|
||||||
...prev,
|
authMethod: form.authMethod,
|
||||||
authMethod: 'Select Auth'
|
password: form.password,
|
||||||
}));
|
privateKey: form.privateKey,
|
||||||
}
|
keyType: form.keyType,
|
||||||
}, []);
|
passphrase: form.passphrase
|
||||||
|
});
|
||||||
const isFormValid = () => {
|
setIsHidden(true);
|
||||||
if (!form.authMethod || form.authMethod === 'Select Auth') return false;
|
|
||||||
if (form.authMethod === 'rsaKey' && !form.rsaKey) return false;
|
|
||||||
if (form.authMethod === 'password' && !form.password) return false;
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (event) => {
|
const handleFileChange = (e) => {
|
||||||
event.preventDefault();
|
const file = e.target.files[0];
|
||||||
if (isFormValid()) {
|
const supportedKeyTypes = {
|
||||||
handleAuthSubmit(form);
|
'id_rsa': 'RSA',
|
||||||
setForm({ authMethod: 'Select Auth', password: '', rsaKey: '' });
|
'id_ed25519': 'ED25519',
|
||||||
|
'id_ecdsa': 'ECDSA',
|
||||||
|
'id_dsa': 'DSA',
|
||||||
|
'.pem': 'PEM',
|
||||||
|
'.key': 'KEY',
|
||||||
|
'.ppk': 'PPK'
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValidKeyFile = Object.keys(supportedKeyTypes).some(ext =>
|
||||||
|
file.name.toLowerCase().includes(ext) || file.name.endsWith('.pub')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isValidKeyFile) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
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')) {
|
||||||
|
keyType = 'ED25519';
|
||||||
|
} else if (keyContent.includes('BEGIN EC PRIVATE KEY') || keyContent.includes('BEGIN EC PUBLIC KEY')) {
|
||||||
|
keyType = 'ECDSA';
|
||||||
|
} else if (keyContent.includes('BEGIN DSA PRIVATE KEY')) {
|
||||||
|
keyType = 'DSA';
|
||||||
|
}
|
||||||
|
|
||||||
|
setForm({
|
||||||
|
...form,
|
||||||
|
privateKey: keyContent,
|
||||||
|
keyType: keyType,
|
||||||
|
authMethod: 'key'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
} else {
|
||||||
|
alert('Please upload a valid SSH key file (RSA, ED25519, ECDSA, DSA, PEM, or PPK format).');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,31 +92,12 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
|
|||||||
<CssVarsProvider theme={theme}>
|
<CssVarsProvider theme={theme}>
|
||||||
<Modal
|
<Modal
|
||||||
open={!isHidden}
|
open={!isHidden}
|
||||||
onClose={(e, reason) => {
|
onClose={() => setIsHidden(true)}
|
||||||
if (reason !== 'backdropClick') {
|
|
||||||
setIsNoAuthHidden(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ModalDialog
|
<ModalDialog
|
||||||
layout="center"
|
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.tertiary,
|
backgroundColor: theme.palette.general.secondary,
|
||||||
borderColor: theme.palette.general.secondary,
|
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
padding: 3,
|
|
||||||
borderRadius: 10,
|
|
||||||
maxWidth: '500px',
|
|
||||||
width: '100%',
|
|
||||||
maxHeight: '80vh',
|
|
||||||
overflow: 'auto',
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
mx: 2,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ mb: 2 }}>Authentication Required</DialogTitle>
|
<DialogTitle sx={{ mb: 2 }}>Authentication Required</DialogTitle>
|
||||||
@@ -84,8 +107,15 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
|
|||||||
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
|
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
|
||||||
<FormLabel>Authentication Method</FormLabel>
|
<FormLabel>Authentication Method</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
value={form.authMethod || 'Select Auth'}
|
value={form.authMethod}
|
||||||
onChange={(e, val) => setForm(prev => ({ ...prev, authMethod: val, password: '', rsaKey: '' }))}
|
onChange={(e, val) => setForm(prev => ({
|
||||||
|
...prev,
|
||||||
|
authMethod: val,
|
||||||
|
password: '',
|
||||||
|
privateKey: '',
|
||||||
|
keyType: '',
|
||||||
|
passphrase: ''
|
||||||
|
}))}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
@@ -93,40 +123,30 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
|
|||||||
>
|
>
|
||||||
<Option value="Select Auth" disabled>Select Auth</Option>
|
<Option value="Select Auth" disabled>Select Auth</Option>
|
||||||
<Option value="password">Password</Option>
|
<Option value="password">Password</Option>
|
||||||
<Option value="rsaKey">Public Key</Option>
|
<Option value="key">SSH Key</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
{form.authMethod === 'password' && (
|
{form.authMethod === 'password' && (
|
||||||
<FormControl error={!form.password}>
|
<FormControl error={!form.password}>
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<Input
|
<Input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? "text" : "password"}
|
||||||
value={form.password || ''}
|
value={form.password}
|
||||||
onChange={(e) => setForm(prev => ({ ...prev, password: e.target.value }))}
|
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||||
sx={{
|
endDecorator={
|
||||||
backgroundColor: theme.palette.general.primary,
|
<IconButton onClick={() => setShowPassword(!showPassword)}>
|
||||||
color: theme.palette.text.primary,
|
|
||||||
flex: 1
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
marginLeft: 1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{form.authMethod === 'rsaKey' && (
|
{form.authMethod === 'key' && (
|
||||||
<FormControl error={!form.rsaKey}>
|
<Stack spacing={2}>
|
||||||
<FormLabel>Public Key</FormLabel>
|
<FormControl error={!form.privateKey}>
|
||||||
|
<FormLabel>SSH Key</FormLabel>
|
||||||
<Button
|
<Button
|
||||||
component="label"
|
component="label"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -142,28 +162,37 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{form.rsaKey ? 'Change Public Key File' : 'Upload Public Key File'}
|
{form.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
onChange={(e) => {
|
onChange={handleFileChange}
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (event) => {
|
|
||||||
setForm({ ...form, rsaKey: event.target.result });
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
sx={{ display: 'none' }}
|
sx={{ display: 'none' }}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
{form.privateKey && (
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Key Passphrase (optional)</FormLabel>
|
||||||
|
<Input
|
||||||
|
type={showPassphrase ? "text" : "password"}
|
||||||
|
value={form.passphrase || ''}
|
||||||
|
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
|
||||||
|
endDecorator={
|
||||||
|
<IconButton onClick={() => setShowPassphrase(!showPassphrase)}>
|
||||||
|
{showPassphrase ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!isFormValid()}
|
disabled={!form.authMethod || form.authMethod === 'Select Auth' ||
|
||||||
|
(form.authMethod === 'password' && !form.password) ||
|
||||||
|
(form.authMethod === 'key' && !form.privateKey)}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
@@ -174,8 +203,6 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
|
|||||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||||
color: 'rgba(255, 255, 255, 0.3)',
|
color: 'rgba(255, 255, 255, 0.3)',
|
||||||
},
|
},
|
||||||
marginTop: 2,
|
|
||||||
height: '40px',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
@@ -191,10 +218,8 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
|
|||||||
|
|
||||||
NoAuthenticationModal.propTypes = {
|
NoAuthenticationModal.propTypes = {
|
||||||
isHidden: PropTypes.bool.isRequired,
|
isHidden: PropTypes.bool.isRequired,
|
||||||
form: PropTypes.object.isRequired,
|
setIsHidden: PropTypes.func.isRequired,
|
||||||
setForm: PropTypes.func.isRequired,
|
onAuthenticate: PropTypes.func.isRequired,
|
||||||
setIsNoAuthHidden: PropTypes.func.isRequired,
|
|
||||||
handleAuthSubmit: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NoAuthenticationModal;
|
export default NoAuthenticationModal;
|
||||||
Reference in New Issue
Block a user