Completes securing the web front end (vrek gol-wqf95dq, iss-r5vrjx7). - /login: Server Action with a generic error, same-site-only redirect back to ?next=, and throttling of failed logins (10 per address and 100 overall per 15 min). The header shows "Signed in as" with Sign out (iss-nj9wmwp). - First run with no admin: instrumentation prints a one-time setup code, shared with the app through globalThis. /setup requires it, and 5 wrong codes rotate it. "Skip for now" runs unsecured for the browser session behind a red warning banner on every page (iss-9nxdndr). - src/proxy.ts: optimistic redirects to /login or /setup, 401 for the API, and the 12 h sliding session refresh. requirePageAccess() and apiAccessDenied() re-check in the page and all 6 route handlers (iss-76d5wrb). - An expired session now shows "Your session has ended" instead of the camera-login form. - README documents in-app setup, skipping and signing in. Verified with unit tests (383, 99.9% line coverage), end to end against `next start`, and manually in a browser by the user. Refreshes the vrek export. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
139 lines
5.2 KiB
Markdown
139 lines
5.2 KiB
Markdown
# Cameras
|
||
|
||
A web interface for discovering and managing Hikvision and Annke IP cameras on your local network.
|
||
|
||
## Securing the web interface
|
||
|
||
The dashboard and its API are protected by a single admin login. The login is stored in a
|
||
file, **never** in plain text: the password is hashed with [scrypt](https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback)
|
||
(random salt, N=65536, r=8, p=1), so it can't be recovered from the file.
|
||
|
||
| Setting | Default |
|
||
| --- | --- |
|
||
| `ADMIN_AUTH_FILE` (environment variable, e.g. in `.env.local`) | `./.data/admin.json` |
|
||
|
||
If the file doesn't exist, the app starts **unsecured**: anyone who can reach it can view your
|
||
cameras and change their logins. To avoid ever running it that way, create the admin file
|
||
before the first start (below).
|
||
|
||
### Setting it up from the browser instead
|
||
|
||
With no admin file, the server prints a **one-time setup code** in its console at startup:
|
||
|
||
```
|
||
====================================================================
|
||
No admin login is set up: the camera dashboard is NOT secured.
|
||
|
||
To secure it from a browser, open /setup and enter this one-time code:
|
||
|
||
XLZ7-Q4MB
|
||
...
|
||
```
|
||
|
||
Opening the app sends you to `/setup`, which asks for that code plus the new admin username
|
||
and password. Only someone who can see the server console can claim the login. After 5 wrong
|
||
codes a new one is printed.
|
||
|
||
You can also choose **Skip for now and run unsecured**. Every page then shows a red warning
|
||
banner, the API stays open to anyone on the network, and the setup prompt returns the next
|
||
time the browser is restarted.
|
||
|
||
### Signing in
|
||
|
||
Once an admin exists, every page and API call needs a session: pages redirect to `/login`,
|
||
and the API answers `401`. A session lasts **12 hours from your last activity**. After 10
|
||
wrong passwords from one address (or 100 from all addresses) within 15 minutes, logins are
|
||
paused for up to 15 minutes. **Sign out** (top right) ends the session in that browser; to
|
||
sign out everywhere, change the password.
|
||
|
||
### Create the admin login (recommended)
|
||
|
||
```bash
|
||
npm run admin:create
|
||
```
|
||
|
||
It asks for a username and a password (at least 12 characters; typing is hidden, and you
|
||
confirm it), then writes the file with owner-only permissions (`0600`). Options:
|
||
|
||
```bash
|
||
npm run admin:create -- --username admin # only ask for the password
|
||
npm run admin:create -- --force # replace the existing admin (reset the password)
|
||
printf '%s\n' "$PW" | npm run admin:create -- --username admin # non-interactive, e.g. provisioning
|
||
```
|
||
|
||
### Without npm: a Node one-liner
|
||
|
||
Any Node 24+ can produce the same file. The password is read from the terminal without
|
||
echoing, so it never appears in your shell history or the process list:
|
||
|
||
```bash
|
||
(read -rs PW && export PW && umask 077 && mkdir -p .data && node -e '
|
||
const c = require("node:crypto"), N = 65536, r = 8, p = 1, salt = c.randomBytes(16);
|
||
const key = c.scryptSync(process.env.PW, salt, 64, { N, r, p, maxmem: 256 * N * r });
|
||
const passwordHash = ["scrypt", N, r, p, salt.toString("base64"), key.toString("base64")].join("$");
|
||
console.log(JSON.stringify({ version: 1, username: "admin", passwordHash }, null, 2));
|
||
' > .data/admin.json)
|
||
```
|
||
|
||
The parentheses run it in a subshell, so the password variable and the stricter `umask` end
|
||
with it.
|
||
|
||
Change `username: "admin"` to taste. Usernames are 1–64 letters, digits, or `. _ @ -`.
|
||
|
||
### File format
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"username": "admin",
|
||
"passwordHash": "scrypt$65536$8$1$<salt, base64>$<64-byte key, base64>"
|
||
}
|
||
```
|
||
|
||
A file that exists but is malformed stops the app from authenticating anyone rather than
|
||
quietly turning security off; fix it or delete it.
|
||
|
||
### Forgot the password?
|
||
|
||
Run `npm run admin:create -- --force` (or delete the file and create it again). Changing the
|
||
password signs out every existing session.
|
||
|
||
---
|
||
|
||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||
|
||
## Getting Started
|
||
|
||
First, run the development server:
|
||
|
||
```bash
|
||
npm run dev
|
||
# or
|
||
yarn dev
|
||
# or
|
||
pnpm dev
|
||
# or
|
||
bun dev
|
||
```
|
||
|
||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||
|
||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||
|
||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||
|
||
## Learn More
|
||
|
||
To learn more about Next.js, take a look at the following resources:
|
||
|
||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||
|
||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||
|
||
## Deploy on Vercel
|
||
|
||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||
|
||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|