Files
sencho/frontend
Anso 2435da232b fix(api-tokens): harden rate limiting and surface list-load errors (#1292)
* fix(api-tokens): scope per-token rate limits to live tokens

Forged or token-shaped Authorization headers no longer mint their own
rate-limit budget. The key generator now grants a per-token budget only to
a real, active token and falls back to per-IP keying for anything else, so
a single source cannot evade the global limiter by rotating fake tokens.
The validated token is memoized on the request, so authentication reuses
it without a second database lookup.

Token validation (format, checksum, lookup, revocation, expiry) is now a
single shared helper used by the HTTP auth middleware, the WebSocket
upgrade handler, and the rate-limit key generator, replacing two
near-identical inline copies that could drift apart. The last-used
timestamp write is throttled so a busy token no longer writes to the
database on every request.

* fix(api-tokens): surface token list-load failures with a retry

A failed load of the API tokens list was swallowed: a server error
rendered the empty "no tokens yet" state with no sign that anything went
wrong. The list now shows an error card with a Retry action and raises a
toast on any non-ok response or network error, matching the create and
revoke flows. Adds a troubleshooting entry for the error.

* test(api-tokens): seed tokens via the shared test helper

The new hardening and WS-scope suites computed sha256 of a raw token
directly, which CodeQL flags as js/insufficient-password-hash (a false
positive: these are 256-bit CSPRNG opaque tokens, not passwords). Route
token creation through the existing apiTokenTestHelper and read the stored
token_hash back from the row, so the suites no longer hash anything
themselves. Also removes the duplicated createToken helpers.

* fix(api-tokens): key the rate limiter by the same credential auth uses

The rate-limit key generator checked the session cookie before the
Authorization bearer, while authMiddleware authenticates bearer-over-cookie
(bearerToken || cookieToken). A request could send a Bearer API token plus a
forged cookie and be keyed by the cookie's (forgeable, rotatable) username,
sidestepping the per-token / per-IP keying the limiter applies to API tokens:
a valid token would lose its own bucket, and a forged token-shaped bearer
would no longer collapse to per-IP.

Reorder the generator to mirror auth: process the bearer first (validate the
API token and key per-token or fall back to per-IP; otherwise decode the JWT
by username/sub), and consult the cookie only when there is no bearer.
Regression tests cover a valid and a forged sen_sk_ bearer, each sent with a
forged cookie.
2026-06-03 08:18:16 -04:00
..

React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see this documentation.

Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      // Other configs...

      // Remove tseslint.configs.recommended and replace with this
      tseslint.configs.recommendedTypeChecked,
      // Alternatively, use this for stricter rules
      tseslint.configs.strictTypeChecked,
      // Optionally, add this for stylistic rules
      tseslint.configs.stylisticTypeChecked,

      // Other configs...
    ],
    languageOptions: {
      parserOptions: {
        project: ['./tsconfig.node.json', './tsconfig.app.json'],
        tsconfigRootDir: import.meta.dirname,
      },
      // other options...
    },
  },
])

You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:

// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      // Other configs...
      // Enable lint rules for React
      reactX.configs['recommended-typescript'],
      // Enable lint rules for React DOM
      reactDom.configs.recommended,
    ],
    languageOptions: {
      parserOptions: {
        project: ['./tsconfig.node.json', './tsconfig.app.json'],
        tsconfigRootDir: import.meta.dirname,
      },
      // other options...
    },
  },
])