Add BMAD planning artifacts: brief, PRD, architecture, UX, spec, tickets
Includes the BMAD install config, the initiative ticket tree, the portal context-resources scripts, and the .gitignore change that tracks BMAD files (generated _bmad/render/ is ignored). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sk72AxLwQnYttF6VFSxQLt
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
97b2a95062
commit
24de5878ed
+36
@@ -0,0 +1,36 @@
|
||||
---
|
||||
scope: ping-mee self-service portal, whole system (greenfield)
|
||||
purpose: build substrate for epics/stories; architecture spine
|
||||
altitude: initiative
|
||||
updated: 2026-10-15T11:22
|
||||
---
|
||||
|
||||
|
||||
- (constraint) Input: PRD _bmad-output/prds/prd-ping-mee-self-service-platform-2026-10-15 (prd.md FR-1..FR-29, addendum.md) is binding; its non-goals and decisions are inherited silently.
|
||||
- (constraint) Deployment: ONE Docker container on Linux behind existing reverse proxy (TLS terminated there); all config + secrets via env vars; nothing ping-mee-specific baked in; internet-exposed.
|
||||
- (constraint) Integrations: OIDC (authentik, generic OIDC); LDAPS to AD with a least-privilege service account (OU + attrs givenName,sn,displayName,mail,telephoneNumber,mobile,password); SMTP (IONOS, configurable); SMB share (write source/<sam>.jpg, read processed/<sam>.md5); existing PowerShell sync scripts unchanged.
|
||||
- (constraint) Scale: ~10 users; hard security bar (no injection, no session takeover, no cross-user access, throttling in front of every AD bind).
|
||||
- (constraint) Open from PRD for architecture: Q-1 share unreachable, Q-2 mail retry/IONOS limits, Q-3 languages, Q-4 absolute session max, Q-5 data store/backup, Q-6 removed-AD-user cleanup, Q-7 removed-picture pending, Q-8 time zone, Q-9 md5 timing.
|
||||
- (decision) Mode: COACHING. Audience: only Ebbe + AI agents (spine only; no extra human-facing artifact requested yet). User has NO stack preference - delegates choice to architect.
|
||||
- (version) Django: 5.2 LTS (security support to Apr 2028), 6.0.x current as of Oct 2026 per web search (source: endoflife.date / djangoproject.com); next LTS 6.2 Apr 2027. Verify exact pin when scaffolding.
|
||||
- (version) ldap3 (pure Python): last PyPI release 2.9.1 ~4 years ago, single maintainer -> stagnant. python-ldap: healthy, releases within last 3 months, needs libldap/libsasl build deps. Both can set AD unicodePwd over ldaps.
|
||||
- (version) pillow-heif: 1.3.0 (Feb 2026) on PyPI per search, newer 1.6.0 in Arch testing Aug 2026; HEIC/AVIF decode 8/10/12-bit. Verify pin.
|
||||
- (version) Fluent UI Blazor (Microsoft.FluentUI.AspNetCore.Components) 4.13.x documents .NET 8/9; .NET 10 support not confirmed -> weakens the .NET option.
|
||||
- (decision) STACK [user-chosen, option A]: Python + Django (5.2 LTS or current; pin at scaffold), server-rendered pages, python-ldap (not ldap3: stagnant), Pillow + pillow-heif. Rejected: ASP.NET/Blazor (HEIC, .NET10 Fluent unconfirmed, Blazor circuits vs session kill), Node/React (SPA attack surface, HEIC), Go (cgo HEIC, hand-built). Binds: all units use Django ORM/sessions/CSRF/templates; Prevents: parallel hand-rolled web/auth layers.
|
||||
- (direction) Office 365 look via Microsoft Fluent web components as static assets in server-rendered templates (UNVERIFIED - check at UX/scaffold).
|
||||
- (decision) PARADIGM [ADOPTED by user]: modular monolith, Django project, one app per feature (accounts, profile, picture, announcements, adminui, audit) + external-world adapters with ONE OWNER each: directory (only LDAP/service account), mailer (only SMTP), share (only picture folders), idp (OIDC). No other module imports ldap/smtplib or touches share paths. Prevents: scattered AD/file access, injection surface.
|
||||
- (decision) INVARIANT [agreed]: self-service actions always operate on the session user; no username taken from URL/form. Every state change writes its Audit Entry in the SAME transaction in the service layer (not view). Roles from AD group membership cached briefly (default 60s, setting).
|
||||
- (decision) DATA STORE (Q-5) [user-confirmed constraints]: SQLite on a mounted volume, ONE gunicorn worker with threads + one in-process scheduler thread; scale-out not supported without moving to PostgreSQL. Backups: Veeam copy-job copies directories -> portal must write consistent SQLite backups (online backup API) into a backups/ dir on the volume on a schedule; Veeam must not rely on copying the live DB file.
|
||||
- (decision) Q-4: absolute session max 4h (plus 1h idle). Q-3: German AND English, all text translatable from day one. Q-8: store UTC, display Europe/Berlin default (configurable), zone named in e-mails. Share: host mounts CIFS, container gets bind mounts source (rw) + processed (ro).
|
||||
- (decision) CLIENT IP: reverse proxy is Zoraxy (per issue tobychui/zoraxy#93): appends real client IP to X-Forwarded-For and forwards an existing X-Real-IP UNCHANGED (spoofable). Rule: if TCP peer is in TRUSTED_PROXIES, client IP = rightmost X-Forwarded-For entry (trusted-proxy count configurable); NEVER use X-Real-IP; else use peer address. Verify with a test request at deploy.
|
||||
- (decision) ENVIRONMENTS: no staging and no test AD. Only prod AD/authentik. Test OU can be created: a non-prod instance of the SAME image points at a test OU with a test service account scoped to it (OU, groups, share paths all config). Adapters have fakes; automated tests need no real AD/IdP/SMTP; manual smoke checklist against the test OU.
|
||||
- (decision) DEPLOY: docker compose by hand on Linux host; image location not stated (assume built on host or private registry - deferred). Veeam copy-job notifies on copy failure only; portal MUST show last-successful-backup age + failed jobs (backup, mail outbox) in Admin UI because a portal-side backup failure would be invisible to Veeam. No alert e-mail; Admin UI status is enough (user).
|
||||
- (decision) SECURITY [proposed, no objection]: Authlib for OIDC (code flow + PKCE, verify state/nonce/signature); throttle counters in DB keyed endpoint+user and endpoint+IP, checked BEFORE any directory call; sensitive actions re-verify password via directory adapter after throttle; passwords memory-only, never logged; secure HttpOnly cookies, CSRF everywhere, strict CSP (no inline script), security headers; DB sessions with username column; one-query session kill.
|
||||
- (decision) OPS [proposed, no objection]: multi-stage image, non-root, only data/backups/share writable; entrypoint migrates then starts, fail-fast on missing env; /healthz checks DB only (not AD); JSON logs to stdout with request id, no secrets; 30s idempotent state-driven scheduler tick (no scheduler lib) running publish, outbox send, token expiry, audit retention, DB backup, nightly AD reconciliation; upgrade = backup, migrate; rollback = restore + previous tag.
|
||||
- (decision) MAIL [proposed]: outbox table, per-recipient rows, retries/backoff + rate limit for IONOS, no direct sending. Q-6: nightly reconciliation deletes opt-outs/pending changes/picture state of users gone from OU; audit kept to retention. Q-1: share adapter atomic write (tmp+rename) with timeouts, error to user, no state change. Q-7/Q-9: Pending clears when processed md5 == stored md5; for removal when processed md5 != previous md5; sync-writes-md5-before-AD limitation accepted + timeout message.
|
||||
- (version) nh3 (Rust ammonia binding) is the maintained HTML sanitizer; bleach deprecated Jan 2023. nh3 0.2.x on PyPI. Markdown parser: markdown-it-py (not verified; pin at scaffold).
|
||||
- (version) @fluentui/web-components v3.0.0 stable 29 Jun 2026, v3.0.1 7 Jul 2026; framework-agnostic. DECISION: vendor built assets into the image at build time (no runtime CDN) - strict CSP and supply-chain; Node only in build stage.
|
||||
- (version) Authlib 1.7.x current (June 2026 release) - verified by search; mozilla-django-oidc 4.0.1 (2024) older.
|
||||
- (event) Gate: lint clean; adversarial pass done inline (no subagents). Fixes: LDAPS CA bundle via env (internal CA); SessionUser(key,sam) canonical identity; AD-call-inside-transaction semantics; mailer owns outbox; email plain-text + sanitized HTML; AD-19 one owner per table; single logout deferred. Independent subagent reviewers NOT run.
|
||||
- (event) spine finalized
|
||||
- (change) Update pass from UX run: AD-2 read allow-list incl. Password Policy + pwdLastSet; AD-13 accent colour + contrast warning; AD-15 per-device preferences cookies + de formal/informal; AD-16 rich-text editor -> Markdown, assets bundled; capability map FR-30..32; stack row for editor library (chosen at scaffold).
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
---
|
||||
name: 'ping-mee Self-Service Portal'
|
||||
type: architecture-spine
|
||||
purpose: build-substrate
|
||||
altitude: initiative
|
||||
paradigm: 'Modular monolith with adapters (ports and adapters) for every external system'
|
||||
scope: 'The whole Portal: one Django container, its adapters to AD, OIDC, SMTP and the picture share, its data, and its operation'
|
||||
status: final
|
||||
created: '2026-10-15'
|
||||
updated: '2026-10-15'
|
||||
binds: ['PRD FR-1..FR-32', 'PRD §8 cross-cutting requirements', 'PRD Q-1..Q-9', 'UX DESIGN.md and EXPERIENCE.md']
|
||||
sources: ['_bmad-output/ux-designs/ux-ping-mee-self-service-platform-2026-10-15/DESIGN.md', '_bmad-output/ux-designs/ux-ping-mee-self-service-platform-2026-10-15/EXPERIENCE.md', '_bmad-output/prds/prd-ping-mee-self-service-platform-2026-10-15/prd.md', '_bmad-output/prds/prd-ping-mee-self-service-platform-2026-10-15/addendum.md', '_bmad-output/briefs/brief-ping-mee-self-service-platform-2026-10-15/brief.md', 'context-resources/*.ps1']
|
||||
companions: ['_bmad-output/prds/prd-ping-mee-self-service-platform-2026-10-15/prd.md']
|
||||
---
|
||||
|
||||
# Architecture Spine — ping-mee Self-Service Portal
|
||||
|
||||
## Design Paradigm
|
||||
|
||||
Modular monolith with adapters. One Django project, one Django app per feature, and one adapter per external system. Features call adapters only through services; adapters know nothing about features.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
V[views / templates] --> S[feature services]
|
||||
S --> AUD[audit service]
|
||||
S --> ACC[accounts: session user, roles]
|
||||
S --> AD[adapters]
|
||||
SCH[scheduler tick] --> S
|
||||
AD --> DIR[directory: LDAPS]
|
||||
AD --> MAIL[mailer: SMTP]
|
||||
AD --> SHR[share: picture folders]
|
||||
AD --> IDP[idp: OIDC]
|
||||
S --> DB[(SQLite)]
|
||||
```
|
||||
|
||||
Arrows are the only allowed dependency direction. Views never call adapters. Features never import each other's models; they call each other's services.
|
||||
|
||||
## Invariants & Rules
|
||||
|
||||
### AD-1 — Adapters have exactly one owner each
|
||||
- **Binds:** all FRs touching AD, mail, the share or the IdP
|
||||
- **Prevents:** scattered LDAP, SMTP or file access, each with its own escaping and error handling
|
||||
- **Rule:** only `adapters/directory` imports `ldap`; only `adapters/mailer` imports `smtplib`; only `adapters/share` opens paths under the source or processed folders; only `adapters/idp` talks OIDC. Each adapter exposes a small interface with an in-memory fake.
|
||||
|
||||
### AD-2 — Directory access is allow-listed and injection-proof
|
||||
- **Binds:** FR-1, FR-5, FR-8, FR-9, FR-10, FR-14, FR-20; NFR §8.1
|
||||
- **Prevents:** LDAP injection, writes outside the OU, writes of unlisted attributes, the service account used anywhere else
|
||||
- **Rule:** the Service Account is used only inside `directory`. Filters are built only with LDAP filter escaping; DNs are never built from input — the DN is found by searching `sAMAccountName` inside the configured OU. Writable attributes are a fixed allow-list: `givenName`, `sn`, `displayName`, `mail`, `telephoneNumber`, `mobile`, `unicodePwd`. Readable attributes are also a fixed allow-list: the writable ones, `sAMAccountName`, `memberOf` (or a group-membership query), `thumbnailPhoto`, `pwdLastSet`, the account's enabled and locked state, and the Password Policy (the domain's `minPwdLength`, `pwdProperties`, `pwdHistoryLength`, `minPwdAge`, `maxPwdAge`, and the user's `msDS-ResultantPSO` when a fine-grained policy applies). The Service Account's read rights are limited to the same list. Everything runs over LDAPS with certificate verification against a CA bundle supplied by environment variable (the homelab uses an internal CA); verification is never disabled.
|
||||
|
||||
### AD-3 — Self-service acts on the session user only
|
||||
- **Binds:** FR-5, FR-8, FR-10..FR-15, FR-21
|
||||
- **Prevents:** editing another user through a crafted URL or form
|
||||
- **Rule:** no self-service view, service or form accepts a username, user id or DN. The acting user is read from the session as one object, `SessionUser(key, sam)`: `key` is the lower-case value used for every database key, `sam` is the exact `sAMAccountName` from AD used for AD calls and file names. No module derives either value itself.
|
||||
|
||||
### AD-4 — Audit entry in the same transaction as the change
|
||||
- **Binds:** FR-24, FR-28
|
||||
- **Prevents:** changes without a record; records without a change
|
||||
- **Rule:** every state-changing service function writes its Audit Entry (who, what, when, IP) in the same database transaction, in the service layer, never in the view. Entries never contain passwords, tokens or picture content. A service that changes AD makes the AD call inside the transaction, before commit. A failed AD call rolls back and records a failure entry in its own transaction; a commit failure after a successful AD call is logged at error level with the change details.
|
||||
|
||||
### AD-5 — Identity, roles and account state come from AD, cached briefly
|
||||
- **Binds:** FR-1, FR-2, FR-25
|
||||
- **Prevents:** stale admin rights; a disabled account staying signed in; a directory outage granting access
|
||||
- **Rule:** `accounts` resolves username, Publisher and Admin membership, and enabled/locked state from `directory` and caches the result per session for a configured time (default 60 s). A directory failure denies elevated roles and new sessions. The OIDC `preferred_username` must equal `sAMAccountName`.
|
||||
|
||||
### AD-6 — Sessions are server-side with a kill-by-user primitive
|
||||
- **Binds:** FR-3, FR-7, FR-11
|
||||
- **Prevents:** sessions that cannot be ended; mismatched idle and absolute limits
|
||||
- **Rule:** sessions live in the database with the username in its own column. Idle limit 1 h, absolute limit 4 h, both settings. `end_sessions(username, except_current=bool)` is the only way to end sessions and is used by password change, undo, sign-out and disabled-account detection. Cookies are secure, HttpOnly and SameSite=Lax.
|
||||
|
||||
### AD-7 — Throttle before any outbound call; client IP from a trusted proxy only
|
||||
- **Binds:** FR-4, FR-6, FR-10, FR-11, FR-12
|
||||
- **Prevents:** AD lockout by an attacker; spoofed IPs defeating throttling
|
||||
- **Rule:** counters live in the database, keyed by endpoint plus user and endpoint plus IP, and are checked before `directory`, `mailer` or upload processing. Client IP: if the TCP peer is in `TRUSTED_PROXIES`, take the last `X-Forwarded-For` entry (the one the proxy appended); otherwise use the peer address. `X-Real-IP` is never used.
|
||||
|
||||
### AD-8 — Sensitive actions re-verify the password; passwords live only in memory
|
||||
- **Binds:** FR-5, FR-6, FR-10
|
||||
- **Prevents:** session hijack changing the password or e-mail; password leakage
|
||||
- **Rule:** password change and e-mail change call `directory.verify_password(user, password)` after the throttle check. Passwords are never stored, logged, audited or placed in error reports; sensitive POST parameters are excluded from logging and error pages.
|
||||
|
||||
### AD-9 — Link actions: GET shows, POST applies; tokens are hashed and single-use
|
||||
- **Binds:** FR-10, FR-11
|
||||
- **Prevents:** mail scanners confirming or undoing changes; token theft from the database
|
||||
- **Rule:** confirmation (1 h) and undo (24 h) tokens are random, stored only as hashes, single-use and expiring. A GET renders a page naming the change; only a POST with CSRF applies it.
|
||||
|
||||
### AD-10 — The picture pipeline is fixed
|
||||
- **Binds:** FR-12..FR-15; Q-1, Q-7, Q-9
|
||||
- **Prevents:** unsafe image input; a different file name than the sync script reads; wrong Pending state
|
||||
- **Rule:** accepted input is JPEG, PNG, HEIC or WebP up to 10 MB; SVG and everything else is refused. Decode with pixel and size limits, apply orientation, strip all metadata, crop square, re-encode as a 512×512 JPEG. The `share` adapter writes `<sAMAccountName>.jpg` to the source folder atomically (temp file, then rename) with timeouts and records the MD5 in the database. The original upload is never stored. Pending clears when `processed/<name>.md5` (case-insensitive, trimmed) equals the stored MD5; for a removal it clears when that file differs from the previous MD5. After a configured time the Portal shows "taking longer than expected". If the share is unreachable the user sees an error and nothing changes.
|
||||
|
||||
### AD-11 — All mail goes through an outbox
|
||||
- **Binds:** FR-10, FR-11, FR-7, FR-20; Q-2
|
||||
- **Prevents:** lost or duplicated mail on restart; blocking requests on SMTP; hitting provider limits
|
||||
- **Rule:** the `mailer` module owns the outbox table; every other module calls `mailer.enqueue(template, recipient, context)` and nothing else. The scheduler calls `mailer.flush()`, which sends rows with retries, backoff and a configured rate limit. Recipients of an Announcement are resolved at publish time: all Users in the OU with a valid `mail` value, minus stored opt-outs; users without `mail` are recorded as skipped. Only opt-outs are stored. Every e-mail has a plain-text part; an HTML part, if any, passes the same sanitizer as the page (AD-16). Failures are visible in the Admin UI.
|
||||
|
||||
### AD-12 — One scheduler thread running idempotent, state-driven jobs
|
||||
- **Binds:** FR-17, FR-20, FR-24; Q-5, Q-6
|
||||
- **Prevents:** duplicate runs; jobs that depend on process memory
|
||||
- **Rule:** the app runs one gunicorn worker with threads and one scheduler thread ticking every 30 s. Every job reads the database for what is due and is safe to repeat: publish due Announcements, send the outbox, expire tokens and sessions, apply audit retention, write the database backup, reconcile with AD nightly (delete opt-outs, pending changes and picture state of users no longer in the OU; audit entries stay until retention). Each run records its status for the Admin UI.
|
||||
|
||||
### AD-13 — Configuration is partitioned and typed
|
||||
- **Binds:** FR-23, FR-26
|
||||
- **Prevents:** secrets in the database or UI; a runtime setting that opens a security hole
|
||||
- **Rule:** environment variables hold everything that identifies a system or contains a secret (AD, LDAPS, Service Account, OIDC, SMTP, share paths, group names, trusted proxies, time zone default). Runtime settings (accent colour, backgrounds, portal name, default form of address, session limits, throttle limits, audit retention) live in the database and are declared in one registry with type, default and minimum value. Saving the accent colour computes its contrast against the on-brand text colour for the light and the dark theme and returns the result and a warning when it is below 4.5:1; the save is still allowed. A missing or invalid environment variable stops startup with a clear message. Nothing in the repository names ping-mee.
|
||||
|
||||
### AD-14 — SQLite with WAL on a volume, plus consistent backup files
|
||||
- **Binds:** FR-27, FR-28; Q-5
|
||||
- **Prevents:** inconsistent copies of a live database; invisible backup failures
|
||||
- **Rule:** the database is SQLite in WAL mode on the `/data` volume. The scheduler writes consistent backup files using the SQLite online backup API into `/backups` with a configured retention. Host-side copy jobs (Veeam) copy `/backups`, not the live file. The Admin UI shows the age of the last successful backup.
|
||||
|
||||
### AD-15 — Time and language
|
||||
- **Binds:** FR-17, FR-29; Q-3, Q-8
|
||||
- **Prevents:** mismatched time zones; untranslatable strings
|
||||
- **Rule:** all timestamps are stored in UTC. Display uses the configured zone (default Europe/Berlin) and e-mails name the zone. Every user-visible string goes through Django translation from the first line of code; German and English ship, and German has a formal and an informal register. Per-device Preferences (theme: light, dark or system; language; German register) are stored in cookies and never on the server. The server reads them to render the page (a theme attribute on the root element, not inline script); `system` is resolved by CSS only. Language defaults from `Accept-Language`. Server-sent e-mails use the portal-wide default register.
|
||||
|
||||
### AD-16 — User content is rendered safely
|
||||
- **Binds:** FR-16, FR-19, FR-22; NFR §8.1
|
||||
- **Prevents:** script injection; remote code from CDNs; unsafe uploads by Admins
|
||||
- **Rule:** templates auto-escape. Announcement bodies are edited in a browser rich-text editor (toolbar: bold, italic, heading, list, link) that converts to Markdown. The server treats the submitted Markdown as untrusted: it is stored as Markdown, rendered by a Markdown parser, and sanitized with an allow-list sanitizer on every render; raw HTML is not allowed. A strict Content-Security-Policy forbids inline scripts and external script sources; all front-end assets, including the rich-text editor and the Fluent web components, are built into the image, with no runtime CDN. Admin background images follow the same re-encode and size limits as pictures and are served only to signed-in Users.
|
||||
|
||||
### AD-17 — Errors are typed and mapped in one place
|
||||
- **Binds:** FR-5, FR-9, FR-10, FR-12, FR-14, §8.3
|
||||
- **Prevents:** raw exceptions or directory messages reaching users; inconsistent messages
|
||||
- **Rule:** services raise typed errors (`ValidationFailed`, `Throttled`, `Forbidden`, `DirectoryUnavailable`, `ShareUnavailable`, `MailUnavailable`). One mapping layer turns them into translated user messages and HTTP codes. AD password-policy rejections are mapped to specific messages.
|
||||
|
||||
### AD-18 — Logs and health
|
||||
- **Binds:** FR-27, FR-28; §8.3
|
||||
- **Prevents:** secrets in logs; a healthy-looking container that is broken, or one restarted because AD is down
|
||||
- **Rule:** logs are JSON on stdout with a request ID, never containing secrets or personal content. `/healthz` checks the database only, never AD, SMTP or the share.
|
||||
|
||||
### AD-19 — Every table has exactly one owning app
|
||||
- **Binds:** all features; NFR §8.1
|
||||
- **Prevents:** two modules writing the same entity in incompatible ways
|
||||
- **Rule:** `accounts` owns sessions and throttle counters; `profile` owns pending e-mail change tokens; `picture` owns picture state; `announcements` owns announcements and opt-outs (the profile page calls its service); `mailer` owns the outbox; `adminui` owns settings; `scheduler` owns job runs; `audit` owns audit entries and is called by all. Other apps read an entity only through its owner's service.
|
||||
|
||||
## Consistency Conventions
|
||||
|
||||
| Concern | Convention |
|
||||
| --- | --- |
|
||||
| Naming | Django apps: `accounts`, `profile`, `picture`, `announcements`, `adminui`, `audit`. Adapters under `adapters/`: `directory`, `mailer`, `share`, `idp`. Services in `services.py` per app. Glossary terms from the PRD are used for class and table names. |
|
||||
| Data and formats | Primary keys are integers. Usernames stored lower-case; the exact `sAMAccountName` from AD is used for file names. Timestamps UTC. Money: none. Tokens: random, hashed at rest. |
|
||||
| State and cross-cutting | Mutations only in services, inside a transaction, with the audit entry (AD-4). Entity ownership per AD-19. Errors per AD-17. Settings via the registry (AD-13). No business logic in views or templates. |
|
||||
| Testing | Every adapter has an in-memory fake; automated tests use fakes. A manual smoke checklist runs a non-production instance of the same image against a test OU. |
|
||||
|
||||
## Stack
|
||||
|
||||
| Name | Version |
|
||||
| --- | --- |
|
||||
| Python | current supported release at scaffold; pin in image |
|
||||
| Django | 5.2 LTS (security fixes to April 2028) |
|
||||
| python-ldap | current at scaffold (maintained) |
|
||||
| Authlib | 1.7.x |
|
||||
| Pillow + pillow-heif | current at scaffold (pillow-heif 1.3+) |
|
||||
| nh3 + markdown-it-py | current at scaffold |
|
||||
| gunicorn | one worker, threads |
|
||||
| SQLite | WAL mode |
|
||||
| @fluentui/web-components | 3.x, built into the image |
|
||||
| Rich-text editor (browser, outputs Markdown) | chosen and pinned at scaffold after a web check; must be open source and bundled into the image |
|
||||
| Docker, Docker Compose | on a Linux host; share mounted by the host (CIFS) |
|
||||
|
||||
## Structural Seed
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
U[User browser] -->|HTTPS| RP[Zoraxy reverse proxy]
|
||||
RP -->|HTTP| P[Portal container]
|
||||
P -->|OIDC| AK[authentik]
|
||||
P -->|LDAPS| DC[Active Directory]
|
||||
P -->|SMTP| IO[IONOS mail]
|
||||
P -->|bind mount source rw, processed ro| SH[(Share via host CIFS mount)]
|
||||
P --> V[(volume /data SQLite)]
|
||||
P --> B[(volume /backups)]
|
||||
B -.copy job.-> VE[Veeam]
|
||||
SH -.read source.-> SY[PowerShell sync task on DC]
|
||||
SY -->|thumbnailPhoto| DC
|
||||
```
|
||||
|
||||
```text
|
||||
portal/
|
||||
config/ # settings, env parsing, setting registry
|
||||
adapters/ # directory, mailer, share, idp (+ fakes)
|
||||
accounts/ profile/ picture/ announcements/ adminui/ audit/
|
||||
scheduler/ # tick loop and jobs
|
||||
templates/ static/ # Fluent web components built into the image
|
||||
tests/
|
||||
Dockerfile # multi-stage, non-root
|
||||
compose.yaml
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
SESSION { string username }
|
||||
AUDIT_ENTRY { string actor }
|
||||
ANNOUNCEMENT ||--o{ OUTBOX_MAIL : queues
|
||||
OPT_OUT { string username }
|
||||
PENDING_TOKEN { string username }
|
||||
PICTURE_STATE { string username }
|
||||
SETTING { string key }
|
||||
JOB_RUN { string job }
|
||||
```
|
||||
|
||||
Users themselves are not stored: AD is the source of truth. The Portal stores only data keyed by `username`.
|
||||
|
||||
## Capability → Architecture Map
|
||||
|
||||
| Capability / Area | Lives in | Governed by |
|
||||
| --- | --- | --- |
|
||||
| Sign-in, roles, sessions (FR-1..FR-3) | `accounts`, `adapters/idp`, `adapters/directory` | AD-5, AD-6 |
|
||||
| Dashboard (FR-30) | `profile` (view only; calls `announcements` and `picture` services) | AD-1, AD-3 |
|
||||
| Preferences (FR-31) | `accounts` (cookie middleware) | AD-15 |
|
||||
| Throttling (FR-4, FR-6) | `accounts` | AD-7 |
|
||||
| Password change and Password Policy (FR-5..FR-7) | `profile` | AD-2, AD-3, AD-8 |
|
||||
| Profile data, e-mail change (FR-8..FR-11) | `profile` | AD-2, AD-3, AD-8, AD-9 |
|
||||
| Picture (FR-12..FR-15) | `picture`, `adapters/share` | AD-10 |
|
||||
| Announcements and mail (FR-16..FR-21) | `announcements`, `adapters/mailer` | AD-11, AD-12, AD-16 |
|
||||
| Admin UI and Status page (FR-22..FR-25, FR-32) | `adminui`, `audit`, `scheduler` job records | AD-4, AD-12, AD-13, AD-14, AD-16 |
|
||||
| Platform and operations (FR-26..FR-29) | `config`, `scheduler`, image | AD-12..AD-15, AD-18 |
|
||||
|
||||
## Deferred
|
||||
|
||||
- Container image location (registry or build on the host): decide at first deployment.
|
||||
- Default values for throttle limits, backup retention and the Pending timeout: set at implementation, within the setting registry.
|
||||
- The rich-text editor library: chosen at scaffold. The look and component set are in the UX documents.
|
||||
- Moving to PostgreSQL or more than one worker: only if the user count grows.
|
||||
- Alert e-mails or monitoring beyond the Admin UI: not needed now.
|
||||
- A staging environment: not available; the test OU takes its place.
|
||||
- Whether sign-out also ends the authentik session (single logout): decided at first deployment.
|
||||
Reference in New Issue
Block a user