mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 20:46:58 +00:00
7.9 KiB
7.9 KiB
Task: Enforce API Key/JWT and Namespace in Go Client (Auto-Resolve Namespace) and Guard All Operations
Owner: To be assigned Status: Ready to implement
Objective
Implement strict client-side access enforcement in the Go client (pkg/client) so that:
- An API key or JWT is required by default to use the client.
- The client auto-resolves the namespace from the provided API key or JWT without requiring callers to pass the namespace per call.
- Per-call namespace overrides via context are still allowed for compatibility, but must match the resolved namespace; otherwise, deny the call.
- All operations (Storage, PubSub, Database/RQLite, and NetworkInfo) are guarded and return access errors when unauthenticated or namespace-mismatched.
- No backward compatibility guarantees required.
Note: This is client-side enforcement for now. Protocol-level auth/ACL for libp2p can be added later.
High-level behavior
ClientConfig.RequireAPIKeydefaults to true. If true and neitherAPIKeynorJWTis present,Connect()fails.- Namespace is automatically derived:
- From JWT: parse claims and read
Namespaceclaim (no network roundtrip). Verification of signature is not required for this task; parsing is enough to derive namespace. Optionally, add a TODO hook for future verification against JWKS if provided. - From API key: the namespace must be embedded in the key using a documented format (below). The client parses it locally and derives the namespace without any remote calls.
- From JWT: parse claims and read
- All calls check that any provided per-call namespace override matches the derived namespace, else return an “access denied: namespace mismatch” error.
- All modules are guarded: Database (RQLite), Storage, PubSub, and NetworkInfo.
API key and JWT formats
- JWT: RS256 token with claim
Namespace(string). We will parse claims (unverified) to obtainNamespace. - API key: change to an encoded format that includes the namespace so the client can parse locally. Options (pick one and implement consistently):
- Option A (dotted):
ak_<random>.<namespace> - Option B (colon):
ak_<random>:<namespace> - Option C (base64 JSON): base64url of
{ "kid": "...", "ns": "<namespace>" }prefixed byak_
- Option A (dotted):
For simplicity and readability, choose Option B: ak_<random>:<namespace>.
- Parsing rules:
- If
APIKeycontains a single colon, split and use the right side asnamespace(trim spaces). If empty -> error. - If more than one colon or invalid format -> error.
- If
Changes to implement
1) Client configuration and types
- File:
pkg/client/interface.go- Extend
ClientConfig:Namespace string// optional; if empty, auto-derived from API key or JWT; if still empty, fallback toAppName.RequireAPIKey bool// default true; when true, require eitherAPIKeyorJWT.JWT string// optional bearer token; used for namespace derivation and future protocol auth.
- Update
DefaultClientConfig(appName string)to set:RequireAPIKey: trueNamespace: ""(meaning auto)
- Extend
2) Namespace resolution and access gating
- File:
pkg/client/client.go- At construction or
Connect()time:- Implement
deriveNamespace():- If
config.Namespace != "", use it. - Else if
config.JWT != "", parse JWT claims (unverified) and readNamespaceclaim. - Else if
config.APIKey != "", parseak_<random>:<namespace>and extract namespace. - Else use
config.AppName. - Store the resolved namespace back into
config.Namespace.
- If
- Enforce presence of credentials:
- If
config.RequireAPIKeyis true AND bothconfig.APIKeyandconfig.JWTare empty -> return erroraccess denied: API key or JWT required.
- If
- Implement
- Add
func (c *Client) requireAccess(ctx context.Context) errorthat:- If
RequireAPIKeyand bothAPIKeyandJWTare empty -> erroraccess denied: credentials required. - Resolve per-call namespace override from context (via storage/pubsub helpers below). If present and
override != c.config.Namespace-> erroraccess denied: namespace mismatch.
- If
- At construction or
3) Guard all operations
- File:
pkg/client/implementations.go- At the start of each public method, call
client.requireAccess(ctx)and return the error if any.- DatabaseClientImpl:
Query,Transaction,CreateTable,DropTable,GetSchema. - StorageClientImpl:
Get,Put,Delete,List,Exists. - NetworkInfoImpl:
GetPeers,GetStatus,ConnectToPeer,DisconnectFromPeer.
- DatabaseClientImpl:
- For Storage operations, ensure we propagate the effective namespace:
- If override present and equals
config.Namespace, pass that context through; else usestorage.WithNamespace(ctx, config.Namespace).
- If override present and equals
- At the start of each public method, call
4) PubSub context-based namespace override (parity with Storage)
- Files:
pkg/pubsub/*- Add:
type ctxKey stringconst CtxKeyNamespaceOverride ctxKey = "pubsub_ns_override"func WithNamespace(ctx context.Context, ns string) context.Context
- Update topic naming in
manager.goandsubscriptions.go/publish.go:- Before computing
namespacedTopic, check for ctx override; if present and non-empty, use it; else fall back tom.namespace.
- Before computing
- Add:
5) Client context helper
- New file:
pkg/client/context.go- Add
func WithNamespace(ctx context.Context, ns string) context.Contextthat applies both storage and pubsub overrides by chaining:ctx = storage.WithNamespace(ctx, ns)ctx = pubsub.WithNamespace(ctx, ns)- return
ctx
- Add
6) Documentation updates
- Files:
README.md,AI_CONTEXT.md- Document the new client auth behavior:
- An API key or JWT is required by default (
RequireAPIKey=true). - Namespace auto-derived from token:
- JWT claim
Namespace. - API key format
ak_<random>:<namespace>.
- JWT claim
- Per-call override via
client.WithNamespace(ctx, ns)allowed but must match derived namespace. - All modules (Storage, PubSub, Database, NetworkInfo) are guarded.
- An API key or JWT is required by default (
- Provide usage examples for constructing
ClientConfigwith API key or JWT and making calls.
- Document the new client auth behavior:
Helper details
- JWT parsing: implement a minimal helper to split the token and base64url-decode the payload; read
Namespacefield from JSON. Do not verify signature for this task. If parsing fails, return a clear error. - API key parsing: simple split on
:; trim spaces; validate non-empty.
Error messages (standardize)
- Missing credentials:
access denied: API key or JWT required - Namespace mismatch:
access denied: namespace mismatch - Client not connected: keep existing
client not connectederror.
Acceptance criteria
- Without credentials and
RequireAPIKey=true,Connect()returns error and no operations are allowed. - With API key
ak_abc123:myapp, the client auto-resolves namespacemyapp; operations succeed. - With JWT containing
{ "Namespace": "myapp" }, the client auto-resolvesmyapp; operations succeed. - If a caller sets
client.WithNamespace(ctx, "otherNS")while resolved namespace ismyapp, any operation returnsaccess denied: namespace mismatch. - PubSub topic names use the override when present (and allowed) else the resolved namespace.
- NetworkInfo methods are also guarded and require credentials.
Out of scope (for this task)
- Protocol-level auth or verification of JWT signatures against JWKS.
- ETH payments/subscriptions and tier enforcement. (Separate design/implementation.)
Files to modify/add
- Modify:
pkg/client/interface.gopkg/client/client.gopkg/client/implementations.gopkg/pubsub/manager.gopkg/pubsub/subscriptions.gopkg/pubsub/publish.go(if exists; add override resolution there too)README.md,AI_CONTEXT.md
- Add:
pkg/pubsub/context.go(if not present)pkg/client/context.go
Notes
- Keep logs concise and avoid leaking tokens in logs. You may log the resolved namespace at
INFOlevel on connect. - Ensure thread-safety when accessing
Client.configfields (use existing locks if needed).