@termix-ssh/plugin-sdk 1.0.12 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/PLUGIN-GUIDE.md +106 -59
- package/dist/backend.d.ts +534 -1
- package/dist/backend.js +7 -0
- package/dist/capabilities.js +7 -0
- package/dist/db.js +1 -0
- package/dist/desktop.d.ts +0 -243
- package/dist/frontend.d.ts +23 -2
- package/dist/host/{PluginsTab-EV4MSGY6.js → PluginsTab-S33WIIGC.js} +511 -178
- package/dist/host/{chunk-AWR6FYDO.js → chunk-2IV3VX7F.js} +5552 -5550
- package/dist/host/{chunk-RUPLD6RF.js → chunk-3L45NNT5.js} +29 -84
- package/dist/host/{chunk-VEWOYAMZ.js → chunk-57DJU2ZA.js} +1 -1
- package/dist/host/{chunk-XKA3DBB2.js → chunk-V44FCJBQ.js} +135 -54
- package/dist/host/{chunk-X4QZKO5G.js → chunk-VFTB2RDP.js} +54 -5
- package/dist/host/core-classes.json +1 -1
- package/dist/host/{linked-server-B4NEG4MA.js → linked-server-SCJH7DSL.js} +1 -1
- package/dist/host/{rbac-api-OUO3LUOC.js → rbac-api-KA5IPQQL.js} +1 -1
- package/dist/host/testing-host.js +4 -4
- package/dist/host/types/src/types/index.d.ts +10 -0
- package/dist/host/types/src/types/onboarding.d.ts +0 -2
- package/dist/host/types/src/types/ui-types.d.ts +3 -1
- package/dist/host/types/src/ui/api/audit-log-api.d.ts +0 -10
- package/dist/host/types/src/ui/api/auth-methods-api.d.ts +2 -0
- package/dist/host/types/src/ui/api/credentials-api.d.ts +0 -22
- package/dist/host/types/src/ui/api/plugins-api.d.ts +4 -0
- package/dist/host/types/src/ui/api/settings-api.d.ts +6 -6
- package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +6 -28
- package/dist/host/types/src/ui/main-axios.d.ts +3 -3
- package/dist/host/types/src/ui/onboarding/onboarding-registry.d.ts +3 -1
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +6 -7
- package/dist/host/types/src/ui/plugins/plugin-model.d.ts +20 -0
- package/dist/host/types/src/ui/plugins/use-plugins-manager.d.ts +3 -0
- package/dist/host/types/src/ui/sidebar/AuthEnrollmentSections.d.ts +4 -1
- package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +10 -0
- package/dist/host/ui.js +36 -210
- package/dist/host/{use-permissions-6VSHHUMB.js → use-permissions-LLN6TLAL.js} +1 -1
- package/dist/manifest.d.ts +10 -1
- package/dist/manifest.js +16 -1
- package/dist/ssh-keys.d.ts +29 -0
- package/dist/ssh-keys.js +85 -0
- package/dist/testing.d.ts +52 -15
- package/dist/testing.js +279 -2
- package/dist/vitest-preset.js +23 -0
- package/package.json +5 -1
- package/schema/manifest.schema.json +12 -0
- package/dist/host/types/src/ui/sidebar/host-export-payload.d.ts +0 -30
package/dist/testing.js
CHANGED
|
@@ -100,7 +100,7 @@ function emptyHostRecord(id, userId) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
function applyHostInput(record, input) {
|
|
103
|
-
const { tags, pluginSettings: _settings, forceKeyboardInteractive: _fki, ...rest } = input;
|
|
103
|
+
const { tags, pluginSettings: _settings, pluginEnable: _pluginEnable, forceKeyboardInteractive: _fki, password: _password, key: _key, keyPassword: _keyPassword, keyType: _keyType, sudoPassword: _sudoPassword, protocolAuth: _protocolAuth, ...rest } = input;
|
|
104
104
|
const next = { ...record };
|
|
105
105
|
for (const [key, value] of Object.entries(rest)) {
|
|
106
106
|
if (value !== undefined)
|
|
@@ -124,6 +124,9 @@ export function createFakeContext(options = {}) {
|
|
|
124
124
|
const pluginId = options.pluginId ?? "test-plugin";
|
|
125
125
|
const disposals = [];
|
|
126
126
|
const emitted = [];
|
|
127
|
+
const hostStatusProviders = [];
|
|
128
|
+
const activityProviders = [];
|
|
129
|
+
const hostLockProviders = [];
|
|
127
130
|
const kv = new Map();
|
|
128
131
|
const tables = [];
|
|
129
132
|
const syncEntities = [];
|
|
@@ -136,6 +139,7 @@ export function createFakeContext(options = {}) {
|
|
|
136
139
|
const settingsValidators = new Set();
|
|
137
140
|
const sshConnections = [];
|
|
138
141
|
const hostShares = [];
|
|
142
|
+
const hostShareSets = [];
|
|
139
143
|
const desktopWindows = [];
|
|
140
144
|
const externalClientLaunches = [];
|
|
141
145
|
const credentialReads = [];
|
|
@@ -158,6 +162,7 @@ export function createFakeContext(options = {}) {
|
|
|
158
162
|
};
|
|
159
163
|
const secretStore = new Map();
|
|
160
164
|
const audits = [];
|
|
165
|
+
const auditSubscribers = new Set();
|
|
161
166
|
const activities = [];
|
|
162
167
|
const trackedSessions = [];
|
|
163
168
|
const interactions = [];
|
|
@@ -195,6 +200,7 @@ export function createFakeContext(options = {}) {
|
|
|
195
200
|
revokedSessions: [],
|
|
196
201
|
loginFailures: new Map(),
|
|
197
202
|
userTokens: [],
|
|
203
|
+
policies: [],
|
|
198
204
|
};
|
|
199
205
|
const sshClient = options.sshClient ?? {};
|
|
200
206
|
let actor = options.actor;
|
|
@@ -261,7 +267,7 @@ export function createFakeContext(options = {}) {
|
|
|
261
267
|
files: {
|
|
262
268
|
// A fake path; a test that needs a real folder uses the OS temp dir
|
|
263
269
|
// itself, not this double.
|
|
264
|
-
dataDir: async () => `/tmp/${pluginId}`,
|
|
270
|
+
dataDir: async () => options.dataDir ?? `/tmp/${pluginId}`,
|
|
265
271
|
},
|
|
266
272
|
db: options.db
|
|
267
273
|
? {
|
|
@@ -461,6 +467,27 @@ export function createFakeContext(options = {}) {
|
|
|
461
467
|
},
|
|
462
468
|
disposables,
|
|
463
469
|
hosts: {
|
|
470
|
+
importMany: async (rows) => {
|
|
471
|
+
const created = rows.map(() => nextHostId++);
|
|
472
|
+
return {
|
|
473
|
+
success: created.length,
|
|
474
|
+
updated: 0,
|
|
475
|
+
skipped: 0,
|
|
476
|
+
failed: 0,
|
|
477
|
+
errors: [],
|
|
478
|
+
createdHostIds: created,
|
|
479
|
+
};
|
|
480
|
+
},
|
|
481
|
+
importSshConfig: async () => ({
|
|
482
|
+
success: 0,
|
|
483
|
+
updated: 0,
|
|
484
|
+
skipped: 0,
|
|
485
|
+
failed: 0,
|
|
486
|
+
errors: [],
|
|
487
|
+
createdHostIds: [],
|
|
488
|
+
}),
|
|
489
|
+
exportMany: async () => ({ hosts: [] }),
|
|
490
|
+
registerImportNormalizer: () => () => { },
|
|
464
491
|
list: async () => [...hostsById.values()],
|
|
465
492
|
get: async (hostId) => hostsById.get(hostId) ?? null,
|
|
466
493
|
checkAccess: async (hostId) => {
|
|
@@ -502,6 +529,10 @@ export function createFakeContext(options = {}) {
|
|
|
502
529
|
hostShares.push({ hostId, targets, permissionLevel, durationHours });
|
|
503
530
|
return { hostId, shared: true };
|
|
504
531
|
},
|
|
532
|
+
setShares: async (hostId, grants) => {
|
|
533
|
+
hostShareSets.push({ hostId, grants: [...grants] });
|
|
534
|
+
return { hostId, shared: true };
|
|
535
|
+
},
|
|
505
536
|
listUsers: async () => options.shareableUsers ?? [],
|
|
506
537
|
listRoles: async () => options.shareableRoles ?? [],
|
|
507
538
|
trackSession: (hostId) => {
|
|
@@ -519,7 +550,37 @@ export function createFakeContext(options = {}) {
|
|
|
519
550
|
recordActivity: async (hostId, type, hostName) => {
|
|
520
551
|
activities.push({ hostId, type, hostName });
|
|
521
552
|
},
|
|
553
|
+
probeTargets: async (filter) => (options.probeTargets ?? []).filter((target) => !filter?.hostIds || filter.hostIds.includes(target.id)),
|
|
554
|
+
sessionActive: (hostId) => options.activeSessions?.includes(hostId) ?? false,
|
|
555
|
+
activity: {
|
|
556
|
+
provide: (provider) => {
|
|
557
|
+
activityProviders.push(provider);
|
|
558
|
+
return () => {
|
|
559
|
+
const at = activityProviders.indexOf(provider);
|
|
560
|
+
if (at >= 0)
|
|
561
|
+
activityProviders.splice(at, 1);
|
|
562
|
+
};
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
locks: {
|
|
566
|
+
provide: (provider) => {
|
|
567
|
+
hostLockProviders.push(provider);
|
|
568
|
+
return () => {
|
|
569
|
+
const at = hostLockProviders.indexOf(provider);
|
|
570
|
+
if (at >= 0)
|
|
571
|
+
hostLockProviders.splice(at, 1);
|
|
572
|
+
};
|
|
573
|
+
},
|
|
574
|
+
},
|
|
522
575
|
status: {
|
|
576
|
+
provide: (provider) => {
|
|
577
|
+
hostStatusProviders.push(provider);
|
|
578
|
+
return () => {
|
|
579
|
+
const at = hostStatusProviders.indexOf(provider);
|
|
580
|
+
if (at >= 0)
|
|
581
|
+
hostStatusProviders.splice(at, 1);
|
|
582
|
+
};
|
|
583
|
+
},
|
|
523
584
|
get: async (hostId) => hostStatuses.get(hostId) ?? null,
|
|
524
585
|
check: async (hostId) => hostStatuses.get(hostId) ?? null,
|
|
525
586
|
reportLogin: (hostId, outcome) => {
|
|
@@ -543,11 +604,36 @@ export function createFakeContext(options = {}) {
|
|
|
543
604
|
after: (delayMs, fn) => schedule("after", delayMs, fn),
|
|
544
605
|
},
|
|
545
606
|
audit: {
|
|
607
|
+
subscribe: (handler) => {
|
|
608
|
+
auditSubscribers.add(handler);
|
|
609
|
+
return () => {
|
|
610
|
+
auditSubscribers.delete(handler);
|
|
611
|
+
};
|
|
612
|
+
},
|
|
613
|
+
list: async (listOptions) => {
|
|
614
|
+
const matching = filterAuditEntries(options.auditEntries ?? [], listOptions?.filters).reverse();
|
|
615
|
+
const offset = listOptions?.offset ?? 0;
|
|
616
|
+
return {
|
|
617
|
+
entries: matching.slice(offset, offset + (listOptions?.limit ?? 50)),
|
|
618
|
+
total: matching.length,
|
|
619
|
+
};
|
|
620
|
+
},
|
|
621
|
+
exportBatches: async function* (filters, batchSize) {
|
|
622
|
+
const matching = filterAuditEntries(options.auditEntries ?? [], filters);
|
|
623
|
+
const size = batchSize ?? 500;
|
|
624
|
+
for (let at = 0; at < matching.length; at += size) {
|
|
625
|
+
yield matching.slice(at, at + size);
|
|
626
|
+
}
|
|
627
|
+
},
|
|
546
628
|
record: async (entry) => {
|
|
547
629
|
audits.push({ ...entry });
|
|
548
630
|
},
|
|
549
631
|
},
|
|
632
|
+
network: {
|
|
633
|
+
tcpProbe: async (host, port) => options.reachable?.(host, port) ?? false,
|
|
634
|
+
},
|
|
550
635
|
ssh: {
|
|
636
|
+
probe: async (hostId, probeOptions) => options.reachable?.(hostId, probeOptions?.port) ?? false,
|
|
551
637
|
connect: async (host, connectOptions) => {
|
|
552
638
|
sshConnections.push({ host, options: connectOptions });
|
|
553
639
|
return {
|
|
@@ -559,6 +645,16 @@ export function createFakeContext(options = {}) {
|
|
|
559
645
|
dispose: () => { },
|
|
560
646
|
};
|
|
561
647
|
},
|
|
648
|
+
exec: async (host) => {
|
|
649
|
+
sshConnections.push({ host });
|
|
650
|
+
return {
|
|
651
|
+
stdout: "",
|
|
652
|
+
stderr: "",
|
|
653
|
+
code: 0,
|
|
654
|
+
truncated: false,
|
|
655
|
+
timedOut: false,
|
|
656
|
+
};
|
|
657
|
+
},
|
|
562
658
|
withConnection: async (host, connectOptions, fn) => {
|
|
563
659
|
sshConnections.push({ host, pool: connectOptions.pool });
|
|
564
660
|
return fn(sshClient);
|
|
@@ -682,6 +778,23 @@ export function createFakeContext(options = {}) {
|
|
|
682
778
|
expiresAt: new Date(Date.now() + ttl * 1000).toISOString(),
|
|
683
779
|
};
|
|
684
780
|
},
|
|
781
|
+
policy: {
|
|
782
|
+
provide: (provider) => {
|
|
783
|
+
auth.policies.push(provider);
|
|
784
|
+
return () => {
|
|
785
|
+
const at = auth.policies.indexOf(provider);
|
|
786
|
+
if (at >= 0)
|
|
787
|
+
auth.policies.splice(at, 1);
|
|
788
|
+
};
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
listSecondFactors: () => [
|
|
792
|
+
...auth.secondFactors.map((factor) => ({
|
|
793
|
+
id: factor.id,
|
|
794
|
+
pluginId,
|
|
795
|
+
})),
|
|
796
|
+
...(options.secondFactors ?? []),
|
|
797
|
+
],
|
|
685
798
|
},
|
|
686
799
|
users: {
|
|
687
800
|
getLocale: async (userId) => options.locales?.[userId] ?? null,
|
|
@@ -696,8 +809,10 @@ export function createFakeContext(options = {}) {
|
|
|
696
809
|
return { success: true };
|
|
697
810
|
},
|
|
698
811
|
available: () => options.desktopAvailable ?? false,
|
|
812
|
+
local: options.desktopLocal ?? unavailableDesktopLocal(),
|
|
699
813
|
},
|
|
700
814
|
credentials: {
|
|
815
|
+
list: async () => [...(options.credentialSummaries ?? [])],
|
|
701
816
|
listSshKeys: async () => [
|
|
702
817
|
...(options.sshKeyCredentials ?? []),
|
|
703
818
|
...createdSshKeys.map((key) => ({
|
|
@@ -716,6 +831,12 @@ export function createFakeContext(options = {}) {
|
|
|
716
831
|
credentialReads.push({ hostId, protocol });
|
|
717
832
|
return options.protocolTargets?.[`${hostId}:${protocol}`] ?? null;
|
|
718
833
|
},
|
|
834
|
+
publicKey: async (credentialId) => (options.sshKeyCredentials ?? []).find((k) => k.id === credentialId)
|
|
835
|
+
?.publicKey ?? null,
|
|
836
|
+
resolveKeyRefs: async (key, passphrase) => ({
|
|
837
|
+
key,
|
|
838
|
+
passphrase: passphrase ?? undefined,
|
|
839
|
+
}),
|
|
719
840
|
registerSecretResolver: (scheme, resolve) => {
|
|
720
841
|
auth.secretResolvers.set(scheme, resolve);
|
|
721
842
|
disposals.push(() => {
|
|
@@ -863,6 +984,9 @@ export function createFakeContext(options = {}) {
|
|
|
863
984
|
return errors;
|
|
864
985
|
},
|
|
865
986
|
disposals,
|
|
987
|
+
hostStatusProviders,
|
|
988
|
+
activityProviders,
|
|
989
|
+
hostLockProviders,
|
|
866
990
|
emitted,
|
|
867
991
|
kv,
|
|
868
992
|
tables,
|
|
@@ -873,6 +997,7 @@ export function createFakeContext(options = {}) {
|
|
|
873
997
|
coreSettings,
|
|
874
998
|
sshConnections,
|
|
875
999
|
hostShares,
|
|
1000
|
+
hostShareSets,
|
|
876
1001
|
auth,
|
|
877
1002
|
desktopWindows,
|
|
878
1003
|
externalClientLaunches,
|
|
@@ -886,6 +1011,10 @@ export function createFakeContext(options = {}) {
|
|
|
886
1011
|
tls,
|
|
887
1012
|
secretStore,
|
|
888
1013
|
audits,
|
|
1014
|
+
emitAudit: (entry) => {
|
|
1015
|
+
for (const handler of auditSubscribers)
|
|
1016
|
+
handler(entry);
|
|
1017
|
+
},
|
|
889
1018
|
activities,
|
|
890
1019
|
trackedSessions,
|
|
891
1020
|
interactions,
|
|
@@ -930,6 +1059,49 @@ export function createFakeContext(options = {}) {
|
|
|
930
1059
|
* (users:impersonate on issueUserToken), and users:read on ctx.users. Reading a plugin's own settings is deliberately
|
|
931
1060
|
* ungated. As the SDK grows a member, add its gate here in the same shape.
|
|
932
1061
|
*/
|
|
1062
|
+
function filterAuditEntries(entries, filters) {
|
|
1063
|
+
return entries.filter((entry) => (filters?.userId === undefined || entry.userId === filters.userId) &&
|
|
1064
|
+
(filters?.action === undefined || entry.action === filters.action) &&
|
|
1065
|
+
(filters?.resourceType === undefined ||
|
|
1066
|
+
entry.resourceType === filters.resourceType) &&
|
|
1067
|
+
(filters?.success === undefined || entry.success === filters.success) &&
|
|
1068
|
+
(filters?.startDate === undefined ||
|
|
1069
|
+
entry.timestamp >= filters.startDate) &&
|
|
1070
|
+
(filters?.endDate === undefined || entry.timestamp <= filters.endDate));
|
|
1071
|
+
}
|
|
1072
|
+
function gateDesktopLocal(target, check) {
|
|
1073
|
+
const wrap = (value) => {
|
|
1074
|
+
if (typeof value === "function") {
|
|
1075
|
+
return (...args) => {
|
|
1076
|
+
check();
|
|
1077
|
+
return value(...args);
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
if (value && typeof value === "object") {
|
|
1081
|
+
return new Proxy(value, {
|
|
1082
|
+
get: (obj, key) => wrap(obj[key]),
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
return value;
|
|
1086
|
+
};
|
|
1087
|
+
return new Proxy(target, {
|
|
1088
|
+
get: (obj, key) => key === "available"
|
|
1089
|
+
? obj.available
|
|
1090
|
+
: wrap(obj[key]),
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
function unavailableDesktopLocal() {
|
|
1094
|
+
const fail = () => {
|
|
1095
|
+
throw new Error("ctx.desktop.local needs the desktopLocal test option");
|
|
1096
|
+
};
|
|
1097
|
+
const deep = () => new Proxy(() => fail(), { get: () => deep(), apply: () => fail() });
|
|
1098
|
+
return {
|
|
1099
|
+
available: () => false,
|
|
1100
|
+
info: fail,
|
|
1101
|
+
fs: deep(),
|
|
1102
|
+
pty: deep(),
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
933
1105
|
export function createMockCtx(options = {}) {
|
|
934
1106
|
const granted = new Set(options.capabilities ?? []);
|
|
935
1107
|
const checked = [];
|
|
@@ -938,6 +1110,7 @@ export function createMockCtx(options = {}) {
|
|
|
938
1110
|
actor: options.actor,
|
|
939
1111
|
settings: options.settings,
|
|
940
1112
|
coreSettings: options.coreSettings,
|
|
1113
|
+
dataDir: options.dataDir,
|
|
941
1114
|
sshClient: options.sshClient,
|
|
942
1115
|
db: options.db,
|
|
943
1116
|
router: options.router,
|
|
@@ -949,8 +1122,14 @@ export function createMockCtx(options = {}) {
|
|
|
949
1122
|
services: options.services,
|
|
950
1123
|
protocolTargets: options.protocolTargets,
|
|
951
1124
|
sshKeyCredentials: options.sshKeyCredentials,
|
|
1125
|
+
credentialSummaries: options.credentialSummaries,
|
|
952
1126
|
desktopAvailable: options.desktopAvailable,
|
|
1127
|
+
desktopLocal: options.desktopLocal,
|
|
1128
|
+
auditEntries: options.auditEntries,
|
|
953
1129
|
hostStatuses: options.hostStatuses,
|
|
1130
|
+
probeTargets: options.probeTargets,
|
|
1131
|
+
activeSessions: options.activeSessions,
|
|
1132
|
+
reachable: options.reachable,
|
|
954
1133
|
notificationChannels: options.notificationChannels,
|
|
955
1134
|
fetch: options.fetch,
|
|
956
1135
|
process: options.process,
|
|
@@ -961,6 +1140,7 @@ export function createMockCtx(options = {}) {
|
|
|
961
1140
|
baseUrl: options.baseUrl,
|
|
962
1141
|
linkedUsers: options.linkedUsers,
|
|
963
1142
|
loginAttemptLimit: options.loginAttemptLimit,
|
|
1143
|
+
secondFactors: options.secondFactors,
|
|
964
1144
|
locales: options.locales,
|
|
965
1145
|
manifest: {
|
|
966
1146
|
capabilities: options.capabilities ?? [],
|
|
@@ -1077,6 +1257,24 @@ export function createMockCtx(options = {}) {
|
|
|
1077
1257
|
},
|
|
1078
1258
|
},
|
|
1079
1259
|
hosts: {
|
|
1260
|
+
importMany: async (rows, importOptions) => {
|
|
1261
|
+
require("hosts:write");
|
|
1262
|
+
if (importOptions?.credentials?.length)
|
|
1263
|
+
require("credentials:write");
|
|
1264
|
+
return ctx.hosts.importMany(rows, importOptions);
|
|
1265
|
+
},
|
|
1266
|
+
importSshConfig: async (content, importOptions) => {
|
|
1267
|
+
require("hosts:write");
|
|
1268
|
+
return ctx.hosts.importSshConfig(content, importOptions);
|
|
1269
|
+
},
|
|
1270
|
+
exportMany: async (exportOptions) => {
|
|
1271
|
+
require("hosts:export");
|
|
1272
|
+
return ctx.hosts.exportMany(exportOptions);
|
|
1273
|
+
},
|
|
1274
|
+
registerImportNormalizer: (normalizer) => {
|
|
1275
|
+
require("hosts:write");
|
|
1276
|
+
return ctx.hosts.registerImportNormalizer(normalizer);
|
|
1277
|
+
},
|
|
1080
1278
|
list: async () => {
|
|
1081
1279
|
require("hosts:read");
|
|
1082
1280
|
return ctx.hosts.list();
|
|
@@ -1109,6 +1307,10 @@ export function createMockCtx(options = {}) {
|
|
|
1109
1307
|
require("hosts:write");
|
|
1110
1308
|
return ctx.hosts.share(hostId, targets, permissionLevel, durationHours);
|
|
1111
1309
|
},
|
|
1310
|
+
setShares: async (hostId, grants) => {
|
|
1311
|
+
require("hosts:write");
|
|
1312
|
+
return ctx.hosts.setShares(hostId, grants);
|
|
1313
|
+
},
|
|
1112
1314
|
listUsers: async () => {
|
|
1113
1315
|
require("hosts:write");
|
|
1114
1316
|
return ctx.hosts.listUsers();
|
|
@@ -1125,7 +1327,32 @@ export function createMockCtx(options = {}) {
|
|
|
1125
1327
|
require("hosts:read");
|
|
1126
1328
|
return ctx.hosts.recordActivity(hostId, type, hostName);
|
|
1127
1329
|
},
|
|
1330
|
+
probeTargets: async (filter) => {
|
|
1331
|
+
require("hosts:read");
|
|
1332
|
+
return ctx.hosts.probeTargets(filter);
|
|
1333
|
+
},
|
|
1334
|
+
sessionActive: (hostId) => {
|
|
1335
|
+
require("hosts:read");
|
|
1336
|
+
return ctx.hosts.sessionActive(hostId);
|
|
1337
|
+
},
|
|
1338
|
+
activity: {
|
|
1339
|
+
provide: (provider) => {
|
|
1340
|
+
require("hosts:activity-provide");
|
|
1341
|
+
return ctx.hosts.activity.provide(provider);
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
1344
|
+
locks: {
|
|
1345
|
+
provide: (provider) => {
|
|
1346
|
+
require("hosts:lock");
|
|
1347
|
+
return ctx.hosts.locks.provide(provider);
|
|
1348
|
+
},
|
|
1349
|
+
},
|
|
1128
1350
|
status: {
|
|
1351
|
+
provide: (provider) => {
|
|
1352
|
+
require("hosts:read");
|
|
1353
|
+
require("hosts:status-provide");
|
|
1354
|
+
return ctx.hosts.status.provide(provider);
|
|
1355
|
+
},
|
|
1129
1356
|
get: async (hostId) => {
|
|
1130
1357
|
require("hosts:read");
|
|
1131
1358
|
return ctx.hosts.status.get(hostId);
|
|
@@ -1146,11 +1373,20 @@ export function createMockCtx(options = {}) {
|
|
|
1146
1373
|
},
|
|
1147
1374
|
ssh: {
|
|
1148
1375
|
...ctx.ssh,
|
|
1376
|
+
probe: async (hostId, probeOptions) => {
|
|
1377
|
+
require("ssh:connect");
|
|
1378
|
+
return ctx.ssh.probe(hostId, probeOptions);
|
|
1379
|
+
},
|
|
1149
1380
|
connect: async (host, connectOptions) => {
|
|
1150
1381
|
require("ssh:connect");
|
|
1151
1382
|
require("credentials:use");
|
|
1152
1383
|
return ctx.ssh.connect(host, connectOptions);
|
|
1153
1384
|
},
|
|
1385
|
+
exec: async (host, command, execOptions) => {
|
|
1386
|
+
require("ssh:connect");
|
|
1387
|
+
require("credentials:use");
|
|
1388
|
+
return ctx.ssh.exec(host, command, execOptions);
|
|
1389
|
+
},
|
|
1154
1390
|
withConnection: async (host, connectOptions, fn) => {
|
|
1155
1391
|
require("ssh:connect");
|
|
1156
1392
|
require("credentials:use");
|
|
@@ -1236,6 +1472,13 @@ export function createMockCtx(options = {}) {
|
|
|
1236
1472
|
require("users:impersonate");
|
|
1237
1473
|
return ctx.auth.issueUserToken(userId, tokenOptions);
|
|
1238
1474
|
},
|
|
1475
|
+
policy: {
|
|
1476
|
+
provide: (provider) => {
|
|
1477
|
+
require("auth:policy");
|
|
1478
|
+
return ctx.auth.policy.provide(provider);
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
listSecondFactors: () => ctx.auth.listSecondFactors(),
|
|
1239
1482
|
},
|
|
1240
1483
|
users: {
|
|
1241
1484
|
getLocale: async (userId) => {
|
|
@@ -1243,6 +1486,21 @@ export function createMockCtx(options = {}) {
|
|
|
1243
1486
|
return ctx.users.getLocale(userId);
|
|
1244
1487
|
},
|
|
1245
1488
|
},
|
|
1489
|
+
audit: {
|
|
1490
|
+
record: (entry) => ctx.audit.record(entry),
|
|
1491
|
+
subscribe: (handler) => {
|
|
1492
|
+
require("audit:read");
|
|
1493
|
+
return ctx.audit.subscribe(handler);
|
|
1494
|
+
},
|
|
1495
|
+
list: async (listOptions) => {
|
|
1496
|
+
require("audit:read");
|
|
1497
|
+
return ctx.audit.list(listOptions);
|
|
1498
|
+
},
|
|
1499
|
+
exportBatches: (filters, batchSize) => {
|
|
1500
|
+
require("audit:read");
|
|
1501
|
+
return ctx.audit.exportBatches(filters, batchSize);
|
|
1502
|
+
},
|
|
1503
|
+
},
|
|
1246
1504
|
desktop: {
|
|
1247
1505
|
openIsolatedWindow: async (request) => {
|
|
1248
1506
|
require("desktop:window");
|
|
@@ -1253,8 +1511,13 @@ export function createMockCtx(options = {}) {
|
|
|
1253
1511
|
return ctx.desktop.launchExternalClient(request);
|
|
1254
1512
|
},
|
|
1255
1513
|
available: () => ctx.desktop.available(),
|
|
1514
|
+
local: gateDesktopLocal(ctx.desktop.local, () => require("desktop:local")),
|
|
1256
1515
|
},
|
|
1257
1516
|
credentials: {
|
|
1517
|
+
list: async () => {
|
|
1518
|
+
require("credentials:use");
|
|
1519
|
+
return ctx.credentials.list();
|
|
1520
|
+
},
|
|
1258
1521
|
listSshKeys: async () => {
|
|
1259
1522
|
require("credentials:use");
|
|
1260
1523
|
return ctx.credentials.listSshKeys();
|
|
@@ -1271,6 +1534,14 @@ export function createMockCtx(options = {}) {
|
|
|
1271
1534
|
}
|
|
1272
1535
|
return ctx.credentials.resolveHostProtocol(hostId, protocol);
|
|
1273
1536
|
},
|
|
1537
|
+
publicKey: async (credentialId) => {
|
|
1538
|
+
require("credentials:use");
|
|
1539
|
+
return ctx.credentials.publicKey(credentialId);
|
|
1540
|
+
},
|
|
1541
|
+
resolveKeyRefs: async (key, passphrase) => {
|
|
1542
|
+
require("credentials:read");
|
|
1543
|
+
return ctx.credentials.resolveKeyRefs(key, passphrase);
|
|
1544
|
+
},
|
|
1274
1545
|
registerSecretResolver: (scheme, resolve) => {
|
|
1275
1546
|
require("auth:provide");
|
|
1276
1547
|
ctx.credentials.registerSecretResolver(scheme, resolve);
|
|
@@ -1294,6 +1565,12 @@ export function createMockCtx(options = {}) {
|
|
|
1294
1565
|
require("network:outbound");
|
|
1295
1566
|
return ctx.fetch(url, init);
|
|
1296
1567
|
},
|
|
1568
|
+
network: {
|
|
1569
|
+
tcpProbe: async (host, port, probeOptions) => {
|
|
1570
|
+
require("network:probe");
|
|
1571
|
+
return ctx.network.tcpProbe(host, port, probeOptions);
|
|
1572
|
+
},
|
|
1573
|
+
},
|
|
1297
1574
|
process: {
|
|
1298
1575
|
run: async (file, args, runOptions) => {
|
|
1299
1576
|
require("process:spawn");
|
package/dist/vitest-preset.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Two projects: tests/backend runs in node, tests/frontend in jsdom.
|
|
10
10
|
*/
|
|
11
|
+
import { createRequire } from "node:module";
|
|
11
12
|
import fs from "node:fs";
|
|
12
13
|
import path from "node:path";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
@@ -84,6 +85,28 @@ export function pluginVitestConfig(configUrl, options = {}) {
|
|
|
84
85
|
// Vite must be allowed to read it, and React must come from the plugin.
|
|
85
86
|
const sdkDir = fs.realpathSync(fileURLToPath(new URL("..", import.meta.url)));
|
|
86
87
|
const server = { fs: { allow: [pluginDir, sdkDir] } };
|
|
88
|
+
// dedupe alone is not enough: react-dom or the testing library loaded from
|
|
89
|
+
// the linked SDK's own node_modules pull in a second React, and hooks then
|
|
90
|
+
// see no renderer.
|
|
91
|
+
if (!sdkDir.startsWith(pluginDir)) {
|
|
92
|
+
const pluginRequire = createRequire(path.join(pluginDir, "package.json"));
|
|
93
|
+
for (const name of [
|
|
94
|
+
"react",
|
|
95
|
+
"react-dom",
|
|
96
|
+
"@testing-library/react",
|
|
97
|
+
"react-i18next",
|
|
98
|
+
"i18next",
|
|
99
|
+
]) {
|
|
100
|
+
if (alias[name])
|
|
101
|
+
continue;
|
|
102
|
+
try {
|
|
103
|
+
alias[name] = path.dirname(pluginRequire.resolve(`${name}/package.json`));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Not installed in the plugin: leave it to normal resolution.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
87
110
|
const resolve = {
|
|
88
111
|
alias,
|
|
89
112
|
dedupe: ["react", "react-dom", "react-i18next", "i18next"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@termix-ssh/plugin-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "The contract between Termix core and a plugin: types, the capability catalog and the manifest schema.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -85,6 +85,10 @@
|
|
|
85
85
|
"types": "./dist/ssh-certs.d.ts",
|
|
86
86
|
"default": "./dist/ssh-certs.js"
|
|
87
87
|
},
|
|
88
|
+
"./ssh-keys": {
|
|
89
|
+
"types": "./dist/ssh-keys.d.ts",
|
|
90
|
+
"default": "./dist/ssh-keys.js"
|
|
91
|
+
},
|
|
88
92
|
"./tsconfig.plugin.json": "./tsconfig.plugin.json",
|
|
89
93
|
"./schema/manifest.schema.json": "./schema/manifest.schema.json",
|
|
90
94
|
"./ui": {
|
|
@@ -151,9 +151,11 @@
|
|
|
151
151
|
"process:spawn",
|
|
152
152
|
"users:write",
|
|
153
153
|
"auth:provide",
|
|
154
|
+
"auth:policy",
|
|
154
155
|
"system:tls",
|
|
155
156
|
"device:serial",
|
|
156
157
|
"hosts:write",
|
|
158
|
+
"hosts:export",
|
|
157
159
|
"credentials:use",
|
|
158
160
|
"credentials:write",
|
|
159
161
|
"network:outbound",
|
|
@@ -163,7 +165,12 @@
|
|
|
163
165
|
"events:core",
|
|
164
166
|
"notify:send",
|
|
165
167
|
"audit:read",
|
|
168
|
+
"network:probe",
|
|
169
|
+
"hosts:status-provide",
|
|
170
|
+
"hosts:activity-provide",
|
|
171
|
+
"hosts:lock",
|
|
166
172
|
"desktop:window",
|
|
173
|
+
"desktop:local",
|
|
167
174
|
"users:impersonate",
|
|
168
175
|
"db:core-refs",
|
|
169
176
|
"hosts:read",
|
|
@@ -1261,6 +1268,11 @@
|
|
|
1261
1268
|
"enum": ["mirror", "local", "server"],
|
|
1262
1269
|
"description": "How a desktop linked to a server treats this plugin: mirror follows the server, local is decided on each desktop, server never runs on a linked desktop. Defaults to mirror."
|
|
1263
1270
|
},
|
|
1271
|
+
"runtime": {
|
|
1272
|
+
"type": "string",
|
|
1273
|
+
"enum": ["desktop"],
|
|
1274
|
+
"description": "desktop: the plugin only loads on a desktop app's own embedded backend, never on a server. It implies desktop local and may declare desktop:local."
|
|
1275
|
+
},
|
|
1264
1276
|
"nativeDependencies": {
|
|
1265
1277
|
"type": "array",
|
|
1266
1278
|
"minItems": 1,
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export type FieldGroup = "connection" | "notes" | "tags" | "proxy" | "jumpHosts" | "featureFlags" | "advanced";
|
|
2
|
-
export interface ExportPayload {
|
|
3
|
-
version?: string;
|
|
4
|
-
exportedAt?: string;
|
|
5
|
-
credentials?: Record<string, unknown>[];
|
|
6
|
-
hosts: Record<string, unknown>[];
|
|
7
|
-
}
|
|
8
|
-
export declare const SECRET_KEYS: string[];
|
|
9
|
-
/**
|
|
10
|
-
* Keys inside a plugin's JSON host settings that hold secrets, per plugin and
|
|
11
|
-
* field, from each manifest's `secretKeys`.
|
|
12
|
-
*/
|
|
13
|
-
export type PluginSecretKeys = Record<string, Record<string, string[]>>;
|
|
14
|
-
/** Reads the secret keys the plugins declare in their host settings. */
|
|
15
|
-
export declare function pluginSecretKeys(summaries: Array<{
|
|
16
|
-
id: string;
|
|
17
|
-
contributes?: {
|
|
18
|
-
settings?: {
|
|
19
|
-
host?: {
|
|
20
|
-
fields?: Array<{
|
|
21
|
-
key: string;
|
|
22
|
-
secretKeys?: string[];
|
|
23
|
-
}>;
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
} | null;
|
|
27
|
-
}>): PluginSecretKeys;
|
|
28
|
-
export declare function hostKey(host: Record<string, unknown>): string;
|
|
29
|
-
export declare function buildExportPayload(raw: ExportPayload, selected: Set<string> | null, groups: Set<FieldGroup>, withCredentials: boolean, pluginSecrets?: PluginSecretKeys): ExportPayload;
|
|
30
|
-
export declare function maskSecrets(payload: ExportPayload, pluginSecrets?: PluginSecretKeys): ExportPayload;
|