Objavljen vir
Prenos vir ZIP
CH-J Server Manager
Brskajte po imenikih in datotekah za določeno izdajo programa.
CH-J / Server Manager
339 datoteke
core-20261014T184258Z-3fbed0b93fdf
Dodano: Datum, ki ni zabeležen · Zgrajena:
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
normalizeHost(input.host);22
const host = String(input.host).trim();23
const username = String(input.username || "").trim();24
if (!username || username.length > 128 || /[\0\r\n]/.test(username)) throw new Error("Invalid SSH username.");25
const authMethod = input.authMethod === "privateKey" ? "privateKey" : "password";26
const privateKeyPath = String(input.privateKeyPath || "").trim();27
if (authMethod === "privateKey" && (!privateKeyPath || /[\0\r\n]/.test(privateKeyPath))) {28
throw new Error("Select a private key file.");29
}30
const now = new Date().toISOString();31
return {32
id: existing?.id || crypto.randomUUID(),33
label: String(input.label || host).trim().slice(0, 100) || host,34
host,35
port: normalizePort(input.port),36
username,37
authMethod,38
privateKeyPath: authMethod === "privateKey" ? privateKeyPath : "",39
tags: Array.isArray(input.tags) ? [...new Set(input.tags.map((tag) => String(tag).trim()).filter(Boolean))].slice(0, 20) : [],40
createdAt: existing?.createdAt || now,41
updatedAt: now,42
lastUsedAt: existing?.lastUsedAt || null43
};44
}46
function hostKeyId(host, port) {47
return `${normalizeHost(host)}:${normalizePort(port)}`;48
}50
function validateStoredPassword(value) {51
const password = String(value ?? "");52
if (!password || password.length > 1024 || password.includes("\0")) throw new Error("Stored SSH password is invalid.");53
return password;54
}56
function passwordStore(data) {57
if (!data.secrets || typeof data.secrets !== "object" || Array.isArray(data.secrets)) data.secrets = {};58
if (!data.secrets.profilePasswords || typeof data.secrets.profilePasswords !== "object" || Array.isArray(data.secrets.profilePasswords)) {59
data.secrets.profilePasswords = {};60
}61
return data.secrets.profilePasswords;62
}64
class ProfileService {65
constructor(vaultStore) {66
this.vaultStore = vaultStore;67
}69
list() {70
const data = this.vaultStore.getData();71
const passwords = passwordStore(data);72
return data.profiles73
.map((profile) => ({ ...profile, hasStoredPassword: typeof passwords[profile.id] === "string" }))74
.sort((left, right) => (right.lastUsedAt || right.updatedAt).localeCompare(left.lastUsedAt || left.updatedAt));75
}77
get(id) {78
const data = this.vaultStore.getData();79
const profile = data.profiles.find((item) => item.id === id);80
if (!profile) throw new Error("Server profile was not found.");81
return { ...profile, hasStoredPassword: typeof passwordStore(data)[profile.id] === "string" };82
}84
save(input) {85
let saved;86
this.vaultStore.update((data) => {87
const index = data.profiles.findIndex((item) => item.id === input.id);88
const existing = index >= 0 ? data.profiles[index] : null;89
saved = normalizeProfile(input, existing);90
if (index >= 0) data.profiles[index] = saved;91
else data.profiles.push(saved);92
const passwords = passwordStore(data);93
if (saved.authMethod !== "password" || input.storePassword === false) {94
delete passwords[saved.id];95
} else if (input.storePassword === true) {96
const password = String(input.password ?? "");97
if (password) passwords[saved.id] = validateStoredPassword(password);98
else if (typeof passwords[saved.id] !== "string") throw new Error("Enter the SSH password to store it in the vault.");99
}100
});101
return this.get(saved.id);102
}104
delete(id) {105
let removed = false;106
this.vaultStore.update((data) => {107
const length = data.profiles.length;108
data.profiles = data.profiles.filter((item) => item.id !== id);109
delete passwordStore(data)[id];110
removed = length !== data.profiles.length;111
});112
return { removed };113
}115
markUsed(id) {116
this.vaultStore.update((data) => {117
const profile = data.profiles.find((item) => item.id === id);118
if (profile) profile.lastUsedAt = new Date().toISOString();119
});120
}122
getHostKey(host, port) {123
return this.vaultStore.getData().hostKeys[hostKeyId(host, port)] || null;124
}126
getStoredPassword(profileId) {127
const data = this.vaultStore.getData();128
if (!data.profiles.some((profile) => profile.id === profileId)) throw new Error("Server profile was not found.");129
const password = passwordStore(data)[profileId];130
return typeof password === "string" ? password : null;131
}133
trustHostKey(host, port, fingerprint) {134
const normalized = String(fingerprint || "").trim();135
if (!/^[a-f0-9]{64}$/i.test(normalized)) throw new Error("Invalid SHA-256 host fingerprint.");136
this.vaultStore.update((data) => {137
data.hostKeys[hostKeyId(host, port)] = normalized.toLowerCase();138
});139
return { host: normalizeHost(host), port: normalizePort(port), fingerprint: normalized.toLowerCase() };140
}142
removeHostKey(host, port) {143
this.vaultStore.update((data) => { delete data.hostKeys[hostKeyId(host, port)]; });144
return { removed: true };145
}146
}148
module.exports = { ProfileService, hostKeyId, normalizeHost, normalizePort, normalizeProfile, validateStoredPassword };SHA-256
2b172cbd66bbb6b87471112db6fb80ce96b1c6d81287c9dcb394ab1c7511a5b1CH-J Proprietary Software License 1.14 · Vir je na voljo pod CH-J Proprietary Software License 1.14. Njena razpoložljivost ne spreminja pogojev licence ali dodeljuje dodatnih pravic.
Arhiv SHA-256
3df08cf5e899262a6ce92d7ec2caf5c42b683f1f2b57055662b845ff7dde67a8