189 lines
6.5 KiB
Markdown
189 lines
6.5 KiB
Markdown
# OpenSpec Change: network-plugin-auth
|
|
|
|
- **Change ID**: network-plugin-auth
|
|
- **Package**: `@suaveplan/network-plugin-auth` (new)
|
|
- **Status**: PROPOSED
|
|
- **Date**: 2026-05-01
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
Implement mesh membership authentication and cross-mesh relay authorization. The `auth` package exists in genesis (`@suaveplan/auth`) but provides generic OAuth/JWT/session primitives. There is no mesh-specific authentication: verifying that a peer is allowed to join a specific mesh, issuing capability tokens that constrain which tiers a peer can occupy, or validating trust relationships between meshes for relay traffic. This plugin bridges `@suaveplan/auth` into the mesh architecture.
|
|
|
|
---
|
|
|
|
## Why
|
|
|
|
**Current state**: `network-plugin-crypto-messages` signs messages with Ed25519 and verifies server-signed session/mesh tokens. However, there is no authentication of the peer itself before it joins a mesh, no capability-based tier assignment, and no cross-mesh relay trust validation.
|
|
|
|
**What's missing**:
|
|
|
|
- Peer authentication before joining a mesh
|
|
- Mesh capability tokens (who can be Tier 0 vs Tier 1 vs Tier 2)
|
|
- Cross-mesh relay authorization (which meshes trust each other)
|
|
- Message integrity at relay hops
|
|
|
|
---
|
|
|
|
## Core components
|
|
|
|
### 1. `MeshMembershipVerifier`
|
|
|
|
Verifies that a peer is authorized to join a mesh.
|
|
|
|
```ts
|
|
class MeshMembershipVerifier {
|
|
constructor(options: {
|
|
authProvider: AuthProvider; // from @suaveplan/auth
|
|
meshId: string;
|
|
serverPubKey: Uint8Array;
|
|
});
|
|
verifyJoinRequest(request: JoinRequest): Promise<JoinResult>;
|
|
}
|
|
|
|
interface JoinRequest {
|
|
peerId: string;
|
|
meshId: string;
|
|
authToken: string; // JWT or opaque token from @suaveplan/auth
|
|
publicKey: Uint8Array; // Ed25519 public key for future message verification
|
|
claimedCapabilities: Capability[];
|
|
}
|
|
|
|
interface JoinResult {
|
|
accepted: boolean;
|
|
peerId: string;
|
|
assignedTier: number; // 0, 1, or 2
|
|
capabilityToken: CapabilityToken;
|
|
reason?: string;
|
|
}
|
|
```
|
|
|
|
**Verification steps**:
|
|
|
|
1. Validate `authToken` via `@suaveplan/auth`.
|
|
2. Check that `publicKey` matches the identity in `authToken`.
|
|
3. Verify `claimedCapabilities` against the mesh's allowed capability set.
|
|
4. Issue a `CapabilityToken` signed by the server that constrains the peer's tier and permissions.
|
|
|
|
### 2. `CapabilityTokenIssuer`
|
|
|
|
Issues and validates capability tokens for mesh operations.
|
|
|
|
```ts
|
|
interface CapabilityToken {
|
|
peerId: string;
|
|
meshId: string;
|
|
tier: number; // max tier this peer can occupy
|
|
capabilities: Capability[];
|
|
issuedAt: number;
|
|
expiresAt: number;
|
|
serverSignature: Uint8Array;
|
|
}
|
|
|
|
enum Capability {
|
|
HOST = "host", // can be Tier 0
|
|
SUBHOST = "subhost", // can be Tier 1
|
|
RELAY = "relay", // can act as intra-mesh relay
|
|
BRIDGE = "bridge", // can act as cross-mesh relay backbone
|
|
AUDIT = "audit", // can write to audit log
|
|
CREATE_SESSION = "create_session",
|
|
JOIN_SESSION = "join_session",
|
|
}
|
|
|
|
class CapabilityTokenIssuer {
|
|
constructor(options: {
|
|
serverKeyPair: Ed25519KeyPair;
|
|
defaultTtlMs: number; // default 3600000 (1 hour)
|
|
});
|
|
issue(peerId: string, meshId: string, tier: number, capabilities: Capability[]): CapabilityToken;
|
|
validate(token: CapabilityToken): boolean;
|
|
renew(token: CapabilityToken): CapabilityToken;
|
|
}
|
|
```
|
|
|
|
### 3. `CrossMeshTrustValidator`
|
|
|
|
Validates trust relationships between meshes for relay traffic.
|
|
|
|
```ts
|
|
class CrossMeshTrustValidator {
|
|
constructor(options: {
|
|
trustStore: TrustStore; // meshId → trusted meshId set
|
|
serverPubKey: Uint8Array;
|
|
});
|
|
validateRelay(sourceMeshId: string, targetMeshId: string): boolean;
|
|
validateBackbone(hostId: string, meshId: string): boolean;
|
|
addTrust(fromMeshId: string, toMeshId: string): void;
|
|
removeTrust(fromMeshId: string, toMeshId: string): void;
|
|
}
|
|
|
|
interface TrustStore {
|
|
getTrustedMeshes(meshId: string): string[];
|
|
isTrusted(fromMeshId: string, toMeshId: string): boolean;
|
|
}
|
|
```
|
|
|
|
**Trust model**:
|
|
|
|
- By default, meshes do NOT trust each other.
|
|
- Trust is established by the central server signing a `MeshTrustToken` that authorizes relay between two meshes.
|
|
- Tier 0 hosts verify `MeshTrustToken` before accepting backbone connections from another mesh's host.
|
|
- `CrossMeshTrustValidator` caches trust decisions with TTL (default 5 minutes).
|
|
|
|
---
|
|
|
|
## Error codes
|
|
|
|
```ts
|
|
const MESH_AUTH_CODES = defineCodes("MESH_AUTH", [
|
|
"JOIN_REJECTED",
|
|
"TOKEN_EXPIRED",
|
|
"TOKEN_INVALID_SIGNATURE",
|
|
"CAPABILITY_DENIED",
|
|
"TIER_NOT_ALLOWED",
|
|
"TRUST_NOT_ESTABLISHED",
|
|
"BACKBONE_UNTRUSTED",
|
|
"AUTH_PROVIDER_ERROR",
|
|
] as const);
|
|
```
|
|
|
|
`MeshAuthError extends SuaveplanError<MeshAuthErrorCode>`.
|
|
|
|
---
|
|
|
|
## Dependencies
|
|
|
|
- **Consumes**: `@suaveplan/network` (`NetworkNode`, `NetworkBus`), `@suaveplan/auth` (JWT validation, session management), `@suaveplan/network-plugin-crypto-messages` (Ed25519 signatures, server tokens), `@suaveplan/network-plugin-registry` (peer public keys), `@suaveplan/error`, `@suaveplan/types`, `@opentelemetry/api`.
|
|
- **Blocked on**: `network-core` (shipped), `network-plugin-crypto-messages`, `@suaveplan/auth`.
|
|
|
|
---
|
|
|
|
## Verification approach
|
|
|
|
- `src/verifier/verifier.spec-conformance.test.ts` — pins join request validation, tier assignment, capability checks.
|
|
- `src/issuer/issuer.spec-conformance.test.ts` — pins token issue/validate/renew, signature verification, expiry.
|
|
- `src/trust/trust-validator.spec-conformance.test.ts` — pins trust store, relay validation, backbone validation, cache TTL.
|
|
- `tests/integration/auth.spec-conformance.test.ts` — end-to-end: peer joins mesh, receives capability token, relay is validated.
|
|
- 100/100/100/100 Istanbul coverage.
|
|
|
|
---
|
|
|
|
## Conformance requirements (proposed)
|
|
|
|
- `NET-106` — `MeshMembershipVerifier` validates `authToken` via `@suaveplan/auth` before allowing mesh join.
|
|
- `NET-107` — `MeshMembershipVerifier` checks that the peer's `publicKey` matches the identity in `authToken`.
|
|
- `NET-108` — `CapabilityTokenIssuer` issues tokens signed by the server that constrain tier and capabilities.
|
|
- `NET-109` — `CapabilityToken` includes `peerId`, `meshId`, `tier`, `capabilities`, `issuedAt`, `expiresAt`, and `serverSignature`.
|
|
- `NET-110` — `CrossMeshTrustValidator` rejects relay between meshes unless a trust relationship is established.
|
|
- `NET-111` — `CrossMeshTrustValidator` caches trust decisions with a configurable TTL.
|
|
- `NET-112` — Tier 0 hosts verify `MeshTrustToken` before accepting backbone connections.
|
|
|
|
---
|
|
|
|
## Out of scope
|
|
|
|
- **Fine-grained ACL per message type** → v2; v1 uses capability tokens.
|
|
- **Revocation lists** → v2; v1 relies on short token expiry.
|
|
- **Zero-knowledge proofs** → future research.
|