diff --git a/docs/APP-INTEGRATION-GUIDE.md b/docs/APP-INTEGRATION-GUIDE.md index 1ab8d9e..b9dd1b2 100644 --- a/docs/APP-INTEGRATION-GUIDE.md +++ b/docs/APP-INTEGRATION-GUIDE.md @@ -19,7 +19,7 @@ Only these values differ per site, set via env vars at provision time: | Env var | Example (HNF) | Notes | |---------|--------------|-------| | `DOMAIN` | `manage.hotelnumberfour.com` | Per-site domain for NPM + auth cookie | -| `OFFICE_PUBLIC_IP` | `x.x.x.x` | Site WAN IP for offsite access restriction | +| `OFFICE_IP_CHECK` | `10.4.0.0/22,auto` | Onsite matchers (IP/CIDR/DDNS/`auto`/`disabled`) for offsite restriction | | `LAN_SUBNET` | `10.4.0.0/22` | Used when assigning NPM LXC's LAN IP | **Forgejo deploy webhooks**: each hotel's management container registers its own webhook @@ -667,6 +667,27 @@ proxy_set_header X-Forwarded-Proto $scheme; The `X-Real-IP` header is what the auth service uses for offsite access restriction. +### Offsite restriction in an app (shared `ip-check.js`) + +Each app enforces the offsite rule locally for users without `offsite_allowed`. +Copy `ip-check.js` (from the `auth` or `noticeboard` repo) into the app and call +`isOnsite(clientIP)`: + +```js +import { isOnsite } from './ip-check.js' +// ...after verifying the cookie: +if (!payload.offsite_allowed) { + const clientIP = request.headers['x-real-ip'] || request.ip + if (!(await isOnsite(clientIP))) return reply.status(403).send({ error: 'Access restricted to site network' }) +} +``` + +`OFFICE_IP_CHECK` is a comma-separated list; a request is onsite if ANY matcher +matches. Each matcher: an IP, a CIDR, a DDNS hostname, or `auto` (self-detect the +site's public IP via an external service — no DDNS client needed). Recommended +for a dynamic public IP: `10.4.0.0/22,auto` (LAN clients match the CIDR; +public-IP/hairpin clients match `auto`). `disabled` turns the check off. + --- ## Portal Registration