mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
chore: lock the plugin boundary and remove 2.9.0 leftovers
This commit is contained in:
1 parent
6489b62ece
commit
2961297989
399 files changed
+3964
-5627
No files matched your search
@@ -1,57 +1,33 @@
|
||||
/**
|
||||
* The 2.8 login URLs that something outside Termix still depends on:
|
||||
* identity providers configured with the old callback and back-channel
|
||||
* logout URLs, and clients built against 2.8 such as Termix-Mobile. Each one
|
||||
* forwards to the login method or plugin route that replaced it, so none of
|
||||
* them can sign anyone in on its own.
|
||||
* The 2.8 login URLs clients built against 2.8 (Termix-Mobile) still call.
|
||||
* Each forwards to the login pipeline for the method it names, so none of
|
||||
* them can sign anyone in on its own. Old identity provider callbacks are
|
||||
* redirected by the owning plugin's manifest (contributes.http.legacyRedirects).
|
||||
*/
|
||||
|
||||
import type { Request, Router } from "express";
|
||||
import type { Router } from "express";
|
||||
import { authLogger } from "../../utils/logger.js";
|
||||
import { getRequestBasePath } from "../../utils/request-origin.js";
|
||||
import {
|
||||
listLegacySsoProviders,
|
||||
startRedirectLogin,
|
||||
verifyFormLogin,
|
||||
} from "./auth-routes.js";
|
||||
|
||||
/**
|
||||
* A path on this install, keeping the query string. Relative, so the browser
|
||||
* stays on the scheme and host it used, whatever a proxy told us.
|
||||
*/
|
||||
function pluginUrl(req: Request, path: string): string {
|
||||
const query = req.originalUrl.indexOf("?");
|
||||
return `${getRequestBasePath(req)}${path}${
|
||||
query >= 0 ? req.originalUrl.slice(query) : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
export function registerAuthCompatRoutes(router: Router): void {
|
||||
/**
|
||||
* @openapi
|
||||
* /users/oidc-config:
|
||||
* /users/{method}/authorize:
|
||||
* get:
|
||||
* summary: Get the default SSO provider's public configuration (2.8 route)
|
||||
* description: Kept for 2.8 clients. Redirects to the sso plugin's /plugin-api/sso/config.
|
||||
* tags:
|
||||
* - Auth
|
||||
* responses:
|
||||
* 307:
|
||||
* description: Redirect to the sso plugin.
|
||||
*/
|
||||
router.get("/oidc-config", (req, res) => {
|
||||
res.redirect(307, pluginUrl(req, "/plugin-api/sso/config"));
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/oidc/authorize:
|
||||
* get:
|
||||
* summary: Start an SSO login (2.8 route)
|
||||
* description: Kept for 2.8 clients such as Termix-Mobile. Same as /users/auth/oidc/start, with the provider in providerId.
|
||||
* summary: Start a redirect login (2.8 route)
|
||||
* description: Kept for 2.8 clients such as Termix-Mobile, which call /users/oidc/authorize. Same as /users/auth/{method}/start, with the provider in providerId.
|
||||
* tags:
|
||||
* - Auth
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: method
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: providerId
|
||||
* schema:
|
||||
@@ -64,59 +40,19 @@ export function registerAuthCompatRoutes(router: Router): void {
|
||||
* 200:
|
||||
* description: The authorization URL in auth_url.
|
||||
* 404:
|
||||
* description: SSO is not available.
|
||||
* description: That login method is not available.
|
||||
*/
|
||||
router.get("/oidc/authorize", (req, res) =>
|
||||
router.get("/:method/authorize", (req, res) =>
|
||||
startRedirectLogin(
|
||||
req,
|
||||
res,
|
||||
"oidc",
|
||||
String(req.params.method),
|
||||
typeof req.query.providerId === "string" && req.query.providerId
|
||||
? req.query.providerId
|
||||
: null,
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/oidc/callback:
|
||||
* get:
|
||||
* summary: SSO callback (2.8 URL)
|
||||
* description: The redirect URI identity providers were set up with before 2.9. Permanently redirects to /plugin-api/sso/callback with the same query, so existing provider configurations keep working.
|
||||
* tags:
|
||||
* - Auth
|
||||
* responses:
|
||||
* 308:
|
||||
* description: Redirect to the sso plugin's callback.
|
||||
* post:
|
||||
* summary: SSO callback, form post (2.8 URL)
|
||||
* description: Same as the GET form.
|
||||
* tags:
|
||||
* - Auth
|
||||
* responses:
|
||||
* 308:
|
||||
* description: Redirect to the sso plugin's callback.
|
||||
*/
|
||||
router.all("/oidc/callback", (req, res) => {
|
||||
res.redirect(308, pluginUrl(req, "/plugin-api/sso/callback"));
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/oidc/backchannel-logout:
|
||||
* post:
|
||||
* summary: OIDC back-channel logout (2.8 URL)
|
||||
* description: Permanently redirects to /plugin-api/sso/backchannel-logout, so identity providers set up before 2.9 keep working.
|
||||
* tags:
|
||||
* - Auth
|
||||
* responses:
|
||||
* 308:
|
||||
* description: Redirect to the sso plugin.
|
||||
*/
|
||||
router.post("/oidc/backchannel-logout", (req, res) => {
|
||||
res.redirect(308, pluginUrl(req, "/plugin-api/sso/backchannel-logout"));
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/sso-providers:
|
||||
@@ -140,25 +76,31 @@ export function registerAuthCompatRoutes(router: Router): void {
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/ldap/login:
|
||||
* /users/{method}/login:
|
||||
* post:
|
||||
* summary: LDAP login (2.8 route)
|
||||
* description: Kept for 2.8 clients. Same as /users/auth/ldap/verify, with the provider in providerId.
|
||||
* summary: Form login (2.8 route)
|
||||
* description: Kept for 2.8 clients, which call /users/ldap/login. Same as /users/auth/{method}/verify, with the provider in providerId.
|
||||
* tags:
|
||||
* - Auth
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: method
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Login successful, or a second factor is required.
|
||||
* 401:
|
||||
* description: Invalid credentials.
|
||||
* 404:
|
||||
* description: LDAP is not available.
|
||||
* description: That login method is not available.
|
||||
*/
|
||||
router.post("/ldap/login", (req, res) =>
|
||||
router.post("/:method/login", (req, res) =>
|
||||
verifyFormLogin(
|
||||
req,
|
||||
res,
|
||||
"ldap",
|
||||
String(req.params.method),
|
||||
req.body?.providerId != null ? String(req.body.providerId) : null,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -130,7 +130,7 @@ router.get(
|
||||
* properties:
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [terminal, file_manager, server_stats, tunnel, docker, telnet, vnc, rdp]
|
||||
* description: An activity type a plugin's tab declares, lowercase letters, digits and underscores.
|
||||
* hostId:
|
||||
* type: integer
|
||||
* hostName:
|
||||
@@ -177,7 +177,7 @@ router.post(
|
||||
if (result.status === "invalid_type") {
|
||||
return res.status(400).json({
|
||||
error:
|
||||
"Invalid activity type. Must be 'terminal', 'file_manager', 'server_stats', 'tunnel', 'docker', 'telnet', 'vnc', or 'rdp'",
|
||||
"Invalid activity type. Use lowercase letters, digits and underscores.",
|
||||
});
|
||||
}
|
||||
if (result.status === "rate_limited") {
|
||||
|
||||
@@ -48,9 +48,8 @@ export async function deleteUserAndRelatedData(
|
||||
userId,
|
||||
);
|
||||
|
||||
// session_recordings is retained rather than deleted, by design: it
|
||||
// outlives the account. The session-recording plugin listens for
|
||||
// user.deleted and anonymizes its own rows instead of cascading.
|
||||
// Plugins drop or anonymize their own rows on user.deleted, or rely on
|
||||
// their refUser() foreign keys cascading.
|
||||
const { pluginEvents, TOPICS } = await import("../../plugins/events.js");
|
||||
pluginEvents.emit(TOPICS.userDeleted, { userId });
|
||||
|
||||
@@ -68,16 +67,9 @@ export async function deleteUserAndRelatedData(
|
||||
|
||||
await createCurrentSshCredentialUsageRepository().deleteByUserId(userId);
|
||||
|
||||
// file manager recent/pinned/shortcuts and transfer_recent cascade on the
|
||||
// user's refUser() foreign key, as the file-manager plugin's adopted
|
||||
// tables.
|
||||
|
||||
await createCurrentRecentActivityRepository().deleteByUserId(userId);
|
||||
await createCurrentDismissedAlertRepository().deleteByUserId(userId);
|
||||
|
||||
// snippets, snippet_folders and snippet_access cascade on the user's
|
||||
// refUser() foreign key, as the snippets plugin's adopted tables.
|
||||
|
||||
await createCurrentHostFolderRepository().deleteByUserId(userId);
|
||||
|
||||
await createCurrentHostSidebarPreferenceRepository().deleteByUserId(userId);
|
||||
@@ -88,12 +80,7 @@ export async function deleteUserAndRelatedData(
|
||||
await createCurrentHostRepository().deleteByUserId(userId);
|
||||
await createCurrentCredentialRepository().deleteByUserId(userId);
|
||||
|
||||
// homepage_items, homepage_layouts, dashboard_service_links and
|
||||
// secret_sources cascade on the user's refUser() foreign key, as the
|
||||
// homepage and secret-sources plugins' adopted tables. The secret
|
||||
// source's token in ctx.secrets is cleaned up generically below, with
|
||||
// every other plugin_settings row for this user. The opkssh and vault
|
||||
// plugins' tables cascade the same way, and so do termix-identity's.
|
||||
// Plugin tables with a refUser() column cascade on the user row.
|
||||
|
||||
await createCurrentOpenTabRepository().deleteByUserId(userId);
|
||||
await createCurrentUserPreferenceRepository().deleteByUserId(userId);
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* The 2.8 auth callback URLs something outside Termix still depends on: the
|
||||
* OPKSSH and Step CA redirect URIs registered with identity providers, and
|
||||
* the Vault one listed in Vault OIDC roles.
|
||||
* Each forwards to the plugin route that replaced it, so with the plugin off
|
||||
* it signs nobody in.
|
||||
*/
|
||||
|
||||
import type { Request, Router } from "express";
|
||||
import { getRequestBasePath } from "../../utils/request-origin.js";
|
||||
|
||||
/** A path on this install, keeping the query string. */
|
||||
function localUrl(req: Request, path: string): string {
|
||||
const query = req.originalUrl.indexOf("?");
|
||||
return `${getRequestBasePath(req)}${path}${
|
||||
query >= 0 ? req.originalUrl.slice(query) : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
export function registerHostAuthCompatRoutes(router: Router): void {
|
||||
/**
|
||||
* @openapi
|
||||
* /host/opkssh-callback:
|
||||
* get:
|
||||
* summary: OPKSSH callback (2.8 URL)
|
||||
* description: The redirect URI OPKSSH identity providers were set up with before 2.9. Redirects to /plugin-api/opkssh/callback with the same subpath and query, so existing provider configurations keep working.
|
||||
* tags:
|
||||
* - Auth
|
||||
* responses:
|
||||
* 307:
|
||||
* description: Redirect to the opkssh plugin's callback.
|
||||
*/
|
||||
router.all(["/opkssh-callback", "/opkssh-callback/*rest"], (req, res) => {
|
||||
const rest = req.path.slice("/opkssh-callback".length);
|
||||
res.redirect(307, localUrl(req, `/plugin-api/opkssh/callback${rest}`));
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /host/step-ca-callback:
|
||||
* get:
|
||||
* summary: Step CA callback (2.8 URL)
|
||||
* description: The redirect URI Step CA identity providers were set up with before 2.9. Redirects to /plugin-api/step-ca/callback with the same query, so existing provider configurations keep working.
|
||||
* tags:
|
||||
* - Auth
|
||||
* responses:
|
||||
* 307:
|
||||
* description: Redirect to the step-ca plugin's callback.
|
||||
*/
|
||||
router.get("/step-ca-callback", (req, res) => {
|
||||
res.redirect(307, localUrl(req, "/plugin-api/step-ca/callback"));
|
||||
});
|
||||
}
|
||||
|
||||
export function registerVaultCompatRoutes(router: Router): void {
|
||||
/**
|
||||
* @openapi
|
||||
* /vault/oidc/callback:
|
||||
* get:
|
||||
* summary: Vault OIDC callback (2.8 URL)
|
||||
* description: The redirect URI Vault OIDC roles allowed before 2.9. Redirects to /plugin-api/vault/oidc/callback with the same query, so existing Vault roles keep working.
|
||||
* tags:
|
||||
* - Vault
|
||||
* responses:
|
||||
* 307:
|
||||
* description: Redirect to the vault plugin's callback.
|
||||
*/
|
||||
router.get("/oidc/callback", (req, res) => {
|
||||
res.redirect(307, localUrl(req, "/plugin-api/vault/oidc/callback"));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The 2.8 Termix ID resolver URLs, which servers fetch from provisioning
|
||||
* scripts. Permanently redirected to the termix-identity plugin's public
|
||||
* resolver; curl -L follows them.
|
||||
*/
|
||||
export function registerTermixIdCompatRoutes(router: Router): void {
|
||||
/**
|
||||
* @openapi
|
||||
* /termix-id/u/{handle}:
|
||||
* get:
|
||||
* summary: Termix ID resolver (2.8 URL)
|
||||
* description: Permanently redirects to /plugin-api/termix-identity/u/{handle}, and likewise for /termix-id/u/{handle}/{algo} and /termix-id/u/{handle}/ca, so servers provisioned with the old URL keep working.
|
||||
* tags:
|
||||
* - Termix ID
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: handle
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 308:
|
||||
* description: Redirect to the termix-identity plugin's resolver.
|
||||
*/
|
||||
router.get(["/u/:handle", "/u/:handle/:algo"], (req, res) => {
|
||||
const rest = req.path.slice("/u".length);
|
||||
res.redirect(308, localUrl(req, `/plugin-api/termix-identity/u${rest}`));
|
||||
});
|
||||
}
|
||||
@@ -396,10 +396,7 @@ export function registerHostFolderRoutes(
|
||||
const hostIds = hostsToDelete.map((host) => host.id);
|
||||
|
||||
if (hostIds.length > 0) {
|
||||
// file manager recent/pinned/shortcuts, transfer_recent, command
|
||||
// history and session recordings cascade on the host's refHost()
|
||||
// foreign key, as the file-manager, ssh-terminal and
|
||||
// session-recording plugins' adopted tables.
|
||||
// Plugin rows tied to a host cascade on their refHost() foreign keys.
|
||||
|
||||
await createCurrentSshCredentialUsageRepository().deleteByHostIds(
|
||||
hostIds,
|
||||
|
||||
@@ -80,7 +80,7 @@ export function containsOwnerPrivateAuthUpdate(
|
||||
);
|
||||
}
|
||||
|
||||
export const FOLDER_PATH_SEPARATOR = " / ";
|
||||
const FOLDER_PATH_SEPARATOR = " / ";
|
||||
|
||||
/**
|
||||
* Re-paths a folder string when its ancestor folder is renamed. Returns the new
|
||||
|
||||
@@ -132,7 +132,7 @@ const SHARE_LEVELS = ["connect", "view", "edit", "manage"];
|
||||
* Drops the fields a shared host's recipient may not read: a field declares
|
||||
* the lowest share level that sees it with shareRead. The owner sees all.
|
||||
*/
|
||||
export function visibleHostPluginSettings(
|
||||
function visibleHostPluginSettings(
|
||||
values: HostPluginSettings,
|
||||
host: Record<string, unknown>,
|
||||
): HostPluginSettings {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { getErrorMessage } from "../../utils/error-message.js";
|
||||
import { isQuickConnectAuthType } from "../../hosts/connect/auth-provider-registry.js";
|
||||
import { ensureCoreSshAuthProviders } from "../../hosts/connect/core-providers.js";
|
||||
import { applyFolderAccessRules } from "../../utils/folder-access-inheritance.js";
|
||||
import { findUsableCredential } from "../../hosts/usable-credential.js";
|
||||
import type { AuthenticatedRequest } from "../../../types/index.js";
|
||||
@@ -44,7 +46,6 @@ import {
|
||||
withHostPluginSettings,
|
||||
} from "./host-plugin-settings.js";
|
||||
import { validateParentHostId } from "./host-parent-validation.js";
|
||||
import { registerHostAuthCompatRoutes } from "./host-compat-routes.js";
|
||||
import { registerHostFolderRoutes } from "./host-folder-routes.js";
|
||||
import { registerHostNetworkRoutes } from "./host-network-routes.js";
|
||||
import { registerHostBulkRoutes } from "./host-bulk-routes.js";
|
||||
@@ -74,9 +75,9 @@ const upload = multer({ storage: multer.memoryStorage() });
|
||||
/**
|
||||
* Tells whoever is polling this host that its details changed.
|
||||
*
|
||||
* An event rather than the HTTP POST to localhost:30005 this used to be: host
|
||||
* metrics is a plugin now and has no port of its own. Fire and forget, so a
|
||||
* subscriber that throws cannot fail the host update that caused it.
|
||||
* An event, so whichever plugin polls the host can listen for it. Fire and
|
||||
* forget, so a subscriber that throws cannot fail the host update that
|
||||
* caused it.
|
||||
*/
|
||||
function notifyStatsHostUpdated(
|
||||
hostId: number,
|
||||
@@ -556,7 +557,7 @@ router.post(
|
||||
* description: SSH username
|
||||
* authType:
|
||||
* type: string
|
||||
* enum: [password, key, credential]
|
||||
* description: Any registered SSH auth type that works for an unsaved host (password, key, credential, agent, none, or one a plugin marks for Quick Connect).
|
||||
* description: Authentication method
|
||||
* password:
|
||||
* type: string
|
||||
@@ -618,6 +619,13 @@ router.post(
|
||||
return res.status(400).json({ error: "Missing required fields" });
|
||||
}
|
||||
|
||||
ensureCoreSshAuthProviders();
|
||||
if (!isQuickConnectAuthType(String(authType))) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "That auth type cannot be used for Quick Connect" });
|
||||
}
|
||||
|
||||
try {
|
||||
let resolvedPassword = password;
|
||||
let resolvedKey = key;
|
||||
@@ -2436,8 +2444,6 @@ registerHostBulkRoutes(
|
||||
requireDataAccess,
|
||||
);
|
||||
|
||||
registerHostAuthCompatRoutes(router);
|
||||
|
||||
registerHostNetworkRoutes(router, {
|
||||
authenticateJWT,
|
||||
requireViewPermission: permissionManager.requirePermission("hosts.view"),
|
||||
|
||||
@@ -28,16 +28,6 @@ function getTabTtlMs(): number {
|
||||
return (minutes && minutes > 0 ? minutes : DEFAULT_TAB_TTL_MINUTES) * 60_000;
|
||||
}
|
||||
|
||||
// Legacy tab types that were renamed. Normalize on read so previously saved
|
||||
// tabs still restore to the correct (renamed) tab type.
|
||||
const LEGACY_TAB_TYPE_MAP: Record<string, string> = {
|
||||
stats: "host-metrics",
|
||||
};
|
||||
|
||||
function normalizeTabType(tabType: string): string {
|
||||
return LEGACY_TAB_TYPE_MAP[tabType] ?? tabType;
|
||||
}
|
||||
|
||||
router.get("/", authenticateJWT, async (req: Request, res: Response) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
try {
|
||||
@@ -46,9 +36,7 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
|
||||
userId,
|
||||
cutoff,
|
||||
);
|
||||
return res.json(
|
||||
tabs.map((tab) => ({ ...tab, tabType: normalizeTabType(tab.tabType) })),
|
||||
);
|
||||
return res.json(tabs);
|
||||
} catch (e) {
|
||||
databaseLogger.error("Failed to get open tabs", e, {
|
||||
operation: "get_open_tabs",
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import express, { type Request, type Response } from "express";
|
||||
import { databaseLogger } from "../../utils/logger.js";
|
||||
import { getPluginRouter } from "../../plugins/http.js";
|
||||
import { getRequestBasePath } from "../../utils/request-origin.js";
|
||||
import type { PluginLegacyRedirect } from "@termix/plugin-sdk/manifest";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -78,21 +80,44 @@ export function mountPluginApi(app: express.Express): void {
|
||||
app.use("/plugin-api", router);
|
||||
}
|
||||
|
||||
export interface ActiveLegacyRoutes {
|
||||
id: string;
|
||||
legacyPaths: string[];
|
||||
legacyRedirects: PluginLegacyRedirect[];
|
||||
}
|
||||
|
||||
function under(path: string, prefix: string): boolean {
|
||||
return path === prefix || path.startsWith(`${prefix}/`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serves the old URLs plugins declare in contributes.http.legacyPaths, each
|
||||
* under "/<plugin id>/", through that plugin's router with the prefix
|
||||
* removed. Mounted after every core route, so core wins a clash.
|
||||
* Serves the old URLs plugins declare in contributes.http. A legacyPaths
|
||||
* entry, under "/<plugin id>/", runs through that plugin's router with the
|
||||
* prefix removed. A legacyRedirects entry redirects to the plugin's own URL,
|
||||
* keeping the rest of the path and the query. Mounted after every core route,
|
||||
* so core wins a clash.
|
||||
*/
|
||||
export function mountPluginLegacyPaths(
|
||||
app: express.Express,
|
||||
listActive: () => Array<{ id: string; legacyPaths: string[] }>,
|
||||
listActive: () => ActiveLegacyRoutes[],
|
||||
): void {
|
||||
app.use((req: Request, res: Response, next) => {
|
||||
for (const plugin of listActive()) {
|
||||
const matched = plugin.legacyPaths.some(
|
||||
(path) => req.path === path || req.path.startsWith(`${path}/`),
|
||||
const redirect = plugin.legacyRedirects.find((entry) =>
|
||||
under(req.path, entry.from),
|
||||
);
|
||||
if (!matched) continue;
|
||||
if (redirect) {
|
||||
const rest = req.path.slice(redirect.from.length);
|
||||
const query = req.originalUrl.indexOf("?");
|
||||
res.redirect(
|
||||
redirect.status ?? 307,
|
||||
`${getRequestBasePath(req)}/plugin-api/${plugin.id}${redirect.to}${rest}${
|
||||
query >= 0 ? req.originalUrl.slice(query) : ""
|
||||
}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!plugin.legacyPaths.some((path) => under(req.path, path))) continue;
|
||||
const pluginRouter = getPluginRouter(plugin.id);
|
||||
if (!pluginRouter) {
|
||||
res.status(404).json({ error: "Plugin not installed or not running" });
|
||||
|
||||
@@ -18,12 +18,14 @@ export interface SshAuthProviderSummary {
|
||||
credentialType: boolean;
|
||||
needsUserInteraction: boolean;
|
||||
supportsBackground: boolean;
|
||||
/** Offered in Quick Connect, for a host that is never saved. */
|
||||
quickConnect: boolean;
|
||||
available: boolean;
|
||||
/** Set when the type belongs to a plugin that is disabled or missing. */
|
||||
missingPlugin?: { id: string; name: string };
|
||||
}
|
||||
|
||||
export function summarizeSshAuthProviders(): SshAuthProviderSummary[] {
|
||||
function summarizeSshAuthProviders(): SshAuthProviderSummary[] {
|
||||
ensureCoreSshAuthProviders();
|
||||
const summaries: SshAuthProviderSummary[] = listSshAuthProviders().map(
|
||||
(provider) => ({
|
||||
@@ -35,6 +37,7 @@ export function summarizeSshAuthProviders(): SshAuthProviderSummary[] {
|
||||
credentialType: !!provider.credentialType,
|
||||
needsUserInteraction: !!provider.needsUserInteraction,
|
||||
supportsBackground: provider.supportsBackground !== false,
|
||||
quickConnect: !!provider.quickConnect,
|
||||
available: true,
|
||||
}),
|
||||
);
|
||||
@@ -50,6 +53,7 @@ export function summarizeSshAuthProviders(): SshAuthProviderSummary[] {
|
||||
credentialType: false,
|
||||
needsUserInteraction: false,
|
||||
supportsBackground: false,
|
||||
quickConnect: false,
|
||||
available: false,
|
||||
missingPlugin: { id: owner.pluginId, name: owner.pluginName },
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user