mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
feat: harden plugin api for separate repos, signed updates and generic external login
This commit is contained in:
1 parent
fb9c168b6b
commit
80944301dd
310 files changed
+6577
-4927
No files matched your search
@@ -68,7 +68,7 @@ import type {
|
||||
import type { SyncEntityRegistration } from "./backend.js";
|
||||
import type { PluginDatabase } from "./backend.js";
|
||||
import type {
|
||||
PluginNativeRdpRequest,
|
||||
PluginExternalClientRequest,
|
||||
PluginProtocolTarget,
|
||||
PluginHostStatusEntry,
|
||||
PluginNotification,
|
||||
@@ -297,8 +297,8 @@ export interface FakePluginContext {
|
||||
title?: string;
|
||||
ignoreCert?: boolean;
|
||||
}>;
|
||||
/** Every ctx.desktop.launchNativeRdp call, in order. */
|
||||
nativeRdpLaunches: PluginNativeRdpRequest[];
|
||||
/** Every ctx.desktop.launchExternalClient call, in order. */
|
||||
externalClientLaunches: PluginExternalClientRequest[];
|
||||
/** Every ctx.credentials.resolveHostProtocol call, in order. */
|
||||
credentialReads: Array<{ hostId: number; protocol: string }>;
|
||||
/** Every ctx.credentials.createSshKey call, with the id it answered. */
|
||||
@@ -361,6 +361,11 @@ export interface FakePluginContext {
|
||||
runScheduled: () => Promise<void>;
|
||||
/** Changes the acting user, as core's request middleware would. */
|
||||
setActor: (userId: string | undefined) => void;
|
||||
/**
|
||||
* Runs `fn` as `userId`, the way a request from that user would. For test
|
||||
* code standing in for a caller; unlike ctx.asUser it needs no capability.
|
||||
*/
|
||||
actAs: <T>(userId: string, fn: () => Promise<T> | T) => Promise<T>;
|
||||
/** Provided or seeded services, by name or "<service>#<provider>". */
|
||||
services: Map<string, object>;
|
||||
}
|
||||
@@ -497,7 +502,8 @@ export function createFakeContext(
|
||||
const sshConnections: FakePluginContext["sshConnections"] = [];
|
||||
const hostShares: FakePluginContext["hostShares"] = [];
|
||||
const desktopWindows: FakePluginContext["desktopWindows"] = [];
|
||||
const nativeRdpLaunches: FakePluginContext["nativeRdpLaunches"] = [];
|
||||
const externalClientLaunches: FakePluginContext["externalClientLaunches"] =
|
||||
[];
|
||||
const credentialReads: FakePluginContext["credentialReads"] = [];
|
||||
const createdSshKeys: FakePluginContext["createdSshKeys"] = [];
|
||||
const notifications: FakePluginContext["notifications"] = [];
|
||||
@@ -695,10 +701,12 @@ export function createFakeContext(
|
||||
|
||||
registry: {
|
||||
provide: (key, value) => {
|
||||
assertOwnRegistryKey(pluginId, key);
|
||||
registryProviders.set(key, value);
|
||||
},
|
||||
consume: (key) => registryProviders.get(key) as never,
|
||||
revoke: (key, value) => {
|
||||
assertOwnRegistryKey(pluginId, key);
|
||||
if (!registryProviders.has(key)) return false;
|
||||
if (value !== undefined && registryProviders.get(key) !== value) {
|
||||
return false;
|
||||
@@ -1128,8 +1136,8 @@ export function createFakeContext(
|
||||
desktopWindows.push(request);
|
||||
return { success: true };
|
||||
},
|
||||
launchNativeRdp: async (request) => {
|
||||
nativeRdpLaunches.push(request);
|
||||
launchExternalClient: async (request) => {
|
||||
externalClientLaunches.push(request);
|
||||
return { success: true };
|
||||
},
|
||||
available: () => options.desktopAvailable ?? false,
|
||||
@@ -1309,7 +1317,7 @@ export function createFakeContext(
|
||||
hostShares,
|
||||
auth,
|
||||
desktopWindows,
|
||||
nativeRdpLaunches,
|
||||
externalClientLaunches,
|
||||
credentialReads,
|
||||
createdSshKeys,
|
||||
notifications,
|
||||
@@ -1337,6 +1345,15 @@ export function createFakeContext(
|
||||
setActor: (userId) => {
|
||||
actor = userId;
|
||||
},
|
||||
actAs: async (userId, fn) => {
|
||||
const previous = actor;
|
||||
actor = userId;
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
actor = previous;
|
||||
}
|
||||
},
|
||||
services,
|
||||
};
|
||||
}
|
||||
@@ -1477,6 +1494,11 @@ export function createMockCtx(
|
||||
const gatedCtx: PluginContext = {
|
||||
...ctx,
|
||||
|
||||
asUser: async (userId, fn) => {
|
||||
require("users:impersonate");
|
||||
return ctx.asUser(userId, fn);
|
||||
},
|
||||
|
||||
db: {
|
||||
define: async (definition) => {
|
||||
require("db:own");
|
||||
@@ -1487,7 +1509,7 @@ export function createMockCtx(
|
||||
return guardedDb.client();
|
||||
},
|
||||
refs: async () => {
|
||||
require("db:own");
|
||||
require("db:core-refs");
|
||||
return guardedDb.refs();
|
||||
},
|
||||
persist: async (persistOptions) => {
|
||||
@@ -1745,9 +1767,9 @@ export function createMockCtx(
|
||||
require("desktop:window");
|
||||
return ctx.desktop.openIsolatedWindow(request);
|
||||
},
|
||||
launchNativeRdp: async (request) => {
|
||||
launchExternalClient: async (request) => {
|
||||
require("desktop:window");
|
||||
return ctx.desktop.launchNativeRdp(request);
|
||||
return ctx.desktop.launchExternalClient(request);
|
||||
},
|
||||
available: () => ctx.desktop.available(),
|
||||
},
|
||||
@@ -1882,7 +1904,7 @@ export interface TestDbOptions {
|
||||
* Runs after the core stub tables exist and before the plugin's migrations,
|
||||
* e.g. to create the legacy table an adoption migration renames.
|
||||
*/
|
||||
before?: (sqlite: TestSqlite) => void | Promise<void>;
|
||||
before?: (sqlite: TestSqlite) => unknown;
|
||||
/** Skip applying the migrations, to apply them by hand later. */
|
||||
skipMigrations?: boolean;
|
||||
}
|
||||
@@ -2046,6 +2068,8 @@ export interface RenderWithAppOptions {
|
||||
ready?: boolean;
|
||||
/** Stands in for app.api and usePluginApi(), e.g. a stub of the routes. */
|
||||
api?: import("./frontend.js").PluginApiClient;
|
||||
/** What app.desktop.remoteServerUrl() answers. Read on every call. */
|
||||
remoteServerUrl?: () => string | null | Promise<string | null>;
|
||||
}
|
||||
|
||||
/** A call a plugin made on the shell, recorded instead of performed. */
|
||||
@@ -2150,3 +2174,11 @@ export async function renderWithApp(
|
||||
const host = (await import(/* @vite-ignore */ TEST_HOST)) as PluginTestHost;
|
||||
return host.renderPlugin(plugin, options);
|
||||
}
|
||||
|
||||
function assertOwnRegistryKey(pluginId: string, key: string): void {
|
||||
if (typeof key !== "string" || !key.startsWith(`${pluginId}.`)) {
|
||||
throw new Error(
|
||||
`Plugin ${pluginId} may only use registry keys under "${pluginId}.", not "${key}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user