fix(server): enforce numeric ssh_port end-to-end; harden UI input; coerce in API/DB; fix runtime handler import
This commit is contained in:
13
server.js
13
server.js
@@ -8,7 +8,18 @@ import stripAnsi from 'strip-ansi';
|
|||||||
import { spawn as ptySpawn } from 'node-pty';
|
import { spawn as ptySpawn } from 'node-pty';
|
||||||
import { getSSHExecutionService } from './src/server/ssh-execution-service.js';
|
import { getSSHExecutionService } from './src/server/ssh-execution-service.js';
|
||||||
import { getDatabase } from './src/server/database-prisma.js';
|
import { getDatabase } from './src/server/database-prisma.js';
|
||||||
import { registerGlobalErrorHandlers } from './src/server/logging/globalHandlers.js';
|
// Fallback minimal global error handlers for Node runtime (avoid TS import)
|
||||||
|
function registerGlobalErrorHandlers() {
|
||||||
|
if (registerGlobalErrorHandlers._registered) return;
|
||||||
|
registerGlobalErrorHandlers._registered = true;
|
||||||
|
process.on('uncaughtException', (err) => {
|
||||||
|
console.error('uncaught_exception', err);
|
||||||
|
});
|
||||||
|
process.on('unhandledRejection', (reason) => {
|
||||||
|
console.error('unhandled_rejection', reason);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
registerGlobalErrorHandlers._registered = false;
|
||||||
|
|
||||||
const dev = process.env.NODE_ENV !== 'production';
|
const dev = process.env.NODE_ENV !== 'production';
|
||||||
const hostname = '0.0.0.0';
|
const hostname = '0.0.0.0';
|
||||||
|
|||||||
@@ -122,7 +122,21 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel
|
|||||||
const handleChange = (field: keyof CreateServerData) => (
|
const handleChange = (field: keyof CreateServerData) => (
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||||
) => {
|
) => {
|
||||||
setFormData(prev => ({ ...prev, [field]: e.target.value }));
|
// Special handling for numeric ssh_port: keep it strictly numeric
|
||||||
|
if (field === 'ssh_port') {
|
||||||
|
const raw = (e.target as HTMLInputElement).value ?? '';
|
||||||
|
const digitsOnly = raw.replace(/\D+/g, '');
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
ssh_port: digitsOnly ? parseInt(digitsOnly, 10) : undefined,
|
||||||
|
}));
|
||||||
|
if (errors.ssh_port) {
|
||||||
|
setErrors(prev => ({ ...prev, ssh_port: undefined }));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData(prev => ({ ...prev, [field]: (e.target as HTMLInputElement).value }));
|
||||||
// Clear error when user starts typing
|
// Clear error when user starts typing
|
||||||
if (errors[field]) {
|
if (errors[field]) {
|
||||||
setErrors(prev => ({ ...prev, [field]: undefined }));
|
setErrors(prev => ({ ...prev, [field]: undefined }));
|
||||||
@@ -246,14 +260,17 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
id="ssh_port"
|
id="ssh_port"
|
||||||
|
inputMode="numeric"
|
||||||
|
pattern="[0-9]*"
|
||||||
|
autoComplete="off"
|
||||||
value={formData.ssh_port ?? 22}
|
value={formData.ssh_port ?? 22}
|
||||||
onChange={handleChange('ssh_port')}
|
onChange={handleChange('ssh_port')}
|
||||||
className={`w-full px-3 py-2 border rounded-md shadow-sm bg-card text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring ${
|
className={`w-full px-3 py-2 border rounded-md shadow-sm bg-card text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring ${
|
||||||
errors.ssh_port ? 'border-destructive' : 'border-border'
|
errors.ssh_port ? 'border-destructive' : 'border-border'
|
||||||
}`}
|
}`}
|
||||||
placeholder="22"
|
placeholder="22"
|
||||||
min="1"
|
min={1}
|
||||||
max="65535"
|
max={65535}
|
||||||
/>
|
/>
|
||||||
{errors.ssh_port && <p className="mt-1 text-sm text-destructive">{errors.ssh_port}</p>}
|
{errors.ssh_port && <p className="mt-1 text-sm text-destructive">{errors.ssh_port}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class DatabaseServicePrisma {
|
|||||||
// Server CRUD operations
|
// Server CRUD operations
|
||||||
async createServer(serverData: CreateServerData) {
|
async createServer(serverData: CreateServerData) {
|
||||||
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
||||||
|
const normalizedPort = ssh_port !== undefined ? parseInt(String(ssh_port), 10) : 22;
|
||||||
|
|
||||||
let ssh_key_path = null;
|
let ssh_key_path = null;
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ class DatabaseServicePrisma {
|
|||||||
auth_type: auth_type ?? 'password',
|
auth_type: auth_type ?? 'password',
|
||||||
ssh_key,
|
ssh_key,
|
||||||
ssh_key_passphrase,
|
ssh_key_passphrase,
|
||||||
ssh_port: ssh_port ?? 22,
|
ssh_port: Number.isNaN(normalizedPort) ? 22 : normalizedPort,
|
||||||
ssh_key_path,
|
ssh_key_path,
|
||||||
key_generated: Boolean(key_generated),
|
key_generated: Boolean(key_generated),
|
||||||
color,
|
color,
|
||||||
@@ -60,6 +61,7 @@ class DatabaseServicePrisma {
|
|||||||
|
|
||||||
async updateServer(id: number, serverData: CreateServerData) {
|
async updateServer(id: number, serverData: CreateServerData) {
|
||||||
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData;
|
||||||
|
const normalizedPort = ssh_port !== undefined ? parseInt(String(ssh_port), 10) : undefined;
|
||||||
|
|
||||||
// Get existing server to check for key changes
|
// Get existing server to check for key changes
|
||||||
const existingServer = await this.getServerById(id);
|
const existingServer = await this.getServerById(id);
|
||||||
@@ -109,7 +111,7 @@ class DatabaseServicePrisma {
|
|||||||
auth_type: auth_type ?? 'password',
|
auth_type: auth_type ?? 'password',
|
||||||
ssh_key,
|
ssh_key,
|
||||||
ssh_key_passphrase,
|
ssh_key_passphrase,
|
||||||
ssh_port: ssh_port ?? 22,
|
ssh_port: normalizedPort ?? 22,
|
||||||
ssh_key_path,
|
ssh_key_path,
|
||||||
key_generated: key_generated !== undefined ? Boolean(key_generated) : (existingServer?.key_generated ?? false),
|
key_generated: key_generated !== undefined ? Boolean(key_generated) : (existingServer?.key_generated ?? false),
|
||||||
color,
|
color,
|
||||||
|
|||||||
Reference in New Issue
Block a user