Commit Graph

19 Commits

Author SHA1 Message Date
wmantly 7d9c63b049 Air-gap fixes + in-app /docs (README/DEPLOYMENT/api.md/docs/*)
Air-gap:
- DynamicRecord.refreshAll() called getPublicIp() (api.ipify.org,
  icanhazip.com, ifconfig.me) every 4h on a timer regardless of
  whether any dynamic records were configured -- the one background
  call in the repo not actually gated by feature use. Now skips the
  lookup entirely when there's nothing to refresh.
- Removed the stray, unauthenticated GET /test page (a leftover
  jq-repeat demo) that loaded jQuery + Mustache from external CDNs.
- Removed a dead IE<9-only html5shim script tag pointing at a domain
  that no longer resolves.

Docs:
- New GET /docs (index) and /docs/:slug routes render this project's
  own README, DEPLOYMENT, api.md, and docs/*.md server-side via
  marked (new dependency) -- so the documentation is readable from
  the running app with no route to GitHub Pages, where it otherwise
  only lives. Public, no auth, same tier as the health endpoint.
- .dockerignore/Dockerfile updated: docs/, DEPLOYMENT.md, and
  nodejs/api.md were previously excluded from the image entirely
  ("served via GitHub Pages, not from the image") -- now copied in
  alongside README.md/tos.md-style, since they're needed at runtime.
2026-07-16 15:26:10 -04:00
wmantly 1df9916c91 Add standalone backup script and admin update-check banner
ops/backup.sh snapshots Redis (BGSAVE, dynamic RDB path lookup) and
./config for standalone deployments, with retention. A background
service polls GitHub releases every 24h and surfaces an admin-only
banner in the UI when a newer version is published.
2026-07-15 22:32:31 -04:00
wmantly 3f4f98ec4e Unify nav: drop Profile nav item, fold API Tokens into Profile page
- Nav bar already showed the logged-in user's name linking to /profile
  (cl-username); just removed the separate "Profile" and "API Tokens"
  nav items now that Profile covers both.
- Merge api_tokens.ejs into profile.ejs as a section below the existing
  profile card. /api-tokens 301-redirects to /profile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 23:44:05 -04:00
wmantly 9d34ff96e2 Add a footer, move GitHub link out of the nav (#130)
proxy had no page footer at all — no copyright, no license link, no
build/version info — unlike sso-manager-node, which already had one
(now cleaned up in a matching change there). Bring the two in line:

- Added a footer to bottom.ejs: theta42 logo/link, "© <year> theta42"
  with an MIT License link, a GitHub link, and build version/hash.
- Moved the GitHub icon link out of the top nav (where it competed
  with actual navigation items) and into the new footer.
- Added nodejs/utils/build_info.js (buildVersion/buildHash/buildYear,
  read once from package.json + git) and wired it into routes/render.js
  so every page has the values the footer needs. Mirrors the same
  helper added to sso-manager-node in the matching cleanup there.
- Copied the theta42.svg logo into nodejs/public/img/ (previously only
  present in sso-manager-node) so both apps' footers render identically.

Verified by rendering top+bottom with the real ejs package: no
template errors, GitHub link present exactly once (in the footer, not
the nav), logo and MIT License link present. npm test: 192/192 pass
(unaffected — view-only + a new leaf utility module).
2026-07-14 20:55:11 -04:00
wmantly a9a48c3445 Add self-service API tokens (PATs) with UI + Bearer auth (#119)
Personal access tokens so scripts/CI can call the management API without an
OIDC browser session. Each logged-in user mints their own token; it
authenticates as the creator (groups snapshotted at mint, mirroring the proxy's
browser AuthToken), and the existing authz layer (Permission.effectiveFor /
roles.resolveEffective) applies unchanged. Local groups and owned-domain rights
are recomputed live; only SSO/LDAP group membership is the mint-time snapshot.

- models/api_token.js: new ApiToken model (prx_<id>_<secret> format; id is the
  lookup key, secret bcrypt-hashed + isPrivate, shown once). add()/rotate()/
  authenticate(); optional expires_at; best-effort last_used_on; groups
  snapshot. No _ttl (persists). Deliberately NOT wrapped in ModelPs (so the
  last_used_on write on the auth path doesn't spam the socket).
- routes/api_token.js: self-service CRUD (list/get/update/delete/rotate),
  owner-scoped (created_by === reqUsername(req), 403 otherwise).
- middleware/auth.js + models/auth.js: accept `Authorization: Bearer prx_...`
  (precedence over the auth-token session header). Builds a synthetic req.token
  that satisfies the only three req.token reads (auth.js .user/.groupsArray,
  authz.js reqUsername .created_by) so the authz layer works unchanged.
  checkApiToken collapses every failure to one generic 401 (no leak).
- views/api_tokens.ejs + routes/render.js (GET /api-tokens): self-service page
  (forceLogin, no admin gate) — create (token shown once), rotate, revoke.
- views/top.ejs: "API Tokens" nav entry visible to all logged-in users.
- public/js/app.js: app.apiToken client module.
- DEPLOYMENT.md + docs/docker.md: API tokens section.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-12 17:12:38 -04:00
wmantly 2acc3644c4 Permissions: rename Grants, add wildcards, local groups, profile
- Rename Grant -> Permission end-to-end (model, routes, view, frontend,
  bootstrap) and add an idempotent redis migration for existing records.
- utils/roles.js: glob domain matching (* = one label, ** = any depth) against
  the full host; authz passes the full hostname.
- Local groups: LocalGroup model + admin routes/UI; membership merged into
  Permission.effectiveFor so app groups behave like SSO groups.
- Subject autocomplete via GET /api/permission/subjects (users + derived groups).
- User profile page (/profile) and username in the navbar; /api/user/me now
  returns merged/local/external groups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 10:54:15 -04:00
wmantly 10abd36340 Add OIDC login and per-domain authorization
Authentication previously implied full authorization: any valid token
could manage every host, DNS provider, domain, and user. This adds SSO
login and a per-domain rights model.

OIDC login (authorization_code + PKCE):
- conf.oidc + conf.auth blocks; clientSecret in (gitignored) secrets.js.
- utils/oidc.js (state/PKCE, code exchange, userinfo) using global fetch.
- models/oidc_state.js: short-lived state store, auto-expiring via
  model-redis 1.5 per-key TTL.
- routes/auth.js: GET /auth/oidc/start + /auth/oidc/callback; JIT-provisions
  a local user, mints an AuthToken carrying the SSO groups, hands the token to
  the browser via a URL fragment. "Log in with SSO" button on the login page.

Authorization (groups + app overrides, per-domain, with ownership):
- models/grant.js + utils/roles.js (pure, unit-tested): effective rights from
  conf.auth (admin users/groups, group->role map), Grant records
  (user|group -> global|domain -> viewer|manager|admin), and ownership
  (created_by). Roles rank admin > manager(owner) > viewer.
- AuthToken stores session groups; middleware/auth.js exposes req.groups.
- middleware/authz.js: requireAdmin, requireDomainRole(minRole, resolveDomain),
  filterViewable. Applied across routes: host mutations need manager on the
  host's domain; reads are filtered to visible domains; DNS providers, user
  management, and grant management are global-admin-only; certs need viewer.
- routes/grant.js: admin CRUD for grants. Anti-lockout via conf.auth.adminUsers
  plus migrations/grant_bootstrap.js.

Frontend: /me returns effective rights; nav gates Users/Grants to admins;
grants management page; OIDC token-fragment handling in app-base.js.

Tests: utils/roles and utils/oidc unit-tested (no redis); wired into the test
scripts. Full suite 89 pass. Also verified end-to-end against redis (grant
resolution, middleware allow/deny/403, list filtering) and the OIDC pure flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:17:05 -04:00
wmantly 8692b5f3d7 Added fav icon 2025-12-31 19:08:28 -05:00
wmantly 5dd07edf1a Moved jq-repeat to use NPM package 2025-12-31 16:01:34 -05:00
wmantly 496bff53c8 Fixes mobile issue with nav bar 2024-08-15 18:47:30 -04:00
wmantly b27e1b1617 Added relations to redis ORM 2024-08-13 15:18:47 -04:00
wmantly e86de04a69 Better UI for DNS 2024-08-11 22:57:18 -04:00
wmantly 3df3341235 DNS stuff 2024-08-10 23:07:32 -04:00
wmantly 441e372987 Added socket.io 2024-07-31 19:01:55 -04:00
wmantly fc66225cf5 new front end #33 2024-07-30 20:47:46 -04:00
wmantly 6d872c9ea5 changed gui title 2020-04-11 23:19:33 -04:00
wmantly 5b672eff84 added moment.js 2020-04-11 22:57:48 -04:00
wmantly 5266aec2b1 gui 2020-04-10 17:04:50 -04:00
wmantly 3f09c4d935 moved GUI from old project 2019-12-12 13:25:56 -05:00