mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 04:43:36 +00:00
Merge pull request #776 from ZacharyZcR/feat/per-user-shared-host-credentials
feat: support per-user credentials on shared hosts
This commit is contained in:
@@ -1008,6 +1008,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 {
|
try {
|
||||||
sqlite.prepare("SELECT sudo_password FROM ssh_data LIMIT 1").get();
|
sqlite.prepare("SELECT sudo_password FROM ssh_data LIMIT 1").get();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -462,6 +462,10 @@ export const hostAccess = sqliteTable("host_access", {
|
|||||||
.default(sql`CURRENT_TIMESTAMP`),
|
.default(sql`CURRENT_TIMESTAMP`),
|
||||||
lastAccessedAt: text("last_accessed_at"),
|
lastAccessedAt: text("last_accessed_at"),
|
||||||
accessCount: integer("access_count").notNull().default(0),
|
accessCount: integer("access_count").notNull().default(0),
|
||||||
|
overrideCredentialId: integer("override_credential_id").references(
|
||||||
|
() => sshCredentials.id,
|
||||||
|
{ onDelete: "set null" },
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sharedCredentials = sqliteTable("shared_credentials", {
|
export const sharedCredentials = sqliteTable("shared_credentials", {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
sharedCredentials,
|
sharedCredentials,
|
||||||
snippets,
|
snippets,
|
||||||
snippetAccess,
|
snippetAccess,
|
||||||
|
sshCredentials,
|
||||||
} from "../db/schema.js";
|
} from "../db/schema.js";
|
||||||
import { eq, and, desc, sql, or, isNull, gte } from "drizzle-orm";
|
import { eq, and, desc, sql, or, isNull, gte } from "drizzle-orm";
|
||||||
import type { Response } from "express";
|
import type { Response } from "express";
|
||||||
@@ -1524,4 +1525,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;
|
export default router;
|
||||||
|
|||||||
@@ -75,8 +75,52 @@ export async function resolveHostById(
|
|||||||
if (host.credentialId) {
|
if (host.credentialId) {
|
||||||
const ownerId = (host.userId || userId) as string;
|
const ownerId = (host.userId || userId) as string;
|
||||||
try {
|
try {
|
||||||
// Try shared credential first for non-owner users
|
// Try user's own override credential first
|
||||||
if (userId !== ownerId) {
|
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 {
|
try {
|
||||||
const { SharedCredentialManager } =
|
const { SharedCredentialManager } =
|
||||||
await import("../utils/shared-credential-manager.js");
|
await import("../utils/shared-credential-manager.js");
|
||||||
|
|||||||
Reference in New Issue
Block a user