Commit Graph
4 Commits
Author SHA1 Message Date
Hermes d8459a4a87 DC-085 link-first invite — Discord-style share it however you want
Flip POST /api/v1/auth/admin/invites default to no email; always return
the link. Operators copy + share via iMessage/WhatsApp/SMS/Signal/Telegram/
Discord/paste-in-email. Email becomes an opt-in checkbox (was the default).
Add shareText field with pre-formatted message for one-tap paste. Stop
logging raw invite URLs to error.log when SMTP is unconfigured (was just
a dev fallback — link is now in the response). Frontend flips the
checkbox default to unchecked and renders shareText + native share sheet
button (navigator.share) alongside the raw copy-link button. 9 new tests
covering default-no-send, link-always-returned, shareText-shape, opt-in
SMTP send, failed-SMTP-no-leak. Full suite: 2474/2474 + 9 new = 2483.
2026-08-20 04:46:12 -07:00
Krystie 503de258b8 [grade=pending] QA sprint: commit 103 at-risk files from multi-agent sprint work
Committed by Hermes autonomous QA sprint 2026-08-13.
These files were modified during the Aug 12 sprint but never committed.
2026-08-12 17:34:10 -07:00
hermes 273f6b8edb DC-052: license-tier enforcement (Free caps at 3, gates share on Pro)
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
First pricing enforcement. Per PRODUCT-SPEC-DECISIONS.md:
- Free = up to 3 users, no share features
- Pro = unlimited users + Tailscale-mediated share + public share links
- LIFETIME keys are creator-only (Sami runs --lifetime on his dev
  machine; production API rejects LIFETIME codes at activate())
- Free has NO trial; Pro is a deliberate paid choice

Changes:
- src/managers/license-manager.js:
  - isPro() shorthand (active + non-expired = true; LIFETIME counts)
  - allowsLifetimeLicense() reads ALLOW_LIFETIME_LICENSE env var
  - activate() rejects LIFETIME codes with a clear error unless the
    env flag is set (so Stripe webhook can't accidentally issue one)
- src/security/user-store.js: countUsers() helper for the tier gate
- src/utilities/errors.js: PaymentRequiredError (HTTP 402, code DC-402)
- routes/auth/admin.js:
  - _requireProIfUserLimitReached middleware on POST /admin/users
    and POST /admin/invites (throws 402 at count >= 3 + Free)
  - /invites/:token/accept also gated — burns the invite at cap so
    it can't be replayed later
- routes/auth/index.js: bridge licenseManager + userStore onto
  req.app.locals so the gate middleware can find them; pass
  licenseManager into the provider registry for future use

Tests: 19 new (license-tier-enforcement.test.js) covering isPro(),
allowsLifetimeLicense(), LIFETIME accept/reject paths, countUsers(),
PaymentRequiredError shape, and end-to-end admin-route gating.

Full suite: 1317/1317 passing across 50 suites.

Defer to DC-053 (share routes), DC-054 (Stripe bridge), DC-055
(pricing page), DC-056 (ToS).
2026-07-20 21:40:26 -07:00
hermes 321334cd33 DC-048: multi-user bootstrap + admin invites (opt-in)
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
Implements the user-store + invite-store + admin routes. The whole
system is opt-in via siteConfig.authProviders.email.enabled = true;
single-user TOTP-only installs see zero behavior change.

Backend:
- src/security/user-store.js: users + allowlist + bootstrap sentinel,
  atomic writes, last-admin protection, defensive dataDir resolver.
- src/security/invite-store.js: single-use tokens (SHA-256 hashed on
  disk), TTL, auto-prune, defensive dataDir resolver.
- routes/auth/admin.js: /me, /admin/users (CRUD), /admin/allowlist,
  /admin/invites (CRUD), public /invites/:token (peek + accept).
- routes/auth/index.js: wires userStore, gates admin router on
  email auth being enabled.
- src/auth/providers/email.js: verify() enforces allowlist, creates
  user record, tags req.user; default-enabled flipped to opt-in.
- src/auth/providers/totp.js: bootstraps system@totp.local admin on
  first verify so current DNS2 operator shows in /admin/users.
- src/security/audit-logger.js: middleware adds userId/userEmail/
  userRole/viaProvider to log details when req.user is tagged.
- PUBLIC_ROUTES + CSRF allowlists updated for invite redemption.

Frontend:
- status/js/admin.js: modal overlay with users list (role-edit,
  delete), invite form (email/role/TTL), copy-link button,
  outstanding-invites list with revoke. Exports window.AdminPanel.
- status/js/core/init.js: calls AdminPanel.attachTrigger so the
  Admin button only appears when /me returns isAdmin=true.

Tests: 35 new tests across 3 files (user-store, invite-store, auth
multistore integration). Full suite: 1298/1298 passing.

Docs: BACKLOG.md marks DC-048 done. CHANGELOG.md [Unreleased]
section gets the DC-048 entry.
2026-07-20 17:44:11 -07:00