mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-10-06 08:59:07 +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.RequireAPIKey
defaults to true. If true and neitherAPIKey
norJWT
is present,Connect()
fails.- Namespace is automatically derived:
- From JWT: parse claims and read
Namespace
claim (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
APIKey
contains 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 eitherAPIKey
orJWT
.JWT string
// optional bearer token; used for namespace derivation and future protocol auth.
- Update
DefaultClientConfig(appName string)
to set:RequireAPIKey: true
Namespace: ""
(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 readNamespace
claim. - 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.RequireAPIKey
is true AND bothconfig.APIKey
andconfig.JWT
are empty -> return erroraccess denied: API key or JWT required
.
- If
- Implement
- Add
func (c *Client) requireAccess(ctx context.Context) error
that:- If
RequireAPIKey
and bothAPIKey
andJWT
are 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 string
const CtxKeyNamespaceOverride ctxKey = "pubsub_ns_override"
func WithNamespace(ctx context.Context, ns string) context.Context
- Update topic naming in
manager.go
andsubscriptions.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.Context
that 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
ClientConfig
with 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
Namespace
field 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 connected
error.
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.go
pkg/client/client.go
pkg/client/implementations.go
pkg/pubsub/manager.go
pkg/pubsub/subscriptions.go
pkg/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
INFO
level on connect. - Ensure thread-safety when accessing
Client.config
fields (use existing locks if needed).