No description
Find a file
Claus Lohmar 5dc0a0150e feat(account): add the account page and the admin Users page
The live instance had no way to add a passkey or a user. Two core pages now
make the signed-in experience usable (SPEC-010):

  * the ACCOUNT page (GET /{GUID}/account) shows the user's factors (passkey
    count · TOTP enrolled), an "Add a passkey" button that LINKS to the
    existing D-041 ceremony screen — the WebAuthn flow is never duplicated —
    and a "Don't ask again" control writing users.passkey_opt_out;
  * the admin USERS page (GET /{GUID}/users) lists every user (name · email ·
    role · manager · identity status) and creates one: only roles below the
    creator (D-019), the manager defaulting to the creator, then users.Create
    -> identities.Mint -> the /{GUID} invite is EMAILED and shown for
    copy/paste.

Both ride the door as core dispatch modules, so the door still gates them
(session + role + CSRF). The entrypoint requires admin for the users
functions; account stays at the team floor.

Additive support: auth.Identities.TOTPEnrolled, users.Users.List,
server.Deps.Credentials (the banner's "has a passkey?" read) and the
CoreDispatchNames guard now covers account/users.
2026-10-15 07:58:18 +00:00
.opencode chore: bootstrap MyDay repository 2026-10-13 11:52:30 +00:00
cmd/myday feat(account): add the account page and the admin Users page 2026-10-15 07:58:18 +00:00
deploy docs: add the Zoraxy deployment guide and systemd unit 2026-10-15 06:15:41 +00:00
docs docs(spec-005): record the TOTP-step render fix 2026-10-15 07:07:33 +00:00
internal feat(account): add the account page and the admin Users page 2026-10-15 07:58:18 +00:00
.gitignore chore(gitignore): ignore only the root build binary, not the cmd/myday source 2026-10-14 10:53:46 +00:00
AGENT.md docs: add the project plan, M0/M0.1 specs, agent guide and README 2026-10-14 10:53:44 +00:00
config.example.yaml feat(setup): gate setup with the config toggle and an optional code 2026-10-15 06:47:33 +00:00
go.mod chore(go): add the Go module manifest 2026-10-14 10:53:46 +00:00
go.sum chore(go): add the Go module manifest 2026-10-14 10:53:46 +00:00
PROJECT_PLAN.md docs: update the plan, specs and deploy guide for the setup toggle (D-045) 2026-10-15 06:47:36 +00:00
README.md docs: update the plan, specs and deploy guide for the setup toggle (D-045) 2026-10-15 06:47:36 +00:00

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.


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

# 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 — enable setup, then complete the wizard

The first user becomes the top admin (the root of the chain). Setup is gated by the setup config toggle (D-045): enable it, start MyDay, complete the wizard — the app then writes setup: false and the instance becomes productive.

# config.yaml
setup: true        # enable the /setup wizard
setup_code: ""     # OPTIONAL second factor — set it if the domain is reachable by others
  1. Start MyDay. The startup banner states the mode and prints the setup URL.
  2. Open the wizard at /setup (e.g. https://myday.sechpoint.app/setup).
    • setup_code empty → the wizard is served directly;
    • setup_code set → the code form comes first; enter the code printed in the startup banner.
  3. Complete the wizard — domain → top admin (TOTP) → mail/locale. It creates the admin and writes setup: false to config.yaml.
  4. Restart so the app adopts the persisted secrets: systemctl restart myday.

While setup: true and setup is unfinished:

  • GET / points at the wizard, and GET /setup serves it (or the code form);
  • every other route answers 404 — there is no app yet.

While setup: false and no admin exists, the instance is locked: GET / and GET /setup show a page telling you to set setup: true and restart, and every other route 404. This state is not publicly claimable — a fresh instance never exposes its bootstrap by accident.

Residual risk: while setup: true, whoever reaches /setup first can claim the admin. Set setup_code (or complete setup immediately) if the domain is open to others. The code is single-use and the entry form is rate-limited.

Read the code again (when one is set):

journalctl -u myday                          # inside the container
pct exec 11020 -- journalctl -u myday        # from the Proxmox host

Only after the top admin exists should you put MyDay behind a reverse proxy and expose it.

Note: the setup wizard enrolls TOTP; the admin adds a passkey at first sign-in on the real (https) origin, via the passkey nudge.


Deployment — behind Zoraxy (TLS at the proxy)

Full walkthrough: docs/deploy-zoraxy.md — topology · config · the Zoraxy ProxyHost · the firewall rule · the setup: true bootstrap · systemd · the misconfig warnings. This section is the summary.

The reference deployment terminates TLS at Zoraxy at 10.1.0.10; the app itself speaks plain HTTP. Config (config.example.yaml → config.yaml, D-043):

  • App — server.listen: "0.0.0.0:8080"; the app binds any address (there is no setup-mode bind refusal).
  • Proxy — Zoraxy terminates TLS and forwards to http://<app-host>:8080, setting X-Forwarded-For and X-Forwarded-Proto.
  • trusted_proxies — ["10.1.0.10"] (Zoraxy). The app honours X-Forwarded-Proto from a trusted proxy to decide a request is https, and then sets the session cookie Secure + __Host- and sends HSTS. Over plain http it keeps the bare, non-Secure cookie. A wrong value fails silently — no Secure cookie, no HSTS.
  • base_url — https://myday.sechpoint.app: the public origin, used in invite/recovery links.
  • Firewall — :8080 MUST be reachable only from 10.1.0.10. Anything else that can reach the port bypasses the proxy and its TLS; the app would then serve plain HTTP to that client.
# on a fresh instance: enable setup, start MyDay, then open the domain at /setup
#   config.yaml:  setup: true
#   (optional)    setup_code: "…"   # a second factor; the printed code comes first
sudo systemctl restart myday
# read the mode / setup code in the log:
journalctl -u myday                          # inside the container
pct exec 11020 -- journalctl -u myday        # from the Proxmox host

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 the plan & decision log of record
AGENT.md instructions for AI agents working here
docs/specs/ implementation specs
docs/deploy-zoraxy.md the Zoraxy deployment guide (D-043)
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.