NextMyDay/README.md
Claus Lohmar 04c2394c34 docs: record the M0 review fixes and correct the README
REVIEW-001 triage updated (H1, H2, M1c, M1s, L3s, L1c/L2s, L8c, M2s, M3s,
M2c, L1s, L5s, L4s, L7c marked FIXED; L3c, L4c, L5c, L6c stay open with the
reason), and each fix is tied to its code and test.

- SPEC-003: the door pipeline (XFF right→left, the per-request setup gate,
  the isolated hardening headers, the pinned setup module), §6 the `__Host-`
  cookie and the startup-generated signing keys, §7 loopback enforcement
  per request, new AC-10…AC-12, D-2 amended (session_key removed), and a new
  §11 that maps every REVIEW-001 finding to its fix.
- SPEC-005 / SPEC-006 / SPEC-007: the same three points where they belong
  (the wizard's loopback + module pin, the nudge's configured-origin signal,
  the async /access notification ruled in).
- PROJECT_PLAN §10 register: A13 re-stated as enforced (per request + pin);
  A2/A5/A8 re-stated as enforced where the review verified them; A3/A6/A9/A15
  now enforced by this pass; A11 left open (L4c). §14 change log entries.
- README: the status said "planning / pre-alpha · nothing is built" — it is
  stale. State M0+M0.1, note that /setup PERSISTS config.yaml and a restart
  adopts the generated secrets, and state the per-request loopback rule.
2026-10-14 11:30:17 +00:00

102 lines
3.8 KiB
Markdown

# MyDay
> A self-hosted employee **time, absence & reporting** app by **Sechpoint**.
> Delivered as a **PWA** for mobile and desktop. **Single-tenant** — one company per instance.
**Status:** 🚧 **M0 + M0.1 implemented** — the auth & routing foundation and the web/PWA layer
(the SPEC-002…009 slices) are built: routing · identity/sessions · the door · `/setup` ·
enroll/login (+ the passkey nudge) · roles/chain/recovery · the module registry · the web shell.
Next milestone: **M1 — org structure**. The design of record is
[`PROJECT_PLAN.md`](PROJECT_PLAN.md).
---
## What it will do
| Area | |
|---|---|
| **Org structure** | users, the manager hierarchy, departments, positions |
| **Time** | presence and timesheets (a per-user mode) |
| **Absence** | leave requests, approvals, balances |
| **Reporting** | rollups and aggregations |
Built as **one Go binary**: a small core (`MyDay.auth` — identity, sessions, the door) plus
drop-in **modules**.
---
## Running it
```bash
# 1. configure
cp config.example.yaml config.yaml # then edit; config.yaml is gitignored
# 2. build
go build -o myday ./cmd/myday
# 3. run
./myday
```
---
## ⚠️ First run — do NOT expose it publicly
The **first user becomes the top admin** (the root of the chain). So the instance bootstraps
**loopback-only**, running a **`/setup` wizard** until that admin exists.
```bash
# on the server it listens on 127.0.0.1:8080 only (config.example.yaml)
ssh -L 8080:127.0.0.1:8080 user@server # from your workstation
# then open http://127.0.0.1:8080/setup and complete the wizard
```
> **Loopback is enforced per request, not just at bind.** While in setup mode the app answers **404**
> to any request whose client is not loopback — so a reverse proxy must **not** sit in front of a
> fresh instance, even one bound to `127.0.0.1`. Bootstrap over the SSH tunnel (above) first.
> **Reach the app over `localhost` / `127.0.0.1` — not a hostname.** With an `https://` `base_url`
> the CSRF and session cookies are marked **`Secure`**, so they are **not** sent over plain
> `http://`; and only a **loopback** origin counts as a trustworthy/secure context for the
> browser's **WebAuthn** dialogs. So for first run and any human testing, open the app at
> **`http://localhost:8080`** or **`http://127.0.0.1:8080`** (via the tunnel above) — never at a
> bare server hostname.
Only **after** the top admin exists should you put MyDay behind a reverse proxy and expose it.
---
## What `/setup` writes — and the restart
The wizard **persists `config.yaml`** (`setup: false`, the base URL, mail/locale, and the secrets)
next to the loaded file — never over the committed `config.example.yaml`. If the file is not
writable it **prints** the values for you to save by hand, and setup still completes.
The **secrets are generated at startup** when a value is still `CHANGE_ME…`, so the wizard's
ceremony is never signed with a known key. The persisted file then holds **those same values** —
so **a restart adopts them** and the admin's TOTP secret (sealed with the generated key) keeps
working. Boot a productive instance from the **written `config.yaml`**, not from the example:
`config.example.yaml`'s placeholders fail closed in productive mode by design.
---
## Documentation
| | |
|---|---|
| [`PROJECT_PLAN.md`](PROJECT_PLAN.md) | the plan & decision log of record |
| [`AGENT.md`](AGENT.md) | instructions for AI agents working here |
| [`docs/specs/`](docs/specs/) | implementation specs |
| [`config.example.yaml`](config.example.yaml) | configuration template |
---
## Security
- Secrets live in **`config.yaml`** (gitignored) — **never commit them**.
- Auth is **Passkey (primary) + TOTP (fallback)**.
- See `PROJECT_PLAN.md` §10 for the deferred-security register and its **beta gate**.
---
## License
TBD.