wraith/backend/prisma/schema.prisma
Vantz Stockwell 6d76558bc3 feat: multi-user isolation with admin/user roles
Full per-user data isolation across all tables:
- Migration adds userId FK to hosts, host_groups, credentials, ssh_keys,
  connection_logs. Backfills existing data to admin@wraith.local.
- All services scope queries by userId from JWT (req.user.sub).
  Users can only see/modify their own data. Cross-user access returns 403.
- Two roles: admin (full access + user management) and user (own data only).
- Admin endpoints: list/create/edit/delete users, reset password, reset TOTP.
  Protected by AdminGuard. Admins cannot delete themselves or remove own role.
- JWT payload now includes role. Frontend auth store exposes isAdmin getter.
- Seed script fixed: checks for admin@wraith.local specifically (not any user).
  Uses upsert, seeds with role=admin. Migration cleans up duplicate users.
- Connection logs now attributed to the connecting user via WS auth.
- Deleting a user CASCADEs to all their hosts, credentials, keys, and logs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 12:57:38 -04:00

134 lines
4.7 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
passwordHash String @map("password_hash")
displayName String? @map("display_name")
role String @default("user")
totpSecret String? @map("totp_secret")
totpEnabled Boolean @default(false) @map("totp_enabled")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
hosts Host[]
hostGroups HostGroup[]
credentials Credential[]
sshKeys SshKey[]
connectionLogs ConnectionLog[]
@@map("users")
}
model HostGroup {
id Int @id @default(autoincrement())
name String
parentId Int? @map("parent_id")
userId Int @map("user_id")
sortOrder Int @default(0) @map("sort_order")
parent HostGroup? @relation("GroupTree", fields: [parentId], references: [id], onDelete: SetNull)
children HostGroup[] @relation("GroupTree")
hosts Host[]
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("host_groups")
}
model Host {
id Int @id @default(autoincrement())
name String
hostname String
port Int @default(22)
protocol Protocol @default(ssh)
groupId Int? @map("group_id")
credentialId Int? @map("credential_id")
userId Int @map("user_id")
tags String[] @default([])
notes String?
color String? @db.VarChar(7)
sortOrder Int @default(0) @map("sort_order")
hostFingerprint String? @map("host_fingerprint")
lastConnectedAt DateTime? @map("last_connected_at")
group HostGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
connectionLogs ConnectionLog[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("hosts")
}
model Credential {
id Int @id @default(autoincrement())
name String
username String?
domain String?
type CredentialType
userId Int @map("user_id")
encryptedValue String? @map("encrypted_value")
sshKeyId Int? @map("ssh_key_id")
sshKey SshKey? @relation(fields: [sshKeyId], references: [id], onDelete: SetNull)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
hosts Host[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("credentials")
}
model SshKey {
id Int @id @default(autoincrement())
name String
keyType String @map("key_type") @db.VarChar(20)
fingerprint String?
publicKey String? @map("public_key")
userId Int @map("user_id")
encryptedPrivateKey String @map("encrypted_private_key")
passphraseEncrypted String? @map("passphrase_encrypted")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
credentials Credential[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("ssh_keys")
}
model ConnectionLog {
id Int @id @default(autoincrement())
hostId Int @map("host_id")
userId Int @map("user_id")
protocol Protocol
connectedAt DateTime @default(now()) @map("connected_at")
disconnectedAt DateTime? @map("disconnected_at")
host Host @relation(fields: [hostId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("connection_logs")
}
model Setting {
key String @id
value String
@@map("settings")
}
enum Protocol {
ssh
rdp
}
enum CredentialType {
password
ssh_key
}