feat: add auth extension points and one ssh connect pipeline

This commit is contained in:
LukeGus committed 2026-09-23 00:44:33 -05:00
1 parent d3c209af13
commit d774df8b41
159 files changed
+43243 -7451

No files matched your search

+42 -57
View File
@@ -90,6 +90,10 @@ import {
makeHostTabs,
} from "./HostManagerTabs";
import { useSshAuthEditors } from "@/plugin-host/auth-registry";
import { useSshAuthProviders } from "@/hooks/useSshAuthProviders";
import { SshAuthProviderFields } from "./SshAuthProviderFields";
import type { PluginSettingsField } from "@termix/plugin-sdk/manifest";
import { ensureLegacyAuthUI } from "@/auth/legacy-auth-ui";
import {
SecretReferenceHint,
SecretSourceManager,
@@ -102,17 +106,7 @@ import {
const CUSTOM_FONT_OPTION = "__custom__";
/** The SSH auth types core itself handles. Plugins add the rest. */
const CORE_AUTH_METHODS = [
"password",
"key",
"credential",
"vault",
"none",
"opkssh",
"stepca",
"agent",
];
ensureLegacyAuthUI();
export function HostEditor({
host,
@@ -373,10 +367,27 @@ export function HostEditor({
(editor) => editor.id === authMethod,
);
const ActiveAuthEditor = activeAuthEditor?.component;
const sshAuthProviders = useSshAuthProviders();
const authEditorLabel = (method: string) => {
const editor = sshAuthEditors.find((item) => item.id === method);
return editor ? t(editor.titleKey) : method;
const option = sshAuthProviders.find(method);
if (option?.editorTitleKey) return t(option.editorTitleKey);
if (option?.labelKey) return t(option.labelKey, { defaultValue: method });
return method;
};
const selectableAuthMethods = sshAuthProviders.providers
.filter((option) => option.available)
.map((option) => option.type);
const currentAuthOption = sshAuthProviders.find(authMethod);
// The host uses a type nothing here provides (its plugin is off).
const missingAuthNotice =
sshAuthProviders.loaded && authMethod && !currentAuthOption?.available
? currentAuthOption?.missingPlugin
? t("hosts.authTypeNeedsPlugin", {
type: authMethod,
plugin: currentAuthOption.missingPlugin.name,
})
: t("hosts.authTypeUnknown", { type: authMethod })
: null;
const availableCredentials =
quickCreatedCredential &&
!credentials.some(
@@ -611,10 +622,7 @@ export function HostEditor({
role="radiogroup"
aria-label={t("hosts.authenticationMethod")}
>
{[
...CORE_AUTH_METHODS,
...sshAuthEditors.map((editor) => editor.id),
].map((m) => (
{selectableAuthMethods.map((m) => (
<button
key={m}
type="button"
@@ -640,6 +648,11 @@ export function HostEditor({
{t("hosts.sharing.ownerOnlyControl")}
</p>
)}
{missingAuthNotice && (
<p className="text-[10px] text-destructive">
{missingAuthNotice}
</p>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 border-t border-border pt-4 mt-1">
<div className="flex flex-col gap-1.5">
@@ -669,26 +682,6 @@ export function HostEditor({
{t("hosts.oidcUsernameHint")}
</p>
)}
{authMethod === "stepca" && (
<div className="flex flex-col gap-2 border-t border-border pt-3">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
{t("hosts.stepcaLabel")}
</span>
<a
href="https://smallstep.com/docs/step-ca/provisioners/#oauthoidc-single-sign-on"
target="_blank"
rel="noreferrer"
className="text-[10px] text-accent-brand hover:underline"
>
{t("hosts.docsLink")}
</a>
</div>
<p className="text-[10px] text-muted-foreground">
{t("hosts.stepcaDesc")}
</p>
</div>
)}
{activeAuthEditor?.hintKey && (
<p className="text-[10px] text-muted-foreground/60">
{t(activeAuthEditor.hintKey)}
@@ -1056,26 +1049,18 @@ export function HostEditor({
</>
)}
</div>
{authMethod === "opkssh" && (
<div className="flex flex-col gap-2 border-t border-border pt-3">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
{t("hosts.opksshLabel")}
</span>
<a
href="https://docs.termix.site/features/authentication/opkssh"
target="_blank"
rel="noreferrer"
className="text-[10px] text-accent-brand hover:underline"
>
{t("hosts.docsLink")}
</a>
</div>
<p className="text-[10px] text-muted-foreground">
{t("hosts.opksshDesc")}
</p>
</div>
)}
{!ActiveAuthEditor &&
currentAuthOption?.available &&
currentAuthOption.pluginId !== "core" &&
currentAuthOption.fields.length > 0 && (
<SshAuthProviderFields
pluginId={currentAuthOption.pluginId}
hostId={host?.id ? Number(host.id) : undefined}
fields={
currentAuthOption.fields as unknown as PluginSettingsField[]
}
/>
)}
{ActiveAuthEditor && (
<ActiveAuthEditor
form={form}