caspian-utils 0.1.7 → 0.1.9
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.
- package/dist/docs/auth.md +2 -0
- package/dist/docs/core-runtime-map.md +1 -1
- package/dist/docs/database.md +25 -14
- package/dist/docs/websockets.md +24 -4
- package/package.json +1 -1
package/dist/docs/auth.md
CHANGED
|
@@ -329,6 +329,8 @@ The pasted `main.py` auth middleware currently behaves like this:
|
|
|
329
329
|
|
|
330
330
|
Because `AuthMiddleware` initializes `StateManager` on every request, auth flows can also use [state.md](./state.md) for transient request-scoped success or error state. Keep identity, session lifetime, and authorization decisions in `auth.sign_in(...)`, `auth.sign_out(...)`, and the auth decorators rather than moving them into the state manager.
|
|
331
331
|
|
|
332
|
+
`AuthMiddleware` only runs on `scope["type"] == "http"`; it early-returns on WebSocket scopes. A private page route therefore does not protect a WebSocket. To keep socket auth aligned with page auth, reuse the same `Auth` instance inside the socket guard: bind the socket with `Auth.set_request(websocket)` (a `WebSocket` exposes `.session`, the one piece `Auth` reads) and call `auth.is_authenticated()`, `auth.get_payload()`, and `auth.check_role(...)`. See [websockets.md](./websockets.md) for the full guard pattern and the read-only-session caveat.
|
|
333
|
+
|
|
332
334
|
## The `auth` Object
|
|
333
335
|
|
|
334
336
|
The global `auth` singleton owns the session lifecycle.
|
|
@@ -113,7 +113,7 @@ Use these behavior checkpoints when AI needs the fastest verification path for a
|
|
|
113
113
|
| Runtime area | Verify these behaviors |
|
|
114
114
|
| --- | --- |
|
|
115
115
|
| `main.py` routing and request flow | route registration, path and query injection, static asset handling, session-middleware wiring, response-header middleware, and exception rendering |
|
|
116
|
-
| `main.py` WebSockets | `cfg.websocket` route-registration gate, `@app.websocket(...)` paths,
|
|
116
|
+
| `main.py` WebSockets | `cfg.websocket` route-registration gate, `@app.websocket(...)` paths, and the shared transport loop (message-size, idle-timeout, close codes, connection cleanup). Authorization is a single guard in `src/lib/websocket/websocket_security.py` (`authorize_websocket`) that runs the origin check before `accept()` and delegates auth to `Auth`. The HTTP middleware stack skips `scope["type"] == "websocket"`, so socket auth is enforced by that guard, not by `AuthMiddleware` |
|
|
117
117
|
| `public/js/pp-reactive-v2.js` browser runtime | SPA interception, history-vs-push scroll behavior, `pp-reset-scroll` semantics, and browser-side `pp.rpc()` behavior |
|
|
118
118
|
| `casp.auth` | auth settings, signin and signout flow, provider wiring, and page protection behavior |
|
|
119
119
|
| `casp.rpc` and streamed RPC responses | middleware interception, CSRF and session expectations, registry behavior, and helper-level RPC contracts |
|
package/dist/docs/database.md
CHANGED
|
@@ -16,7 +16,7 @@ This page documents the Prisma workflow for Caspian projects where `caspian.conf
|
|
|
16
16
|
|
|
17
17
|
When a project enables Prisma, use `prisma/schema.prisma` for schema management, `prisma.config.ts` for Prisma config and seed wiring, and reuse an app-owned `src/lib/prisma/` package when the project includes one.
|
|
18
18
|
|
|
19
|
-
High-priority rule: when `caspian.config.json` has `prisma: true`, Python-side database access must use the generated Prisma Python ORM. Route `page()` functions, route-owned `@rpc()` actions, auth handlers, upload flows, and shared helpers should import the generated client from `src/lib/prisma/**` instead of inventing direct driver calls, hand-written SQL wrappers, JSON manifests as active stores, app-specific HTTP fetches, or browser-side database fetches. Use raw SQL only through Prisma as a narrow fallback when the generated ORM cannot express a query clearly.
|
|
19
|
+
High-priority rule: when `caspian.config.json` has `prisma: true`, Python-side database access must use the generated Prisma Python ORM. Route `page()` functions, route-owned `@rpc()` actions, auth handlers, upload flows, and shared helpers should import the generated client from `src/lib/prisma/**` instead of inventing direct driver calls, hand-written SQL wrappers, JSON manifests as active stores, app-specific HTTP fetches, or browser-side database fetches. For normal reads and writes, default to Prisma methods such as `find_many`, `find_unique`, `create`, `update`, `delete`, `delete_many`, aggregates, includes, and transactions. Use raw SQL only through Prisma as a narrow fallback when the generated ORM cannot express a query clearly, and treat that fallback as provider-specific code that may break when a project moves between SQLite, MySQL, and PostgreSQL.
|
|
20
20
|
|
|
21
21
|
Treat `caspian.config.json` as the single source of truth for whether Prisma is enabled in a workspace. If `prisma` is false and the user wants Prisma, ask first, then update `caspian.config.json` and run `npx casp update project` before assuming Prisma-managed files exist.
|
|
22
22
|
|
|
@@ -31,7 +31,7 @@ The standard Prisma flow in Caspian is:
|
|
|
31
31
|
5. Run `npx ppy generate` so the Python ORM classes stay aligned with the updated schema.
|
|
32
32
|
6. Reuse the shared Python database layer in `src/lib/prisma/` when Python route or RPC code needs database access, and never hand-edit generated ORM classes.
|
|
33
33
|
|
|
34
|
-
Use this workflow instead of writing raw SQL first. Drop to raw SQL only through Prisma when a query cannot be expressed clearly with the generated client.
|
|
34
|
+
Use this workflow instead of writing raw SQL first. For normal CRUD and relation work, stay on the generated Prisma API. Drop to raw SQL only through Prisma when a query cannot be expressed clearly with the generated client, and assume that raw SQL requires extra review for cross-database portability.
|
|
35
35
|
|
|
36
36
|
## Environment Setup
|
|
37
37
|
|
|
@@ -49,7 +49,7 @@ Example for PostgreSQL in async-friendly production environments:
|
|
|
49
49
|
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
For most local development, SQLite is the simplest starting point. For production or higher concurrency workloads, prefer PostgreSQL or MySQL.
|
|
52
|
+
For most local development, SQLite is the simplest starting point. For production or higher concurrency workloads, prefer PostgreSQL or MySQL. Regardless of the starting provider, keep application queries on the Prisma ORM whenever possible so schema and query behavior stay easier to migrate across providers.
|
|
53
53
|
|
|
54
54
|
## Global Prisma Configuration
|
|
55
55
|
|
|
@@ -318,16 +318,27 @@ async with prisma.transaction() as tx:
|
|
|
318
318
|
)
|
|
319
319
|
```
|
|
320
320
|
|
|
321
|
-
### Raw SQL Fallback
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
321
|
+
### Raw SQL Fallback
|
|
322
|
+
|
|
323
|
+
Use this escape hatch sparingly. Raw SQL is not the default query style in a Prisma-enabled Caspian project.
|
|
324
|
+
|
|
325
|
+
Before using `query_raw()` or `execute_raw()`, confirm that the generated Prisma API cannot express the query clearly with normal methods such as `find_many`, `find_unique`, `create`, `update`, `delete`, `delete_many`, `aggregate`, relation `include`, or transactions.
|
|
326
|
+
|
|
327
|
+
Important portability warning:
|
|
328
|
+
|
|
329
|
+
- Raw SQL is provider-specific. Placeholder style, identifier quoting, JSON operators, case-sensitivity rules, date functions, and aggregate behavior can differ across SQLite, MySQL, and PostgreSQL.
|
|
330
|
+
- A raw query that works in SQLite may fail after a move to PostgreSQL or MySQL even when the Prisma schema migrates cleanly.
|
|
331
|
+
- If a project may switch providers, prefer Prisma ORM methods first and treat raw SQL as an exception that needs provider-aware review.
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
users = await prisma.user.find_many(
|
|
335
|
+
where={
|
|
336
|
+
"email": {"contains": "@gmail.com"},
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
If raw SQL is still unavoidable, keep it tightly scoped, document why the ORM was insufficient, and verify it against the current datasource provider in `prisma/schema.prisma`.
|
|
331
342
|
|
|
332
343
|
## Recommended Project Rules
|
|
333
344
|
|
|
@@ -339,7 +350,7 @@ Use raw SQL sparingly and only through Prisma. Prefer the generated Prisma API w
|
|
|
339
350
|
- Use `await` with Prisma operations.
|
|
340
351
|
- Convert Prisma objects to template-safe dictionaries when rendering HTML.
|
|
341
352
|
- Validate incoming mutation data before calling `create`, `update`, or `delete` operations.
|
|
342
|
-
- Prefer Prisma queries over raw SQL, and
|
|
353
|
+
- Prefer Prisma queries over raw SQL. If raw SQL is unavoidable, keep it provider-aware, narrowly scoped, and documented so future database-provider migrations do not silently break application behavior.
|
|
343
354
|
|
|
344
355
|
## AI Retrieval Notes
|
|
345
356
|
|
package/dist/docs/websockets.md
CHANGED
|
@@ -43,8 +43,9 @@ WEBSOCKET_PATH = "/ws/channel"
|
|
|
43
43
|
|
|
44
44
|
@app.websocket(WEBSOCKET_PATH)
|
|
45
45
|
async def websocket_live_endpoint(websocket: WebSocket):
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
# ONE guard authorizes the connection (origin + auth) and returns the
|
|
47
|
+
# identity, or None after closing. Pass require_auth / roles per channel.
|
|
48
|
+
if await authorize_websocket(websocket, require_auth=True) is None:
|
|
48
49
|
return
|
|
49
50
|
|
|
50
51
|
await manager.connect(websocket)
|
|
@@ -58,6 +59,8 @@ async def websocket_live_endpoint(websocket: WebSocket):
|
|
|
58
59
|
manager.disconnect(websocket)
|
|
59
60
|
```
|
|
60
61
|
|
|
62
|
+
Keep the authorization gate in one reusable helper rather than re-deriving auth in each endpoint. A public and a private channel should differ only by their `require_auth`/`roles` policy and their broadcast pool, not by a second copy of origin and session logic. Keep authenticated and guest traffic in separate connection managers so a private broadcast can never fan out to a guest connection.
|
|
63
|
+
|
|
61
64
|
Keep reusable socket helpers in `src/lib/**` when they are shared by more than one endpoint or route. Good candidates include connection managers, session helpers, auth-payload extraction, payload normalization, and shared broadcast code.
|
|
62
65
|
|
|
63
66
|
Keep route-specific browser UI in `src/app/**/index.html` and route-specific first-render values in the matching `index.py`. For example, an `index.py` can pass `websocket_path` and a development-only BrowserSync-aware `websocket_url` into the template with `render_page(__file__, {...})`.
|
|
@@ -108,11 +111,28 @@ This is one of the narrow cases where direct browser APIs and event listeners ar
|
|
|
108
111
|
|
|
109
112
|
## Auth And Sessions
|
|
110
113
|
|
|
111
|
-
HTTP route privacy does not automatically make a WebSocket endpoint private.
|
|
114
|
+
HTTP route privacy does not automatically make a WebSocket endpoint private. The HTTP middleware stack (`AuthMiddleware`, `CSRFMiddleware`, `RPCMiddleware`, body-size, security-header, and diagnostics layers) early-returns on every non-`http` scope, so a WebSocket handshake (`scope["type"] == "websocket"`) is never seen by `AuthMiddleware`. The only middleware that runs on socket scopes is `SessionMiddleware`, which exposes `websocket.session`. Each authenticated socket endpoint must therefore authorize itself.
|
|
115
|
+
|
|
116
|
+
Align that self-check with the rest of the app by delegating to Caspian's `Auth` instead of re-implementing session/`exp`/payload parsing. `Auth` methods read only `request.session`, and a `WebSocket` exposes `.session`, so binding the socket as the request context reuses the exact HTTP auth logic as a single source of truth:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from casp.auth import Auth
|
|
120
|
+
|
|
121
|
+
Auth.set_request(websocket) # WebSocket exposes .session
|
|
122
|
+
auth = Auth.get_instance()
|
|
123
|
+
if auth.is_authenticated():
|
|
124
|
+
payload = auth.get_payload() or {}
|
|
125
|
+
if roles and not auth.check_role(payload, roles):
|
|
126
|
+
... # forbidden -> close 1008
|
|
127
|
+
else:
|
|
128
|
+
... # unauthenticated -> close 1008 if required
|
|
129
|
+
```
|
|
112
130
|
|
|
113
131
|
Use close code `1008` for policy violations such as failed origin checks or missing authentication. If the endpoint accepts before rejecting, send a small JSON error payload first so the browser UI can show a useful message.
|
|
114
132
|
|
|
115
|
-
|
|
133
|
+
Treat the socket auth check as read-only. `SessionMiddleware` only writes the session cookie on `http.response.start`, which never fires for a WebSocket, so session mutations made during a connection (including `refresh_session` / sliding `exp`) are not persisted back to the cookie. Do not rely on the socket to extend a session; verify identity and, if needed, drive session refresh from an HTTP request. Do not assume HTTP `AuthMiddleware` behavior applies to `scope["type"] == "websocket"`.
|
|
134
|
+
|
|
135
|
+
Path-based `Auth` helpers (`is_private_route`, `get_required_roles`) read the HTTP route configuration and do not know about `/ws/*` paths. For socket RBAC, pass the required roles explicitly into the guard rather than asking `Auth` to classify the socket path.
|
|
116
136
|
|
|
117
137
|
## Origin And Proxy Checks
|
|
118
138
|
|