chore: bootstrap gscMy on @gsc/web-kit + PAM/JIT request flow

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>
This commit is contained in:
Super User
2026-05-18 13:46:13 +02:00
commit be1c4fe5f9
96 changed files with 49849 additions and 0 deletions

111
scripts/seed-settings.ts Normal file
View File

@@ -0,0 +1,111 @@
/**
* Seed settings definitions into gsc_core.admin.settings_definitions.
* Upserts on (category, key) unique constraint.
*
* Usage: npx tsx scripts/seed-settings.ts
*/
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
interface SettingDef {
category: string;
key: string;
dataType: string;
defaultValue: unknown;
description: string;
allowedValues?: unknown;
displayOrder: number;
}
const SETTINGS: SettingDef[] = [
// ─── user.general ──────────────────────────────────────────
{ category: "user.general", key: "language", dataType: "string", defaultValue: "en", description: "Display language", allowedValues: ["en", "de", "fr"], displayOrder: 1 },
{ category: "user.general", key: "timezone", dataType: "string", defaultValue: "Europe/Helsinki", description: "User timezone for timestamps and scheduling", displayOrder: 2 },
{ category: "user.general", key: "dateFormat", dataType: "string", defaultValue: "DD/MM/YYYY", description: "Date display format", allowedValues: ["DD/MM/YYYY", "MM/DD/YYYY", "YYYY-MM-DD"], displayOrder: 3 },
{ category: "user.general", key: "theme", dataType: "string", defaultValue: "light", description: "UI theme", allowedValues: ["light", "dark", "system"], displayOrder: 4 },
{ category: "user.general", key: "compactMode", dataType: "boolean", defaultValue: false, description: "Reduce spacing for condensed layout", displayOrder: 5 },
{ category: "user.general", key: "showWelcomeScreen", dataType: "boolean", defaultValue: true, description: "Show welcome greeting on dashboard", displayOrder: 6 },
{ category: "user.general", key: "defaultLandingPage", dataType: "string", defaultValue: "/", description: "Default page after login", allowedValues: ["/", "/marketplace", "/account/profile"], displayOrder: 7 },
// ─── user.notifications ────────────────────────────────────
{ category: "user.notifications", key: "notifyEmail", dataType: "boolean", defaultValue: true, description: "Receive important updates via email", displayOrder: 1 },
{ category: "user.notifications", key: "notifyBrowser", dataType: "boolean", defaultValue: true, description: "Show desktop push notifications", displayOrder: 2 },
{ category: "user.notifications", key: "notifyActivity", dataType: "boolean", defaultValue: true, description: "Get notified about mentions, comments, shares", displayOrder: 3 },
{ category: "user.notifications", key: "notifySecurityAlerts", dataType: "boolean", defaultValue: true, description: "Notify about suspicious activity", displayOrder: 4 },
{ category: "user.notifications", key: "notifyProductUpdates", dataType: "boolean", defaultValue: false, description: "Get notified about new features", displayOrder: 5 },
// ─── user.email ────────────────────────────────────────────
{ category: "user.email", key: "messagesPerPage", dataType: "string", defaultValue: "50", description: "Messages per page in email client", allowedValues: ["25", "50", "100"], displayOrder: 1 },
{ category: "user.email", key: "defaultReplyMode", dataType: "string", defaultValue: "reply", description: "Default reply mode", allowedValues: ["reply", "replyAll"], displayOrder: 2 },
{ category: "user.email", key: "emailSignature", dataType: "string", defaultValue: "", description: "Email signature text", displayOrder: 3 },
{ category: "user.email", key: "composeFormat", dataType: "string", defaultValue: "html", description: "Email compose format", allowedValues: ["html", "plain"], displayOrder: 4 },
{ category: "user.email", key: "readReceipts", dataType: "boolean", defaultValue: true, description: "Request read receipts by default", displayOrder: 5 },
// ─── user.calendar ─────────────────────────────────────────
{ category: "user.calendar", key: "calendarDefaultView", dataType: "string", defaultValue: "week", description: "Default calendar view", allowedValues: ["month", "week", "day", "agenda"], displayOrder: 1 },
{ category: "user.calendar", key: "weekStartsOn", dataType: "string", defaultValue: "monday", description: "First day of week", allowedValues: ["monday", "sunday", "saturday"], displayOrder: 2 },
{ category: "user.calendar", key: "workingHoursStart", dataType: "string", defaultValue: "08:00", description: "Working hours start time", displayOrder: 3 },
{ category: "user.calendar", key: "workingHoursEnd", dataType: "string", defaultValue: "17:00", description: "Working hours end time", displayOrder: 4 },
{ category: "user.calendar", key: "defaultReminder", dataType: "string", defaultValue: "15", description: "Default reminder (minutes before)", allowedValues: ["5", "10", "15", "30", "60"], displayOrder: 5 },
{ category: "user.calendar", key: "defaultEventDuration", dataType: "string", defaultValue: "60", description: "Default event duration (minutes)", allowedValues: ["15", "30", "60", "90"], displayOrder: 6 },
// ─── user.privacy ──────────────────────────────────────────
{ category: "user.privacy", key: "profileVisibility", dataType: "string", defaultValue: "contacts", description: "Who can see your profile", allowedValues: ["public", "contacts", "private"], displayOrder: 1 },
{ category: "user.privacy", key: "showEmail", dataType: "boolean", defaultValue: false, description: "Show email on profile", displayOrder: 2 },
{ category: "user.privacy", key: "showPhone", dataType: "boolean", defaultValue: false, description: "Show phone on profile", displayOrder: 3 },
{ category: "user.privacy", key: "showLocation", dataType: "boolean", defaultValue: true, description: "Show location on profile", displayOrder: 4 },
{ category: "user.privacy", key: "showOnlineStatus", dataType: "boolean", defaultValue: true, description: "Show online status", displayOrder: 5 },
{ category: "user.privacy", key: "allowDirectMessages", dataType: "boolean", defaultValue: true, description: "Allow direct messages", displayOrder: 6 },
{ category: "user.privacy", key: "allowGroupInvites", dataType: "boolean", defaultValue: true, description: "Allow group invites", displayOrder: 7 },
{ category: "user.privacy", key: "allowMentions", dataType: "boolean", defaultValue: true, description: "Allow @mentions", displayOrder: 8 },
{ category: "user.privacy", key: "allowAnalytics", dataType: "boolean", defaultValue: true, description: "Share anonymous usage data", displayOrder: 9 },
{ category: "user.privacy", key: "allowPersonalization", dataType: "boolean", defaultValue: true, description: "Allow personalized recommendations", displayOrder: 10 },
{ category: "user.privacy", key: "allowThirdPartyIntegrations", dataType: "boolean", defaultValue: false, description: "Allow connected apps to access data", displayOrder: 11 },
{ category: "user.privacy", key: "emailMarketing", dataType: "boolean", defaultValue: false, description: "Receive marketing emails", displayOrder: 12 },
{ category: "user.privacy", key: "emailProductUpdates", dataType: "boolean", defaultValue: true, description: "Receive product update emails", displayOrder: 13 },
{ category: "user.privacy", key: "emailSecurityAlerts", dataType: "boolean", defaultValue: true, description: "Receive security alert emails", displayOrder: 14 },
{ category: "user.privacy", key: "emailActivityDigest", dataType: "boolean", defaultValue: true, description: "Receive weekly activity digest", displayOrder: 15 },
{ category: "user.privacy", key: "activityHistoryRetention", dataType: "string", defaultValue: "90d", description: "Activity history retention period", allowedValues: ["30d", "90d", "1y", "forever"], displayOrder: 16 },
{ category: "user.privacy", key: "searchHistoryEnabled", dataType: "boolean", defaultValue: true, description: "Save search history", displayOrder: 17 },
{ category: "user.privacy", key: "sessionTimeout", dataType: "number", defaultValue: 60, description: "Session timeout in minutes", displayOrder: 18 },
// ─── user.security ─────────────────────────────────────────
{ category: "user.security", key: "loginNotifications", dataType: "boolean", defaultValue: true, description: "Notify on new device logins", displayOrder: 1 },
];
async function main() {
console.log(`Seeding ${SETTINGS.length} settings definitions...`);
for (const s of SETTINGS) {
await prisma.settingsDefinition.upsert({
where: { category_key: { category: s.category, key: s.key } },
update: {
dataType: s.dataType,
defaultValue: s.defaultValue as never,
description: s.description,
allowed_values: s.allowedValues ? (s.allowedValues as never) : undefined,
display_order: s.displayOrder,
},
create: {
category: s.category,
key: s.key,
dataType: s.dataType,
defaultValue: s.defaultValue as never,
description: s.description,
allowed_values: s.allowedValues ? (s.allowedValues as never) : undefined,
display_order: s.displayOrder,
},
});
}
console.log("Done.");
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());