CH-J Server Managerspráva serverov nad SSH
Menu
Uverejnený zdroj

CH-J Server Manager

Prechádzať adresáre a súbory pre konkrétne vydanie aplikácie.

Stiahnuť zdroj ZIP
CH-J / Server Manager 339 súbory
core-20261014T184258Z-3fbed0b93fdf Pridané: Dátum nezaznamenaný · Postavený:
Súbory
profileService.js Pridané: Dátum nezaznamenaný 5,6 KB · 148 riadky
Stiahnuť súbor
1"use strict";
3const crypto = require("node:crypto");
4const net = require("node:net");
6const 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])?))*$/;
8function 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();
14function 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;
20function 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 || null
43 };
46function hostKeyId(host, port) {
47 return `${normalizeHost(host)}:${normalizePort(port)}`;
50function 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;
56function 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;
64class 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.profiles
73 .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 }
148module.exports = { ProfileService, hostKeyId, normalizeHost, normalizePort, normalizeProfile, validateStoredPassword };
SHA-2562b172cbd66bbb6b87471112db6fb80ce96b1c6d81287c9dcb394ab1c7511a5b1

CH-J Proprietary Software License 1.14 · Zdroj sa poskytuje v rámci CH-J Proprietary Software License 1.14. Jeho dostupnosť nemení licenčné podmienky ani neposkytuje dodatočné práva.

Archív SHA-2563df08cf5e899262a6ce92d7ec2caf5c42b683f1f2b57055662b845ff7dde67a8