docs: update architecture for custom sign-in UI and CI pipeline
- CLAUDE.md: add custom sign-in UI section, update routing table, document auto-redirect, CI-built images, no local builds, dev override without volume mounts - Design spec: reflect final implementation — custom Logto image, no CUSTOM_UI_PATH, no init containers, bundled favicon Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
19
CLAUDE.md
19
CLAUDE.md
@@ -35,12 +35,23 @@ All services on one hostname. Two env vars control everything: `PUBLIC_HOST` + `
|
|||||||
| `/platform/*` | cameleer-saas:8080 | SPA + API (`server.servlet.context-path: /platform`) |
|
| `/platform/*` | cameleer-saas:8080 | SPA + API (`server.servlet.context-path: /platform`) |
|
||||||
| `/server/*` | cameleer3-server-ui:80 | Server dashboard (strip-prefix + `BASE_PATH=/server`) |
|
| `/server/*` | cameleer3-server-ui:80 | Server dashboard (strip-prefix + `BASE_PATH=/server`) |
|
||||||
| `/` | redirect → `/platform/` | Via `docker/traefik-dynamic.yml` |
|
| `/` | redirect → `/platform/` | Via `docker/traefik-dynamic.yml` |
|
||||||
| `/*` (catch-all) | logto:3001 (priority=1) | Sign-in, OIDC, interaction, assets |
|
| `/*` (catch-all) | cameleer-logto:3001 (priority=1) | Custom sign-in UI, OIDC, interaction |
|
||||||
|
|
||||||
- SPA assets at `/_app/` (Vite `assetsDir: '_app'`) to avoid conflict with Logto's `/assets/`
|
- SPA assets at `/_app/` (Vite `assetsDir: '_app'`) to avoid conflict with Logto's `/assets/`
|
||||||
- Logto `ENDPOINT` = `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` (same domain, same origin)
|
- Logto `ENDPOINT` = `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` (same domain, same origin)
|
||||||
- TLS: self-signed cert init container (`traefik-certs`) for dev, ACME for production
|
- TLS: self-signed cert init container (`traefik-certs`) for dev, ACME for production
|
||||||
- Root `/` → `/platform/` redirect via Traefik file provider (`docker/traefik-dynamic.yml`)
|
- Root `/` → `/platform/` redirect via Traefik file provider (`docker/traefik-dynamic.yml`)
|
||||||
|
- LoginPage auto-redirects to Logto OIDC (no intermediate button)
|
||||||
|
|
||||||
|
### Custom sign-in UI (`ui/sign-in/`)
|
||||||
|
|
||||||
|
Separate Vite+React SPA replacing Logto's default sign-in page. Visually matches cameleer3-server LoginPage.
|
||||||
|
|
||||||
|
- Built as custom Logto Docker image (`cameleer-logto`): `ui/sign-in/Dockerfile` = node build stage + `FROM ghcr.io/logto-io/logto:latest` + COPY dist over `/etc/logto/packages/experience/dist/`
|
||||||
|
- Uses `@cameleer/design-system` components (Card, Input, Button, FormField, Alert)
|
||||||
|
- Authenticates via Logto Experience API (4-step: init → verify password → identify → submit → redirect)
|
||||||
|
- `CUSTOM_UI_PATH` env var does NOT work for Logto OSS — must volume-mount or replace the experience dist directory
|
||||||
|
- Favicon bundled in `ui/sign-in/public/favicon.svg` (served by Logto, not SaaS)
|
||||||
|
|
||||||
### Auth enforcement
|
### Auth enforcement
|
||||||
|
|
||||||
@@ -83,7 +94,11 @@ SaaS admin credentials (`SAAS_ADMIN_USER`/`SAAS_ADMIN_PASS`) work for both the S
|
|||||||
- Gitea-hosted: `gitea.siegeln.net/cameleer/`
|
- Gitea-hosted: `gitea.siegeln.net/cameleer/`
|
||||||
- CI: `.gitea/workflows/` — Gitea Actions
|
- CI: `.gitea/workflows/` — Gitea Actions
|
||||||
- K8s target: k3s cluster at 192.168.50.86
|
- K8s target: k3s cluster at 192.168.50.86
|
||||||
- Docker builds: multi-stage, buildx with registry cache, `--provenance=false` for Gitea compatibility
|
- Docker images: CI builds and pushes all images — Dockerfiles use multi-stage builds, no local builds needed
|
||||||
|
- `cameleer-saas` — SaaS app (frontend + JAR baked in)
|
||||||
|
- `cameleer-logto` — custom Logto with sign-in UI baked in
|
||||||
|
- Docker builds: `--no-cache`, `--provenance=false` for Gitea compatibility
|
||||||
|
- `docker-compose.dev.yml` — exposes ports for direct access, sets `SPRING_PROFILES_ACTIVE: dev`. No volume mounts — all artifacts come from CI-built images
|
||||||
- Design system: import from `@cameleer/design-system` (Gitea npm registry)
|
- Design system: import from `@cameleer/design-system` (Gitea npm registry)
|
||||||
|
|
||||||
## Disabled Skills
|
## Disabled Skills
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Custom Logto Sign-In UI
|
# Custom Logto Sign-In UI — IMPLEMENTED
|
||||||
|
|
||||||
## Problem
|
## Problem
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ Replace Logto's sign-in UI with a custom React SPA that visually matches the cam
|
|||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
**MVP**: Username/password sign-in only.
|
**MVP (implemented)**: Username/password sign-in only.
|
||||||
**Later**: Sign-up, forgot password, social login, MFA.
|
**Later**: Sign-up, forgot password, social login, MFA.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -21,19 +21,22 @@ Separate Vite+React app at `ui/sign-in/`. Own `package.json`, shares `@cameleer/
|
|||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
Added as a Docker build stage in the SaaS `Dockerfile`. The dist is copied into the SaaS image at `/app/sign-in-dist/`.
|
Custom Logto Docker image (`cameleer-logto`). `ui/sign-in/Dockerfile` has a multi-stage build: node stage builds the SPA, then copies dist over Logto's built-in experience directory at `/etc/logto/packages/experience/dist/`. CI builds and pushes the image.
|
||||||
|
|
||||||
|
**Note**: `CUSTOM_UI_PATH` env var does NOT work for Logto OSS — it's a Logto Cloud-only feature. The correct approach for self-hosted is replacing the experience dist directory.
|
||||||
|
|
||||||
### Deploy
|
### Deploy
|
||||||
|
|
||||||
SaaS entrypoint (`docker/saas-entrypoint.sh`) copies the built sign-in dist to a shared Docker volume (`signinui`). Logto reads from that volume via `CUSTOM_UI_PATH=/etc/logto/custom-ui`.
|
`docker-compose.yml` pulls the pre-built `cameleer-logto` image. No init containers, no shared volumes, no local builds needed.
|
||||||
|
|
||||||
### Flow
|
### Auth Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
User visits /platform/ → SaaS redirects to Logto OIDC
|
User visits /platform/ → LoginPage auto-redirects to Logto OIDC
|
||||||
→ Logto sets interaction cookie, serves custom sign-in UI
|
→ Logto sets interaction cookie, serves custom sign-in UI
|
||||||
→ User enters credentials → Experience API 4-step flow
|
→ User enters credentials → Experience API 4-step flow
|
||||||
→ Logto redirects back to /platform/callback with auth code
|
→ Logto redirects back to /platform/callback with auth code
|
||||||
|
→ SaaS app exchanges code for token, user lands on dashboard
|
||||||
```
|
```
|
||||||
|
|
||||||
## Experience API
|
## Experience API
|
||||||
@@ -60,39 +63,22 @@ Matches cameleer3-server LoginPage exactly:
|
|||||||
- `Alert` for errors
|
- `Alert` for errors
|
||||||
- Background: `var(--bg-base)` (light beige)
|
- Background: `var(--bg-base)` (light beige)
|
||||||
- Dark mode support via design-system tokens
|
- Dark mode support via design-system tokens
|
||||||
|
- Favicon bundled in `ui/sign-in/public/favicon.svg` (served by Logto, not SaaS)
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
### New
|
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
|
| `ui/sign-in/Dockerfile` | Multi-stage: node build + FROM logto:latest + COPY dist |
|
||||||
| `ui/sign-in/package.json` | React 19 + @cameleer/design-system + Vite 6 |
|
| `ui/sign-in/package.json` | React 19 + @cameleer/design-system + Vite 6 |
|
||||||
| `ui/sign-in/.npmrc` | Gitea npm registry for @cameleer scope |
|
|
||||||
| `ui/sign-in/tsconfig.json` | TypeScript config |
|
|
||||||
| `ui/sign-in/vite.config.ts` | Vite build config |
|
|
||||||
| `ui/sign-in/index.html` | Entry HTML |
|
|
||||||
| `ui/sign-in/src/main.tsx` | React mount + design-system CSS import |
|
|
||||||
| `ui/sign-in/src/App.tsx` | App shell |
|
|
||||||
| `ui/sign-in/src/SignInPage.tsx` | Login form with Experience API integration |
|
| `ui/sign-in/src/SignInPage.tsx` | Login form with Experience API integration |
|
||||||
| `ui/sign-in/src/SignInPage.module.css` | CSS Modules (matches server LoginPage) |
|
| `ui/sign-in/src/SignInPage.module.css` | CSS Modules (matches server LoginPage) |
|
||||||
| `ui/sign-in/src/experience-api.ts` | Typed Experience API client |
|
| `ui/sign-in/src/experience-api.ts` | Typed Experience API client (4-step flow) |
|
||||||
| `docker/saas-entrypoint.sh` | Copies sign-in dist to shared volume |
|
| `ui/sign-in/src/main.tsx` | React mount + design-system CSS import |
|
||||||
| `.gitattributes` | LF line endings |
|
| `ui/sign-in/public/favicon.svg` | Cameleer camel logo (bundled in dist) |
|
||||||
|
| `ui/src/auth/LoginPage.tsx` | Auto-redirects to Logto OIDC (no button) |
|
||||||
### Modified
|
| `.gitea/workflows/ci.yml` | Builds and pushes `cameleer-logto` image |
|
||||||
|
| `docker-compose.yml` | Uses `cameleer-logto` image (no build directive) |
|
||||||
| File | Change |
|
|
||||||
|------|--------|
|
|
||||||
| `Dockerfile` | Add `sign-in-frontend` build stage, copy dist to runtime image |
|
|
||||||
| `docker-compose.yml` | Add `CUSTOM_UI_PATH` to Logto, `signinui` shared volume |
|
|
||||||
|
|
||||||
## Docker Timing
|
|
||||||
|
|
||||||
Logto starts before SaaS (dependency chain: postgres → logto → logto-bootstrap → cameleer-saas). The `CUSTOM_UI_PATH` volume is initially empty. This is acceptable because:
|
|
||||||
1. Nobody hits the sign-in page until redirected from `/platform/`
|
|
||||||
2. By that time, SaaS has started and copied the dist files
|
|
||||||
3. Logto serves static files at request time, not startup time
|
|
||||||
|
|
||||||
## Future Extensions
|
## Future Extensions
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user