enlace_auth.stores#
Per-user stores for enlace.
Apps never import from this package. Apps read request.state.store — a
MutableMapping scoped to {user_id}/{app_id}/ via PrefixedStore.
Public helpers:
PrefixedStore— wraps any MutableMapping with a key prefix.sanitize_key— path-traversal guard for user-supplied keys.make_file_store_factory— file-backed MutableMapping factory.StoreInjectionMiddleware— pure-ASGI middleware that injectsscope["state"]["store"]based onuser_idandapp_id.make_store_router— FastAPI router for/api/{app_id}/store/{key}.
Functions
|
Return a |
|
Return a router exposing the per-user store of each app that has one. |
|
Return |
Classes
|
Transparently prepend a prefix to every key operation on a base store. |
|
Pure-ASGI middleware that injects |
- class enlace_auth.stores.PrefixedStore(base, prefix)[source]#
Bases:
MutableMappingTransparently prepend a prefix to every key operation on a base store.
- class enlace_auth.stores.StoreInjectionMiddleware(app, *, base_store=None)[source]#
Bases:
objectPure-ASGI middleware that injects
request.state.store.
- enlace_auth.stores.make_file_store_factory(root, *, use_dol=False)[source]#
Return a
StoreFactorybacked by JSON files underroot.factory(name)returns aMutableMappingrooted atroot/name/.Defaults to a small stdlib implementation that auto-creates parent directories on write. Pass
use_dol=Trueto usedol.Filesinstead (pulls in the soft dep and expects flat keys).- Return type:
- enlace_auth.stores.make_store_router(*, base_store_getter, protected_apps, share_access=None, max_items=5000)[source]#
Return a router exposing the per-user store of each app that has one.
Routes:
GET /api/{app_id}/store?prefix=<p>→{"items": {key: {"value", "etag"}}, "truncated"}GET /api/{app_id}/store/{key}→{"value": v}with anETagheaderPUT /api/{app_id}/store/{key}— conditional withIf-Match: <etag>orIf-None-Match: *DELETE /api/{app_id}/store/{key}—If-Matchlikewise
A failed precondition is 412 with the current
{"value", "etag"}(valuenull when the key is absent), so the client can resolve and retry.protected_appsnames the apps with a per-user store:protected:userapps and those whoseapp.tomlsetsuser_store = true(a callable is re-read per request).?owner=<email>on any route acts on that user’s data instead of the caller’s, whenshare_access(app_id, owner, caller)says so ("rw", or"ro"for GET only); otherwise 404, as if the owner had nothing. Withoutshare_accessevery?owner=naming someone else is 404. Seemisc/docs/decisions/0001-owner-granted-data-shares.md.The router assumes
PlatformAuthMiddlewarehas setrequest.state.user_id(public apps get it too, when the visitor is signed in). CSRF on writes is theCSRFMiddleware’s, through itsenforce_prefixes.- Return type:
APIRouter
- enlace_auth.stores.sanitize_key(key)[source]#
Return
keyunchanged if safe for use as a store path component.Raises
ValueErrorwith a specific reason if the key is unsafe. The goal is fail-fast: we want the caller to see exactly why a key was rejected, not a silently rewritten value.- Return type:
Modules
MutableMapping-backed store factories for enlace. |
|
Store injection middleware and per-app store router. |
|
PrefixedStore — MutableMapping wrapper that scopes keys under a prefix. |
|
Key sanitization for user-supplied store keys. |