Vydané zdroje
Stáhnout zdroje ZIP
CH-J Server Manager
Procházejte adresáře a soubory konkrétního vydání aplikace.
Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.
1
"use strict";3
const crypto = require("node:crypto");4
const net = require("node:net");6
const HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*$/;8
function normalizeHost(value) {9
const host = String(value || "").trim();10
if (!host || (!net.isIP(host) && !HOSTNAME_PATTERN.test(host))) throw new Error("Invalid server host name or IP address.");11
return host.toLowerCase();12
}14
function normalizePort(value) {15
const port = Number(value || 22);16
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("SSH port must be between 1 and 65535.");17
return port;18
}20
function normalizeProfile(input, existing = null) {21
const host = normalizeHost(input.host);22
const username = String(input.username || "").trim();23
if (!username || username.length > 128 || /[\0\r\n]/.test(username)) throw new Error("Invalid SSH username.");24
const authMethod = input.authMethod === "privateKey" ? "privateKey" : "password";25
const privateKeyPath = String(input.privateKeyPath || "").trim();26
if (authMethod === "privateKey" && (!privateKeyPath || /[\0\r\n]/.test(privateKeyPath))) {27
throw new Error("Select a private key file.");28
}29
const now = new Date().toISOString();30
return {31
id: existing?.id || crypto.randomUUID(),32
label: String(input.label || host).trim().slice(0, 100) || host,33
host,34
port: normalizePort(input.port),35
username,36
authMethod,37
privateKeyPath: authMethod === "privateKey" ? privateKeyPath : "",38
tags: Array.isArray(input.tags) ? [...new Set(input.tags.map((tag) => String(tag).trim()).filter(Boolean))].slice(0, 20) : [],39
createdAt: existing?.createdAt || now,40
updatedAt: now,41
lastUsedAt: existing?.lastUsedAt || null42
};43
}45
function hostKeyId(host, port) {46
return `${normalizeHost(host)}:${normalizePort(port)}`;47
}49
function validateStoredPassword(value) {50
const password = String(value ?? "");51
if (!password || password.length > 1024 || password.includes("\0")) throw new Error("Stored SSH password is invalid.");52
return password;53
}55
function passwordStore(data) {56
if (!data.secrets || typeof data.secrets !== "object" || Array.isArray(data.secrets)) data.secrets = {};57
if (!data.secrets.profilePasswords || typeof data.secrets.profilePasswords !== "object" || Array.isArray(data.secrets.profilePasswords)) {58
data.secrets.profilePasswords = {};59
}60
return data.secrets.profilePasswords;61
}63
class ProfileService {64
constructor(vaultStore) {65
this.vaultStore = vaultStore;66
}68
list() {69
const data = this.vaultStore.getData();70
const passwords = passwordStore(data);71
return data.profiles72
.map((profile) => ({ ...profile, hasStoredPassword: typeof passwords[profile.id] === "string" }))73
.sort((left, right) => (right.lastUsedAt || right.updatedAt).localeCompare(left.lastUsedAt || left.updatedAt));74
}76
get(id) {77
const data = this.vaultStore.getData();78
const profile = data.profiles.find((item) => item.id === id);79
if (!profile) throw new Error("Server profile was not found.");80
return { ...profile, hasStoredPassword: typeof passwordStore(data)[profile.id] === "string" };81
}83
save(input) {84
let saved;85
this.vaultStore.update((data) => {86
const index = data.profiles.findIndex((item) => item.id === input.id);87
const existing = index >= 0 ? data.profiles[index] : null;88
saved = normalizeProfile(input, existing);89
if (index >= 0) data.profiles[index] = saved;90
else data.profiles.push(saved);91
const passwords = passwordStore(data);92
if (saved.authMethod !== "password" || input.storePassword === false) {93
delete passwords[saved.id];94
} else if (input.storePassword === true) {95
const password = String(input.password ?? "");96
if (password) passwords[saved.id] = validateStoredPassword(password);97
else if (typeof passwords[saved.id] !== "string") throw new Error("Enter the SSH password to store it in the vault.");98
}99
});100
return this.get(saved.id);101
}103
delete(id) {104
let removed = false;105
this.vaultStore.update((data) => {106
const length = data.profiles.length;107
data.profiles = data.profiles.filter((item) => item.id !== id);108
delete passwordStore(data)[id];109
removed = length !== data.profiles.length;110
});111
return { removed };112
}114
markUsed(id) {115
this.vaultStore.update((data) => {116
const profile = data.profiles.find((item) => item.id === id);117
if (profile) profile.lastUsedAt = new Date().toISOString();118
});119
}121
getHostKey(host, port) {122
return this.vaultStore.getData().hostKeys[hostKeyId(host, port)] || null;123
}125
getStoredPassword(profileId) {126
const data = this.vaultStore.getData();127
if (!data.profiles.some((profile) => profile.id === profileId)) throw new Error("Server profile was not found.");128
const password = passwordStore(data)[profileId];129
return typeof password === "string" ? password : null;130
}132
trustHostKey(host, port, fingerprint) {133
const normalized = String(fingerprint || "").trim();134
if (!/^[a-f0-9]{64}$/i.test(normalized)) throw new Error("Invalid SHA-256 host fingerprint.");135
this.vaultStore.update((data) => {136
data.hostKeys[hostKeyId(host, port)] = normalized.toLowerCase();137
});138
return { host: normalizeHost(host), port: normalizePort(port), fingerprint: normalized.toLowerCase() };139
}141
removeHostKey(host, port) {142
this.vaultStore.update((data) => { delete data.hostKeys[hostKeyId(host, port)]; });143
return { removed: true };144
}145
}147
module.exports = { ProfileService, hostKeyId, normalizeHost, normalizePort, normalizeProfile, validateStoredPassword };SHA-256: 481effd92acbcd039a63c1f04042d490aafdf6e614254c8e5f1b866ea8008b9a
SHA-256 archivu: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0