docs(readme): document missing CLI flags and env vars - #1346
Merged
Conversation
Adds --revision-coalesce-window, --log-format, --totp-encryption-key, --enable-metrics/--metrics-host/--metrics-port, --snapshot and its related flags, and --restore-upload-max-size (plus their env var equivalents) to the Configuration tables, matching cmd/leafwiki/main.go. Also adds the env-only LEAFWIKI_LOG_LEVEL. API-key flags are left out for now.
mrchypark
added a commit
to mrchypark/leafwiki
that referenced
this pull request
Jul 29, 2026
* feat(editor): make preview split resizable (perber#1279) * feat(tree): drag & drop page/section reordering in the page tree (perber#1307) Reorder and move pages/sections directly in the sidebar tree by dragging rows: drop between rows to reorder (insertion indicator), drop onto a section to move into it (auto-expands collapsed sections on hover), with optimistic updates and a silent tree re-sync. Same-parent reorders use the existing sort endpoint; cross-parent drops use the move endpoint, which now accepts an optional position for atomic move+placement (MoveNodeToPosition; omitted position appends, so existing callers are unchanged). Also folds in two fixes surfaced while testing the feature: - Firefox performed a full page navigation on drop because the post-drag click bypasses React's synthetic handlers; click suppression is now a native window-level capture listener armed during the drag. - MarkdownPreview re-parsed and re-rendered the whole article on every tree store update (byId identity churn); the rendered markdown element is now memoized on the resolved content string. Fixes perber#1110 * feat(favorites): add per-user favorite pages (perber#1310) Adds a private, per-user favorites list, separate from the global editorial "Pinned Pages" flag: any authenticated user (not just editor/admin) can favorite/unfavorite any page they can read. Backend: new internal/favorites package (own favorites.db, outside resync's reach per ADR-0001), AddFavorite/RemoveFavorite/ListFavorites use cases, PUT/DELETE /api/pages/:id/favorite and GET /api/favorites routes, cascade cleanup on page delete and user delete. Frontend: stores/favorites.ts (optimistic add/remove), star toggle on tree rows and the page viewer header, new "Favorites" sidebar accordion section between Pinned and Pages, loaded/cleared reactively off the session user id in App.tsx. * refactor(sqliteutil): share open/corruption-recovery logic across stores (perber#1311) Extract the "open → ensure schema → on recoverable SQLite corruption, wipe and retry once" sequence, previously copy-pasted near-verbatim in tags, links, properties, and search's store constructors, into a single sqliteutil.RetryOnCorruption helper. Each store keeps its own opening mechanics (eager sql.Open, links' lazy Connect(), search's fully lazy withDB); only the recovery policy is now shared. * feat(preview): add AutoHotkey syntax highlighting (perber#1312) Co-authored-by: Chen Zhang <c436zhan@gmail.com> * feat: .leafwikiignore (perber#1263) IgnoreFile struct wrapping github.com/sabhiram/go-gitignore with LoadFromDir(), Matches(path, isDir), and PatternCount() methods. * docs: Document external edits, resync triggers, and ID write-back in README (perber#1316) Covers the two resync trigger paths (admin UI, SIGUSR1/SIGHUP) and what happens when a manually added .md file has no leafwiki_id yet. * feat(auth): add TOTP core service and storage layer Adds the storage and cryptography foundation for optional per-user TOTP two-factor authentication: additive users.db schema migration, a TOTP service (secret generation/verification via pquerna/otp, AES-256-GCM encryption at rest, bcrypt-hashed recovery codes), and the accompanying UserStore/UserService/SessionStore methods. Not yet wired into login or any HTTP endpoint. * feat(auth): add two-step TOTP login handshake and self-service methods AuthService.Login now issues a short-lived, single-use login challenge instead of tokens when a user has TOTP enabled; CompleteTOTPLogin finishes the handshake with a TOTP or recovery code. Also adds StartTOTPSetup/ConfirmTOTPSetup/DisableTOTP/GetTOTPStatus for the self-service flow, with "revoke every other session, keep the current one" wired through RevokeAllUserSessionsExceptCurrent. Wires the --totp-encryption-key/LEAFWIKI_TOTP_ENCRYPTION_KEY config through main.go and the wiki.go composition root: if unset, TOTP self-service is simply unavailable rather than failing startup, since V1 has no separate feature toggle and every existing install would otherwise need a new secret on upgrade. Startup still fails if a key is provided but shorter than the required 32 bytes. No HTTP routes changed yet. * feat(auth): expose TOTP login and self-service HTTP endpoints New endpoints on the auth domain: - POST /api/auth/login/totp completes the handshake started by /api/auth/login when it returns {requiresTotp, loginChallengeToken}; cookies are only set on success. Shares its rate-limit budget with /api/auth/login. - POST /api/users/me/totp/setup/start|confirm, disable, and GET .../status implement self-service enable/disable, behind their own rate limiter separate from the pre-auth login endpoints. New LocalizedError codes (auth_totp_invalid_code, _challenge_invalid, _not_configured, _already_enabled, _setup_not_started, _not_enabled) mapped to HTTP statuses in errors.go. * feat(users): add two-factor authentication UI LoginForm now handles the two-step handshake: a password step, then (only if the account has TOTP enabled) a single code step accepting either a TOTP or recovery code. New TOTPSetupDialog (password -> QR code/manual key/code -> one-time recovery codes) and TOTPDisableDialog (password + code), opened from two new conditional items in the user avatar dropdown. QR rendering uses qrcode.react. UserManagement gets a "2FA" status column reusing the existing settings__pill idiom. Adds the "users" i18n namespace (didn't exist before), new login.totp.* keys in auth.json, and 6 new errors.json entries for the backend's TOTP error templates. Also fixes /api/auth/me (handleMe) to return totpEnabled, which had been missed when PublicUser gained the field. * test(e2e): add TOTP end-to-end coverage Covers the full flow through a real browser: enable TOTP (password -> QR/manual-key/code -> recovery codes) -> log out -> log in with a TOTP code; wrong code rejected; login with a recovery code and its single-use enforcement; disable and confirm plain password login resumes. generateTotpCode() reimplements RFC 6238 directly (HMAC-SHA1, no new dependency) rather than adding a library just for this; verified against the server's own pquerna/otp-based codes before use. e2e/run.sh now passes --totp-encryption-key to both the docker and local run modes, without which every self-service TOTP endpoint would 503. Verified: the 4 new TOTP tests pass, and a full run of the existing e2e suite (151 tests, 3 pre-existing skips) shows no regressions. * fix(auth): close recovery-code double-redemption race Recovery-code consumption was a read-then-write: verifyTOTPOrRecoveryCode matched the code against an in-memory snapshot of the stored hashes and wrote back the reduced list unconditionally, with no transaction or concurrency guard. Two concurrent requests presenting the same valid recovery code (e.g. two parallel POST /api/auth/login/totp calls) could both read the code as present before either wrote back, letting a single recovery code authenticate more than one session instead of exactly one. Fixes it with optimistic concurrency: UserStore.ConsumeRecoveryCodeHash does a compare-and-swap UPDATE guarded by the row's current totp_recovery_codes_json, so only the first writer can succeed; the loser gets swapped=false and verifyTOTPOrRecoveryCode re-reads the current hashes and retries (bounded, denying on exhaustion rather than risking ambiguity). Also sets a busy_timeout on the users.db connection so legitimate concurrent writers wait and retry instead of failing immediately with SQLITE_BUSY, which the compare-and-swap path does not itself retry on. Adds regression tests at both the store level (concurrent compare-and- swap attempts, exactly one wins) and the AuthService level (concurrent logins with the same recovery code, exactly one succeeds). * fix(auth): close TOTP lockout bypass and DisableTOTP panic from code review Two correctness/security bugs found by an 8-angle diff review, both confirmed by independent verification: - Login() unconditionally reset the shared per-user lockout counter on a correct password, before checking whether TOTP was required. Since CompleteTOTPLogin uses the same counter to rate-limit TOTP/recovery-code guesses, an attacker who already knows the password could resubmit it to wipe out failed TOTP attempts, defeating the 5-attempt lockout entirely and turning the 6-digit code into an unlimited-guess target. Login no longer resets the counter while TOTP is still required; it's only reset once the full handshake (password + TOTP/recovery code) succeeds. - DisableTOTP was missing the `a.totp == nil` guard present in every other TOTP entry point. If an operator runs without --totp-encryption-key while a user still has TOTP enabled (e.g. the key was removed after being used), calling disable panicked instead of returning a clean error. Login() had the same gap the other way: it issued a login challenge for such a user that CompleteTOTPLogin's own nil-check could never redeem, permanently locking them out instead of failing at the password step with a clear error. Also addresses smaller findings from the same review: - StartTOTPSetup/DisableTOTP each fetched the user row twice (once inside DoesIDAndPasswordMatch, once via a separate GetUserByID). It now returns the user it already fetched. - TOTP secret decryption failures reaching CompleteTOTPLogin/ConfirmTOTPSetup/ DisableTOTP were bare fmt.Errorf, violating this repo's convention that domain errors reaching HTTP handlers must be *sharederrors.LocalizedError; now wrapped as auth_totp_verification_failed (503). - Removed the unconditional UpdateRecoveryCodeHashes, dead in production since recovery-code consumption moved to the atomic ConsumeRecoveryCodeHash compare-and-swap; left in place it was a foot-gun for reintroducing the race that method was added to close. - handleConfirmTOTPSetup/handleDisableTOTP now log when ReadRefresh fails, since that silently falls back to revoking every session for the user (including the one performing the action) instead of just the others. - Migrated "Change Own Password"/"Loading users..."/"No users found." to react-i18next, since these components were already being touched to add new translated TOTP strings. New regression tests: repeated correct-password logins interleaved with wrong TOTP guesses must still hit the account lock; DisableTOTP without a configured TOTP service returns auth_totp_not_configured instead of panicking. Full backend suite (incl. -race), frontend suite, and the full Playwright e2e suite (148 passed, 3 pre-existing skips) all pass. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * feat(snapshot): complete full-backup snapshot scheduler, retention, and admin API Finishes the previously backend-only snapshot package (PR perber#1255): adds an automatic Scheduler mirroring the git-backup one, retention pruning after each run, collision-safe snapshot IDs, panic-safe status handling, and the internal/wiki/snapshot admin HTTP layer (status/list/trigger/download/delete) wired into main.go behind --snapshot/--snapshot-interval/--snapshot-retention. * feat(snapshot): add Full Backup settings UI New /settings/snapshots admin page to trigger, list, download, and delete full backups, gated behind the snapshotEnabled config flag and kept separate from the existing Git Content Backup page since this one includes the full database. * docs: document Full Backup feature and manual restore procedure Adds a README section alongside the existing Git Backup docs covering CLI flags/env vars, what's included (root/assets/branding/schema/users.db), retention behavior, and the manual stop-extract-restart restore steps. * fix(snapshot): address Copilot review findings - Guard handleList/handleDownload/handleDelete against a nil manager (only reachable if Routes were ever registered while disabled), matching the existing handleStatus/handleTrigger pattern instead of panicking. - Add Cache-Control: no-store / Pragma: no-cache to the download response, since the zip contains users.db (password hashes, TOTP secrets). - triggerNow() now reloads status/list in a finally block so a failed trigger (e.g. 409 already-running) doesn't leave the UI stale. * feat: API key handling - behind feature flag (perber#1281) * docs: remove API Keys section from README (perber#1322) Removed the experimental API Keys section from the README. * feat: restore from full backup snapshot (perber#1323) Adds a live, zero-downtime restore path for Full Backup snapshots (validate, gate writes, swap files, hot-swap AuthService's user store, invalidate sessions, reload branding, resync), an offline `restore-snapshot` CLI subcommand for disaster recovery, and a self-restart fallback for the rare case a failed restore can't be cleanly rolled back. Also fixes a pre-existing gap where branding.json (site name/logo/favicon selection) was never included in snapshot ZIPs. * fix: fixes tree auth sidebar (perber#1325) * fix: totp button styling (perber#1326) * feat: add --log-format flag to switch between text and json logging (perber#1328) Defaults to text (more readable for operators tailing logs directly); json remains available via --log-format json / LEAFWIKI_LOG_FORMAT=json. * refactor(auth): extract SessionManager from AuthService Splits JWT/session issuance, refresh, revocation, and validation out of AuthService into a standalone SessionManager, leaving AuthService with just local password + TOTP verification. AuthService's public API is unchanged (RefreshToken/RevokeRefreshToken/RevokeAllUserSessions/ ValidateToken now delegate to sessions), so no caller outside internal/core/auth needed to change. This sets up the seam a future native OIDC provider would reuse (SessionManager.IssueSession) without duplicating session handling, per ADR-0009. Behavior-preserving: full existing test suite passes unmodified; only construction call sites were updated for the smaller NewAuthService/new NewSessionManager constructor signatures. * fix(auth): SessionManager robustness + token-type/timing findings from review - ValidateToken now requires typ=="access" before resolving a user. It previously accepted any validly-signed token regardless of typ, so a refresh token (long-lived, and never checked against the session store here) could be used as an access token. Pre-existing on main since the original JWT auth commit; not introduced by the SessionManager extraction, but caught by review while the code was fresh. - RefreshToken/ValidateToken now return ErrSessionManagerNotWired instead of panicking if resolveUser is unset (only possible if a SessionManager is used directly instead of via NewAuthService, which always wires it). - IssueSession/RefreshToken now store the new refresh session's expiry using the exp already embedded in the freshly generated refresh token, instead of a second, independent now().Add(lifetime) call a moment later — the two could drift by up to a second, leaving a token cryptographically valid while its session row was already treated as expired (or vice versa). Also pre-existing on main. All three are pinned down with regression tests (session_manager_test.go), using a small injectable clock seam (SessionManager.now) to make the timing issue deterministically reproducible rather than a real-time race. * feat(auth): structured logging + Prometheus metrics for auth events Adds observability across the auth domain (ADR-0008): - Prometheus counters (auth_login_attempts_total, auth_totp_verifications_total, auth_sessions_total, auth_totp_enrollment_total) wired through the wiki/auth use cases. - Structured slog logging in AuthService, SessionManager, APIKeyService, SessionStore, UserService (user create/update/delete/password-change/ admin-reset events), replacing ad-hoc log/slog.Default() calls with a per-component logger. - UserResolver's preload failure is no longer logged at the point it occurs (only returned), avoiding a double-log with main.go's top-level fatal logging, per ADR-0008's "log once, at the swallow point" rule. * test(auth): TOTP recovery-code security regression tests - Recovery codes must only ever be stored as hashes, never plaintext, so no later read path (GetUserByID, admin user list, etc.) can re-display them once ConfirmTOTPSetup has shown them. - GetTOTPStatus's RecoveryCodesRemaining must correctly reach 0 after all codes have been consumed one by one, while TOTP itself stays enabled. * test(http): viewer role cannot move or sort pages Regression coverage for authorization on the move/sort endpoints — mirrors the existing viewer-cannot-delete test pattern. * test(restore): write-gate concurrency, self-restart exec, and permission-denied swap coverage - TestManager_Restore_BlocksConcurrentWritesDuringSwap: real goroutines racing WriteGate.TryEnter against an actual in-flight Manager restore, closing the gap between the gate primitive's own tests and the HTTP middleware's gate tests, neither of which exercised a real restore. - SelfRestart gains an execFn seam over syscall.Exec so it's testable without terminating the test binary; covers argv/env propagation and error passthrough. - Swapper.SwapAll: new regression for a permission-denied move-aside (live content must stay untouched, RollbackAll must stay a safe no-op). * fix(cli): validate --user-management-url like --login-url/--logout-url It's rendered as a plain <a href> in the frontend, but an unsafe scheme (e.g. javascript:) there is still attacker-controlled markup — so it gets the same http(s)-only, no-relative-path validation as the other two redirect flags, refusing to start otherwise. Also extracts the restore-snapshot subcommand into a testable runRestoreSnapshotCommand function (usage-error vs. restore-error are now unit tested directly, rather than only reachable via the CLI path). * fix(ui): TOC side panel entries show full text as a hover tooltip Long entry titles get truncated (ellipsis) in the collapsed side panel; a title attribute now surfaces the full text on hover. Also adds right padding so the ellipsis doesn't visually crowd the panel edge. * test(ui): add coverage for LoginForm and TOTP setup/disable dialogs Previously untested: the two-step credentials→TOTP login handshake in LoginForm, and the TOTPSetupDialog/TOTPDisableDialog self-service flows. * fix(auth): avoid logging login identifiers * fix(ui): patch brace-expansion transitive dependency * fix(markdown): recover unquoted colon in leafwiki_* frontmatter fields (perber#1332) An externally-authored page whose leafwiki_title (or other leafwiki_* string field) contains an unquoted colon, e.g. "ADR-0001: Filesystem as Source of Truth", failed YAML frontmatter parsing entirely and was silently dropped from the tree during filesystem reconstruction. parseFrontmatterYAML now falls back to a real-parse-validated sanitizer that quotes leafwiki_* values which don't parse on their own (unquoted colons, but also stray backticks/@ signs), while leaving genuine YAML constructs (flow-collections, anchors, block scalars) alone so malformed ones keep failing loudly. The sanitizer is anchored to column 0 so it can never reach into an unrelated field's indented block-scalar content. Frontmatter now exposes WasRepaired(), and node_store logs a warning when a file's frontmatter needed this recovery, so auto-repair isn't silent. * fix(tree): skip duplicate leafwiki_id during reconstruct instead of a… (perber#1333) A duplicate leafwiki_id across two files used to hard-abort the whole filesystem resync (including startup). Now the later-occurring duplicate (and, for a section, its whole subtree) is skipped without touching disk, logged at Warn, and the rest of the tree reconstructs normally. * chore: bump postcss from 8.5.16 to 8.5.23 in /ui/leafwiki-ui (perber#1336) Bumps [postcss](https://github.com/postcss/postcss) from 8.5.16 to 8.5.23. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](postcss/postcss@8.5.16...8.5.23) --- updated-dependencies: - dependency-name: postcss dependency-version: 8.5.23 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(ci): add /rebase ChatOps slash command for PRs (perber#1338) Lets collaborators with write access rebase a PR onto its base branch by commenting /rebase, using peter-evans/rebase under the default GITHUB_TOKEN. Permission is checked via the collaborator permission API before anything runs. * chore: bump dompurify from 3.4.11 to 3.4.12 in /ui/leafwiki-ui (perber#1337) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.11 to 3.4.12. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.4.11...3.4.12) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.12 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(e2e): patch brace-expansion transitive dependency (perber#1339) Same override pattern already applied to ui/leafwiki-ui (989aaf3); resolves brace-expansion to 5.0.8 without downgrading @trivago/prettier-plugin-sort-imports. * feat(metrics): expose build version via leafwiki_build_info gauge (perber#1335) Adds a Prometheus leafwiki_build_info{version} gauge so the running build version is scrapable alongside the existing HTTP/workflow metrics. * fix(toc): cap sticky side panel height so it stops overflowing the viewport (perber#1340) The ToC side panel was position: sticky with no max-height, so pages with many headings grew the panel past the bottom of the window with no way to scroll or reach the lower entries. It now caps to the available height and scrolls the entry list internally once it's too long, while staying sticky and visible during scroll. * feat(restore): add restore-from-uploaded-ZIP and fix Windows users.db… (perber#1341) Adds POST /restore/upload with a configurable max size (--restore-upload-max-size / LEAFWIKI_RESTORE_UPLOAD_MAX_SIZE) so a backup ZIP can be restored without CLI access. Also replaces the previous best-effort Windows warning with an actual fix: AuthService now suspends the UserStore's DB connection before a live restore renames users.db, avoiding a sharing-violation race on Windows. * fix(auth): hide TOTP enable option when no encryption key is configured (perber#1343) Expose totpAvailable via /api/config so the frontend can gate the "Enable two-factor authentication" menu item on server-side TOTP support, instead of only failing after the user submits their password. * docs(readme): document missing CLI flags and env vars (perber#1346) Adds --revision-coalesce-window, --log-format, --totp-encryption-key, --enable-metrics/--metrics-host/--metrics-port, --snapshot and its related flags, and --restore-upload-max-size (plus their env var equivalents) to the Configuration tables, matching cmd/leafwiki/main.go. Also adds the env-only LEAFWIKI_LOG_LEVEL. API-key flags are left out for now. * feat(i18n): close hardcoded-string gaps in frontend features (perber#1347) Migrates page, importer, assets, history, page-switcher, toolbar, tags, designtoggle, imagepreview, and links features to react-i18next (new page/importer/assets/history locale namespaces), and closes follow-up gaps discovered in editor, preview, tree, users, and viewer features that had picked up hardcoded strings since their migration. * feat(i18n): close remaining hardcoded-string gaps (perber#1349) Migrate leftover hardcoded English strings to react-i18next across tree, editor, viewer, and settings components, and fix API/store fallback errors that bypassed translated toast messages via mapApiError's err.message passthrough. Adds a common.json namespace for cross-cutting UI (error boundary, 404 page, dialog primitives, sidebar tabs). * docs: Update bug report template for platform options (perber#1348) Added options for running on different platforms in the bug report template. * fix: preserve fork behavior after upstream sync * test: retain upstream auth coverage * test(e2e): follow favorite action menu * fix(deps): update x/text for CVE-2026-56852 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Abu Hossain Foysal <ahfoysal30@gmail.com> Co-authored-by: Aurelio Arcabascio <140868181+arcabaa@users.noreply.github.com> Co-authored-by: perber <patrick.erber@gmail.com> Co-authored-by: c436zhan <zc19970919@gmail.com> Co-authored-by: Chen Zhang <c436zhan@gmail.com> Co-authored-by: John Porter <john@designermonkey.co.uk> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Stefan <zorak1103@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds --revision-coalesce-window, --log-format, --totp-encryption-key, --enable-metrics/--metrics-host/--metrics-port, --snapshot and its related flags, and --restore-upload-max-size (plus their env var equivalents) to the Configuration tables, matching cmd/leafwiki/main.go. Also adds the env-only LEAFWIKI_LOG_LEVEL. API-key flags are left out for now.