Add security document and move session notes into docs
docs/SECURITY.md covers the layers of protection, what is stored, third parties, limits and open items to investigate. SESSION.md moves to docs/ and is rewritten as the handoff for removing ticket subjects next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f137afd5f
commit
3e41565ac1
@ -8,7 +8,8 @@ A help desk dashboard for ManageEngine ServiceDesk Plus Cloud (`ithelp.mainecc.e
|
|||||||
|
|
||||||
- **Goal and next steps:** per-user settings (built, needs live checks) and panel modules. See `docs/ROADMAP.md`.
|
- **Goal and next steps:** per-user settings (built, needs live checks) and panel modules. See `docs/ROADMAP.md`.
|
||||||
- **Decisions, API gotchas and known issues:** `docs/NOTES.md`. Read it before touching any SDP query.
|
- **Decisions, API gotchas and known issues:** `docs/NOTES.md`. Read it before touching any SDP query.
|
||||||
- **Latest session summary and handoff:** `SESSION.md`.
|
- **Security model and open items:** `docs/SECURITY.md`. Update it when anything touching sign-in, sessions, data or hosting changes.
|
||||||
|
- **Latest session summary and handoff:** `docs/SESSION.md`.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
|
|||||||
@ -108,6 +108,8 @@ The sign-in is kept in an encrypted cookie in that browser for `SESSION_DAYS` (7
|
|||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
The full security model, its limits and the open items are in [docs/SECURITY.md](docs/SECURITY.md).
|
||||||
|
|
||||||
- **Network:** NPM only lets in MCCS' public IP addresses (campuses and VPN). From anywhere else the subdomain doesn't answer.
|
- **Network:** NPM only lets in MCCS' public IP addresses (campuses and VPN). From anywhere else the subdomain doesn't answer.
|
||||||
- **Allowed domains:** any Zoho account can complete the Zoho consent screen, so the server only creates a session for emails at `ALLOWED_EMAIL_DOMAINS`, and checks again on every call. A refused sign-in has its Zoho token revoked straight away.
|
- **Allowed domains:** any Zoho account can complete the Zoho consent screen, so the server only creates a session for emails at `ALLOWED_EMAIL_DOMAINS`, and checks again on every call. A refused sign-in has its Zoho token revoked straight away.
|
||||||
- **Per-user sign-in:** every request needs the user's own Zoho sign-in. There is no shared token, so a reachable page with no sign-in shows nothing.
|
- **Per-user sign-in:** every request needs the user's own Zoho sign-in. There is no shared token, so a reachable page with no sign-in shows nothing.
|
||||||
|
|||||||
71
SESSION.md
71
SESSION.md
@ -1,71 +0,0 @@
|
|||||||
# Session summary: 2026-10-15
|
|
||||||
|
|
||||||
A Claude Code session that turned the closed-ticket board into a dashboard, then rebuilt sign-in so it can be hosted. Background is in `CLAUDE.md`, `docs/ROADMAP.md` and `docs/NOTES.md`.
|
|
||||||
|
|
||||||
## Where things stand
|
|
||||||
|
|
||||||
The dashboard is now a stateless server meant to run as the Docker image `v0idf1sh/sdp-dashboard` on the RackNerd VPS, behind Nginx Proxy Manager. NPM's access list limits the site to MCCS' public IP blocks. Every user signs in with their own Zoho account. Filtering is still the central region from `.env`.
|
|
||||||
|
|
||||||
It's committed and pushed on branch `v2.0.0`. **Tested live on 2026-10-15** as a bare-metal run on the VPS (Node, `PORT=3005`) behind `dash-test` in NPM: Zoho sign-in, live SDP data, Sign out, and the IP access list (mobile data gets 403 Forbidden) all work. Still to check: hourly token renewal, and the first Docker image build and deploy.
|
|
||||||
|
|
||||||
## Auth rebuild and hosting (this session's last piece of work)
|
|
||||||
|
|
||||||
- **Sign-in:** Zoho web OAuth (`/auth/login` → Zoho consent → `/auth/callback`). It needs a new **Server-based Application** client in the Zoho API console, with redirect URI `PUBLIC_URL/auth/callback`; the old Non-Browser client is no longer used. Scopes: `SDPOnDemand.requests.READ` and `AaaServer.profile.READ` (for the name in the header).
|
|
||||||
- **Sessions:** the Zoho tokens live in an AES-256-GCM encrypted, `HttpOnly`, `SameSite=Lax` cookie (`Secure` over https), keyed by `SESSION_SECRET`. Each sign-in lasts `SESSION_DAYS` (default 7). The access token renews automatically; concurrent calls share one renewal. Sign out revokes the refresh token at Zoho. A rejected token, from Zoho or a 401 from SDP, sends the page back to the sign-in button.
|
|
||||||
- **Stateless server:** removed `boardService.js`, `tokenStore.js`, the device-code flow, `ADMIN_KEY` and `data/token.json` (phase 1 was superseded). The page runs the checks itself (`/api/closed` every minute, with 12 hours on first load; `/api/workload` every `WORKLOAD_MINUTES`) and keeps the closed feed. All `/api` routes, weather included, need a sign-in. `/healthz` is public for the Docker healthcheck.
|
|
||||||
- **Docker:** `Dockerfile` (node:24-alpine, runs as the `node` user, with a healthcheck) and `.dockerignore`. The example `docker-compose.yml` publishes **no ports**, because Docker's port publishing bypasses ufw and would skip NPM's access list. It joins NPM's network instead (`NPM_NETWORK`, default `npm_default`).
|
|
||||||
- **CI:** `.gitea/workflows/docker.yml` runs on `v*-prod*` tag pushes on the `self-hosted` runner. It logs in with the `DHUB_UNAME` and `DHUB_PASS` secrets, then builds and pushes `:<tag>` and `:latest`. Restarting on the VPS is manual.
|
|
||||||
- **Docs:** README (setup, deployment, NPM, signing in, security), `env.example`, `CLAUDE.md`, `docs/NOTES.md` (Zoho OAuth gotchas, decisions) and `docs/ROADMAP.md` (new architecture, plus the planned design for per-browser settings).
|
|
||||||
|
|
||||||
### Verified
|
|
||||||
|
|
||||||
- End to end against the real `server.js` over a unix socket, with axios stubbed: login redirect and state cookie; callback with a forged or missing state, a refused sign-in and a bad code; cookie flags and encryption; tokens never reach the page; a tampered cookie; three concurrent calls with an expired token renewing once; a revoked refresh token; a session past `SESSION_DAYS`; an SDP 401 signing the user out; sign-out revoking the token.
|
|
||||||
- The page script with stubbed DOM and fetch: the signed-out panel, `?signin=` messages, first-load backfill with no ding, a ding for a new close, and signing out mid-session.
|
|
||||||
- **Live, bare metal behind NPM (2026-10-15):** Zoho sign-in, SDP data, Sign out, and the access list blocking mobile data.
|
|
||||||
- **Not verified yet:** token renewal after an hour, the Docker build, the Gitea workflow, the production container behind `sdp-dashboard:3000`.
|
|
||||||
|
|
||||||
## Earlier the same day (dashboard work)
|
|
||||||
|
|
||||||
1. **Dashboard redesign** (`index.html`, `tickets.js`, `boardService.js`): stat tiles; a technician × status table with Total open and Closed today columns; the live closed feed in a side column; a "Sound on close" toggle, on by default; Refresh now. The table refreshes every `WORKLOAD_MINUTES` (default 5). The closed watcher still checks every minute.
|
|
||||||
2. **Central region scoping:** `SITE_NAMES` and `GROUP_NAMES` in `.env` are applied to every SDP query, and each ticket is also re-checked locally.
|
|
||||||
3. **Unassigned requests counter:** `UNASSIGNED_EXCLUDED_STATUSES` mirrors the ManageEngine "Central Region Unassigned" view. **Verified to match the view exactly.** The debug list is kept but hidden (`SHOW_UNASSIGNED_LIST = false` in `index.html`).
|
|
||||||
4. **Weather card** (`src/weather.js`, `/api/weather`): OpenWeatherMap current weather and 5-day forecast with icons, using `OWM_API_KEY` and `OWM_ZIP_CODE`, cached for 10 minutes. Working.
|
|
||||||
5. **Sign-in changes:** the Zoho page opens in a new tab, the button only appears once the device code exists, and the page checks quickly after Connect is clicked.
|
|
||||||
6. **Table layout:** totals moved to the left so they don't get cut off; the auth panel text is wider.
|
|
||||||
7. **SDP query fixes:**
|
|
||||||
- `greater_than` → `greater than`. The wrong spelling made SDP ignore all the criteria and return about 124k tickets.
|
|
||||||
- Paging now dedupes by ID and stops early when the total can't be reached.
|
|
||||||
- The "partial" note now names which query was cut off.
|
|
||||||
- **Verified fixed:** the "partial" note is gone.
|
|
||||||
8. **Small fixes:** `[hidden]` CSS bug (Refresh now showed when disconnected); a misplaced comment in `server.js`.
|
|
||||||
9. **README rewritten.** The screenshots it had then were removed after the auth rebuild; new ones are due.
|
|
||||||
10. **Credits:** page footer, README, `package.json` contributors, `server.js` header. Isaac Godin keeps the design credit.
|
|
||||||
11. **License:** MIT (LICENSE copyright Skylar Grant; package.json now says MIT, not ISC).
|
|
||||||
12. **Repo structure for working with Claude:** `CLAUDE.md`, `docs/ROADMAP.md`, `docs/NOTES.md`.
|
|
||||||
|
|
||||||
## Settings in `.env`
|
|
||||||
|
|
||||||
New: `PUBLIC_URL`, `SESSION_SECRET`, `SESSION_DAYS` (optional), `TRUST_PROXY` (optional). `CLIENT_ID` and `CLIENT_SECRET` must now come from the Server-based client. Gone: `ADMIN_KEY`, `TOKEN_FILE`. Unchanged: `SITE_NAMES`, `GROUP_NAMES`, `TECHNICIAN_NAMES`, `STATUS_NAMES`, `UNASSIGNED_EXCLUDED_STATUSES`, `WORKLOAD_MINUTES`, `OWM_API_KEY`, `OWM_ZIP_CODE`. All are documented in `src/env.example` and the README.
|
|
||||||
|
|
||||||
## Known issues
|
|
||||||
|
|
||||||
- On a typical screen the status table scrolls sideways ("Need Info" gets cut off), because the page is capped at 1,320px.
|
|
||||||
- No screenshots in the README for now (`images/` removed); Skylar is retaking them.
|
|
||||||
- The README's reference links for Zoho's device flow were removed; links for the server-based (web) flow haven't been added.
|
|
||||||
- Each open dashboard polls SDP with its own token. That's fine for a few viewers; watch SDP rate limits if wallboards multiply.
|
|
||||||
|
|
||||||
## Next steps
|
|
||||||
|
|
||||||
Already done: Zoho Server-based client, NPM proxy hosts (`dash` and `dash-test`) with the MCCS access list, Cloudflare DNS, a wildcard certificate, and the bare-metal test.
|
|
||||||
|
|
||||||
1. **Token renewal:** keep a tab open for more than an hour and confirm it stays signed in.
|
|
||||||
2. **First image:** check the `DHUB_UNAME` and `DHUB_PASS` secrets and the runner's `self-hosted` label, then push a `v*-prod*` tag and watch the workflow.
|
|
||||||
3. **Production container:** use a production `.env` with **no `PORT` line** and `PUBLIC_URL=https://` the production subdomain, check NPM's network name, then `docker compose up -d`. Confirm `docker ps` shows it healthy and the production subdomain signs in.
|
|
||||||
4. **Screenshots** for the README.
|
|
||||||
5. **Then:** per-browser settings, as designed in `docs/ROADMAP.md` (not started).
|
|
||||||
|
|
||||||
## To pick up
|
|
||||||
|
|
||||||
Start a Claude Code session in this repo and say something like:
|
|
||||||
|
|
||||||
*"Read SESSION.md and docs/ROADMAP.md. The per-user sign-in works live on bare metal; next is the first Docker deploy. Help me check the production container, then we'll plan the per-browser settings panel."*
|
|
||||||
129
docs/SECURITY.md
Normal file
129
docs/SECURITY.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# Security
|
||||||
|
|
||||||
|
How the help desk dashboard protects MCCS ServiceDesk Plus data: who can reach it, who can sign in, what each person can see, and what the server keeps. This describes version 2.1 (October 2026).
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- The site only answers MCCS network addresses.
|
||||||
|
- Every person signs in with their own Zoho account, and only accounts at MCCS email domains are let in.
|
||||||
|
- Every ServiceDesk Plus (SDP) query runs as the signed-in person, with read-only access. The dashboard can never show anyone more than SDP already lets them see.
|
||||||
|
- Zoho tokens never reach the web page and are never stored on the server.
|
||||||
|
- The server keeps no ticket data. The only things it stores are each person's dashboard preferences and a list of site, group and technician names.
|
||||||
|
|
||||||
|
## Layers of protection
|
||||||
|
|
||||||
|
A request has to pass every layer below, in order, before any MCCS data is involved.
|
||||||
|
|
||||||
|
### 1. Network: MCCS addresses only
|
||||||
|
|
||||||
|
- The dashboard runs on a VPS behind Nginx Proxy Manager (NPM). An NPM access list allows MCCS' public IP blocks (campuses and VPN) and refuses everything else with 403 Forbidden. This was tested live on 2026-10-15: an MCCS address got in, and a phone on mobile data was refused.
|
||||||
|
- The application's container **publishes no ports**. Docker's port publishing bypasses the host firewall, so a published port could skip NPM's access list. Instead, NPM reaches the container over a private Docker network.
|
||||||
|
- HTTPS is enforced by NPM ("Force SSL") with a valid certificate.
|
||||||
|
|
||||||
|
### 2. Sign-in: personal Zoho accounts at MCCS domains
|
||||||
|
|
||||||
|
- There is no shared account or service token. Every person signs in through Zoho's standard web sign-in (OAuth 2.0 authorization code flow). Their Zoho password and MFA are handled entirely by Zoho; the dashboard never sees them.
|
||||||
|
- **Domain restriction.** Any Zoho account holder in the world can complete Zoho's consent screen, so the dashboard checks the email address Zoho reports. A session is only created for emails at the domains in `ALLOWED_EMAIL_DOMAINS` (exact match, so lookalikes such as `evilmainecc.edu` or `mainecc.edu.example.com` are refused). Refused sign-ins have their Zoho token revoked immediately. The check is repeated on **every** request, not just at sign-in, so removing a domain cuts off existing sessions too. The server refuses to start if the setting is missing.
|
||||||
|
- **Sign-in forgery protection.** Each sign-in carries a one-time random `state` value tied to the browser that started it, compared in constant time. A sign-in started elsewhere, or older than 10 minutes, is rejected.
|
||||||
|
- **Sign-out** deletes the session and revokes the Zoho refresh token at Zoho, so it can't be reused.
|
||||||
|
|
||||||
|
### 3. Sessions: encrypted, browser-held, time-limited
|
||||||
|
|
||||||
|
- The session (the person's Zoho tokens, name and email) lives only in a browser cookie, encrypted and authenticated with AES-256-GCM using a server-side secret (`SESSION_SECRET`). A cookie that has been altered, truncated or made with a different secret is rejected.
|
||||||
|
- The cookie is `HttpOnly` (page scripts, including any injected ones, can't read it), `Secure` (sent over HTTPS only) and `SameSite=Lax` (not sent with cross-site form posts or background requests).
|
||||||
|
- Each sign-in expires after `SESSION_DAYS` (7 by default) no matter how active it is. Zoho access tokens last about an hour and are renewed automatically. If Zoho or SDP stops accepting a token (for example, the account was disabled or the access revoked), the person is signed out on their next request.
|
||||||
|
- The server keeps no session list, so there is no session database to steal.
|
||||||
|
|
||||||
|
### 4. Data access: read-only, as the signed-in person
|
||||||
|
|
||||||
|
- The only SDP permission the dashboard asks for is `SDPOnDemand.requests.READ`, plus Zoho's basic profile read (for the name and email). It can't create, change or delete anything in SDP.
|
||||||
|
- Every SDP query uses the signed-in person's own token. **SDP's own permissions decide what comes back.** The region and technician filters in the dashboard only narrow that down; they can never widen it. A person who changes their settings to another site sees only what SDP would already show them in ServiceDesk Plus itself.
|
||||||
|
- The site, group and technician name suggestions in Settings are shared between users. Before returning them, the server makes the caller's own token read from SDP. If SDP refuses that person, they get no names.
|
||||||
|
- API responses are marked `Cache-Control: no-store`, so browsers and proxies don't keep copies of ticket data.
|
||||||
|
|
||||||
|
### 5. The web page
|
||||||
|
|
||||||
|
- The page never receives Zoho tokens. It only receives ticket summaries (number, subject, technician, status and times) and counts.
|
||||||
|
- Everything shown on the page that came from SDP or from saved settings is HTML-escaped, so a ticket subject or a technician name can't inject script into the page.
|
||||||
|
- Response headers block other sites from framing the dashboard (`X-Frame-Options: DENY`, against clickjacking), stop browsers guessing file types (`X-Content-Type-Options: nosniff`), and keep the dashboard's address out of requests to other sites (`Referrer-Policy: same-origin`).
|
||||||
|
- Changing settings only accepts JSON requests, which a malicious site can't send cross-site with the person's cookie.
|
||||||
|
|
||||||
|
### 6. What the server stores
|
||||||
|
|
||||||
|
| Stored | Where | Contents |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Saved settings | `settings.json` in the data volume | For each person who saved settings: their email, the sites, groups and technicians they chose, refresh times, weather location, hidden panels. No tokens, no ticket data. |
|
||||||
|
| Name suggestions | `names.json` in the data volume | Site, group and technician names seen on recent tickets. No ticket data. |
|
||||||
|
| Server log | Docker's container log | Sign-ins and sign-outs (email and IP address), refused sign-ins, and SDP error messages. No tokens, no ticket contents. |
|
||||||
|
|
||||||
|
Ticket data passes through the server's memory while a request is answered and is then discarded. It is never written to disk.
|
||||||
|
|
||||||
|
Every value a person saves is checked by the server: names must be plain text of limited length, lists have size limits, refresh intervals have minimums (so a misconfigured dashboard can't hammer SDP), and weather locations must come from a fixed list of campuses.
|
||||||
|
|
||||||
|
### 7. Secrets
|
||||||
|
|
||||||
|
- The Zoho client secret, `SESSION_SECRET` and the OpenWeatherMap key live only in the `.env` file on the server. `.env` is excluded from the Git repository (`.gitignore`) and from the Docker image (`.dockerignore`), so the published image contains no secrets.
|
||||||
|
- Docker Hub credentials for the build pipeline are stored as Gitea repository secrets.
|
||||||
|
|
||||||
|
### 8. Container and hosting
|
||||||
|
|
||||||
|
- The container runs as an unprivileged user (`node`), not root, on a minimal Alpine Linux base image with production-only dependencies.
|
||||||
|
- The server runs with `NODE_ENV=production`, so error pages don't reveal stack traces.
|
||||||
|
- Images are built by a Gitea Actions workflow, only from version tags, and deployed to the VPS by hand.
|
||||||
|
|
||||||
|
### 9. Third parties
|
||||||
|
|
||||||
|
| Service | What it receives |
|
||||||
|
| --- | --- |
|
||||||
|
| Zoho (accounts.zoho.com, US) | Sign-in and token renewal. This is MCCS' existing identity provider for SDP. |
|
||||||
|
| ServiceDesk Plus (ithelp.mainecc.edu) | Read-only queries, made with the signed-in person's token. |
|
||||||
|
| OpenWeatherMap | A campus zip code, from the server. No MCCS data. Viewers' browsers load the weather icons directly. |
|
||||||
|
| Google Fonts | Viewers' browsers load the page fonts. No MCCS data. |
|
||||||
|
|
||||||
|
## Limits and assumptions
|
||||||
|
|
||||||
|
These are the boundaries of the protections above:
|
||||||
|
|
||||||
|
- **The domain check isn't a role check.** Anyone with a Zoho account at an allowed domain can sign in. What they then see is limited by their own SDP permissions. A non-technician would only see what SDP shows them, but they would see the default region's site, group and technician names. Checking that the person is an SDP technician at sign-in is the planned next step.
|
||||||
|
- **A signed-in screen is a signed-in screen.** A wallboard or unlocked computer shows whatever the signed-in person can see until the session expires or they sign out.
|
||||||
|
- **The protections assume** NPM's access list stays attached to the proxy host, `.env` stays private on the server, and staff Zoho accounts are themselves protected (strong passwords and MFA, enforced by Zoho).
|
||||||
|
|
||||||
|
## Open items to investigate
|
||||||
|
|
||||||
|
These haven't been checked yet. They are listed roughly by importance, and each should be marked done here, with the date and outcome, once it has been.
|
||||||
|
|
||||||
|
1. **Policy approval for where data is hosted.** Ticket subjects can name students or describe their situation, so they may be covered by FERPA. Ticket data passes through a VPS at RackNerd, administered by Skylar Grant rather than MCCS IT. Get written approval from MCCS IT security for the hosting arrangement and for who has administrative access to the server.
|
||||||
|
2. **What a non-technician sees.** Until the technician check is built, test with a non-IT staff account at an allowed domain and record exactly what the dashboard shows them.
|
||||||
|
3. **Who shares the allowed IP addresses.** Ask the network team what traffic leaves through the allowed address blocks. If campus or guest Wi-Fi does, the network layer protects less than it appears to, and sign-in becomes the main barrier.
|
||||||
|
4. **Paths around Nginx Proxy Manager.** Check that:
|
||||||
|
- the VPS's bare IP address and NPM's default site don't reach the dashboard
|
||||||
|
- no other proxy host forwards to `sdp-dashboard:3000`
|
||||||
|
- no untrusted containers share NPM's Docker network, from which the app can be called without the access list (sign-in would still be required)
|
||||||
|
5. **The build and deploy pipeline.** A compromised build could put a malicious image on the server, where it would run with the real secrets. Review:
|
||||||
|
- access to the self-hosted Gitea runner, which holds the Docker Hub credentials
|
||||||
|
- two-factor authentication on the Docker Hub account
|
||||||
|
- whether the image repository is public
|
||||||
|
- dependency version ranges in `package.json`
|
||||||
|
|
||||||
|
Consider deploying exact version tags instead of `latest`, and running `npm audit` before each release.
|
||||||
|
6. **Content-Security-Policy and HSTS.** HTML escaping is currently the only defense against injected script. A Content-Security-Policy that limits scripts to the dashboard itself, and allows only Google Fonts and OpenWeatherMap images from outside, would contain any mistake. NPM can also send an HSTS header.
|
||||||
|
7. **SDP API rate limits.** Find out whether Zoho limits SDP API calls per user or per organization. If the limit is per organization, many open dashboards, or one misbehaving one, could use up the allowance that other MCCS integrations depend on.
|
||||||
|
8. **Wallboard sessions.** A wallboard signed in as a person keeps that person's access for up to 7 days. Consider a dedicated account with limited SDP permissions for wallboards, or a shorter `SESSION_DAYS`.
|
||||||
|
9. **Hardening the VPS.** Review:
|
||||||
|
- SSH: key-only sign-in and no root login
|
||||||
|
- operating system patching
|
||||||
|
- `.env` file permissions (`chmod 600`)
|
||||||
|
- whether backups or snapshots copy `.env` or the data volume elsewhere
|
||||||
|
- how long Docker keeps container logs, which contain staff email and IP addresses
|
||||||
|
10. **Ownership of the Zoho client and secrets.** Record which Zoho account owns the API client, and what happens if that person leaves. Write down how to rotate `CLIENT_SECRET` and `SESSION_SECRET`. Changing `SESSION_SECRET` immediately signs everyone out, which serves as an emergency switch.
|
||||||
|
11. **Email verification in Zoho.** The domain check trusts the email address Zoho reports. Confirm that Zoho won't accept an unverified address as an account's primary email, or that MCCS has verified its domains in Zoho, which settles the question.
|
||||||
|
12. **Independent review and automated tests.** So far, testing has been one live run plus simulated tests written by the developers. A short review by MCCS IT, and automated tests run on every release, would give independent assurance.
|
||||||
|
|
||||||
|
## How this was checked
|
||||||
|
|
||||||
|
- Live, on 2026-10-15: sign-in, sign-out, live data, and the IP access list (an MCCS address allowed; mobile data refused with 403).
|
||||||
|
- With SDP and Zoho simulated: forged and missing sign-in state, refused and failed sign-ins, tampered cookies, expired sessions, revoked tokens, SDP rejecting a token, tokens never appearing in page responses, settings validation, outside and lookalike email domains, older cookies from outside accounts, and name suggestions refused when SDP refuses the caller.
|
||||||
|
|
||||||
|
## Reporting a problem
|
||||||
|
|
||||||
|
Contact Skylar Grant. Please don't post security problems in public issue trackers or chat channels.
|
||||||
97
docs/SESSION.md
Normal file
97
docs/SESSION.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# Session summary: 2026-10-15 (v2.1.0)
|
||||||
|
|
||||||
|
A Claude Code session that added per-user settings, closed a sign-in gap, and wrote the security document. Background is in `CLAUDE.md`, `docs/ROADMAP.md`, `docs/NOTES.md` and `docs/SECURITY.md`.
|
||||||
|
|
||||||
|
## Where things stand
|
||||||
|
|
||||||
|
- v2.0.0 (per-user Zoho sign-in, Docker) is running in production.
|
||||||
|
- Branch `v2.1.0` is pushed with four commits on top of v2.0.0, and tagged **`v2.1.0-prod-hotfix`** (`2f137af`). The tag starts the Gitea image build. **The build and the production deploy haven't been confirmed.**
|
||||||
|
- Since that tag, these changes are **not committed**: `SECURITY.md` and `SESSION.md` moved into `docs/`, the open items section in `docs/SECURITY.md`, and the doc links in `CLAUDE.md` and the README.
|
||||||
|
|
||||||
|
### Before deploying v2.1.0 on the VPS
|
||||||
|
|
||||||
|
1. Add `ALLOWED_EMAIL_DOMAINS=mainecc.edu` (plus any campus domains staff use) to the production `.env`. **The server won't start without it.**
|
||||||
|
2. Add the `sdp-data:/app/data` volume to the compose file (on the service, plus the top-level `volumes:`), as in the repo's `docker-compose.yml`. Without it, saved settings are lost each time the container restarts.
|
||||||
|
3. `docker compose pull && docker compose up -d`, then check:
|
||||||
|
- an MCCS account signs in and gets the setup prompt
|
||||||
|
- a personal Zoho account is sent back with "not allowed"
|
||||||
|
|
||||||
|
## What this session did
|
||||||
|
|
||||||
|
### Per-user settings (commits `9479b12`, `53c519e`, `817fa47`)
|
||||||
|
|
||||||
|
- **⚙ Settings** button, and a setup prompt on someone's first visit (Save, or Use the defaults).
|
||||||
|
- Settings per user:
|
||||||
|
- sites, groups and technicians, entered as tags with name suggestions; typing any name still works
|
||||||
|
- how often the status table recounts (2 to 60 minutes) and how long the feed lasts (1 to 24 hours)
|
||||||
|
- weather location, from the campus list in `src/places.js`
|
||||||
|
- hidden panels (stat tiles, weather, feed) and hidden status columns
|
||||||
|
|
||||||
|
The ding plays even with the feed hidden. Statuses and sound aren't per user.
|
||||||
|
- **Storage:** on the server in `DATA_DIR/settings.json`, keyed by the lowercased Zoho email, so settings follow people between browsers. Only values that differ from `.env` are stored. This replaced the planned `localStorage` design, and the container now needs a volume (`/app/data` in Docker).
|
||||||
|
- **Name suggestions** (`/api/names`, `src/names.js`): one region-wide query of recent tickets (30 days, up to 500 tickets, names only). It's refreshed at most hourly, shared by all users, and kept in `DATA_DIR/names.json`. No new Zoho scope was needed. A typed name that differs from SDP's only in case is saved with SDP's spelling.
|
||||||
|
- **Server side:**
|
||||||
|
- The server reads each user's settings itself; the page doesn't send them with each call.
|
||||||
|
- `PUT` and `DELETE /api/settings` validate everything (`LIMITS` in `src/settings.js`).
|
||||||
|
- Weather is cached per zip.
|
||||||
|
- The closed feed's hard-coded 12-hour cut-off was removed, since the feed length is now a setting.
|
||||||
|
|
||||||
|
### Sign-in limited to email domains (commit `2f137af`)
|
||||||
|
|
||||||
|
- **The gap:** any Zoho account can approve our client. An outside account got a session, and `/api/session` gave it the `.env` site, group and technician names. **Production v2.0.0 still has this gap until v2.1.0 is deployed.**
|
||||||
|
- **The fix:** `ALLOWED_EMAIL_DOMAINS` is required.
|
||||||
|
- The callback refuses other domains and revokes their Zoho token.
|
||||||
|
- `requireSession` checks the domain again on every call, so older cookies stop working.
|
||||||
|
- Matching is exact, so lookalike domains are refused.
|
||||||
|
- `/api/names` now makes the caller's own token read one row from SDP before it returns the shared names.
|
||||||
|
|
||||||
|
### Documents
|
||||||
|
|
||||||
|
- `docs/SECURITY.md`, written for MCCS IT leadership and reviewers. It covers each layer of protection, what is stored, third parties, the limits, **12 open items to investigate**, and how everything was tested.
|
||||||
|
- The README, `env.example`, `NOTES.md` (decisions) and `ROADMAP.md` were updated as each piece landed.
|
||||||
|
|
||||||
|
### Verified
|
||||||
|
|
||||||
|
- **Server, end to end** over a unix socket with axios stubbed:
|
||||||
|
- defaults and the first-visit state
|
||||||
|
- name suggestions: the region-wide query, the cache, and the access check before the cache is served
|
||||||
|
- validation of every field, non-JSON bodies, and malformed JSON
|
||||||
|
- saving: only values that differ from the defaults are stored, and names take SDP's spelling
|
||||||
|
- the closed and workload queries use the saved region and feed length
|
||||||
|
- weather per zip
|
||||||
|
- reset, and saved settings surviving a restart
|
||||||
|
- the domain check: outside, lookalike and missing emails, a second allowed domain, an outsider's pre-existing cookie, and startup without the setting
|
||||||
|
- **Page, in jsdom** with stubbed fetch:
|
||||||
|
- the setup prompt, and suggestions leaving out names already chosen
|
||||||
|
- tag adding and removal, Enter, Backspace, and a typed but un-added name counting on save
|
||||||
|
- a server error shown inside the dialog, and the save body
|
||||||
|
- after a save: the feed restarting, the recount, the weather reloading, and hidden panels and columns
|
||||||
|
- reset, and the full-width layout
|
||||||
|
- **Not verified:**
|
||||||
|
- anything against real SDP or Zoho
|
||||||
|
- how the dialog looks; there's no Chrome or Edge on the dev Mac for a screenshot
|
||||||
|
- the Gitea build of the hotfix tag
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
1. **Remove ticket subjects (first thing tomorrow).** Ticket subjects can name students or describe their situation, which is a FERPA risk. They reach the page in two places:
|
||||||
|
- **Closed feed:** `fetchClosedTickets` in `src/tickets.js` requests `subject` and returns it. The page shows it in `renderTicket` in `src/public/index.html`.
|
||||||
|
- **Unassigned list:** `fetchUnassigned` requests `subject` and returns it. The page shows it in `renderUnassignedList`, the hidden debug list.
|
||||||
|
|
||||||
|
Drop `subject` from `fields_required` and from the returned objects, so it never leaves SDP, and update both render functions. The server never writes subjects to disk; they only pass through memory and the page. Afterwards, update `docs/SECURITY.md` (the page section, and open item 1) and `docs/NOTES.md`.
|
||||||
|
2. **Technician check at sign-in** (the ROADMAP item). Ask SDP whether the signed-in user is a technician. That probably needs a users or technicians read scope; confirm the exact scope name and endpoint in Zoho's docs. A new scope means everyone signs in again. It would also allow real pick lists for technicians.
|
||||||
|
3. **Commit** the document moves and the open items.
|
||||||
|
4. **Deploy v2.1.0**, following the checklist above. Then try it live: the quality of the name suggestions, the setup prompt, and the volume surviving a `pull` and `up -d`.
|
||||||
|
5. **Work through the open items in `docs/SECURITY.md`**, starting with MCCS IT approval for the hosting and the non-technician test.
|
||||||
|
6. **Carried over from earlier sessions:**
|
||||||
|
- check that the token renews after an hour
|
||||||
|
- README screenshots
|
||||||
|
- links to Zoho's documentation for the web sign-in flow
|
||||||
|
- the status table scrolling sideways (see `NOTES.md`)
|
||||||
|
- fill in campus satellites and confirm the zip codes in `src/places.js`
|
||||||
|
|
||||||
|
## To pick up
|
||||||
|
|
||||||
|
Start a Claude Code session in this repo and say something like:
|
||||||
|
|
||||||
|
> Read docs/SESSION.md and docs/SECURITY.md. First, remove ticket subjects from everything the server requests and the page shows (FERPA). Then we'll add the SDP technician check at sign-in.
|
||||||
Loading…
Reference in New Issue
Block a user