caspian-utils 0.0.32 → 0.0.33

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.
Files changed (2) hide show
  1. package/dist/docs/auth.md +71 -53
  2. package/package.json +1 -1
package/dist/docs/auth.md CHANGED
@@ -56,6 +56,7 @@ from casp.auth import (
56
56
  - Use `@rpc(require_auth=True, allowed_roles=[...])` for browser-triggered actions that need protection.
57
57
  - Use `StateManager` only for transient auth-adjacent request state; keep the authenticated session itself owned by `casp.auth`.
58
58
  - Keep secrets and provider credentials in `.env`; keep route visibility, redirects, and RBAC policy in `src/lib/auth/auth_config.py`.
59
+ - Do not implement custom `next` parsing or post-login redirect selection inside sign-in UI flows when using the built-in Caspian auth stack. The runtime and centralized auth config already own guest redirects, auth-route redirects, and the default post-login target.
59
60
  - Validate login, signup, reset-password, and profile-mutation inputs before hitting the database or external providers.
60
61
 
61
62
  ## Framework Internals Note
@@ -122,6 +123,23 @@ Important behavior from the current implementation:
122
123
  - `role_identifier` defaults to `role`, and the current `auth.sign_in(...)` flow also normalizes `userRole` into `role` when possible.
123
124
  - `api_auth_prefix` defaults to `/api/auth` and should stay centralized here rather than being hard-coded across routes.
124
125
 
126
+ ## Redirect Ownership
127
+
128
+ Treat redirect behavior as framework-owned plus config-owned, not sign-in-page-owned.
129
+
130
+ - Keep the redirect policy in `src/lib/auth/auth_config.py`, especially `default_signin_redirect` and `default_signout_redirect`.
131
+ - In the current runtime, unauthenticated requests to protected routes are redirected by middleware and decorators to `/signin?next=...`.
132
+ - In the current runtime, authenticated users who hit an auth route such as `/signin` are redirected by the auth layer to `default_signin_redirect`, which defaults to `/dashboard`.
133
+ - `guest_only()` also prefers a safe `next` query value for already-authenticated users and otherwise falls back to `default_signin_redirect`.
134
+ - Because this behavior already exists in `main.py`, `.venv/Lib/site-packages/casp/auth.py`, and `src/lib/auth/auth_config.py`, sign-in implementations should not add their own redirect-decision layer, duplicate `next` handling, or hard-code a dashboard redirect inside the sign-in action.
135
+ - When building sign-in flows, focus on credential validation, calling `auth.sign_in(...)` or the relevant provider flow, and rendering errors. Let Caspian decide where the user goes next.
136
+
137
+ Use this rule of thumb for AI-generated sign-in work:
138
+
139
+ - If the task is only to build or style the sign-in page, do not add redirect support.
140
+ - If the task is to change where authenticated users land after sign-in, update `default_signin_redirect` in `src/lib/auth/auth_config.py` instead of editing the sign-in route logic.
141
+ - If the task is to change how protected routes send guests to sign-in, inspect the installed auth runtime and middleware instead of adding local redirect code to the sign-in page.
142
+
125
143
  ## Choosing Public vs Private Route Mode
126
144
 
127
145
  Make this decision at app setup time in `src/lib/auth/auth_config.py`.
@@ -165,9 +183,9 @@ return AuthSettings(
165
183
 
166
184
  The installed auth code reads several values from `.env` when explicit values are not passed.
167
185
 
168
- - `AUTH_SECRET` backs `AuthSettings.secret_key`.
169
- - App-owned startup helpers may also validate `AUTH_SECRET` and refuse production startup when the value is missing or still on a default placeholder.
170
- - `AUTH_COOKIE_NAME` backs `AuthSettings.cookie_name`.
186
+ - `AUTH_SECRET` backs `AuthSettings.secret_key`.
187
+ - App-owned startup helpers may also validate `AUTH_SECRET` and refuse production startup when the value is missing or still on a default placeholder.
188
+ - `AUTH_COOKIE_NAME` backs `AuthSettings.cookie_name`.
171
189
  - `SESSION_LIFETIME_HOURS` controls `SessionMiddleware.max_age` in the current `main.py` bootstrap.
172
190
  - `APP_ENV=production` enables secure session cookies and the `Secure` flag on the current CSRF cookie.
173
191
  - `CASPIAN_BROWSER_SYNC_PORT` can override the development cookie scope suffix used by the current `main.py` bootstrap.
@@ -225,29 +243,29 @@ This does two things:
225
243
  - applies the centralized settings once at startup
226
244
  - registers OAuth providers once so `AuthMiddleware` can delegate signin and callback paths through `auth.auth_providers(...)`
227
245
 
228
- The current `main.py` also wires the session middleware directly, and some apps delegate the secret lookup to an app-owned helper:
229
-
230
- ```python
231
- from starlette.middleware.sessions import SessionMiddleware
232
- from casp.runtime_security import get_session_secret
233
-
234
-
235
- SESSION_LIFETIME_HOURS = int(os.getenv("SESSION_LIFETIME_HOURS", 7))
236
- IS_PRODUCTION = os.getenv("APP_ENV") == "production"
237
-
238
-
239
- app.add_middleware(
240
- SessionMiddleware,
241
- secret_key=get_session_secret(is_production=IS_PRODUCTION),
242
- session_cookie=os.getenv("AUTH_COOKIE_NAME", "session"),
243
- max_age=SESSION_LIFETIME_HOURS * 3600,
244
- same_site="lax",
246
+ The current `main.py` also wires the session middleware directly, and some apps delegate the secret lookup to an app-owned helper:
247
+
248
+ ```python
249
+ from starlette.middleware.sessions import SessionMiddleware
250
+ from casp.runtime_security import get_session_secret
251
+
252
+
253
+ SESSION_LIFETIME_HOURS = int(os.getenv("SESSION_LIFETIME_HOURS", 7))
254
+ IS_PRODUCTION = os.getenv("APP_ENV") == "production"
255
+
256
+
257
+ app.add_middleware(
258
+ SessionMiddleware,
259
+ secret_key=get_session_secret(is_production=IS_PRODUCTION),
260
+ session_cookie=os.getenv("AUTH_COOKIE_NAME", "session"),
261
+ max_age=SESSION_LIFETIME_HOURS * 3600,
262
+ same_site="lax",
245
263
  https_only=IS_PRODUCTION,
246
264
  path="/",
247
265
  )
248
266
  ```
249
267
 
250
- Keep this wiring in `main.py`. Keep only the policy values in `src/lib/auth/auth_config.py`. Runtime security helpers for session-secret enforcement, safe public-file serving, production-safe error messages, and baseline response headers are package-owned by `casp.runtime_security`; keep the middleware attachment itself in `main.py`.
268
+ Keep this wiring in `main.py`. Keep only the policy values in `src/lib/auth/auth_config.py`. Runtime security helpers for session-secret enforcement, safe public-file serving, production-safe error messages, and baseline response headers are package-owned by `casp.runtime_security`; keep the middleware attachment itself in `main.py`.
251
269
 
252
270
  ## Session Lifetime Rules
253
271
 
@@ -260,40 +278,40 @@ The effective authenticated session lasts only while both are valid.
260
278
 
261
279
  - If the session cookie expires first, the session disappears even if the auth payload expiration was longer.
262
280
  - If the auth payload expires first, `auth.is_authenticated()` clears it even if the session cookie still exists.
263
- - In apps where the request flow does not call `auth.refresh_session()`, `token_auto_refresh=True` still does nothing by itself.
281
+ - In apps where the request flow does not call `auth.refresh_session()`, `token_auto_refresh=True` still does nothing by itself.
264
282
 
265
283
  Keep `SESSION_LIFETIME_HOURS` and `default_token_validity` aligned unless you intentionally want one boundary to end earlier than the other.
266
284
 
267
285
  ## Middleware Order
268
286
 
269
- The current `main.py` adds middleware in this source order:
270
-
271
- ```python
272
- app.add_middleware(RPCMiddleware)
273
- app.add_middleware(AuthMiddleware)
274
- app.add_middleware(CSRFMiddleware)
275
- app.add_middleware(SessionMiddleware, ...)
276
- app.add_middleware(SecurityHeadersMiddleware)
277
- ```
278
-
279
- Because Starlette runs the last-added middleware first, the effective request order is:
280
-
281
- 1. `SecurityHeadersMiddleware`
282
- 2. `SessionMiddleware`
283
- 3. `CSRFMiddleware`
284
- 4. `AuthMiddleware`
285
- 5. `RPCMiddleware`
286
- 6. route handler or RPC endpoint
287
-
288
- Current behavior by layer:
289
-
290
- - `SecurityHeadersMiddleware` can attach baseline response headers from `casp.runtime_security`, such as `X-Content-Type-Options`, framing policy, referrer policy, permissions policy, and production HSTS, while preserving any headers already set by the response. Do not assume this layer emits CSP or owns third-party browser resource allowlists; verify the installed package helper first.
291
- - `SessionMiddleware` provides `request.session` for the rest of the stack.
292
- - `CSRFMiddleware` ensures `request.session["csrf_token"]` exists and emits a scoped CSRF cookie based on `pp_csrf`, for example `pp_csrf_5091` in development or plain `pp_csrf` when no dev scope is active.
293
- - `AuthMiddleware` sets request context with `Auth.set_request(request)`, initializes `StateManager`, runs provider callbacks, skips configured static asset paths, and enforces public, auth, private, and role-based route redirects.
294
- - `RPCMiddleware` handles `POST` requests with `X-PP-RPC: true` and forwards them to Caspian's RPC handler after auth and session setup are already available.
295
-
296
- Keep `SessionMiddleware` immediately inside any outer response-header wrapper such as `SecurityHeadersMiddleware`. If CSRF, auth, or RPC handling runs before the session layer, `request.session` will not be available.
287
+ The current `main.py` adds middleware in this source order:
288
+
289
+ ```python
290
+ app.add_middleware(RPCMiddleware)
291
+ app.add_middleware(AuthMiddleware)
292
+ app.add_middleware(CSRFMiddleware)
293
+ app.add_middleware(SessionMiddleware, ...)
294
+ app.add_middleware(SecurityHeadersMiddleware)
295
+ ```
296
+
297
+ Because Starlette runs the last-added middleware first, the effective request order is:
298
+
299
+ 1. `SecurityHeadersMiddleware`
300
+ 2. `SessionMiddleware`
301
+ 3. `CSRFMiddleware`
302
+ 4. `AuthMiddleware`
303
+ 5. `RPCMiddleware`
304
+ 6. route handler or RPC endpoint
305
+
306
+ Current behavior by layer:
307
+
308
+ - `SecurityHeadersMiddleware` can attach baseline response headers from `casp.runtime_security`, such as `X-Content-Type-Options`, framing policy, referrer policy, permissions policy, and production HSTS, while preserving any headers already set by the response. Do not assume this layer emits CSP or owns third-party browser resource allowlists; verify the installed package helper first.
309
+ - `SessionMiddleware` provides `request.session` for the rest of the stack.
310
+ - `CSRFMiddleware` ensures `request.session["csrf_token"]` exists and emits a scoped CSRF cookie based on `pp_csrf`, for example `pp_csrf_5091` in development or plain `pp_csrf` when no dev scope is active.
311
+ - `AuthMiddleware` sets request context with `Auth.set_request(request)`, initializes `StateManager`, runs provider callbacks, skips configured static asset paths, and enforces public, auth, private, and role-based route redirects.
312
+ - `RPCMiddleware` handles `POST` requests with `X-PP-RPC: true` and forwards them to Caspian's RPC handler after auth and session setup are already available.
313
+
314
+ Keep `SessionMiddleware` immediately inside any outer response-header wrapper such as `SecurityHeadersMiddleware`. If CSRF, auth, or RPC handling runs before the session layer, `request.session` will not be available.
297
315
 
298
316
  ## Current `AuthMiddleware` Flow
299
317
 
@@ -765,7 +783,7 @@ Prefer `AuthSettings`, `configure_auth(...)`, and `auth.settings` in new code.
765
783
  - Expired or malformed payloads are removed during `auth.is_authenticated()` checks.
766
784
  - `auth.get_payload()` returns `None` for missing payloads, a dict for dict payloads, and `{"value": ...}` for non-dict payloads.
767
785
  - OAuth callback helpers return `None` on failure, so calling routes should be prepared for a normal page render or explicit error state when provider login fails.
768
- - The current app bootstrap may wrap auth and session handling in an outer `SecurityHeadersMiddleware`; auth-aware code still depends on `SessionMiddleware`, `CSRFMiddleware`, `AuthMiddleware`, and `RPCMiddleware` staying in the correct relative order so sessions and CSRF state exist before route checks or RPC handling.
786
+ - The current app bootstrap may wrap auth and session handling in an outer `SecurityHeadersMiddleware`; auth-aware code still depends on `SessionMiddleware`, `CSRFMiddleware`, `AuthMiddleware`, and `RPCMiddleware` staying in the correct relative order so sessions and CSRF state exist before route checks or RPC handling.
769
787
  - Password reset, email verification, and other account-recovery workflows are application responsibilities layered on top of `casp.auth`, not built-in auth runtime features.
770
788
  - Route lists and RBAC maps are exact-path checks, not wildcard or prefix rules.
771
789
 
@@ -808,7 +826,7 @@ If an AI agent is deciding how to handle auth in Caspian, apply these rules firs
808
826
  - Do not treat `token_auto_refresh` as a route-privacy switch. In the current app it only affects sliding-session refresh if `auth.refresh_session()` is called.
809
827
  - Use `@require_auth`, `@require_role`, and `@guest_only` for page-level access rules.
810
828
  - Use `@rpc(require_auth=True, allowed_roles=[...])` for protected browser-triggered actions.
811
- - Keep `SessionMiddleware` immediately inside any outer response-header wrapper so auth, CSRF, and RPC handlers can read `request.session`.
829
+ - Keep `SessionMiddleware` immediately inside any outer response-header wrapper so auth, CSRF, and RPC handlers can read `request.session`.
812
830
  - Use [state.md](./state.md) only for transient auth-adjacent request state, not as the auth session store.
813
831
  - Align `SESSION_LIFETIME_HOURS` with `default_token_validity` unless you intentionally want different cookie and auth-payload expiry windows.
814
832
  - Prefer exact route strings in `public_routes`, `auth_routes`, `private_routes`, and `role_based_routes`.
@@ -816,6 +834,6 @@ If an AI agent is deciding how to handle auth in Caspian, apply these rules firs
816
834
  - Keep auth secrets and OAuth provider credentials in `.env`.
817
835
  - Set `AUTH_COOKIE_NAME` explicitly so the session middleware cookie name and auth settings stay aligned.
818
836
  - Use `.venv/Lib/site-packages/casp/auth.py` only when the task is about framework auth internals or debugging installed behavior.
819
- - Use `main.py` plus `casp.runtime_security` when the task is about auth bootstrap, session middleware, safe public-file serving, response headers, or middleware execution order.
837
+ - Use `main.py` plus `casp.runtime_security` when the task is about auth bootstrap, session middleware, safe public-file serving, response headers, or middleware execution order.
820
838
  - Pair auth work with `fetch-data.md` for RPC forms and `validation.md` for credential validation.
821
839
  - Check `routing.md` and `project-structure.md` before creating new auth routes or shared auth helpers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caspian-utils",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "description": "Caspian tooling",
5
5
  "main": "index.js",
6
6
  "scripts": {