CH-J Server ManagerSerververwaltung über SSH
Menü
Veröffentlichte Quellen

CH-J Server Manager

Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.

Quellen als ZIP herunterladen
CH-J Proprietary Software License 1.14

Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.

5,6 KB · 147 ZeilenDatei herunterladen
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 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 || null
42 };
45function hostKeyId(host, port) {
46 return `${normalizeHost(host)}:${normalizePort(port)}`;
49function 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;
55function 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;
63class 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.profiles
72 .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 }
147module.exports = { ProfileService, hostKeyId, normalizeHost, normalizePort, normalizeProfile, validateStoredPassword };

SHA-256: 481effd92acbcd039a63c1f04042d490aafdf6e614254c8e5f1b866ea8008b9a

SHA-256 des Archivs: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0