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

78
Dockerfile Normal file
View File

@@ -0,0 +1,78 @@
# GoSec My UI — Production Dockerfile
#
# Build context MUST be the monorepo root (/srv/k8s):
# podman build -f web/gscMy/Dockerfile -t gsc-my-ui /srv/k8s
# Mirrors web/gscAdmin/Dockerfile.
# Stage 1: Builder.
FROM node:22-alpine AS builder
RUN apk add --no-cache libc6-compat openssl
# @limitless/ui — build dist/ first.
COPY templates/limitless-ui /srv/k8s/templates/limitless-ui
WORKDIR /srv/k8s/templates/limitless-ui
RUN npm install --no-audit --no-fund && npm run build
# @gsc/web-kit — build dist/, drop bundled next-intl/next-auth so they
# resolve from this app's tree (React-context unity).
COPY templates/gsc-web-kit /srv/k8s/templates/gsc-web-kit
WORKDIR /srv/k8s/templates/gsc-web-kit
RUN npm install --no-audit --no-fund && npm run build && \
rm -rf node_modules/next-intl node_modules/next-auth
# @gsc/chat — dist/ is gitignored; build in place.
COPY infra/gscAICoreSystem/frontends/gscBicameralFrontend /srv/k8s/infra/gscAICoreSystem/frontends/gscBicameralFrontend
WORKDIR /srv/k8s/infra/gscAICoreSystem/frontends/gscBicameralFrontend
RUN npm install --no-audit --no-fund && npm run build
# Make @gsc/chat resolvable from inside @gsc/web-kit.
RUN mkdir -p /srv/k8s/templates/gsc-web-kit/node_modules/@gsc && \
ln -sfn ../../../../infra/gscAICoreSystem/frontends/gscBicameralFrontend \
/srv/k8s/templates/gsc-web-kit/node_modules/@gsc/chat
# gscMy — package.json + prisma schema first so postinstall finds it.
WORKDIR /srv/k8s/web/gscMy
COPY web/gscMy/package.json web/gscMy/package-lock.json* ./
COPY web/gscMy/prisma ./prisma
RUN npm install --no-audit --no-fund --no-package-lock
# Link this app's next-intl + next-auth into the kit's node_modules
# so the kit's compiled chrome shares the same module instances.
RUN ln -sfn ../../../web/gscMy/node_modules/next-intl \
/srv/k8s/templates/gsc-web-kit/node_modules/next-intl && \
ln -sfn ../../../web/gscMy/node_modules/next-auth \
/srv/k8s/templates/gsc-web-kit/node_modules/next-auth
# Rest of source.
COPY web/gscMy/. ./
ARG NEXT_PUBLIC_APP_URL=https://my.gosec.internal
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
ENV NEXT_TELEMETRY_DISABLED=1
# Strip local-dev env so it doesn't bake into the image (esp. AUTH_URL
# pointing at my.gosec.cloud:3000).
RUN rm -f .env .env.local .env.development.local .env.production.local
RUN npm run build
# Stage 2: Runtime
FROM node:22-alpine AS runner
RUN apk add --no-cache openssl
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# outputFileTracingRoot=/srv/k8s preserves the web/gscMy subtree in the
# standalone bundle, so server.js lands at /app/web/gscMy/server.js.
COPY --from=builder --chown=node:node /srv/k8s/web/gscMy/.next/standalone ./
COPY --from=builder --chown=node:node /srv/k8s/web/gscMy/public ./web/gscMy/public
COPY --from=builder --chown=node:node /srv/k8s/web/gscMy/.next/static ./web/gscMy/.next/static
USER node
EXPOSE 3000
ENV PORT=3000 HOSTNAME=0.0.0.0
ENTRYPOINT ["node", "web/gscMy/server.js"]