Initial commit for gscMy carved out as its own repo (was tracked
loosely under the monorepo's web/ which is gitignored).
What this contains:
- Auth: next-auth v5 via @gsc/web-kit createAuth (Keycloak only,
identity sourced from claims, no admin.users writes)
- Chrome: @gsc/web-kit AdminShell — replaces the legacy MyShell.
Sidebar JSON config carried over and mapped to DbMenuItem.
- Middleware: createAuthMiddleware. Public: /access-denied,
/auth/keycloak, /signed-out, /api/health, /api/pam/approve.
- RP-initiated signout at /api/auth/signout → Keycloak end_session →
/signed-out (mirrors gscAdmin).
- Phosphor-iconned access-denied + signed-out landing pages.
PAM/JIT request flow (ported from gscAdmin's pre-strip git history):
- /access page (Active + Eligible tables, request modal with
duration slider + justification + optional MFA)
- API: /api/pam/{eligible, active, audit, request, approve/:token,
revoke/:id}
- src/lib/{authz, pam, pam-mail, pam-mfa}.ts — same files as
gscAdmin had before the strip. PAM tables (admin.privilege_*)
are shared with gscAdmin; gscMy uses the same Prisma model defs.
- Top-bar widget shows active grants with countdown + revoke.
Build/Deploy: Dockerfile (monorepo-root context), k8s manifests for
my.gosec.internal, self-signed TLS placeholder, DNS A record.
Keycloak gsc-my client extended to include my.gosec.internal/* in
redirect_uris + web_origins.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
189 lines
8.4 KiB
Plaintext
189 lines
8.4 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
previewFeatures = ["postgresqlExtensions"]
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
extensions = [pgcrypto, uuid_ossp(map: "uuid-ossp", schema: "public")]
|
|
schemas = ["admin", "public"]
|
|
}
|
|
|
|
// ─── admin schema (shared with gscAdmin) ───────────────────────
|
|
|
|
model User {
|
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
|
gscsid String @unique @db.VarChar(64)
|
|
firstName String? @map("first_name") @db.VarChar(128)
|
|
lastName String? @map("last_name") @db.VarChar(128)
|
|
displayName String? @map("display_name") @db.VarChar(256)
|
|
email String? @db.VarChar(256)
|
|
phone String? @db.VarChar(32)
|
|
mobile String? @db.VarChar(32)
|
|
avatarUrl String? @map("avatar_url") @db.VarChar(512)
|
|
timezone String? @default("UTC") @db.VarChar(64)
|
|
locale String? @default("en") @db.VarChar(10)
|
|
status String? @default("active") @db.VarChar(32)
|
|
lastLoginAt DateTime? @map("last_login_at") @db.Timestamptz(6)
|
|
lastActivityAt DateTime? @map("last_activity_at") @db.Timestamptz(6)
|
|
metadata Json? @default("{}")
|
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
|
updatedAt DateTime? @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
|
settings UserSetting[]
|
|
|
|
@@index([gscsid], map: "idx_users_gscsid")
|
|
@@map("users")
|
|
@@schema("admin")
|
|
}
|
|
|
|
model SettingsDefinition {
|
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
|
category String @db.VarChar(64)
|
|
key String @db.VarChar(128)
|
|
dataType String @map("data_type") @db.VarChar(32)
|
|
defaultValue Json? @map("default_value")
|
|
min_value Decimal? @db.Decimal
|
|
max_value Decimal? @db.Decimal
|
|
allowed_values Json?
|
|
allow_customer_override Boolean? @default(true)
|
|
allow_tenant_override Boolean? @default(true)
|
|
allow_user_override Boolean? @default(true)
|
|
description String?
|
|
display_order Int? @default(0)
|
|
customerSettings CustomerSetting[]
|
|
tenantSettings TenantSetting[]
|
|
userSettings UserSetting[]
|
|
|
|
@@unique([category, key])
|
|
@@map("settings_definitions")
|
|
@@schema("admin")
|
|
}
|
|
|
|
model CustomerSetting {
|
|
customerId String @map("customer_id") @db.Uuid
|
|
settingId String @map("setting_id") @db.Uuid
|
|
value Json
|
|
is_mandatory Boolean? @default(false)
|
|
allow_tenant_override Boolean? @default(true)
|
|
updated_by String? @db.Uuid
|
|
updatedAt DateTime? @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
|
setting SettingsDefinition @relation(fields: [settingId], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
|
|
@@id([customerId, settingId])
|
|
@@index([settingId, customerId], map: "idx_customer_settings_lookup")
|
|
@@map("customer_settings")
|
|
@@schema("admin")
|
|
}
|
|
|
|
model TenantSetting {
|
|
tenantId String @map("tenant_id") @db.Uuid
|
|
settingId String @map("setting_id") @db.Uuid
|
|
value Json
|
|
is_mandatory Boolean? @default(false)
|
|
allow_user_override Boolean? @default(true)
|
|
updated_by String? @db.Uuid
|
|
updatedAt DateTime? @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
|
setting SettingsDefinition @relation(fields: [settingId], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
|
|
@@id([tenantId, settingId])
|
|
@@index([settingId, tenantId], map: "idx_tenant_settings_lookup")
|
|
@@map("tenant_settings")
|
|
@@schema("admin")
|
|
}
|
|
|
|
model UserSetting {
|
|
userId String @map("user_id") @db.Uuid
|
|
settingId String @map("setting_id") @db.Uuid
|
|
value Json
|
|
updatedAt DateTime? @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
|
setting SettingsDefinition @relation(fields: [settingId], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
|
|
@@id([userId, settingId])
|
|
@@map("user_settings")
|
|
@@schema("admin")
|
|
}
|
|
|
|
// ─── public schema ─────────────────────────────────────────────
|
|
|
|
model UserActivityLog {
|
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
|
userId String @map("user_id") @db.Uuid
|
|
tenantId String @map("tenant_id") @db.Uuid
|
|
action String @db.VarChar(64)
|
|
target String? @db.VarChar(256)
|
|
metadata Json? @default("{}")
|
|
ipAddress String? @map("ip_address") @db.VarChar(45)
|
|
userAgent String? @map("user_agent") @db.VarChar(512)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
|
|
|
@@index([userId, createdAt(sort: Desc)], map: "idx_user_activity_user")
|
|
@@index([tenantId, createdAt(sort: Desc)], map: "idx_user_activity_tenant")
|
|
@@map("user_activity_log")
|
|
@@schema("public")
|
|
}
|
|
|
|
// ============================================================================
|
|
// Privileged Access Management (JIT elevation) — shared with gscAdmin.
|
|
// Tables created by gscAdmin's `20260518_pam_init` migration; gscMy
|
|
// only needs the model definitions to write/read them.
|
|
// See gscAdmin/docs/pam-plan.md for the full design.
|
|
// ============================================================================
|
|
|
|
model PrivilegePolicy {
|
|
roleName String @id @map("role_name") @db.VarChar(128)
|
|
maxDurationHours Int @default(4) @map("max_duration_hours") @db.SmallInt
|
|
defaultDurationHours Int @default(1) @map("default_duration_hours") @db.SmallInt
|
|
approvalMode String @default("audit") @map("approval_mode") @db.VarChar(16)
|
|
approverEmail String? @map("approver_email") @db.VarChar(256)
|
|
requiresMfa Boolean @default(false) @map("requires_mfa")
|
|
eligibleGroup String @map("eligible_group") @db.VarChar(128)
|
|
description String?
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
|
grants PrivilegeGrant[]
|
|
|
|
@@map("privilege_policies")
|
|
@@schema("admin")
|
|
}
|
|
|
|
model PrivilegeGrant {
|
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
|
gscsid String @db.VarChar(64)
|
|
roleName String @map("role_name") @db.VarChar(128)
|
|
status String @default("pending") @db.VarChar(16)
|
|
approvalToken String? @unique @map("approval_token") @db.VarChar(64)
|
|
requestedAt DateTime @default(now()) @map("requested_at") @db.Timestamptz(6)
|
|
grantedAt DateTime? @map("granted_at") @db.Timestamptz(6)
|
|
expiresAt DateTime @map("expires_at") @db.Timestamptz(6)
|
|
grantedBy String? @map("granted_by") @db.VarChar(256)
|
|
justification String
|
|
mfaEvidence Json? @map("mfa_evidence")
|
|
revokedAt DateTime? @map("revoked_at") @db.Timestamptz(6)
|
|
revokedBy String? @map("revoked_by") @db.VarChar(64)
|
|
revokeReason String? @map("revoke_reason")
|
|
policy PrivilegePolicy @relation(fields: [roleName], references: [roleName], onUpdate: Cascade)
|
|
|
|
@@index([gscsid, roleName], map: "idx_priv_grants_active")
|
|
@@index([expiresAt], map: "idx_priv_grants_expires")
|
|
@@map("privilege_grants")
|
|
@@schema("admin")
|
|
}
|
|
|
|
model PrivilegeAudit {
|
|
id BigInt @id @default(autoincrement())
|
|
ts DateTime @default(now()) @db.Timestamptz(6)
|
|
event String @db.VarChar(32)
|
|
gscsid String @db.VarChar(64)
|
|
roleName String @map("role_name") @db.VarChar(128)
|
|
grantId String? @map("grant_id") @db.Uuid
|
|
actorGscsid String? @map("actor_gscsid") @db.VarChar(64)
|
|
actorEmail String? @map("actor_email") @db.VarChar(256)
|
|
detail Json?
|
|
|
|
@@index([gscsid, ts(sort: Desc)], map: "idx_priv_audit_user_ts")
|
|
@@map("privilege_audit")
|
|
@@schema("admin")
|
|
}
|