mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: move termix identity into a plugin (C6)
This commit is contained in:
1 parent
6e56c90aac
commit
4dbce08379
157 files changed
+20743
-5766
No files matched your search
@@ -1382,11 +1382,42 @@ export interface PluginProtocolTarget {
|
||||
};
|
||||
}
|
||||
|
||||
/** One of the acting user's saved SSH key credentials, public half only. */
|
||||
export interface PluginSshKeyCredential {
|
||||
id: number;
|
||||
name: string;
|
||||
username: string | null;
|
||||
/** "<type> <base64>", derived from the private key when none was stored. Null when it cannot be. */
|
||||
publicKey: string | null;
|
||||
}
|
||||
|
||||
/** A key pair to save as a new credential for the acting user. */
|
||||
export interface PluginSshKeyCredentialInput {
|
||||
name: string;
|
||||
description?: string;
|
||||
username?: string | null;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
/** The key's algorithm token, such as "ssh-ed25519". */
|
||||
keyType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plaintext host credentials, for a plugin that has to hand them to another
|
||||
* program. Needs credentials:read, and every call is audited.
|
||||
*/
|
||||
export interface PluginCredentials {
|
||||
/**
|
||||
* The acting user's own SSH key credentials, without any private material.
|
||||
* Needs credentials:use and the core credentials.view permission. Audited.
|
||||
*/
|
||||
listSshKeys: () => Promise<PluginSshKeyCredential[]>;
|
||||
/**
|
||||
* Saves a key pair as a new encrypted credential owned by the acting user
|
||||
* and returns its id. Needs credentials:write, the core credentials.create
|
||||
* permission and an unlocked data key. Audited.
|
||||
*/
|
||||
createSshKey: (input: PluginSshKeyCredentialInput) => Promise<{ id: number }>;
|
||||
/** Null when the host does not exist or the acting user cannot connect to it. */
|
||||
resolveHostProtocol: (
|
||||
hostId: number,
|
||||
@@ -1627,7 +1658,7 @@ export interface PluginContext {
|
||||
readonly auth: PluginAuth;
|
||||
/** Opens Electron windows outside the main renderer. Needs desktop:window. */
|
||||
readonly desktop: PluginDesktop;
|
||||
/** Plaintext host protocol credentials. Needs credentials:read. */
|
||||
/** Host protocol credentials and the user's saved SSH keys. See PluginCredentials. */
|
||||
readonly credentials: PluginCredentials;
|
||||
/** Audit lines under the plugin's own action names. */
|
||||
readonly audit: PluginAudit;
|
||||
|
||||
@@ -49,6 +49,7 @@ export const CAPABILITY_CATALOG: readonly CapabilityInfo[] = [
|
||||
|
||||
entry("hosts:write", "medium"),
|
||||
entry("credentials:use", "medium"),
|
||||
entry("credentials:write", "medium"),
|
||||
entry("network:outbound", "medium"),
|
||||
entry("network:serve", "medium"),
|
||||
entry("network:broadcast", "medium"),
|
||||
|
||||
@@ -530,6 +530,19 @@ export interface PluginWsTarget {
|
||||
protocols?: string[];
|
||||
}
|
||||
|
||||
/** Where the frontend runs, for a plugin that behaves differently in the desktop app. */
|
||||
export interface DesktopApi {
|
||||
/** True inside the Termix desktop app. */
|
||||
readonly available: boolean;
|
||||
/**
|
||||
* The remote server the desktop app is connected to for sync, or null
|
||||
* outside the desktop app and while it runs standalone.
|
||||
*/
|
||||
remoteServerUrl: () => Promise<string | null>;
|
||||
/** Called when the desktop app connects to or leaves a remote server. */
|
||||
onRemoteServerChange: (listener: () => void) => Disposer;
|
||||
}
|
||||
|
||||
export interface TermixAppInfo {
|
||||
readonly pluginId: string;
|
||||
readonly manifest: PluginManifest;
|
||||
@@ -629,6 +642,8 @@ export interface TermixApp extends TermixAppInfo {
|
||||
options?: { origin?: unknown },
|
||||
) => Promise<PluginWsTarget | null>;
|
||||
tabs: TabsApi;
|
||||
/** The desktop app, when the frontend runs in it. */
|
||||
desktop: DesktopApi;
|
||||
/** Registered cleanup, run when the plugin is disabled. */
|
||||
onDispose: (dispose: Disposer) => void;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ import type {
|
||||
PluginHostShareResult,
|
||||
PluginShareableUser,
|
||||
PluginShareableRole,
|
||||
PluginSshKeyCredential,
|
||||
PluginSshKeyCredentialInput,
|
||||
} from "./backend.js";
|
||||
import type { SyncEntityRegistration } from "./backend.js";
|
||||
import type { PluginDatabase } from "./backend.js";
|
||||
@@ -120,6 +122,8 @@ export interface FakeContextOptions {
|
||||
* "<hostId>:<protocol>". A missing key answers null.
|
||||
*/
|
||||
protocolTargets?: Record<string, PluginProtocolTarget>;
|
||||
/** What ctx.credentials.listSshKeys answers. */
|
||||
sshKeyCredentials?: PluginSshKeyCredential[];
|
||||
/** What ctx.desktop.available() answers. Defaults to false. */
|
||||
desktopAvailable?: boolean;
|
||||
/** What ctx.hosts.status.get and check answer, by host id. */
|
||||
@@ -273,6 +277,8 @@ export interface FakePluginContext {
|
||||
nativeRdpLaunches: PluginNativeRdpRequest[];
|
||||
/** Every ctx.credentials.resolveHostProtocol call, in order. */
|
||||
credentialReads: Array<{ hostId: number; protocol: string }>;
|
||||
/** Every ctx.credentials.createSshKey call, with the id it answered. */
|
||||
createdSshKeys: Array<PluginSshKeyCredentialInput & { id: number }>;
|
||||
/** Every ctx.notify.send call, with the actor it ran as. */
|
||||
notifications: Array<{
|
||||
actor: string | undefined;
|
||||
@@ -392,6 +398,7 @@ export function createFakeContext(
|
||||
const desktopWindows: FakePluginContext["desktopWindows"] = [];
|
||||
const nativeRdpLaunches: FakePluginContext["nativeRdpLaunches"] = [];
|
||||
const credentialReads: FakePluginContext["credentialReads"] = [];
|
||||
const createdSshKeys: FakePluginContext["createdSshKeys"] = [];
|
||||
const notifications: FakePluginContext["notifications"] = [];
|
||||
const fetches: FakePluginContext["fetches"] = [];
|
||||
const processRuns: FakePluginContext["processRuns"] = [];
|
||||
@@ -972,6 +979,25 @@ export function createFakeContext(
|
||||
},
|
||||
|
||||
credentials: {
|
||||
listSshKeys: async () => [
|
||||
...(options.sshKeyCredentials ?? []),
|
||||
...createdSshKeys.map((key) => ({
|
||||
id: key.id,
|
||||
name: key.name,
|
||||
username: key.username ?? null,
|
||||
publicKey: key.publicKey,
|
||||
})),
|
||||
],
|
||||
createSshKey: async (input) => {
|
||||
const id =
|
||||
Math.max(
|
||||
0,
|
||||
...(options.sshKeyCredentials ?? []).map((key) => key.id),
|
||||
...createdSshKeys.map((key) => key.id),
|
||||
) + 1;
|
||||
createdSshKeys.push({ ...input, id });
|
||||
return { id };
|
||||
},
|
||||
resolveHostProtocol: async (hostId, protocol) => {
|
||||
credentialReads.push({ hostId, protocol });
|
||||
return options.protocolTargets?.[`${hostId}:${protocol}`] ?? null;
|
||||
@@ -1049,6 +1075,7 @@ export function createFakeContext(
|
||||
desktopWindows,
|
||||
nativeRdpLaunches,
|
||||
credentialReads,
|
||||
createdSshKeys,
|
||||
notifications,
|
||||
fetches,
|
||||
processRuns,
|
||||
@@ -1105,6 +1132,8 @@ export interface MockContextOptions {
|
||||
sshHosts?: PluginSshHost[];
|
||||
/** What ctx.credentials serves. See FakeContextOptions. */
|
||||
protocolTargets?: Record<string, PluginProtocolTarget>;
|
||||
/** What ctx.credentials.listSshKeys serves. */
|
||||
sshKeyCredentials?: PluginSshKeyCredential[];
|
||||
/** What ctx.desktop.available() answers. */
|
||||
desktopAvailable?: boolean;
|
||||
/** What ctx.hosts.status answers. See FakeContextOptions. */
|
||||
@@ -1164,6 +1193,7 @@ export function createMockCtx(
|
||||
sshHosts: options.sshHosts,
|
||||
services: options.services,
|
||||
protocolTargets: options.protocolTargets,
|
||||
sshKeyCredentials: options.sshKeyCredentials,
|
||||
desktopAvailable: options.desktopAvailable,
|
||||
hostStatuses: options.hostStatuses,
|
||||
notificationChannels: options.notificationChannels,
|
||||
@@ -1462,6 +1492,14 @@ export function createMockCtx(
|
||||
},
|
||||
|
||||
credentials: {
|
||||
listSshKeys: async () => {
|
||||
require("credentials:use");
|
||||
return ctx.credentials.listSshKeys();
|
||||
},
|
||||
createSshKey: async (input) => {
|
||||
require("credentials:write");
|
||||
return ctx.credentials.createSshKey(input);
|
||||
},
|
||||
resolveHostProtocol: async (hostId, protocol) => {
|
||||
require("credentials:read");
|
||||
return ctx.credentials.resolveHostProtocol(hostId, protocol);
|
||||
|
||||
Reference in new issue
Block a user