Commit Graph
100 Commits
Author SHA1 Message Date
wmantlyandClaude Opus 4.8 952c9434a4 Per-host SSO: OpenResty gate + auth location (#57)
- hostfeatures.lua: replace basic-auth-only enforcement with a combined
  apply_auth() that allows if EITHER basic auth OR a valid SSO session passes.
  A "Basic" Authorization header takes the basic path (401 on failure);
  otherwise a browser is 302'd to /__proxy_auth/start. SSO sessions are read
  straight from Redis (proxy_SsoSession_<sid>, sid from the __proxy_sso cookie,
  character-restricted) and matched to the host.
- proxy.conf: add a /__proxy_auth/ location (outside the gate) that forwards to
  the nodejs app so the OIDC flow can run and set the cookie on every host.
- nginx.conf: add the proxy_auth_backend upstream (defaults to 127.0.0.1:3000).

Needs live verification on an OpenResty box (no Lua/nginx runtime in CI here).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:44:30 -04:00
wmantlyandClaude Opus 4.8 87c0d024d5 Per-host SSO: Node auth endpoints + Redis session (#57)
Adds the /__proxy_auth OIDC flow served on every proxied host:
- routes/host_auth.js: /start (PKCE+state, per-host redirect_uri), /callback
  (exchange, enforce the host allow-list via utils/host_sso.identityAllowed,
  mint session + set __proxy_sso cookie), /logout.
- models/sso_session.js: SsoSession (Redis-backed, TTL'd; read directly by the
  Lua gate) and HostSsoState (in-flight auth request).
- utils/oidc.js: per-host redirect_uri override on buildAuthUrl/exchangeCode.
- conf.hostSso (reuses conf.oidc). Allow-list logic unit-tested.

Enforcement (Lua gate + nginx location) lands next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:41:28 -04:00
wmantlyandClaude Opus 4.8 c98214cc76 Host modal: Authentication tab, wildcard-child default, allow-list autocomplete
- Default the "Parent Wildcard" challenge type when a wildcard parent exists.
- Split Authentication (basic auth + SSO) into its own tab; Access keeps IP
  allow/deny.
- Add GET /api/host/auth-suggestions (authenticated host editors, not just
  admins) and datalist-backed "type to search + Add" pickers for the SSO
  allowed-users/groups lists.

Verified in a browser (tab present, datalist populated, picker appends deduped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 12:24:29 -04:00
wmantlyandClaude Opus 4.8 774d8e815e Host form: redesign as a tabbed modal
Replace the dense always-open add panel + inline edit card with a single
Bootstrap modal (shared by Add and Edit) organized into tabs: General, TLS &
Wildcard, Traffic, Headers, Access (IP + basic auth + SSO). Adds per-field
explanations, a full-width proxy list, and an "Add host" button. Preserves all
field names/ids, the challenge-type detection JS, and formAJAX wiring; drops the
form-clone edit mechanism in favor of populating the one modal. Verified in a
browser (add + edit, tab navigation, field population).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 12:10:54 -04:00
wmantlyandClaude Opus 4.8 653c79f099 Per-host SSO: data model + normalization (#57)
Add Host fields sso_enabled / sso_allow_users / sso_allow_groups (empty
allow-lists = any authenticated user) and normalize them (parseAllowList). SSO
reuses conf.oidc and is OR'd with basic auth. Enforcement (session cookie + Lua
+ nginx auth location) lands separately. Unit tests included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 12:01:54 -04:00
wmantlyandClaude Opus 4.8 42a00dd3bf Remove unused invite and SSH-key user features
Both were dead/incomplete: POST /api/user/key called a nonexistent
User.addSSHkey, and the invite flow (POST /api/user/invite, User.invite,
User.addByInvite, InviteToken) had no consumer or UI. Drop the routes, the
InviteToken model, and the per-backing invite methods.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:57:29 -04:00
wmantlyandClaude Opus 4.8 3e5590288a Per-host HTTP basic auth (#57)
Adds opt-in basic auth per Host, following the existing per-host controls
pattern:
- Host fields basicauth_enabled / basicauth_realm / basicauth_users
  ({user: base64(sha1(pw))}). Credentials are parsed to plaintext by the pure
  host_features normalizer and hashed at the route layer (utils/basicauth.js),
  so plaintext never reaches Redis.
- ops/nginx_conf/hostfeatures.lua enforces it in access phase: verifies the
  Authorization header against base64(sha1(password)), fails closed with a 401
  WWW-Authenticate challenge.
- hosts.ejs gains an enable toggle, realm, and a username:password textarea
  (passwords never echoed back; blank keeps the current set).

Unit tests cover hashing (matches the htpasswd {SHA} vector), credential
parsing, and normalization. Note: the Lua path needs verification on a live
OpenResty box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:47:08 -04:00
wmantlyandClaude Opus 4.8 d1586b4d5a Fix user creation and password policy (#48)
Root cause of "can't log in with new credentials": routes/user.js POST called
User.add, which doesn't exist on the redis User model (it has create) — so every
API-created account threw and was never persisted. Switch to User.create and make
the Add button a submit.

Replace the broken password rule (rejected strong "@123Caplowercase", accepted
weak "lowercase1") with a clear policy in utils/password_policy.js: >= 8 chars and
either 12+ chars or 3-of-4 character classes. Enforced server-side on create and
password changes, mirrored in public/lib/js/val.js, with unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:38:09 -04:00
wmantlyandClaude Opus 4.8 6465e3d9f5 Scheduler: refresh DNS provider domain lists on interval (#69)
The host scheduler already checked wildcard cert expiry; add the second half of
the scheduler controller — DnsProvider.refreshAllDomains() re-syncs every
provider's domain list (get -> updateDomains, per-provider errors isolated),
scheduled 30s after start and every 24h alongside the cert check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:31:03 -04:00
wmantly c19cd2243e Merge pull request #115 from theta42/feat/permissions
Feat/permissions
2026-07-11 11:22:14 -04:00
wmantlyandClaude Opus 4.8 6d8dc45209 Validate host/target fields (hostname or IP; host allows */** wildcards)
Backend (utils/hostname_validate.js, enforced in routes/host.js on create/update):
- host: IPv4 or a wildcard pattern whose labels may be normal, "*" (one
  fragment) or "**" (any depth, incl. a bare "**" catch-all) — matching
  Host.lookUp. Lowered Host.host min length to 1 so "**"/"*" pass the model.
- target (ip): IPv4 or a strict hostname, no wildcards.
- Both reject scheme, "/", ":" and whitespace; 422 with per-field keys.

Frontend (val.js) mirrors the rules: host/target validators + hosts.ejs fields
point at them. Unit tests in test/unit/hostname_validate.test.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:18:47 -04:00
wmantlyandClaude Opus 4.8 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 098ff5bc9b Merge pull request #114 from theta42/feat/dynamic-dns
Dynamic DNS: keep A records pointed at the current public (WAN) IP
2026-07-11 00:37:24 -04:00
wmantlyandClaude Opus 4.8 83e9753b18 Dynamic DNS UI: fix blank columns, redesign the record list
The Host and Last-updated columns were blank because they relied on a jq-repeat
parseData hook that the rest of the app doesn't actually use for display (working
rows derive dates from the .momentFromNow class, not parseData).

- Derive fqdn server-side via DynamicRecord.toJSON so it flows through both the
  REST list and websocket payloads; the template uses {{fqdn}} directly.
- Render last-updated with the .momentFromNow class (data-date) like the rest of
  the app instead of a parseData-computed string.
- apply() now clears last_status on success so the UI only surfaces real errors.
- Redesign the section: prominent public-IP badge, cleaner add form, and a
  Bootstrap list-group of records (fqdn, IP badge, "updated N ago", inline error)
  with outline refresh/remove buttons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:32:21 -04:00
wmantlyandClaude Opus 4.8 a2d194f855 Add dynamic DNS: keep A records pointed at the current public IP
For deployments on WAN DHCP, operators can declare A records in the DNS section
that the app updates to this box's current public IP every 4 hours (and
immediately on create).

- utils/public_ip.js: getPublicIp() queries external echo services (ipify +
  fallbacks, configurable) with pure isIPv4/extractIp helpers.
- utils/dns_records.js: pure planARecordUpdate() reconciliation decision.
- models/dns_provider.js: Domain.upsertARecord(name, ip) — provider-agnostic
  upsert via getRecords + deleteRecordById + createRecord (createRecord alone is
  not a reliable cross-provider upsert). Apex ('@') handling added to each
  provider (CloudFlare uses the domain name, Porkbun an empty name, DigitalOcean
  '@') via a new DnsApi.apexName().
- models/dynamic_record.js: DynamicRecord model (deterministic id per host,
  apply()/refreshAll()), registered + ModelPs-wrapped for live UI updates.
- services/dynamic_dns.js + conf: 4h scheduler mirroring host_scheduler.
- routes/dns.js: /dynamic CRUD + /dynamic/ip, gated to domain managers/admins.
- views/dns.ejs: "Dynamic A Records (WAN IP)" card with add form + list.
- test/unit/dynamic_record.test.js: public-IP parsing + reconciliation logic.

Verified end-to-end against a live Porkbun domain (create, idempotent, IP-change,
cleanup) plus unit suite (111 pass).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:55:56 -04:00
wmantly 1f7a9e5ded Merge pull request #113 from theta42/fix/porkbun-domain-sync-zoneid
API issue
2026-07-10 23:42:35 -04:00
wmantly 762b46a5fa Merge branch 'master' into fix/porkbun-domain-sync-zoneid 2026-07-10 23:39:17 -04:00
wmantly 259f1350a3 API issue 2026-07-10 23:36:51 -04:00
wmantly 1638702bcd Merge pull request #112 from theta42/fix/porkbun-domain-sync-zoneid
Fix Porkbun/DigitalOcean domain sync (undefined zoneId aborts create)
2026-07-10 23:30:17 -04:00
wmantly f2bcce07f2 Merge branch 'master' into fix/porkbun-domain-sync-zoneid 2026-07-10 23:28:42 -04:00
wmantly 04a736a2b5 Merge pull request #111 from theta42/feat/per-host-proxy-controls
Per-host reverse-proxy controls: rate limiting, caching, headers, IP allow/deny
2026-07-10 23:28:18 -04:00
wmantlyandClaude Opus 4.8 39b4837fc6 Fix DigitalOcean domain sync (never set .domain)
DigitalOcean.listDomains ran the API response through __parseRes, which does
not map anything to `.domain` and additionally rewrites each item's `.name` to
its subdomain via tld-extract. DO's /v2/domains returns objects keyed `name`
with no zone id, so the domain objects had no usable `.domain` and Domain.create
threw ObjectValidateError ("domain is not string type"), aborting the sync.

Map name -> domain the way CloudFlare.listDomains does and skip __parseRes (it
is for record responses). DO has no zone id; the earlier zoneId-omit fix covers
that. Also request per_page=200 so accounts with >20 domains aren't truncated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:17:43 -04:00
wmantlyandClaude Opus 4.8 618b87fc10 Fix Porkbun (and other zoneless providers) domain sync
updateDomains passed `zoneId: domain.zoneId` unconditionally. Providers with
no zone concept (Porkbun, DigitalOcean) return domains without a zoneId, so
this sent an explicit `undefined`, which model-redis' processKeys rejects
("zoneId is not string type") and aborts the whole sync with a 422. Cloudflare
was unaffected because its domains carry a real zoneId string.

DnsProvider.create's catch only re-threw UnauthorizedDnsApi and swallowed
everything else, returning undefined — so the route then crashed on
`item.id` with an opaque "Cannot read properties of undefined" instead of the
real validation error.

- Omit zoneId from the Domain payload when the provider doesn't supply one.
- Re-throw non-Unauthorized errors from create so failures surface properly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:03:38 -04:00
wmantlyandClaude Opus 4.8 286b50b09b Raise default response-cache TTL to 48h
Bump proxy_cache_valid from 10m to 48h and match proxy_cache_path
`inactive` (must be >= the TTL or entries evict before expiring).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:19:25 -04:00
wmantlyandClaude Opus 4.8 6092468901 Add per-host reverse-proxy controls (rate limit, cache, headers, IP ACL)
Every proxied request flows through one shared OpenResty location whose
behavior is chosen at request time from the host's Redis hash. Add per-host
controls as new Host fields enforced in Lua rather than static nginx config
(which can't key off a per-request variable):

- Rate limiting: per-client-IP token bucket via resty.limit.req
  (ratelimit_enabled/rate/burst), backed by a new `ratelimit` shared dict.
- Response caching: opt-in per host via a global proxy_cache zone gated by
  $skip_cache (respcache_enabled). Off by default; upstream Cache-Control
  still honored.
- Custom/security headers: req_headers (upstream) + resp_headers (client) and
  hsts_enabled, applied in access/header_filter phases.
- IP allow/deny CIDR lists via resty.ipmatcher (deny wins; non-empty allow is
  default-deny).

New ops/nginx_conf/hostfeatures.lua holds the enforcement; proxy.conf's
access_by_lua string becomes a block that calls it, plus a header_filter block.
nodejs/utils/host_features.js is the pure, unit-tested normalize/validate layer
(header/CIDR parsing, range clamping, injection-safe values) applied in
routes/host.js and mirrored by the hosts.ejs edit form. install.sh gains the
ipmatcher rock, the cache dir, and the hostfeatures.lua symlink.

Per-host cache TTL is intentionally deferred (global default only) — see the
plan's limitations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:10:55 -04:00
wmantly e8f1ca56ec Merge pull request #110 from theta42/ops/install-idempotent-symlinks
Make install.sh idempotent; symlink config from the repo
2026-07-10 13:27:44 -04:00
wmantly b11bf24a9e Merge branch 'master' into ops/install-idempotent-symlinks 2026-07-10 13:27:29 -04:00
wmantly 3ecf69ec93 Merge pull request #109 from theta42/fix/targetinfo-http2-coalescing
Fix HTTP/2 coalescing cross-routing (hassio served from metrics)
2026-07-10 13:27:12 -04:00
wmantly 1d0ebcbdc0 Merge branch 'master' into fix/targetinfo-http2-coalescing 2026-07-10 13:27:01 -04:00
wmantly f9d23323c1 Merge pull request #108 from theta42/fix/jquery4-removed-apis
Fix jQuery 4 removed-API crash in app-base.js
2026-07-10 13:26:43 -04:00
wmantly d7c7940835 Merge branch 'master' into fix/jquery4-removed-apis 2026-07-10 13:26:34 -04:00
wmantly cabd1c78ca Merge pull request #107 from theta42/feat/oidc-authz
OIDC login + per-domain authorization
2026-07-10 13:26:21 -04:00
wmantlyandClaude Opus 4.8 0cca3730fb Make install.sh CI/CD-friendly: deterministic force-sync to remote
The installer is meant to be run by CI/CD with no human writes on prod, so
updates should mirror the repo exactly rather than refuse on local drift:

- Replace `git pull --ff-only` with fetch + `checkout -B origin/$BRANCH` +
  `reset --hard` + `clean -fd` so the box always matches origin/$BRANCH.
- Set GIT_TERMINAL_PROMPT=0 so a missing/expired credential fails fast in CI
  instead of hanging on a prompt.
- npm ci --omit=dev (lockfile, production-only) with a plain-install fallback.
- Allow REPO_URL / REPO_DIR / BRANCH to be overridden from the environment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 13:23:31 -04:00
wmantlyandClaude Opus 4.8 cff816fa06 Address CodeQL findings on the OIDC auth flow
- Open redirect / client-side XSS (app-base.js): the post-login `redirect`
  read from the URL fragment was assigned straight to window.location. Add a
  same-origin guard (safeInternalPath) that rejects absolute URLs,
  protocol-relative "//host"/"/\\host", and scheme targets like
  "javascript:". Apply it in consumeTokenFragment and logInRedirect.
- Server-side defense in depth: sanitize `redirect` when storing OidcState
  and when building the callback fragment (utils/safe_redirect.js, shared +
  unit-tested).
- Missing rate limiting: throttle the unauthenticated auth endpoints
  (/login, /oidc/start, /oidc/callback) with express-rate-limit (60/IP/15m).
  Set `trust proxy: 1` so req.ip reflects the real client behind OpenResty.

Adds test/unit/safe_redirect.test.js; unit suite 77 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 13:22:12 -04:00
wmantlyandClaude Opus 4.8 ec168411bf Make ops/install.sh idempotent and symlink config from the repo
Rework the installer so it doubles as an updater:

- Idempotent throughout: `install -d` for dirs, apt source lists rewritten
  in place, `gpg --dearmor --yes`, fallback cert generated only if missing,
  repo cloned or fast-forwarded, and `ln -sfn` symlinks.
- Config is now symlinked straight from the checked-out repo instead of
  wget-ing raw files from GitHub. /etc/openresty/{nginx.conf,autossl.conf,
  sites-enabled/000-proxy}, the targetinfo.lua lualib, and the systemd unit
  all point at $REPO_DIR/ops, so an update is just `git pull` + reload with
  no re-copying. This also drops the external t42-common raw-file
  dependency (autossl.conf / proxy.conf now come from this repo).
- Validate `openresty -t` before reloading so a bad config can't take the
  proxy down; reload if running else restart.
- Fix prior bugs: stray `curl sudo apt-get update`, duplicate openssl cert
  line, and `cd ../nodejs` (now cd $REPO_DIR/nodejs). Require root; add a
  BRANCH override (default master).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 13:14:48 -04:00
wmantlyandClaude Opus 4.8 cd0cc50a19 Fix HTTP/2 coalescing cross-routing in targetinfo.lua
Hosts that share one wildcard cert (e.g. hassio.718it.biz and
metrics.718it.biz under *.718it.biz) resolve to the same IP, so browsers
coalesce them onto a single HTTP/2 connection. The SSL request_domain
phase resolves the connection's first host and caches it in
ngx.ctx.targetInfo; the unguarded `if targetInfo then return targetInfo`
then handed that first host's target to every coalesced request on the
connection -- e.g. hassio.718it.biz served from metrics' 192.168.1.8:8088.

Confirmed via debug logging: for GET requests to hassio.718it.biz on a
coalesced connection, ngx.ctx already held metrics' IP and the function
short-circuited to it.

Guard the reuse by host: only return the cached target when
ngx.ctx.targetInfo_domain matches the requested domain, and record that
domain whenever a target is resolved. A coalesced request for a different
host now re-resolves against its actual Host header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 13:05:43 -04:00
wmantlyandClaude Opus 4.8 08bef1bd00 Fix jQuery 4 removed-API usage in app-base.js
jQuery 4 removed $.isFunction and $.holdReady. Replace the isFunction
checks with typeof and drop the holdReady calls (the redirect already
guards the page). Mirrors the master hotfix (#108) so this branch is
testable on jQuery 4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:24:01 -04:00
wmantlyandClaude Opus 4.8 f4ba086fd2 Fix jQuery 4 removed-API usage in app-base.js
jQuery 4 removed $.isFunction and $.holdReady, which threw at runtime
("$.isFunction is not a function" on the Clear Cache / delete path).

- Replace the three $.isFunction(callback) checks with typeof === 'function'.
- Drop the two $.holdReady calls in forceLogin; the redirect already keeps
  an unauthenticated user off the page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:22:02 -04:00
wmantlyandClaude Opus 4.8 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 9f175f5bf6 Merge pull request #106 from theta42/deps/audit-fix
Update dependencies, clear all npm audit vulnerabilities
2026-07-10 11:14:01 -04:00
wmantly 58ba837cbd Merge branch 'master' into deps/audit-fix 2026-07-10 11:13:51 -04:00
wmantly cdca9fbd40 Merge pull request #105 from theta42/model-redis
TTL-based expiry for wildcard subdomain cache
2026-07-10 11:08:26 -04:00
wmantlyandClaude Opus 4.8 7ee8202a11 Update dependencies and clear all npm audit vulnerabilities
`npm audit fix` resolved all 12 advisories (7 high, 5 moderate) by
bumping transitive deps (axios/form-data/node-forge/path-to-regexp/qs/
ws/socket.io-parser/brace-expansion/follow-redirects/picomatch) within
the existing semver ranges. Audit is now clean (0 vulnerabilities).

Also refreshed direct deps:
- @fortawesome/fontawesome-free 7.1 -> 7.3 (minor)
- ldapts 8.1.2 -> 8.1.8 (patch)
- ejs 3.1 -> 6.0 (major)
- jquery 3.7 -> 4.0 (major)

All views render under ejs 6 (modern <%- include() %> syntax, no legacy
filters) and no jQuery-4-removed APIs are used in the views or public JS.
Unit + integration tests pass (55).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 10:58:38 -04:00
wmantlyandClaude Opus 4.8 7cc7f95019 Add TTL-based expiry for wildcard subdomain cache entries
Adopt model-redis v1.5.0 and give the on-demand is_cache Host records
(and their Cached tracking records) created by Host.addCache a TTL, so
they auto-expire instead of accumulating forever. Only the record hash
carries the TTL, so OpenResty's direct HGETALL sees a miss once it
expires and re-resolves through the lookup path.

The lifetime is configurable via conf.cacheTTL (seconds, default 3600;
0 disables expiry). This also mitigates the matchAny=false "wrong host"
bug: stale leftover cache entries now expire (and are still busted on
parent update), so undefined subdomains stop being served by old caches.

Add test/unit/wildcard_matchany.test.js covering the matchAny routing
decision (defined vs undefined subdomains, mixed-policy sibling
wildcards, and cache-entry behavior) and wire it into the test scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 10:51:06 -04:00
wmantly bb9bbb1d6e Merge pull request #104 from theta42/model-redis
Fixed clearcahce
2026-07-10 00:39:53 -04:00
wmantly 626a4189fa Fixed clearcahce 2026-07-10 00:39:25 -04:00
wmantly 1eda4a7cf5 Merge pull request #103 from theta42/model-redis
Fixed issue with lookup
2026-07-10 00:36:36 -04:00
wmantly 99cdbe6586 Fixed issue with lookup 2026-07-10 00:35:50 -04:00
wmantly c15e7d93ff Merge pull request #102 from theta42/model-redis
Fixed issue with matching wild card
2026-07-10 00:26:15 -04:00
wmantly 1295d4f5ce Fixed issue with matching wild card 2026-07-10 00:24:16 -04:00
wmantly e457cadf8a Merge pull request #101 from theta42/model-redis
Fixed issue with matching wild card
2026-07-10 00:17:20 -04:00
wmantly 56c2fb1a5c Fixed issue with matching wild card 2026-07-10 00:13:40 -04:00
wmantly 655aeb47a2 Merge pull request #100 from theta42/model-redis
Model redis
2026-07-09 22:20:53 -04:00
wmantly 398d2d9979 Merge branch 'master' of github.com:theta42/proxy into model-redis 2026-07-09 22:20:34 -04:00
wmantlyandClaude Opus 4.8 77674c6b1c Upgrade model-redis to ^1.4.0 and redis to ^6.1.0
model-redis 1.4.0 requires redis 6, so bump both together to keep a
single deduped redis version. Verified: unit tests pass (38), and the
real models load and run against live redis 7 on the new stack,
including model-redis 1.4.0's findOrphans().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 20:13:38 -04:00
wmantlyandClaude Opus 4.8 2c32ec0f3a Add wildcard_matchAny routing mode for wildcard hosts
A *.example.com wildcard host now chooses between two routing modes:
- wildcard_matchAny=false (default): only subdomains explicitly defined
  in redis route; undefined subdomains get no match (406)
- wildcard_matchAny=true: any subdomain catches-all to the wildcard
  parent host, preserving the previous behavior

The gate lives in the host_lookup socket service, which is only reached
for domains missing a direct redis entry, so defined children and
**-style hosts are unaffected. Adds the matching-mode selector to the
host add/edit form, shown for wildcard hosts.

Note: existing wildcard hosts have no wildcard_matchAny field and so
default to the stricter "only defined" mode until re-saved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 01:47:21 -04:00
wmantlyandClaude Opus 4.8 2665e7d466 Fix cert deletion key and harden nginx SSL/redis handling
- host.remove() now deletes the cert under the host key instead of the
  Domain relation object, so certs are actually removed from redis
- targetinfo.lua returns the redis connection to the pool via
  set_keepalive instead of leaking one connection per request
- autossl.conf drops TLSv1/1.1 and 3DES, adds TLSv1.3

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 01:29:43 -04:00
wmantly 59bba8273a Fixed issue with cert renew 2026-02-25 22:59:04 -05:00
wmantly bcc777ccdf Delete certs from redis 2026-02-25 22:21:16 -05:00
wmantly 1bacc17a12 Fixed host put on frontend 2026-02-25 19:45:09 -05:00
wmantly 427e9411e7 Better model pub/sub errors 2026-02-25 19:30:54 -05:00
wmantly 585aa14c0b services 2026-02-25 19:17:25 -05:00
wmantly 74b8f1113e Prokbun API fix 2026-02-25 13:49:46 -05:00
wmantly 0a63e05028 Import path fix 2026-02-25 12:35:21 -05:00
wmantly b522e5064a Package updates 2026-02-25 12:16:38 -05:00
wmantly 21ff1b0bb5 DNS route 2026-02-25 12:11:31 -05:00
wmantly 5309719622 Create CNAME 2026-02-11 11:21:00 -05:00
wmantly e2cb6207eb Merge pull request #87 from theta42/model-redis
Moved to model-redis package
2025-12-31 20:45:58 -05:00
wmantlyandClaude Sonnet 4.5 b78376cdf9 Add model-redis package references to documentation
- Added ORM reference in architecture.md (Redis section)
- Added Data Models section in contributing.md with example
- Updated README.md to mention model-redis in Architecture
- Linked to https://www.npmjs.com/package/model-redis for more info

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-31 20:35:55 -05:00
wmantly 5913c5e8c3 Moved to model-redis package 2025-12-31 20:32:21 -05:00
wmantly 25544db1a1 Merge pull request #86 from theta42/conf
Using the simpleworks conf now
2025-12-31 19:30:12 -05:00
wmantly 6cb2880131 Using the simpleworks conf now 2025-12-31 19:28:26 -05:00
wmantly c75ec8015e Merge pull request #85 from theta42/docs
Fav Icon
2025-12-31 19:11:08 -05:00
wmantly 9c762e0e0c Updated versions 2025-12-31 19:08:56 -05:00
wmantly 8692b5f3d7 Added fav icon 2025-12-31 19:08:28 -05:00
wmantly b2267d346e Merge pull request #84 from theta42/docs
Fix architecture documentation - clarify Redis-first lookup
2025-12-31 17:44:40 -05:00
wmantlyandClaude Sonnet 4.5 3dc9e22dc5 Fix architecture documentation - clarify Redis-first lookup
- OpenResty checks Redis FIRST for every request
- Node.js is only queried as fallback when Redis has no entry
- Cached hosts continue working even if Node.js goes down
- Updated diagram to show lookup hierarchy
- Restructured caching strategy to emphasize L1/L2 tiers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-31 17:37:54 -05:00
wmantly a27dbce6ab Merge pull request #83 from theta42/DNS-01
Dns 01
2025-12-31 17:33:16 -05:00
wmantlyandClaude Sonnet 4.5 6b8b08e94d Fix test suite for CI/CD compatibility
- Fix glob patterns in package.json test scripts (explicit file listing)
- Add error handling for chmod in unix_socket_json.js (test environments)
- Fix unhandled promise rejections in DNS provider contract tests
- Replace broken malformed JSON test with proper buffering test
- Add defensive file cleanup in unix socket tests

All 55 tests now pass successfully.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-31 17:26:24 -05:00
wmantly c0cfed1b1d Updated docs 2025-12-31 16:55:14 -05:00
wmantly b5c7f683b0 Set up github 2025-12-31 16:54:49 -05:00
wmantly 76650d2a11 Added tests 2025-12-31 16:51:55 -05:00
wmantly 354abdf19b Gitpage added 2025-12-31 16:51:27 -05:00
wmantly 1bafc99da9 Clean up 2025-12-31 16:02:20 -05:00
wmantly 5dd07edf1a Moved jq-repeat to use NPM package 2025-12-31 16:01:34 -05:00
wmantly 3a31083871 Cleaned up file layout 2025-12-31 16:00:39 -05:00
wmantly ef3fffdef2 Updated DNS-01 validation 2025-12-31 16:00:21 -05:00
wmantly d944ca6957 Cleaned up file layout 2025-12-31 15:57:33 -05:00
wmantly eb7c049a24 Merge pull request #79 from theta42/wmantly-patch-1
Update install.sh
2024-11-12 19:43:55 -05:00
wmantly c51b9c007b Update install.sh 2024-11-12 19:40:47 -05:00
wmantly 22cf1ce19a Merge pull request #78 from theta42/Fixed-install-script
Update install.sh
2024-11-12 19:15:23 -05:00
wmantly de67510a4e Update install.sh 2024-11-12 19:13:27 -05:00
wmantly c6e937d7f1 Merge pull request #71 from theta42/dependabot/npm_and_yarn/nodejs/axios-1.7.4
Bump axios from 1.7.2 to 1.7.4 in /nodejs
2024-08-15 23:35:53 -04:00
wmantly 63012615cc Merge pull request #66 from theta42/dns
DNS
2024-08-15 23:30:42 -04:00
wmantly 6e168b12b3 Fixed issue: Old domains not being removed on refresh 2024-08-15 22:10:15 -04:00
wmantly c418dd39a8 removed unneeded route 2024-08-15 22:09:17 -04:00
wmantly 5d42879c48 Removed print 2024-08-15 22:08:51 -04:00
wmantly 17f8bec34d Polished off DNS api models 2024-08-15 18:51:36 -04:00
wmantly ce814c306b Fixed title whe in dev env 2024-08-15 18:50:52 -04:00
wmantly c1cfa8bb82 Added relations to redis models 2024-08-15 18:49:42 -04:00