mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 18:31:33 +00:00
feat: share credentials with users and roles, inherit data on account deletion (#1342)
* feat: share credentials with users and roles, inherit data on account deletion Credentials can be shared at "use" or "manage" level. Recipients get a copy re-encrypted under their own data key (shared_credential_secrets), kept in step with the owner's row through the same lifecycle hooks as shared host secrets. One gate, findUsableCredential(), replaces the private-namespace lookups so a shared credential works wherever a private one does. Deleting a user now hands their hosts and credentials to a successor (the deleting admin by default) instead of revoking everything they shared. * fix: harden credential ownership transfer
This commit is contained in:
@@ -348,6 +348,54 @@ export class CredentialRepository {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-keys every credential of one user to another: decrypt under the old
|
||||
* owner's key, encrypt under the new one, change the owner. Used when an
|
||||
* account is deleted and its data is inherited rather than dropped.
|
||||
*/
|
||||
async transferAllToUser(
|
||||
fromUserId: string,
|
||||
toUserId: string,
|
||||
): Promise<number[]> {
|
||||
const fromKey = DataCrypto.validateUserAccess(fromUserId);
|
||||
const toKey = DataCrypto.validateUserAccess(toUserId);
|
||||
const rows = await this.context.drizzle
|
||||
.select()
|
||||
.from(sshCredentials)
|
||||
.where(eq(sshCredentials.userId, fromUserId));
|
||||
const moved: number[] = [];
|
||||
for (const row of rows) {
|
||||
const plain = DataCrypto.decryptRecord(
|
||||
"ssh_credentials",
|
||||
row,
|
||||
fromUserId,
|
||||
fromKey,
|
||||
);
|
||||
const {
|
||||
id: _id,
|
||||
userId: _userId,
|
||||
...fields
|
||||
} = plain as Record<string, unknown>;
|
||||
const encrypted = DataCrypto.encryptRecord(
|
||||
"ssh_credentials",
|
||||
{ ...fields, id: row.id },
|
||||
toUserId,
|
||||
toKey,
|
||||
) as Record<string, unknown>;
|
||||
delete encrypted.id;
|
||||
await this.context.drizzle
|
||||
.update(sshCredentials)
|
||||
.set({
|
||||
...(encrypted as Partial<NewCredentialRecord>),
|
||||
userId: toUserId,
|
||||
})
|
||||
.where(eq(sshCredentials.id, row.id));
|
||||
moved.push(row.id);
|
||||
}
|
||||
if (moved.length) await this.afterWrite();
|
||||
return moved;
|
||||
}
|
||||
|
||||
private encryptCredentialRecordForWrite<T extends Record<string, unknown>>(
|
||||
record: T,
|
||||
userId: string,
|
||||
|
||||
Reference in New Issue
Block a user