This commit is contained in:
+106
@@ -0,0 +1,106 @@
|
||||
# computer — web UI
|
||||
|
||||
This app runs inside the root Compose stack. The root [../README.md](../README.md)
|
||||
is the single authoritative deployment/operations guide; [../API.md](../API.md)
|
||||
documents the Go API contract. This file covers only what a frontend
|
||||
developer needs.
|
||||
|
||||
Ownership boundary: the Go API owns persistence, previews, integrations and
|
||||
authentication semantics; this Next.js server owns browser sessions
|
||||
(NextAuth), the trusted API bridge (`lib/api.ts` signs short-lived
|
||||
assertions — the browser never supplies a trusted identity), and rendering.
|
||||
Data requests go through `app/api/[...path]/route.ts`, not local Prisma
|
||||
routes; the pre-port Prisma layer is gone. Schema and seeding live in the Go
|
||||
stack (`../db/schema.sql`, `../db/migrations/`, `make seed`).
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 20.9 or newer
|
||||
- Yarn (Corepack: `corepack enable`)
|
||||
- A running Go API (`API_BASE_URL` — server-only Docker DNS, e.g.
|
||||
`http://app:8080`); the browser uses relative `/api/*` URLs
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
cp .env.example .env # fill in AUTH_SECRET, NEXTAUTH_URL, API_BASE_URL
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
yarn dev # development: http://localhost:3000
|
||||
yarn build && yarn start # production
|
||||
```
|
||||
|
||||
Production deployments run behind the Compose stack and the nginx template
|
||||
in `../deploy/nginx/`. The Content-Security-Policy is emitted per response
|
||||
by `middleware.ts` (nonce-based, so Next's inline bootstrap scripts run);
|
||||
do not add a second `script-src` policy at the proxy — browsers intersect
|
||||
multiple CSPs and the nonce would be stripped.
|
||||
|
||||
## Authentication
|
||||
|
||||
- **Email and password** work out of the box. Sign up at `/signup`. Signup
|
||||
responses are uniform (`{ok:true}`) for fresh, duplicate and reserved
|
||||
addresses — account existence is never disclosed.
|
||||
- **Authelia OIDC is fail-closed.** Setting `OIDC_ISSUER`,
|
||||
`OIDC_CLIENT_ID` and `OIDC_CLIENT_SECRET` only makes the provider appear
|
||||
in configuration; the Go login/callback endpoints deliberately answer
|
||||
**501** until verified external identities are mapped to local users with
|
||||
explicit account-linking rules, and `auth.ts` rejects Authelia sign-ins
|
||||
until then. Configured variables are NOT working sign-in. See
|
||||
[docs/AUTHELIA.md](docs/AUTHELIA.md) and the root `AGENTS.md` boundary.
|
||||
- Sign-out means **sign out everywhere**: the UI first calls
|
||||
`/api/session/revoke` (Go tokenVersion bump) and reports honestly when
|
||||
global revocation fails; the NextAuth `signOut` event is the backstop.
|
||||
Don't remove either path.
|
||||
|
||||
## Encryption and key retention (operator-relevant)
|
||||
|
||||
IMAP mailbox passwords are encrypted with AES-256-GCM. The key derives from
|
||||
the dedicated `ENCRYPTION_KEY` when set, otherwise from the auth secret
|
||||
(`NEXTAUTH_SECRET`/`AUTH_SECRET`). Rows encrypted before `ENCRYPTION_KEY`
|
||||
existed decrypt via the auth-secret fallback and re-encrypt transparently
|
||||
(compare-and-swap — a concurrent password change always wins). Consequences:
|
||||
|
||||
- Back up `ENCRYPTION_KEY`/`AUTH_SECRET` **with** the database; without the
|
||||
matching keys, stored mailbox credentials are unrecoverable.
|
||||
- Rotating an already-dedicated `ENCRYPTION_KEY` needs old-key-aware
|
||||
migration, not just a changed environment variable. Keep the old secret
|
||||
until every credential row has been re-encrypted and verified.
|
||||
|
||||
## Project layout
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| `app/(app)/` | Signed-in screens: pages (primary workspace), bookmark filters (all, calendar, tags, contacts, kanban), collections, settings; `/dashboard` redirects to `/bookmarks` |
|
||||
| `app/api/` | Next routes only: the Go proxy (`[...path]`), NextAuth (`auth/*`), session revoke; everything else is Go's |
|
||||
| `app/share/bookmark/[id]` | Public read-only bookmark page |
|
||||
| `components/` | UI components |
|
||||
| `lib/` | Server-side API bridge (`api.ts`), OIDC config, autosave/pagination helpers |
|
||||
| `middleware.ts` | Per-response nonce CSP (review CR-10) |
|
||||
| `auth.ts` | Auth.js configuration |
|
||||
| `docs/` | Integration docs |
|
||||
|
||||
## Notes
|
||||
|
||||
- URL previews are fetched **server-side by the Go API**
|
||||
(`internal/preview`, guarded by `internal/netguard` and a shared
|
||||
per-user/global budget) when a bookmark is saved or its URL changes —
|
||||
not by this app and not by `open-graph-scraper`.
|
||||
- Date alerts are polled by the browser every 5 minutes and shown as
|
||||
browser notifications; email signal ingestion is Go-side (IMAP).
|
||||
|
||||
## Admins
|
||||
|
||||
Administrator provisioning is operator-driven: list addresses in
|
||||
`ADMIN_EMAILS` and run `make seed` (it never changes existing accounts).
|
||||
As a bootstrap convenience the FIRST signup on a fresh installation may be
|
||||
promoted once (AppSetting `adminBootstrap`, disable with
|
||||
`ALLOW_BOOTSTRAP_SIGNUP=0`); after bootstrap completes, signup never
|
||||
auto-promotes again — even with no admins left. Promotion of an existing
|
||||
allowlisted user requires `emailVerified`; DB roles remain authoritative.
|
||||
Admins can open any account via Admin → "View as" (mutations are blocked
|
||||
while impersonating).
|
||||
Reference in New Issue
Block a user