Fix admin authorization and harden chat requests
This commit is contained in:
@@ -6,18 +6,51 @@ A space for every conversation. Six appearance styles (Grove, Parchment, Aurora,
|
||||
|
||||
## Setup
|
||||
|
||||
Node.js 22 or newer is required.
|
||||
Node.js 22.13 or newer is required.
|
||||
|
||||
1. Install dependencies with `npm install`.
|
||||
Native dependencies also need a working build toolchain. On Windows, install Python
|
||||
and Visual Studio's **Desktop development with C++** workload before `npm ci`.
|
||||
On Linux, install Python, `make` and a C++ compiler; on macOS, use Xcode Command Line
|
||||
Tools. See the [node-gyp prerequisites](https://github.com/nodejs/node-gyp#installation).
|
||||
|
||||
1. Install the locked dependencies with `npm ci`.
|
||||
2. Copy `.env.example` to `.env` and set at least `SESSION_SECRET`.
|
||||
3. Start the app with `npm run dev` for local development or `npm start` for a normal server run.
|
||||
|
||||
Both startup scripts load `.env` when present; environment variables already set by the
|
||||
host take precedence. `.env`, databases, uploads and session files are ignored by Git.
|
||||
Use `npm install` when deliberately updating dependencies and include both
|
||||
`package.json` and `package-lock.json` in the change. CI runs `npm ci`, lint and tests
|
||||
on Node 22 and 24.
|
||||
|
||||
## Scripts
|
||||
|
||||
- `npm start`: run the production server entrypoint.
|
||||
- `npm run dev`: run the server with Node watch mode.
|
||||
- `npm run lint`: syntax-check the backend and browser modules.
|
||||
- `npm test`: run API tests with Node's built-in test runner.
|
||||
- `npm run user:role -- <username> <user|admin>`: assign or revoke a role from the server console.
|
||||
|
||||
## Administrative access
|
||||
|
||||
Accounts have a server-managed `users.role`, constrained to `user` or `admin`. Registration
|
||||
and migrations default to `user`, including accounts named `admin`. Usernames, profile
|
||||
updates and workspace imports never grant roles. Existing installations must explicitly
|
||||
promote their intended administrator after deploying this migration:
|
||||
|
||||
```sh
|
||||
npm run user:role -- your-existing-username admin
|
||||
```
|
||||
|
||||
Run this on the server with the application's `DB_PATH` and environment. To revoke
|
||||
access, run the same command with `user`. Authorization reads the database role on each
|
||||
request, so promotion and revocation apply to existing sessions; renaming retains the role.
|
||||
There is no public role assignment endpoint.
|
||||
|
||||
Async route handlers use `asyncHandler` to forward rejected promises to Express 4's
|
||||
error middleware, following [Express's error handling guidance](https://expressjs.com/en/4x/guide/error-handling/).
|
||||
Duplicate registrations, including concurrent inserts, return HTTP 409 with
|
||||
`code: "USERNAME_TAKEN"`.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
@@ -196,7 +229,7 @@ The server is configured around an Ollama-compatible API surface:
|
||||
|
||||
`qwen3:14b` is the default model for chat and roleplay. The model selector loads available models from `/tags` and selects `qwen3:14b` when available (otherwise the first returned model). Existing chats retain their saved model selection.
|
||||
|
||||
The composer has independent Web Search and Reasoning toggles, both off by default and reset on server acceptance. It sends `{message, chatId, model, webSearch, reasoning}` using streamed fetch to `POST /chat/stream`. Existing SSE events carry `status` (accepted/searching/generating), `thinking` (chunk/fullThinking), `chunk` (chunk/fullReply), `done` (reply/sources), and `error`. Final answers stream incrementally, including persona responses. Persona validation still runs; a corrective pass resets provisional output before streaming its replacement. Only the validated complete answer is persisted.
|
||||
The composer has independent Web Search and Reasoning toggles, both off by default and reset after a completed reply. It sends `{message, chatId, model, webSearch, reasoning}` using streamed fetch to `POST /chat/stream`. SSE events carry `status` (accepted/analyzing/searching/generating), `thinking` (`{chunk}`), `chunk` (`{chunk}`), `replace` (`{target: "reply"|"thinking", text}`), `done` (reply/sources), and `error` (error/status/code/requestId when available). Text events send only incremental text. A corrective persona pass explicitly replaces provisional output before streaming its replacement. Both sends and regenerations batch Markdown rendering per animation frame. Only the validated complete answer is persisted. Clients consuming SSE must handle `replace` events and accumulate deltas; the browser also accepts the older accumulated event format.
|
||||
|
||||
Every Ollama request explicitly sets `stream: true`, `think`, and `keep_alive`. Reasoning is requested and forwarded only when `reasoning === true` and the model supports the boolean thinking API. The shared conservative capability check supports Qwen3, DeepSeek R1 and DeepSeek v3.1 model IDs; unknown models, Qwen coder variants and models requiring thinking levels have the toggle disabled. Internal calls, roleplay openers and regeneration default to `think: false`. There is no extra model call to obtain reasoning.
|
||||
|
||||
@@ -230,7 +263,12 @@ Legacy `chats` migration is handled during startup. New code only reads the sess
|
||||
|
||||
The browser UI remains framework-free and uses native ES modules. Shared behavior lives under `public/js/`:
|
||||
|
||||
- `api.js`: JSON and streaming request helpers
|
||||
- `api.js`: JSON and streaming request helpers with HTTP metadata, cancellation and deadlines
|
||||
- `conversationState.js` / `conversationController.js`: shared state, chat selection, creation, search and drafts
|
||||
- `messageRenderer.js`: sanitized Markdown, message cards, actions and scroll position
|
||||
- `composerController.js`: immediate send locking, captured chat/model, Stop and send recovery
|
||||
- `personaController.js`: persona forms, library actions and roleplay setup
|
||||
- `streamRenderer.js`: delta accumulation, replacement events and batched rendering
|
||||
- `constants.js`: theme and onboarding metadata
|
||||
- `themeController.js`: validated theme/mode persistence and application
|
||||
- `theme-bootstrap.js`: applies stored appearance before page initialization
|
||||
@@ -238,6 +276,21 @@ The browser UI remains framework-free and uses native ES modules. Shared behavio
|
||||
- `statusNotice.js`: shared status and error presentation
|
||||
- `dom.js`: cached chat-page DOM references
|
||||
|
||||
JSON helpers retain the existing `{error}` result convention and add `status`, `code`
|
||||
and `requestId` on failure. Successful empty responses return `{}`. Pass
|
||||
`{signal, timeoutMs}` as the last argument to `get`, `post`, `put`, `del`, or `request`;
|
||||
the default deadline is 30 seconds. `stream` accepts the same options (or the legacy
|
||||
AbortSignal argument), defaults to 660 seconds, and rejects with `ApiError` unless
|
||||
`onError` handles the failure. A `timeoutMs` of 0 disables the deadline. Cancellation,
|
||||
timeout, network and malformed-response failures have distinct codes. HTTP errors
|
||||
keep their status even if the response is HTML or invalid JSON.
|
||||
|
||||
DOMPurify is an npm production dependency pinned in the lockfile and served only at
|
||||
`/vendor/dompurify.js`; `node_modules` is not a static directory. The former CDN version
|
||||
3.0.6 was affected by the maintainer's [mXSS advisory](https://github.com/cure53/DOMPurify/security/advisories/GHSA-gx9m-whjm-85jf).
|
||||
The installed version is 3.4.16. Update it through npm so audits and reproducible installs
|
||||
also cover the browser sanitizer.
|
||||
|
||||
`public/css/style.css` is a self-contained design system rebuilt around semantic tokens, shared components, and deliberate responsive breakpoints. It contains the six visual identities and their dark variants without legacy imports or precedence layers. `public/css/legal.css` contains only the legal-page layout.
|
||||
|
||||
Pure chat message, retry, memory, summary, and persona-state formatting lives in `src/services/chatMessageHelpers.js`; `chatService.js` remains responsible for orchestration and persistence.
|
||||
@@ -250,7 +303,7 @@ Search uses `shouldSearch = webSearchEnabled && (explicitSearchIntent || automat
|
||||
|
||||
The chat service retrieves up to 15 candidates per query and selects 1–5 useful sources dynamically, rejecting empty evidence and wrong-date results. SearXNG `content` maps to `snippet` (an existing non-empty snippet is also accepted). Exact-date event searches use the general index to include archives and try at most one alternate query if the first returns no usable evidence; candidates are merged and deduplicated. Current-news requests retain the news category. The same numbered selected titles, URLs and snippets go immediately before the current question in Ollama's context, into the response, and into storage. Retrieval finishes before either reasoning or answer streaming starts. Instructions require citations, supported partial answers rather than blanket claims of missing details, and honest disclosure when evidence is insufficient. Result content remains untrusted data; no result pages are fetched.
|
||||
|
||||
Set environment variables in the server/container environment, or load the example explicitly with `node --env-file=.env server.js` (the existing npm start command does not load `.env` automatically):
|
||||
Set environment variables in the server/container environment, or in `.env` (loaded by `npm start` and `npm run dev`):
|
||||
|
||||
```dotenv
|
||||
SEARXNG_URL=http://searxng:8080
|
||||
|
||||
Reference in New Issue
Block a user