mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 07:35:24 +00:00
feat: drop the 2.8 columns, moves and aliases kept for a downgrade
This commit is contained in:
1 parent
d3d8269cb7
commit
61f46d2517
196 files changed
+29909
-10511
No files matched your search
@@ -34,14 +34,13 @@ export function pickResolvedPassword(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const USERNAME_PLACEHOLDER =
|
||||
/\$(?:external\.username|oidc\.preferred_username)/g;
|
||||
const USERNAME_PLACEHOLDER = /\$external\.username/g;
|
||||
|
||||
/**
|
||||
* Expands `$external.username` (or its 2.8 spelling `$oidc.preferred_username`)
|
||||
* in an SSH username to the name the connecting user signed in with through
|
||||
* SSO or LDAP. Returns the username unchanged if it has no placeholder or the
|
||||
* user has no external sign-in.
|
||||
* Expands `$external.username` in an SSH username to the name the connecting
|
||||
* user signed in with through SSO or LDAP: the subject of their first
|
||||
* external identity. Returns the username unchanged if it has no placeholder
|
||||
* or the user has no external sign-in.
|
||||
*/
|
||||
export async function expandExternalUsername(
|
||||
username: string | undefined,
|
||||
@@ -52,31 +51,14 @@ export async function expandExternalUsername(
|
||||
}
|
||||
|
||||
try {
|
||||
const { createCurrentUserRepository } =
|
||||
const { createCurrentUserAuthRepository } =
|
||||
await import("../database/repositories/factory.js");
|
||||
const user = await createCurrentUserRepository().findById(userId);
|
||||
let externalName = user?.oidcIdentifier;
|
||||
const identities =
|
||||
await createCurrentUserAuthRepository().listIdentitiesForUser(userId);
|
||||
const externalName = [...identities].sort((a, b) => a.id - b.id)[0]
|
||||
?.subject;
|
||||
if (!externalName) return username;
|
||||
|
||||
const match = /^ldap:(\d+):(.+)$/.exec(externalName);
|
||||
if (match) {
|
||||
// Only strip the prefix for a real LDAP identity, to prevent spoofing
|
||||
// through an SSO subject that happens to look like one.
|
||||
const { createCurrentUserAuthRepository } =
|
||||
await import("../database/repositories/factory.js");
|
||||
const identities =
|
||||
await createCurrentUserAuthRepository().listIdentitiesForUser(userId);
|
||||
if (
|
||||
identities.some(
|
||||
(identity) =>
|
||||
identity.providerId === `ldap:${match[1]}` &&
|
||||
identity.subject === match[2],
|
||||
)
|
||||
) {
|
||||
externalName = match[2];
|
||||
}
|
||||
}
|
||||
|
||||
return username.replace(USERNAME_PLACEHOLDER, () => externalName);
|
||||
} catch {
|
||||
return username;
|
||||
|
||||
@@ -258,6 +258,35 @@ export async function resolveForEditor(input: {
|
||||
return resolveAll(catalog, levels, input.hostId ?? null);
|
||||
}
|
||||
|
||||
/** Each folder's credential, from its auth default, keyed by folder id. */
|
||||
export async function folderCredentialIds(
|
||||
userId: string,
|
||||
): Promise<Map<number, number>> {
|
||||
const result = new Map<number, number>();
|
||||
const rows = await createCurrentHostDefaultsRepository().listOwnedBy(userId);
|
||||
for (const row of rows) {
|
||||
if (
|
||||
row.level !== "folder" ||
|
||||
row.folderId == null ||
|
||||
row.namespace !== CORE_NAMESPACE ||
|
||||
row.key !== "auth"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const auth = decodeDefaultValue(row.value) as {
|
||||
authType?: string;
|
||||
credentialId?: unknown;
|
||||
} | null;
|
||||
if (
|
||||
auth?.authType === "credential" &&
|
||||
typeof auth.credentialId === "number"
|
||||
) {
|
||||
result.set(row.folderId, auth.credentialId);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A folder's credential is its auth default: hosts in the folder that follow
|
||||
* their defaults sign in with it. Null clears it.
|
||||
|
||||
@@ -106,37 +106,11 @@ export async function resolveHostById(
|
||||
host.jumpHosts = [];
|
||||
}
|
||||
}
|
||||
if (typeof host.terminalConfig === "string" && host.terminalConfig) {
|
||||
try {
|
||||
host.terminalConfig = JSON.parse(host.terminalConfig as string);
|
||||
} catch {
|
||||
host.terminalConfig = undefined;
|
||||
}
|
||||
}
|
||||
if (
|
||||
host.terminalConfig &&
|
||||
typeof host.terminalConfig === "object" &&
|
||||
!Array.isArray(host.terminalConfig)
|
||||
) {
|
||||
// 2.8 editors kept the sudo password inside terminal_config. It is only
|
||||
// ever handed out as sudoPassword, and only to the owner.
|
||||
const { sudoPassword: legacySudo, ...rest } = host.terminalConfig as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
if (ownerEquivalent && !host.sudoPassword && legacySudo) {
|
||||
host.sudoPassword = legacySudo;
|
||||
}
|
||||
host.terminalConfig = rest;
|
||||
}
|
||||
// Stored as the text "true"/"false"; the string "false" is truthy and would
|
||||
// make the password provider skip password auth.
|
||||
host.forceKeyboardInteractive =
|
||||
String(host.forceKeyboardInteractive) === "true";
|
||||
// A row the boot copy has not reached yet still has them in terminal_config.
|
||||
host.sshOptions = parseSshOptions(
|
||||
host.sshOptions != null ? host.sshOptions : host.terminalConfig,
|
||||
);
|
||||
host.sshOptions = parseSshOptions(host.sshOptions);
|
||||
if (typeof host.socks5ProxyChain === "string" && host.socks5ProxyChain) {
|
||||
try {
|
||||
host.socks5ProxyChain = JSON.parse(host.socks5ProxyChain as string);
|
||||
@@ -157,25 +131,8 @@ export async function resolveHostById(
|
||||
sharedAuthResolution = await resolveRecipientSshAuth(host, hostId, userId);
|
||||
if (!sharedAuthResolution) return null;
|
||||
} else {
|
||||
let effectiveCredentialId = host.credentialId as number | null | undefined;
|
||||
if (
|
||||
!effectiveCredentialId &&
|
||||
host.authType === "credential" &&
|
||||
host.folder
|
||||
) {
|
||||
try {
|
||||
effectiveCredentialId = await repository.findFolderCredentialId(
|
||||
ownerId,
|
||||
host.folder as string,
|
||||
);
|
||||
} catch (e) {
|
||||
sshLogger.warn("Failed to resolve folder credential for host", {
|
||||
operation: "host_resolver_folder_credential",
|
||||
hostId,
|
||||
error: getErrorMessage(e, "Unknown"),
|
||||
});
|
||||
}
|
||||
}
|
||||
const effectiveCredentialId = host.credentialId as
|
||||
number | null | undefined;
|
||||
|
||||
if (effectiveCredentialId) {
|
||||
try {
|
||||
|
||||
@@ -50,8 +50,7 @@ function optionalString(value: unknown): string | null | undefined {
|
||||
|
||||
/**
|
||||
* The SSH options in a value (a JSON string or an object), typed and with
|
||||
* anything else dropped. A terminal_config value works too, which is how the
|
||||
* 2.8 keys are read.
|
||||
* anything else dropped. A 2.8 client's terminalConfig works too.
|
||||
*/
|
||||
export function parseSshOptions(value: unknown): HostSshOptions {
|
||||
const source = asObject(value);
|
||||
|
||||
Reference in new issue
Block a user