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
@@ -710,9 +710,6 @@ export function registerHostBulkRoutes(
|
||||
sudoPassword: hostData.sudoPassword || null,
|
||||
jumpHosts: jumpHosts ? JSON.stringify(jumpHosts) : null,
|
||||
...importedStatusCheck(hostData as Record<string, unknown>),
|
||||
terminalConfig: hostData.terminalConfig
|
||||
? JSON.stringify(hostData.terminalConfig)
|
||||
: null,
|
||||
// A 2.8 export carries these inside terminalConfig.
|
||||
sshOptions:
|
||||
sshOptionsForWrite({
|
||||
@@ -975,7 +972,6 @@ export function registerHostBulkRoutes(
|
||||
: null,
|
||||
statusCheckEnabled: true,
|
||||
statusCheckInterval: null,
|
||||
terminalConfig: null,
|
||||
sshOptions: null,
|
||||
forceKeyboardInteractive: "false",
|
||||
notes: null,
|
||||
|
||||
@@ -12,7 +12,10 @@ import {
|
||||
} from "../repositories/factory.js";
|
||||
import { isNonEmptyString } from "./host-normalizers.js";
|
||||
import { recompute } from "../../hosts/defaults/recompute.js";
|
||||
import { setFolderCredentialDefault } from "../../hosts/defaults/service.js";
|
||||
import {
|
||||
folderCredentialIds,
|
||||
setFolderCredentialDefault,
|
||||
} from "../../hosts/defaults/service.js";
|
||||
|
||||
type HostFolderRoutesDeps = {
|
||||
authenticateJWT: RequestHandler;
|
||||
@@ -143,10 +146,17 @@ export function registerHostFolderRoutes(
|
||||
}
|
||||
|
||||
try {
|
||||
const folders =
|
||||
await createCurrentHostFolderRepository().listFolders(userId);
|
||||
const [folders, credentials] = await Promise.all([
|
||||
createCurrentHostFolderRepository().listFolders(userId),
|
||||
folderCredentialIds(userId),
|
||||
]);
|
||||
|
||||
res.json(folders);
|
||||
res.json(
|
||||
folders.map((folder) => ({
|
||||
...folder,
|
||||
credentialId: credentials.get(folder.id) ?? null,
|
||||
})),
|
||||
);
|
||||
} catch (err) {
|
||||
sshLogger.error("Failed to fetch folders", err, {
|
||||
operation: "fetch_folders",
|
||||
@@ -238,7 +248,6 @@ export function registerHostFolderRoutes(
|
||||
name,
|
||||
color,
|
||||
icon,
|
||||
normalizedCredentialId,
|
||||
);
|
||||
if (typeof localOnly === "boolean") {
|
||||
await createCurrentHostFolderRepository().setLocalOnly(
|
||||
|
||||
@@ -62,6 +62,7 @@ export const OWNER_PRIVATE_AUTH_FIELDS = {
|
||||
],
|
||||
} as const;
|
||||
|
||||
/** Keys a 2.8 client's terminalConfig may carry that only the owner may set. */
|
||||
export const OWNER_PRIVATE_TERMINAL_CONFIG_FIELDS = [
|
||||
"sudoPassword",
|
||||
"agentSocketPath",
|
||||
@@ -263,18 +264,10 @@ export function stripSensitiveFields(
|
||||
host: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const result = { ...host };
|
||||
const terminalConfigForSudo =
|
||||
host.terminalConfig &&
|
||||
typeof host.terminalConfig === "object" &&
|
||||
!Array.isArray(host.terminalConfig)
|
||||
? (host.terminalConfig as Record<string, unknown>)
|
||||
: undefined;
|
||||
result.hasKey = !!host.key;
|
||||
result.hasKeyPassword = !!host.keyPassword;
|
||||
result.hasPassword = !!host.password;
|
||||
// 2.8 editors kept the sudo password inside terminal_config.
|
||||
result.hasSudoPassword =
|
||||
!!host.sudoPassword || !!terminalConfigForSudo?.sudoPassword;
|
||||
result.hasSudoPassword = !!host.sudoPassword;
|
||||
for (const field of SENSITIVE_FIELDS) {
|
||||
delete result[field];
|
||||
}
|
||||
@@ -283,11 +276,11 @@ export function stripSensitiveFields(
|
||||
typeof result.terminalConfig === "object" &&
|
||||
!Array.isArray(result.terminalConfig)
|
||||
) {
|
||||
const terminalConfig = {
|
||||
...(result.terminalConfig as Record<string, unknown>),
|
||||
};
|
||||
delete terminalConfig.sudoPassword;
|
||||
result.terminalConfig = terminalConfig;
|
||||
const { sudoPassword: _sudo, ...rest } = result.terminalConfig as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
result.terminalConfig = rest;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -397,44 +390,22 @@ export function parseSharedSource(
|
||||
}
|
||||
}
|
||||
|
||||
function parseJsonObject(value: unknown): Record<string, unknown> | null {
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
if (typeof value !== "string" || !value) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
||||
? parsed
|
||||
: null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sshOptions, and terminalConfig in the shape 2.8 clients and exports read:
|
||||
* core's own keys only, the SSH options. Everything else 2.8 kept there now
|
||||
* belongs to plugins' host settings, which they put back through their
|
||||
* hostPayloadLegacy. A sudo password 2.8 kept in terminal_config comes out as
|
||||
* sudoPassword, for the sanitizers to handle.
|
||||
* hostPayloadLegacy.
|
||||
*/
|
||||
export function hostTerminalExport(host: Record<string, unknown>): {
|
||||
sshOptions: HostSshOptions;
|
||||
terminalConfig?: Record<string, unknown>;
|
||||
sudoPassword?: unknown;
|
||||
} {
|
||||
const raw = parseJsonObject(host.terminalConfig);
|
||||
const sshOptions = parseSshOptions(
|
||||
host.sshOptions != null ? host.sshOptions : raw,
|
||||
);
|
||||
const sshOptions = parseSshOptions(host.sshOptions);
|
||||
const terminalConfig: Record<string, unknown> = { ...sshOptions };
|
||||
const legacySudo = !host.sudoPassword ? raw?.sudoPassword : undefined;
|
||||
return {
|
||||
sshOptions,
|
||||
terminalConfig:
|
||||
Object.keys(terminalConfig).length > 0 ? terminalConfig : undefined,
|
||||
...(legacySudo ? { sudoPassword: legacySudo } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
/**
|
||||
* How a host update writes terminal_config and ssh_options, kept apart from
|
||||
* the route so it can be tested on its own.
|
||||
* How a host update writes ssh_options, kept apart from the route so it can
|
||||
* be tested on its own.
|
||||
*/
|
||||
|
||||
import { createCurrentHostResolutionRepository } from "../repositories/factory.js";
|
||||
import { parseSshOptions } from "../../hosts/ssh-options.js";
|
||||
import {
|
||||
OWNER_PRIVATE_SSH_OPTION_FIELDS,
|
||||
OWNER_PRIVATE_TERMINAL_CONFIG_FIELDS,
|
||||
} from "./host-normalizers.js";
|
||||
import { OWNER_PRIVATE_SSH_OPTION_FIELDS } from "./host-normalizers.js";
|
||||
|
||||
export function parseTerminalConfig(
|
||||
value: unknown,
|
||||
@@ -27,63 +24,41 @@ export function parseTerminalConfig(
|
||||
}
|
||||
|
||||
/**
|
||||
* terminal_config is merged into what is stored rather than replaced. The
|
||||
* 2.8 keys the editor no longer sends (the terminal's look, which the
|
||||
* ssh-terminal plugin copies into its host settings, and a sudo password 2.8
|
||||
* editors kept there) stay until 26.10.0 drops the column. A shared editor
|
||||
* never changes the owner's private keys in either JSON. Returns an error
|
||||
* for a terminalConfig that cannot be read.
|
||||
* A 2.8 client's terminalConfig only feeds sshOptions, which the route has
|
||||
* already taken from it. A shared editor never changes the owner's private
|
||||
* SSH options. Returns an error for a terminalConfig that cannot be read.
|
||||
*/
|
||||
export async function mergeStoredTerminalFields(
|
||||
export async function mergeStoredSshOptions(
|
||||
sshDataObj: Record<string, unknown>,
|
||||
hostData: Record<string, unknown>,
|
||||
hostId: number,
|
||||
ownerId: string,
|
||||
isOwner: boolean,
|
||||
): Promise<string | null> {
|
||||
const sentTerminalConfig =
|
||||
hostData.terminalConfig !== undefined && hostData.terminalConfig !== null;
|
||||
const incoming = sentTerminalConfig
|
||||
? parseTerminalConfig(hostData.terminalConfig)
|
||||
: null;
|
||||
if (sentTerminalConfig && !incoming) return "Invalid terminal config";
|
||||
if (
|
||||
hostData.terminalConfig !== undefined &&
|
||||
hostData.terminalConfig !== null &&
|
||||
!parseTerminalConfig(hostData.terminalConfig)
|
||||
) {
|
||||
return "Invalid terminal config";
|
||||
}
|
||||
delete sshDataObj.terminalConfig;
|
||||
|
||||
const sentSudoPassword = isOwner && hostData.sudoPassword !== undefined;
|
||||
if (!incoming && !sentSudoPassword && sshDataObj.sshOptions === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (isOwner || sshDataObj.sshOptions === undefined) return null;
|
||||
|
||||
const stored = await createCurrentHostResolutionRepository().findHostById(
|
||||
hostId,
|
||||
ownerId,
|
||||
);
|
||||
const storedConfig = parseTerminalConfig(stored?.terminalConfig) ?? {};
|
||||
|
||||
if (incoming || (sentSudoPassword && "sudoPassword" in storedConfig)) {
|
||||
const merged = { ...storedConfig, ...(incoming ?? {}) };
|
||||
if (!isOwner) {
|
||||
for (const field of OWNER_PRIVATE_TERMINAL_CONFIG_FIELDS) {
|
||||
if (field in storedConfig) merged[field] = storedConfig[field];
|
||||
else delete merged[field];
|
||||
}
|
||||
const options = parseSshOptions(sshDataObj.sshOptions);
|
||||
const storedOptions = parseSshOptions(stored?.sshOptions);
|
||||
for (const field of OWNER_PRIVATE_SSH_OPTION_FIELDS) {
|
||||
if (storedOptions[field] !== undefined) {
|
||||
options[field] = storedOptions[field];
|
||||
} else {
|
||||
delete options[field];
|
||||
}
|
||||
// A sudo password saved on its own lives in the encrypted column.
|
||||
if (sentSudoPassword) delete merged.sudoPassword;
|
||||
sshDataObj.terminalConfig = JSON.stringify(merged);
|
||||
}
|
||||
|
||||
if (!isOwner && sshDataObj.sshOptions !== undefined) {
|
||||
const options = parseSshOptions(sshDataObj.sshOptions);
|
||||
const storedOptions = parseSshOptions(stored?.sshOptions ?? storedConfig);
|
||||
for (const field of OWNER_PRIVATE_SSH_OPTION_FIELDS) {
|
||||
if (storedOptions[field] !== undefined) {
|
||||
options[field] = storedOptions[field];
|
||||
} else {
|
||||
delete options[field];
|
||||
}
|
||||
}
|
||||
sshDataObj.sshOptions = JSON.stringify(options);
|
||||
}
|
||||
sshDataObj.sshOptions = JSON.stringify(options);
|
||||
return null;
|
||||
}
|
||||
@@ -98,7 +98,7 @@ import {
|
||||
listHostProtocols,
|
||||
} from "../../hosts/protocol-auth/registry.js";
|
||||
import {
|
||||
mergeStoredTerminalFields,
|
||||
mergeStoredSshOptions,
|
||||
parseTerminalConfig,
|
||||
} from "./host-terminal-fields.js";
|
||||
|
||||
@@ -316,11 +316,6 @@ router.post(
|
||||
jumpHosts: Array.isArray(jumpHosts) ? JSON.stringify(jumpHosts) : null,
|
||||
statusCheckEnabled: statusCheckEnabled === false ? 0 : 1,
|
||||
statusCheckInterval: normalizeStatusInterval(statusCheckInterval),
|
||||
terminalConfig: terminalConfig
|
||||
? typeof terminalConfig === "string"
|
||||
? terminalConfig
|
||||
: JSON.stringify(terminalConfig)
|
||||
: null,
|
||||
sshOptions: sshOptionsForWrite({ sshOptions, terminalConfig }) ?? null,
|
||||
forceKeyboardInteractive: forceKeyboardInteractive ? "true" : "false",
|
||||
notes: notes || null,
|
||||
@@ -889,11 +884,6 @@ router.put(
|
||||
jumpHosts: Array.isArray(jumpHosts) ? JSON.stringify(jumpHosts) : null,
|
||||
statusCheckEnabled: statusCheckEnabled === false ? 0 : 1,
|
||||
statusCheckInterval: normalizeStatusInterval(statusCheckInterval),
|
||||
terminalConfig: terminalConfig
|
||||
? typeof terminalConfig === "string"
|
||||
? terminalConfig
|
||||
: JSON.stringify(terminalConfig)
|
||||
: null,
|
||||
forceKeyboardInteractive: forceKeyboardInteractive ? "true" : "false",
|
||||
notes: notes || null,
|
||||
useSocks5: useSocks5 ? 1 : 0,
|
||||
@@ -1081,7 +1071,7 @@ router.put(
|
||||
}
|
||||
}
|
||||
|
||||
const terminalFieldsError = await mergeStoredTerminalFields(
|
||||
const terminalFieldsError = await mergeStoredSshOptions(
|
||||
sshDataObj,
|
||||
hostData,
|
||||
Number(hostId),
|
||||
@@ -1661,11 +1651,7 @@ router.get(
|
||||
}
|
||||
|
||||
const resolved = (await resolveHostCredentials(host, userId)) || host;
|
||||
let value = resolved[field];
|
||||
|
||||
if (!value && field === "sudoPassword") {
|
||||
value = hostTerminalExport(resolved).sudoPassword || null;
|
||||
}
|
||||
const value = resolved[field];
|
||||
|
||||
if (!value) {
|
||||
return res.status(404).json({ error: "No password set" });
|
||||
@@ -1775,10 +1761,7 @@ router.get(
|
||||
credentialId: resolvedHost.credentialId || null,
|
||||
overrideCredentialUsername:
|
||||
!!resolvedHost.overrideCredentialUsername,
|
||||
sudoPassword:
|
||||
resolvedHost.sudoPassword ||
|
||||
hostTerminalExport(resolvedHost).sudoPassword ||
|
||||
null,
|
||||
sudoPassword: resolvedHost.sudoPassword || null,
|
||||
jumpHosts: resolvedHost.jumpHosts
|
||||
? JSON.parse(resolvedHost.jumpHosts as string)
|
||||
: null,
|
||||
@@ -1911,9 +1894,7 @@ router.get(
|
||||
!!resolvedHost.overrideCredentialUsername,
|
||||
sudoPassword: shareMode
|
||||
? null
|
||||
: resolvedHost.sudoPassword ||
|
||||
hostTerminalExport(resolvedHost).sudoPassword ||
|
||||
null,
|
||||
: resolvedHost.sudoPassword || null,
|
||||
jumpHosts: resolvedHost.jumpHosts
|
||||
? JSON.parse(resolvedHost.jumpHosts as string)
|
||||
: null,
|
||||
|
||||
@@ -123,7 +123,6 @@ export function registerUserAdminRoutes(
|
||||
username: u.username,
|
||||
is_admin: u.isAdmin,
|
||||
is_external: isExternalAccount(u),
|
||||
is_oidc: u.isOidc,
|
||||
password_hash: u.passwordHash ? "set" : null,
|
||||
// Management-only details stay admin-eyes-only; regular users hit
|
||||
// this route to pick sharing targets.
|
||||
@@ -470,15 +469,6 @@ export function registerUserAdminRoutes(
|
||||
username,
|
||||
passwordHash: password_hash,
|
||||
isAdmin: false,
|
||||
isOidc: false,
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
issuerUrl: "",
|
||||
authorizationUrl: "",
|
||||
tokenUrl: "",
|
||||
identifierPath: "",
|
||||
namePath: "",
|
||||
scopes: "openid email profile",
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -631,7 +621,7 @@ export function registerUserAdminRoutes(
|
||||
if (!targetUser) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
if (targetUser.isOidc && !targetUser.passwordHash) {
|
||||
if (targetUser.isExternal && !targetUser.passwordHash) {
|
||||
return res.status(400).json({
|
||||
error: "This user authenticates through an external provider",
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ export function registerUserExternalAccountRoutes(
|
||||
* /users/link-external-to-password:
|
||||
* post:
|
||||
* summary: Merge an external account into a password account
|
||||
* description: Moves an external-only account's sign-in identities onto a password account and deletes the external-only account (admin only). A 2.8 SSO account whose only sign-in is the matching legacy OIDC link is accepted as the target too, to merge a duplicate made by 2.9.0. The 2.8 path /users/link-oidc-to-password and its oidcUserId field are accepted too.
|
||||
* description: Moves an external-only account's sign-in identities onto a password account and deletes the external-only account (admin only). A 2.8 SSO account whose only sign-in is the matching legacy OIDC link is accepted as the target too, to merge a duplicate made by 2.9.0.
|
||||
* tags:
|
||||
* - Users
|
||||
* requestBody:
|
||||
@@ -86,7 +86,7 @@ export function registerUserExternalAccountRoutes(
|
||||
*/
|
||||
const link = async (req: Request, res: Response) => {
|
||||
const adminUserId = (req as AuthenticatedRequest).userId;
|
||||
const externalUserId = req.body?.externalUserId ?? req.body?.oidcUserId;
|
||||
const externalUserId = req.body?.externalUserId;
|
||||
const targetUsername = req.body?.targetUsername;
|
||||
|
||||
if (
|
||||
@@ -143,11 +143,6 @@ export function registerUserExternalAccountRoutes(
|
||||
adminUserId,
|
||||
});
|
||||
|
||||
await userRepository.update(targetUser.id, {
|
||||
isOidc: true,
|
||||
oidcIdentifier:
|
||||
externalUser.oidcIdentifier ?? targetUser.oidcIdentifier,
|
||||
});
|
||||
await identities.moveIdentities(externalUserId, targetUser.id);
|
||||
|
||||
await authManager.revokeAllUserSessions(externalUserId);
|
||||
@@ -201,7 +196,7 @@ export function registerUserExternalAccountRoutes(
|
||||
* /users/unlink-external-from-password:
|
||||
* post:
|
||||
* summary: Remove external sign-in from a password account
|
||||
* description: Removes every external sign-in identity from an account that also has a password (admin only). The 2.8 path /users/unlink-oidc-from-password is accepted too.
|
||||
* description: Removes every external sign-in identity from an account that also has a password (admin only).
|
||||
* tags:
|
||||
* - Users
|
||||
* requestBody:
|
||||
@@ -261,10 +256,6 @@ export function registerUserExternalAccountRoutes(
|
||||
});
|
||||
}
|
||||
|
||||
await userRepository.update(targetUser.id, {
|
||||
isOidc: false,
|
||||
oidcIdentifier: null,
|
||||
});
|
||||
await createCurrentUserAuthRepository().unlinkIdentitiesForUser(
|
||||
targetUser.id,
|
||||
);
|
||||
@@ -307,7 +298,4 @@ export function registerUserExternalAccountRoutes(
|
||||
|
||||
router.post("/link-external-to-password", authenticateJWT, link);
|
||||
router.post("/unlink-external-from-password", authenticateJWT, unlink);
|
||||
// 2.8 paths, kept until 26.10.0.
|
||||
router.post("/link-oidc-to-password", authenticateJWT, link);
|
||||
router.post("/unlink-oidc-from-password", authenticateJWT, unlink);
|
||||
}
|
||||
@@ -173,7 +173,7 @@ export function registerUserPasswordResetRoutes(
|
||||
});
|
||||
}
|
||||
|
||||
if (user.isOidc) {
|
||||
if (user.isExternal) {
|
||||
return res.json({
|
||||
message:
|
||||
"If the user exists, a password reset code has been generated. Check docker logs for the code.",
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
isNativeAppRequest,
|
||||
syncSharedCredentialsForUserRoles,
|
||||
} from "../../auth/session-issuer.js";
|
||||
import { EXTERNAL_AUTO_PROVISION_KEY } from "../../auth/provisioning.js";
|
||||
|
||||
const authManager = AuthManager.getInstance();
|
||||
|
||||
@@ -184,15 +185,6 @@ router.post("/create", async (req, res) => {
|
||||
id,
|
||||
username,
|
||||
passwordHash: password_hash,
|
||||
isOidc: false,
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
issuerUrl: "",
|
||||
authorizationUrl: "",
|
||||
tokenUrl: "",
|
||||
identifierPath: "",
|
||||
namePath: "",
|
||||
scopes: "openid email profile",
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -361,7 +353,7 @@ router.post("/proxy-login", async (req, res) => {
|
||||
return res.status(403).json({ error: "Proxy user must already exist" });
|
||||
}
|
||||
if (
|
||||
userRecord.isOidc ||
|
||||
userRecord.isExternal ||
|
||||
(await createCurrentUserAuthRepository().hasSecondFactor(userRecord.id))
|
||||
) {
|
||||
return res.status(409).json({
|
||||
@@ -600,8 +592,7 @@ router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
const hasPassword = user.passwordHash && user.passwordHash.trim() !== "";
|
||||
const isDualAuth =
|
||||
hasPassword && isExternalAccount(user) && !!user.oidcIdentifier;
|
||||
const isDualAuth = hasPassword && isExternalAccount(user);
|
||||
|
||||
const showDonationModal = shouldShowDonationModal(
|
||||
user.registeredAt,
|
||||
@@ -613,8 +604,6 @@ router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
|
||||
username: user.username,
|
||||
is_admin: !!user.isAdmin,
|
||||
is_external: isExternalAccount(user),
|
||||
// 2.8 name, kept until 26.10.0.
|
||||
is_oidc: !!user.isOidc,
|
||||
is_dual_auth: isDualAuth,
|
||||
// Any second factor; the name is what 2.8 clients read.
|
||||
totp_enabled: await createCurrentUserAuthRepository().hasSecondFactor(
|
||||
@@ -944,7 +933,7 @@ router.patch("/registration-allowed", authenticateJWT, async (req, res) => {
|
||||
* /users/external-auto-provision:
|
||||
* get:
|
||||
* summary: Get the external account auto-create setting
|
||||
* description: Whether a new account is created the first time someone signs in through an external login (SSO, LDAP or any login plugin). The 2.8 path /users/oidc-auto-provision is accepted too.
|
||||
* description: Whether a new account is created the first time someone signs in through an external login (SSO, LDAP or any login plugin).
|
||||
* tags:
|
||||
* - Users
|
||||
* responses:
|
||||
@@ -955,7 +944,7 @@ const getExternalAutoProvision: RequestHandler = async (_req, res) => {
|
||||
try {
|
||||
res.json({
|
||||
enabled: await createCurrentSettingsRepository().getBoolean(
|
||||
"oidc_auto_provision",
|
||||
EXTERNAL_AUTO_PROVISION_KEY,
|
||||
false,
|
||||
),
|
||||
});
|
||||
@@ -972,7 +961,7 @@ const getExternalAutoProvision: RequestHandler = async (_req, res) => {
|
||||
* /users/external-auto-provision:
|
||||
* patch:
|
||||
* summary: Set the external account auto-create setting
|
||||
* description: Enables or disables creating an account on first external sign-in. The 2.8 path /users/oidc-auto-provision is accepted too.
|
||||
* description: Enables or disables creating an account on first external sign-in.
|
||||
* tags:
|
||||
* - Users
|
||||
* requestBody:
|
||||
@@ -1006,7 +995,7 @@ const setExternalAutoProvision: RequestHandler = async (req, res) => {
|
||||
return res.status(400).json({ error: "Invalid value for enabled" });
|
||||
}
|
||||
await createCurrentSettingsRepository().set(
|
||||
"oidc_auto_provision",
|
||||
EXTERNAL_AUTO_PROVISION_KEY,
|
||||
enabled ? "true" : "false",
|
||||
);
|
||||
res.json({ enabled });
|
||||
@@ -1022,9 +1011,6 @@ router.patch(
|
||||
authenticateJWT,
|
||||
setExternalAutoProvision,
|
||||
);
|
||||
// 2.8 paths, kept until 26.10.0.
|
||||
router.get("/oidc-auto-provision", getExternalAutoProvision);
|
||||
router.patch("/oidc-auto-provision", authenticateJWT, setExternalAutoProvision);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
@@ -1317,7 +1303,7 @@ router.delete("/delete-account", authenticateJWT, async (req, res) => {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
if (userRecord.isOidc) {
|
||||
if (userRecord.isExternal) {
|
||||
return res.status(403).json({
|
||||
error:
|
||||
"Cannot delete external authentication accounts through this endpoint",
|
||||
|
||||
Reference in new issue
Block a user