mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 21:03:47 +00:00
feat: support per-user credentials on shared hosts
This commit is contained in:
@@ -1007,6 +1007,19 @@ const migrateSchema = () => {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
sqlite.prepare("SELECT override_credential_id FROM host_access LIMIT 1").get();
|
||||
} catch {
|
||||
try {
|
||||
sqlite.exec("ALTER TABLE host_access ADD COLUMN override_credential_id INTEGER REFERENCES ssh_credentials(id) ON DELETE SET NULL");
|
||||
} catch (alterError) {
|
||||
databaseLogger.warn("Failed to add override_credential_id column", {
|
||||
operation: "schema_migration",
|
||||
error: alterError,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
sqlite.prepare("SELECT sudo_password FROM ssh_data LIMIT 1").get();
|
||||
} catch {
|
||||
|
||||
@@ -461,6 +461,10 @@ export const hostAccess = sqliteTable("host_access", {
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
lastAccessedAt: text("last_accessed_at"),
|
||||
accessCount: integer("access_count").notNull().default(0),
|
||||
overrideCredentialId: integer("override_credential_id").references(
|
||||
() => sshCredentials.id,
|
||||
{ onDelete: "set null" },
|
||||
),
|
||||
});
|
||||
|
||||
export const sharedCredentials = sqliteTable("shared_credentials", {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
sharedCredentials,
|
||||
snippets,
|
||||
snippetAccess,
|
||||
sshCredentials,
|
||||
} from "../db/schema.js";
|
||||
import { eq, and, desc, sql, or, isNull, gte } from "drizzle-orm";
|
||||
import type { Response } from "express";
|
||||
@@ -1520,4 +1521,58 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
router.put(
|
||||
"/host-access/:hostId/credential",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).userId!;
|
||||
const hostId = Number.parseInt(String(req.params.hostId), 10);
|
||||
const { credentialId } = req.body;
|
||||
|
||||
if (!hostId || isNaN(hostId)) {
|
||||
return res.status(400).json({ error: "Invalid host ID" });
|
||||
}
|
||||
|
||||
const access = await db
|
||||
.select()
|
||||
.from(hostAccess)
|
||||
.where(
|
||||
and(eq(hostAccess.hostId, hostId), eq(hostAccess.userId, userId)),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (access.length === 0) {
|
||||
return res.status(403).json({ error: "No access to this host" });
|
||||
}
|
||||
|
||||
if (credentialId) {
|
||||
const cred = await db
|
||||
.select({ id: sshCredentials.id })
|
||||
.from(sshCredentials)
|
||||
.where(
|
||||
and(
|
||||
eq(sshCredentials.id, credentialId),
|
||||
eq(sshCredentials.userId, userId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (cred.length === 0) {
|
||||
return res.status(404).json({ error: "Credential not found" });
|
||||
}
|
||||
}
|
||||
|
||||
await db
|
||||
.update(hostAccess)
|
||||
.set({ overrideCredentialId: credentialId || null })
|
||||
.where(eq(hostAccess.id, access[0].id));
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
databaseLogger.error("Failed to set override credential", error);
|
||||
res.status(500).json({ error: "Failed to update credential" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -75,8 +75,52 @@ export async function resolveHostById(
|
||||
if (host.credentialId) {
|
||||
const ownerId = (host.userId || userId) as string;
|
||||
try {
|
||||
// Try shared credential first for non-owner users
|
||||
// Try user's own override credential first
|
||||
if (userId !== ownerId) {
|
||||
try {
|
||||
const { hostAccess } = await import("../database/db/schema.js");
|
||||
const accessRecords = await db
|
||||
.select()
|
||||
.from(hostAccess)
|
||||
.where(
|
||||
and(
|
||||
eq(hostAccess.hostId, hostId),
|
||||
eq(hostAccess.userId, userId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
const overrideCredId = accessRecords[0]?.overrideCredentialId as number | null;
|
||||
if (overrideCredId) {
|
||||
const userCreds = await SimpleDBOps.select(
|
||||
db
|
||||
.select()
|
||||
.from(sshCredentials)
|
||||
.where(
|
||||
and(
|
||||
eq(sshCredentials.id, overrideCredId),
|
||||
eq(sshCredentials.userId, userId),
|
||||
),
|
||||
),
|
||||
"ssh_credentials",
|
||||
userId,
|
||||
);
|
||||
if (userCreds.length > 0) {
|
||||
const cred = userCreds[0] as Record<string, unknown>;
|
||||
host.password = cred.password;
|
||||
host.key = cred.key;
|
||||
host.keyPassword = cred.keyPassword;
|
||||
host.keyType = cred.keyType;
|
||||
if (!host.overrideCredentialUsername) {
|
||||
host.username = cred.username;
|
||||
}
|
||||
host.authType = cred.key ? "key" : cred.password ? "password" : "none";
|
||||
return host as unknown as SSHHost;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// fall through to shared credential
|
||||
}
|
||||
|
||||
try {
|
||||
const { SharedCredentialManager } =
|
||||
await import("../utils/shared-credential-manager.js");
|
||||
|
||||
Reference in New Issue
Block a user