caspian-utils 0.1.26 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,7 +62,8 @@ Use prompts like these to check whether AI lands on the correct docs and files.
62
62
  | Trace the auth cookie and CSRF cookie names used in development. | [index.md](./index.md), [auth.md](./auth.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/auth.py` | development cookie scoping and CSRF or session naming ownership |
63
63
  | Find how route params and query params reach `page()`. | [index.md](./index.md), [routing.md](./routing.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/layout.py` | path-dict delivery, query coercion, and `request` injection |
64
64
  | Decide whether a dashboard child route should use page caching. | [index.md](./index.md), [cache.md](./cache.md), [routing.md](./routing.md), [fetch-data.md](./fetch-data.md) | `main.py`, `.venv/Lib/site-packages/casp/cache_handler.py`, the route's `index.py` | route-level cache ownership, cacheable HTML scope, and invalidation points |
65
- | Add a PulsePoint context provider to a reusable component. | [index.md](./index.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md) | `ts/TemplateCompiler.ts`, `public/js/pp-reactive-v2.js`, component `.html`, `.venv/Lib/site-packages/casp/components_compiler.py` | lowercase HTML-first `*.provider` usage, logical ancestry, single-root template shape |
65
+ | Add a PulsePoint context provider to a reusable component. | [index.md](./index.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md) | `public/js/pp-reactive-v2.js`, component `.html`, `.venv/Lib/site-packages/casp/components_compiler.py` | lowercase HTML-first `*.provider` usage, logical ancestry, single-root template shape |
66
+ | Render a conditional panel, a modal, or a table of rows in a route template. | [index.md](./index.md), [pulsepoint.md](./pulsepoint.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md), [routing.md](./routing.md) | `src/app/**/index.html`, `public/js/pp-reactive-v2.js` | `hidden="{...}"` for conditionals, `<template pp-for="…">` with `key` for lists, quoted brace attributes, and **no** JSX (`{cond && <div/>}`, `{list.map(...)}`, `class={...}`) |
66
67
  | Add upload progress to an existing file manager. | [index.md](./index.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md), [file-uploads.md](./file-uploads.md), [fetch-data.md](./fetch-data.md) | `src/app/**/index.py`, `src/app/**/index.html`, `src/lib/**`, `public/js/pp-reactive-v2.js` | `pp.rpc` upload options, state replacement, persisted metadata, BrowserSync ignore rules |
67
68
  | Add or debug a live WebSocket channel. | [index.md](./index.md), [websockets.md](./websockets.md), [core-runtime-map.md](./core-runtime-map.md), [pulsepoint.md](./pulsepoint.md), [auth.md](./auth.md) | `caspian.config.json`, `main.py`, `src/lib/websocket/**`, `src/app/**`, `settings/bs-config.json` | `websocket` feature gate, endpoint registration, origin checks, auth/session handling, native `WebSocket` client lifecycle, and BrowserSync URL selection |
68
69
  | Decide whether MCP files should be created. | [index.md](./index.md), [mcp.md](./mcp.md), [commands.md](./commands.md), [project-structure.md](./project-structure.md) | `caspian.config.json`, `settings/restart-mcp.ts`, `package.json`, `src/lib/mcp/**` when enabled | `mcp` feature gate, update workflow, nested FastMCP config ownership |
@@ -75,6 +76,7 @@ Treat the table as a prompt pack for spot checks, not as a full validation matri
75
76
  - AI reads only the packaged feature doc and never checks `main.py` or the installed runtime.
76
77
  - AI edits framework internals when the task only requires app-owned route or helper changes.
77
78
  - AI treats runtime HTML examples as authored template examples.
79
+ - AI reads the React comparison in [pulsepoint.md](./pulsepoint.md), [components.md](./components.md), [index.md](./index.md), or [routing.md](./routing.md) as permission to write JSX, and emits `{cond && (<div/>)}`, `{list.map(item => (<tr/>))}`, `className`, `onClick`, or unquoted `class={...}`. The React comparison covers the hook API and component decomposition only. An unquoted brace attribute is invalid HTML: the parser shreds it, the component root never compiles, and the page renders blank with **no console error**. Confirm every brace expression in an attribute is quoted, and that the file would still be valid HTML with all `{}` deleted.
78
80
  - AI generates a route or component template with a valid-looking root element but leaves a sibling top-level `<script>` or second top-level element after it. This includes single-file `html("""...""")` components where the `<script>` is placed after the root's closing tag instead of nested inside it; the string handed to `html(...)` must resolve to one top-level element, script included.
79
81
  - AI starts first-party interactivity with ids, `data-*` attributes, `querySelector`, `addEventListener`, manual `fetch`, manual `innerHTML`, or per-input `pp-ref` payload collection instead of PulsePoint `on*` attributes, form submit events, state, directives, and `pp.rpc()`.
80
82
  - AI bypasses the generated Prisma Python ORM in a Prisma-enabled project with direct database drivers, custom fetch helpers, JSON active stores, browser-side database fetches, or a second app-owned database abstraction.
package/dist/docs/auth.md CHANGED
@@ -119,7 +119,7 @@ Important behavior from the current implementation:
119
119
  - `token_auto_refresh` only changes behavior when the request lifecycle calls `auth.refresh_session()`. In the installed `auth.py`, the flag alone does not refresh expiry by itself.
120
120
  - The framework `AuthSettings` dataclass defaults `is_all_routes_private=True`, but the project example above explicitly changes that to `False`.
121
121
  - In generated app-owned starter config, `src/lib/auth/auth_config.py` starts with `is_all_routes_private=False`, so routes are public by default until the app chooses stricter route protection.
122
- - `public_routes`, `auth_routes`, `private_routes`, and `role_based_routes` are exact path matches in the installed `Auth` methods.
122
+ - `public_routes`, `auth_routes`, `private_routes`, and `role_based_routes` are matched as route **scopes**, not exact paths, in the installed `Auth` methods. An entry covers the path itself *and* everything nested under it, so `/admin` also matches `/admin/users`. Segment boundaries are respected (`/admin` does not match `/administration`), and dynamic segments work (`/orders/[id]` matches `/orders/42` and below). Root `/` is the one exception: it matches only `/`, so listing it in `public_routes` does not make the whole site public. Verify the exact rule in `_route_scope_matches_request` before relying on an entry to protect or expose a subtree.
123
123
  - `private_routes` matters only when `is_all_routes_private=False`.
124
124
  - `role_based_routes` currently expects `PATH -> [ROLES]`, not role names keyed to paths the other way around.
125
125
  - `role_identifier` defaults to `role`, and the current `auth.sign_in(...)` flow also normalizes `userRole` into `role` when possible.
@@ -189,7 +189,7 @@ The installed auth code reads several values from `.env` when explicit values ar
189
189
  - App-owned startup helpers may also validate `AUTH_SECRET` and refuse production startup when the value is missing or still on a default placeholder.
190
190
  - `AUTH_COOKIE_NAME` backs `AuthSettings.cookie_name`.
191
191
  - `SESSION_LIFETIME_HOURS` controls `SessionMiddleware.max_age` in the current `main.py` bootstrap.
192
- - `APP_ENV=production` enables secure session cookies and the `Secure` flag on the current CSRF cookie.
192
+ - `APP_ENV` selects the environment, resolved **fail-closed** by `casp.runtime_security.is_production_environment()`: only an explicit development value (`dev`, `development`, `local`, `staging`, `test`, `testing`) turns the development relaxations on, so an unset or misspelled value is treated as production. Production enables secure session cookies and the `Secure` flag on the CSRF cookie.
193
193
  - `CASPIAN_BROWSER_SYNC_PORT` can override the development cookie scope suffix used by the current `main.py` bootstrap.
194
194
  - `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `GOOGLE_REDIRECT_URI` back `GoogleProvider`.
195
195
  - `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` back `GithubProvider`.
@@ -249,11 +249,11 @@ The current `main.py` also wires the session middleware directly, and some apps
249
249
 
250
250
  ```python
251
251
  from starlette.middleware.sessions import SessionMiddleware
252
- from casp.runtime_security import get_session_secret
252
+ from casp.runtime_security import get_session_secret, is_production_environment
253
253
 
254
254
 
255
255
  SESSION_LIFETIME_HOURS = int(os.getenv("SESSION_LIFETIME_HOURS", 7))
256
- IS_PRODUCTION = os.getenv("APP_ENV") == "production"
256
+ IS_PRODUCTION = is_production_environment()
257
257
 
258
258
 
259
259
  app.add_middleware(
@@ -344,7 +344,7 @@ The current installed methods are:
344
344
  | `auth.is_authenticated()` | Check current auth state | Returns `False` when the payload is missing, malformed, or expired, and clears invalid session data. |
345
345
  | `auth.get_payload()` | Read the signed-in payload | Returns the stored dict payload, or wraps non-dict payloads as `{"value": data}`. |
346
346
  | `auth.refresh_session()` | Extend expiration | Only updates expiry when `token_auto_refresh=True`. |
347
- | `auth.check_role(user, allowed_roles)` | Check RBAC access | Reads the configured role field from the payload and compares it to the allowed roles. |
347
+ | `auth.check_role(user, allowed_roles)` | Check RBAC access | Reads the configured role field (a single role or a list of them) and grants access on any overlap. |
348
348
 
349
349
  Sign-in example:
350
350
 
@@ -715,7 +715,8 @@ In the installed implementation:
715
715
 
716
716
  - `auth.check_role(...)` reads `user[role_identifier]` when the payload is a dict.
717
717
  - If you pass a plain string instead of a dict, that string is treated as the role directly.
718
- - `role_based_routes` is an exact-path lookup with `dict.get(path)`.
718
+ - That field may hold a single role or a list/tuple/set of them, so a user who is both `editor` and `auditor` passes a check for either. Access is granted when the user's roles and the allowed roles intersect.
719
+ - `role_based_routes` keys are matched as route *scopes*, not exact paths: `get_required_roles(path)` ranks every matching key by specificity and returns the most specific one. A key therefore also covers nested paths (`/admin` gates `/admin/users`), and dynamic segments such as `/orders/[id]` match. A static key wins over a dynamic one, which wins over a catch-all.
719
720
  - The installed payload normalization helps when your Prisma include returns a `userRole` object and you want a plain `role` field in the session payload.
720
721
 
721
722
  ## OAuth Providers