diff --git a/TODO.md b/TODO.md index bfc8b93..512c4b6 100644 --- a/TODO.md +++ b/TODO.md @@ -11,7 +11,7 @@ Remaining tasks from [MIGRATION.md](./MIGRATION.md) for the Nebula Key Enclave. | Generated Code | Complete | `internal/keybase/*.go` | | Basic Plugin Functions | Complete | `generate`, `load`, `exec`, `query`, `ping` | | Encryption | Not Started | WebAuthn PRF key derivation needed | -| **UCAN v1.0.0-rc.1** | **CRITICAL** | Go implementation uses deprecated JWT format | +| **UCAN v1.0.0-rc.1** | **In Progress** | Core types, builders, and policies complete. Uses `go-ucan v1.1.0` | | MPC Key Shares | Not Started | Key share management missing | | Database Serialization | Incomplete | Export dumps comments only | @@ -19,114 +19,116 @@ Remaining tasks from [MIGRATION.md](./MIGRATION.md) for the Nebula Key Enclave. ## 1. UCAN v1.0.0-rc.1 Migration (CRITICAL PRIORITY) -> **Breaking Change**: Current Go implementation (`internal/crypto/ucan/`) uses deprecated JWT-based UCAN format. Must migrate to envelope format per v1.0.0-rc.1 spec. +> **Status**: Core implementation complete using `github.com/ucan-wg/go-ucan v1.1.0`. Deprecated JWT-based files deleted. Remaining work is database integration and MPC signing. -### Current State (DEPRECATED - Must Replace) +### Completed Implementation -The following files use the **old JWT-based format** and must be rewritten: +The following files implement UCAN v1.0.0-rc.1 using the official go-ucan library: -| File | Status | Issue | -|------|--------|-------| -| `jwt.go` | DEPRECATED | Uses `github.com/golang-jwt/jwt/v5`, old `can`+`with` format | -| `capability.go` | DEPRECATED | Old Attenuation/Resource/Capability model | -| `verifier.go` | DEPRECATED | JWT parsing, old proof chain format | -| `source.go` | DEPRECATED | JWT token creation with MPC | -| `vault.go` | PARTIAL | VaultCapability needs Policy migration | +| File | Status | Description | +|------|--------|-------------| +| `ucan.go` | ✅ Complete | Type re-exports, Sonr commands, pre-parsed constants | +| `policy.go` | ✅ Complete | PolicyBuilder fluent API, Sonr-specific policy helpers | +| `delegation.go` | ✅ Complete | DelegationBuilder fluent API, Sonr delegation helpers | +| `invocation.go` | ✅ Complete | InvocationBuilder fluent API, Sonr invocation helpers | +| `types.go` | ✅ Complete | ValidationError, Capability, ExecutionResult, Sonr types | -### Reference Implementation (Already Compliant) +### Dependencies Added -These files are already aligned with v1.0.0-rc.1: +- `github.com/ucan-wg/go-ucan v1.1.0` - Official UCAN library +- `github.com/ipld/go-ipld-prime v0.21.0` - IPLD encoding +- `github.com/MetaMask/go-did-it v1.0.0-pre1` - DID handling (indirect) +- `github.com/ipfs/go-cid v0.5.0` - Content addressing (indirect) -- `src/ucan.ts` - TypeScript types with envelope format -- `internal/codec/ucan-schemas.json` - JSON Schema definitions +### Deleted (Deprecated JWT-based) + +- ~~`jwt.go`~~ - Removed +- ~~`capability.go`~~ - Removed +- ~~`verifier.go`~~ - Removed +- ~~`source.go`~~ - Removed +- ~~`internal/crypto/mpc/spec/`~~ - Entire directory removed ### 1.1 Core Data Structures -- [ ] Create `internal/crypto/ucan/types.go` - v1.0.0-rc.1 types - - [ ] `DelegationPayload` struct (iss, aud, sub, cmd, pol, nonce, meta, nbf, exp) - - [ ] `InvocationPayload` struct (iss, sub, aud, cmd, args, prf, meta, nonce, exp, iat, cause) - - [ ] `Delegation` type as `[Signature, DelegationSigPayload]` tuple - - [ ] `Invocation` type as `[Signature, InvocationSigPayload]` tuple - - [ ] `Task` struct (sub, cmd, args, nonce) - - [ ] `ReceiptPayload` struct (iss, ran, out, fx, meta, iat) - - [ ] `RevocationPayload` struct +- [x] Create `internal/crypto/ucan/types.go` - v1.0.0-rc.1 types + - [x] Re-export `Delegation` and `Invocation` from go-ucan + - [x] `Task` struct (sub, cmd, args, nonce) + - [x] `ReceiptPayload` struct (iss, ran, out, fx, meta, iat) + - [x] `RevocationPayload` struct + - [x] `ValidationError` with error codes matching TypeScript + - [x] `Capability` struct (sub, cmd, pol) + - [x] `ExecutionResult[T, E]` generic type + - [x] Sonr-specific types: `VaultCapability`, `DIDCapability`, `DWNCapability` -- [ ] Create `internal/crypto/ucan/policy.go` - Policy Language - - [ ] `PolicyStatement` union type - - [ ] `EqualityStatement` - `["==", selector, value]` / `["!=", selector, value]` - - [ ] `InequalityStatement` - `[">", selector, number]` etc. - - [ ] `LikeStatement` - `["like", selector, glob]` - - [ ] `NotStatement` - `["not", statement]` - - [ ] `AndStatement` / `OrStatement` - logical connectives - - [ ] `AllStatement` / `AnyStatement` - quantifiers - - [ ] `Selector` parser (jq-inspired: `.foo`, `.bar[0]`, `.items[-1]`) - - [ ] `GlobPattern` matcher +- [x] Create `internal/crypto/ucan/policy.go` - Policy Language + - [x] `PolicyBuilder` fluent API with all operators + - [x] `Equal`, `NotEqual` - equality statements + - [x] `GreaterThan`, `LessThan`, etc. - inequality statements + - [x] `Like` - glob pattern matching + - [x] `Not`, `And`, `Or` - logical connectives + - [x] `All`, `Any` - quantifiers + - [x] Sonr helpers: `VaultPolicy`, `DIDPolicy`, `ChainPolicy`, `AccountPolicy` -- [ ] Create `internal/crypto/ucan/command.go` - Command types - - [ ] `Command` type with validation (must start with `/`, lowercase, no trailing slash) - - [ ] Standard commands: `/crud/*`, `/msg/*`, `/ucan/revoke`, `/wasm/run` - - [ ] Custom Sonr commands: `/vault/*`, `/did/*`, `/dwn/*` +- [x] Create `internal/crypto/ucan/ucan.go` - Command types + - [x] `Command` type re-exported from go-ucan + - [x] Sonr commands: `/vault/*`, `/did/*`, `/dwn/*`, `/ucan/revoke` + - [x] Pre-parsed command constants: `VaultRead`, `VaultWrite`, `DIDUpdate`, etc. + - [x] `CommandSubsumes()` helper using go-ucan's `Covers()` method ### 1.2 Envelope Format & Encoding -- [ ] Create `internal/crypto/ucan/envelope.go` - Envelope handling - - [ ] `UCANEnvelope[P]` generic type as `[Signature, {h: VarsigHeader} & P]` - - [ ] Encode envelope to CBOR (requires `github.com/fxamacker/cbor/v2`) - - [ ] Decode envelope from CBOR - - [ ] DAG-JSON encoding for interop - - [ ] CID computation (DAG-CBOR codec, SHA-256 multihash, base58btc) +- [x] Envelope handling via go-ucan library + - [x] `ToSealed()` method produces DAG-CBOR bytes + CID + - [x] `ToDagCbor()`, `ToDagJson()` encoding methods + - [x] CID computation handled by go-ucan -- [ ] Create `internal/crypto/ucan/varsig.go` - Varsig v1 headers - - [ ] Varsig header encoding/decoding - - [ ] Algorithm metadata extraction - - [ ] Support Ed25519, P-256, secp256k1 +- [x] Varsig support via go-ucan library + - [x] Ed25519, P-256, secp256k1 via `go-did-it/crypto` ### 1.3 Delegation Operations -- [ ] Create `internal/crypto/ucan/delegation.go` - Delegation creation/validation - - [ ] `NewDelegation(issuer, audience, subject, cmd, policy, exp, meta)` builder - - [ ] Sign delegation with issuer private key - - [ ] Validate delegation signature - - [ ] Validate delegation payload (temporal, structural) - - [ ] Extract `Capability` from delegation (sub + cmd + pol) +- [x] Create `internal/crypto/ucan/delegation.go` - Delegation creation/validation + - [x] `DelegationBuilder` fluent API + - [x] `NewDelegation`, `NewRootDelegation`, `NewPowerlineDelegation` re-exports + - [x] `BuildSealed(privKey)` for signing + - [x] Sonr helpers: `NewVaultDelegation`, `NewDIDDelegation`, `NewDWNDelegation` + - [x] Temporal options: `ExpiresAt`, `ExpiresIn`, `NotBefore`, `NotBeforeIn` ### 1.4 Invocation Operations -- [ ] Create `internal/crypto/ucan/invocation.go` - Invocation creation/validation - - [ ] `NewInvocation(issuer, subject, cmd, args, proofs, exp)` builder - - [ ] Sign invocation with invoker private key - - [ ] Validate invocation signature - - [ ] Validate proof chain (CID references to delegations) - - [ ] Evaluate policies against invocation args +- [x] Create `internal/crypto/ucan/invocation.go` - Invocation creation/validation + - [x] `InvocationBuilder` fluent API + - [x] `NewInvocation` re-export + - [x] `BuildSealed(privKey)` for signing + - [x] Proof chain management: `Proof()`, `Proofs()` + - [x] Sonr helpers: `VaultReadInvocation`, `VaultSignInvocation`, `DIDUpdateInvocation` ### 1.5 Policy Evaluation Engine -- [ ] Create `internal/crypto/ucan/eval.go` - Policy evaluation - - [ ] `EvaluatePolicy(policy Policy, args Arguments) (bool, error)` - - [ ] Selector resolution against IPLD data - - [ ] Equality comparison (deep IPLD equality) - - [ ] Numeric comparisons - - [ ] Glob pattern matching for `like` operator - - [ ] Logical connectives (`and`, `or`, `not`) - - [ ] Quantifiers (`all`, `any`) over collections +> Note: go-ucan provides `ExecutionAllowed()` on invocations which validates proofs and evaluates policies. + +- [x] Policy evaluation via go-ucan's `invocation.ExecutionAllowed(loader)` +- [ ] Create `internal/crypto/ucan/eval.go` - Additional evaluation helpers (if needed) + - [ ] Custom selector resolution for Sonr-specific args + - [ ] Caching layer for repeated evaluations ### 1.6 Proof Chain Validation -- [ ] Create `internal/crypto/ucan/chain.go` - Chain validation - - [ ] Resolve CID to Delegation (requires delegation store) - - [ ] Validate chain continuity (child.iss == parent.aud) - - [ ] Validate capability attenuation (child.cmd subsumes parent.cmd) - - [ ] Validate policy attenuation (child.pol more restrictive than parent.pol) - - [ ] Validate temporal bounds (child.exp <= parent.exp) - - [ ] Check revocation status for all chain members +> Note: go-ucan handles chain validation internally via `ExecutionAllowed()`. + +- [x] Chain validation via go-ucan library +- [ ] Create `internal/crypto/ucan/store.go` - Delegation store + - [ ] Implement `delegation.Loader` interface + - [ ] `GetDelegation(cid.Cid) (*delegation.Token, error)` + - [ ] Cache loaded delegations for performance ### 1.7 Revocation +- [x] `RevocationInvocation()` helper in `invocation.go` - [ ] Create `internal/crypto/ucan/revocation.go` - Revocation handling - - [ ] `NewRevocation(revoker, delegation_cid)` builder - - [ ] Validate revoker is in delegation's issuer chain - - [ ] Store revocation in database - - [ ] Query revocation status by CID + - [ ] Revocation store implementation + - [ ] `IsRevoked(cid.Cid) (bool, error)` query + - [ ] Integration with chain validation ### 1.8 Database Integration @@ -141,20 +143,18 @@ These files are already aligned with v1.0.0-rc.1: - [ ] `InsertInvocation`, `GetInvocationByCID` - [ ] `InsertRevocation`, `IsRevoked`, `GetRevocationsByDelegation` -### 1.9 Migration from Old Format +### 1.9 MPC Signing Integration -- [ ] Create migration script for existing UCAN data (if any) -- [ ] Remove deprecated files after migration complete: - - [ ] `jwt.go` - Remove entirely - - [ ] `capability.go` - Replace with policy-based capabilities - - [ ] `verifier.go` - Replace with envelope-based verification - - [ ] `source.go` - Replace with envelope-based token creation +- [ ] Create `internal/crypto/ucan/signer.go` - MPC key integration + - [ ] Implement `crypto.PrivateKeySigningBytes` interface for MPC + - [ ] Sign delegations with MPC key shares + - [ ] Sign invocations with MPC key shares ### 1.10 Testing -- [ ] Unit tests for policy evaluation -- [ ] Unit tests for envelope encoding/decoding -- [ ] Unit tests for chain validation +- [ ] Unit tests for builders (DelegationBuilder, InvocationBuilder) +- [ ] Unit tests for policy helpers +- [ ] Unit tests for Sonr-specific invocations - [ ] Interoperability tests against TypeScript implementation - [ ] Test vectors from UCAN spec @@ -478,23 +478,26 @@ These files are already aligned with v1.0.0-rc.1: ## Priority Order -1. **CRITICAL (Spec Compliance)** - - UCAN v1.0.0-rc.1 Migration (Section 1) - - Core data structures (1.1) - - Envelope format (1.2) - - Delegation operations (1.3) - - Policy evaluation (1.5) +1. **CRITICAL (Spec Compliance)** - ✅ Core Complete + - ~~UCAN v1.0.0-rc.1 Migration (Section 1)~~ ✅ Core types, builders, policies done + - ~~Core data structures (1.1)~~ ✅ Using go-ucan v1.1.0 + - ~~Envelope format (1.2)~~ ✅ Handled by go-ucan + - ~~Delegation operations (1.3)~~ ✅ DelegationBuilder complete + - ~~Invocation operations (1.4)~~ ✅ InvocationBuilder complete + - Database integration (1.8) - Next priority + - MPC signing integration (1.9) - Next priority 2. **High Priority (Core Functionality)** - Database Serialization (3.1, 3.2) - Credential Creation (6.2, 4.7) - Key Share Actions (4.1) - Account Actions (4.6) + - UCAN Database Integration (1.8) 3. **Medium Priority (Authorization)** - - Invocation operations (1.4) - - Proof chain validation (1.6) - - Revocation (1.7) + - Delegation store (1.6) + - Revocation store (1.7) + - MPC Signing (1.9) - Encryption Strategy (2.1, 2.2) 4. **Lower Priority (Enhancement)** @@ -506,13 +509,39 @@ These files are already aligned with v1.0.0-rc.1: --- +## Completed Items + +### UCAN v1.0.0-rc.1 Core (January 2025) + +The following was completed using `github.com/ucan-wg/go-ucan v1.1.0`: + +- ✅ Type re-exports from go-ucan (Delegation, Invocation, Command, Policy) +- ✅ Sonr command constants (/vault/*, /did/*, /dwn/*) +- ✅ DelegationBuilder fluent API with Sonr-specific helpers +- ✅ InvocationBuilder fluent API with Sonr-specific helpers +- ✅ PolicyBuilder fluent API with all operators +- ✅ Sonr policy helpers (VaultPolicy, DIDPolicy, ChainPolicy) +- ✅ ValidationError types matching TypeScript definitions +- ✅ Capability, ExecutionResult, and related types + +### Deleted (Deprecated JWT-based) + +- ✅ Deleted `jwt.go` - Old JWT token handling +- ✅ Deleted `capability.go` - Old Attenuation/Resource/Capability model +- ✅ Deleted `verifier.go` - Old JWT verification +- ✅ Deleted `source.go` - Old JWT token creation +- ✅ Deleted `internal/crypto/mpc/spec/` - Old MPC JWT integration +- ✅ Removed `github.com/golang-jwt/jwt/v5` dependency + +--- + ## Deprecated Items (Removed) The following items from the previous TODO have been removed as they reference the **deprecated JWT-based UCAN format**: -- ~~Section 4.1 "Token Validation" - JWT parsing~~ -> Replaced by envelope validation (1.2, 1.3) -- ~~Section 4.2 "Capability Verification" - `can`/`with` format~~ -> Replaced by policy evaluation (1.5) -- ~~Section 4.3 "Proof Chain Validation" - JWT proof strings~~ -> Replaced by CID-based chain (1.6) +- ~~Section 4.1 "Token Validation" - JWT parsing~~ -> Replaced by go-ucan validation +- ~~Section 4.2 "Capability Verification" - `can`/`with` format~~ -> Replaced by policy evaluation +- ~~Section 4.3 "Proof Chain Validation" - JWT proof strings~~ -> Replaced by CID-based chain - ~~Section 3.2 "UCAN Token Actions" - Old format~~ -> Replaced by v1.0.0-rc.1 actions (4.2) - ~~Section 3.3 "Delegation Actions" - Old delegation model~~ -> Merged into Section 1 and 4.2 diff --git a/internal/keybase/actions.go b/internal/keybase/actions.go index 44c9534..54b6dd9 100644 --- a/internal/keybase/actions.go +++ b/internal/keybase/actions.go @@ -72,7 +72,7 @@ func (am *ActionManager) ListAccounts(ctx context.Context) ([]AccountResult, err AddressIndex: row.AddressIndex, Label: label, IsDefault: row.IsDefault == 1, - PublicKey: row.PublicKey, + PublicKey: row.SharePublicKey, Curve: row.Curve, CreatedAt: row.CreatedAt, } @@ -481,7 +481,7 @@ func (am *ActionManager) ResolveDID(ctx context.Context, did string) (*DIDDocume AddressIndex: row.AddressIndex, Label: label, IsDefault: row.IsDefault == 1, - PublicKey: row.PublicKey, + PublicKey: row.SharePublicKey, Curve: row.Curve, CreatedAt: row.CreatedAt, } diff --git a/internal/keybase/actions_delegation.go b/internal/keybase/actions_delegation.go index eab2434..73eb631 100644 --- a/internal/keybase/actions_delegation.go +++ b/internal/keybase/actions_delegation.go @@ -2,37 +2,46 @@ package keybase import ( "context" - "encoding/json" "fmt" ) +// ============================================================================= +// DELEGATION ACTIONS (UCAN v1.0.0-rc.1) +// ============================================================================= + +// DelegationResult represents a delegation in API responses. type DelegationResult struct { - ID int64 `json:"id"` - UcanID int64 `json:"ucan_id"` - Delegator string `json:"delegator"` - Delegate string `json:"delegate"` - Resource string `json:"resource"` - Action string `json:"action"` - Caveats json.RawMessage `json:"caveats"` - Depth int64 `json:"depth"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - ExpiresAt string `json:"expires_at,omitempty"` + ID int64 `json:"id"` + CID string `json:"cid"` + Issuer string `json:"iss"` + Audience string `json:"aud"` + Subject string `json:"sub,omitempty"` + Command string `json:"cmd"` + Policy string `json:"pol,omitempty"` + NotBefore string `json:"nbf,omitempty"` + Expiration string `json:"exp,omitempty"` + IsRoot bool `json:"is_root"` + IsPowerline bool `json:"is_powerline"` + CreatedAt string `json:"created_at"` } -type NewDelegationInput struct { - UcanID int64 `json:"ucan_id"` - Delegator string `json:"delegator"` - Delegate string `json:"delegate"` - Resource string `json:"resource"` - Action string `json:"action"` - Caveats json.RawMessage `json:"caveats,omitempty"` - ParentID int64 `json:"parent_id,omitempty"` - Depth int64 `json:"depth"` - ExpiresAt string `json:"expires_at,omitempty"` +// StoreDelegationParams contains parameters for storing a delegation. +type StoreDelegationParams struct { + CID string `json:"cid"` + Envelope []byte `json:"envelope"` + Issuer string `json:"iss"` + Audience string `json:"aud"` + Subject string `json:"sub,omitempty"` + Command string `json:"cmd"` + Policy string `json:"pol,omitempty"` + NotBefore string `json:"nbf,omitempty"` + Expiration string `json:"exp,omitempty"` + IsRoot bool `json:"is_root"` + IsPowerline bool `json:"is_powerline"` } -func (am *ActionManager) CreateDelegation(ctx context.Context, params NewDelegationInput) (*DelegationResult, error) { +// StoreDelegation stores a new UCAN delegation envelope. +func (am *ActionManager) StoreDelegation(ctx context.Context, params StoreDelegationParams) (*DelegationResult, error) { am.kb.mu.Lock() defer am.kb.mu.Unlock() @@ -40,75 +49,78 @@ func (am *ActionManager) CreateDelegation(ctx context.Context, params NewDelegat return nil, fmt.Errorf("DID not initialized") } - var parentID *int64 - if params.ParentID != 0 { - parentID = ¶ms.ParentID + var sub, pol, nbf, exp *string + if params.Subject != "" { + sub = ¶ms.Subject + } + if params.Policy != "" { + pol = ¶ms.Policy + } + if params.NotBefore != "" { + nbf = ¶ms.NotBefore + } + if params.Expiration != "" { + exp = ¶ms.Expiration } - var expiresAt *string - if params.ExpiresAt != "" { - expiresAt = ¶ms.ExpiresAt + isRoot := int64(0) + if params.IsRoot { + isRoot = 1 } - - caveats := params.Caveats - if caveats == nil { - caveats = json.RawMessage(`{}`) + isPowerline := int64(0) + if params.IsPowerline { + isPowerline = 1 } d, err := am.kb.queries.CreateDelegation(ctx, CreateDelegationParams{ - DidID: am.kb.didID, - UcanID: params.UcanID, - Delegator: params.Delegator, - Delegate: params.Delegate, - Resource: params.Resource, - Action: params.Action, - Caveats: caveats, - ParentID: parentID, - Depth: params.Depth, - ExpiresAt: expiresAt, + DidID: am.kb.didID, + Cid: params.CID, + Envelope: params.Envelope, + Iss: params.Issuer, + Aud: params.Audience, + Sub: sub, + Cmd: params.Command, + Pol: pol, + Nbf: nbf, + Exp: exp, + IsRoot: isRoot, + IsPowerline: isPowerline, }) if err != nil { return nil, fmt.Errorf("create delegation: %w", err) } - return delegationToResult(&d), nil + return delegationToResult(d), nil } -func (am *ActionManager) ListDelegationsByDelegator(ctx context.Context, delegator string) ([]DelegationResult, error) { +// GetDelegationByCID retrieves a delegation by its CID. +func (am *ActionManager) GetDelegationByCID(ctx context.Context, cid string) (*DelegationResult, error) { am.kb.mu.RLock() defer am.kb.mu.RUnlock() - delegations, err := am.kb.queries.ListDelegationsByDelegator(ctx, delegator) + d, err := am.kb.queries.GetDelegationByCID(ctx, cid) if err != nil { - return nil, fmt.Errorf("list delegations by delegator: %w", err) + return nil, fmt.Errorf("get delegation: %w", err) } - results := make([]DelegationResult, len(delegations)) - for i, d := range delegations { - results[i] = *delegationToResult(&d) - } - - return results, nil + return delegationToResult(d), nil } -func (am *ActionManager) ListDelegationsByDelegate(ctx context.Context, delegate string) ([]DelegationResult, error) { +// GetDelegationEnvelope retrieves the raw CBOR envelope for a delegation. +func (am *ActionManager) GetDelegationEnvelope(ctx context.Context, cid string) ([]byte, error) { am.kb.mu.RLock() defer am.kb.mu.RUnlock() - delegations, err := am.kb.queries.ListDelegationsByDelegate(ctx, delegate) + envelope, err := am.kb.queries.GetDelegationEnvelopeByCID(ctx, cid) if err != nil { - return nil, fmt.Errorf("list delegations by delegate: %w", err) + return nil, fmt.Errorf("get delegation envelope: %w", err) } - results := make([]DelegationResult, len(delegations)) - for i, d := range delegations { - results[i] = *delegationToResult(&d) - } - - return results, nil + return envelope, nil } -func (am *ActionManager) ListDelegationsForResource(ctx context.Context, resource string) ([]DelegationResult, error) { +// ListDelegations returns all active delegations for the current DID. +func (am *ActionManager) ListDelegations(ctx context.Context) ([]DelegationResult, error) { am.kb.mu.RLock() defer am.kb.mu.RUnlock() @@ -116,66 +128,191 @@ func (am *ActionManager) ListDelegationsForResource(ctx context.Context, resourc return []DelegationResult{}, nil } - delegations, err := am.kb.queries.ListDelegationsForResource(ctx, ListDelegationsForResourceParams{ - DidID: am.kb.didID, - Resource: resource, - }) + delegations, err := am.kb.queries.ListDelegationsByDID(ctx, am.kb.didID) if err != nil { - return nil, fmt.Errorf("list delegations for resource: %w", err) + return nil, fmt.Errorf("list delegations: %w", err) } results := make([]DelegationResult, len(delegations)) for i, d := range delegations { - results[i] = *delegationToResult(&d) + results[i] = *delegationToResult(d) } return results, nil } -func (am *ActionManager) GetDelegationChain(ctx context.Context, delegationID int64) ([]DelegationResult, error) { +// ListDelegationsByIssuer returns delegations issued by a specific DID. +func (am *ActionManager) ListDelegationsByIssuer(ctx context.Context, issuer string) ([]DelegationResult, error) { am.kb.mu.RLock() defer am.kb.mu.RUnlock() - delegations, err := am.kb.queries.GetDelegationChain(ctx, GetDelegationChainParams{ - ID: delegationID, - ParentID: &delegationID, - }) + delegations, err := am.kb.queries.ListDelegationsByIssuer(ctx, issuer) if err != nil { - return nil, fmt.Errorf("get delegation chain: %w", err) + return nil, fmt.Errorf("list delegations by issuer: %w", err) } results := make([]DelegationResult, len(delegations)) for i, d := range delegations { - results[i] = *delegationToResult(&d) + results[i] = *delegationToResult(d) } return results, nil } -func (am *ActionManager) RevokeDelegation(ctx context.Context, delegationID int64) error { +// ListDelegationsByAudience returns delegations granted to a specific DID. +func (am *ActionManager) ListDelegationsByAudience(ctx context.Context, audience string) ([]DelegationResult, error) { + am.kb.mu.RLock() + defer am.kb.mu.RUnlock() + + delegations, err := am.kb.queries.ListDelegationsByAudience(ctx, audience) + if err != nil { + return nil, fmt.Errorf("list delegations by audience: %w", err) + } + + results := make([]DelegationResult, len(delegations)) + for i, d := range delegations { + results[i] = *delegationToResult(d) + } + + return results, nil +} + +// ListDelegationsForCommand returns delegations that grant a specific command. +func (am *ActionManager) ListDelegationsForCommand(ctx context.Context, cmd string) ([]DelegationResult, error) { + am.kb.mu.RLock() + defer am.kb.mu.RUnlock() + + if am.kb.didID == 0 { + return []DelegationResult{}, nil + } + + delegations, err := am.kb.queries.ListDelegationsForCommand(ctx, ListDelegationsForCommandParams{ + DidID: am.kb.didID, + Cmd: cmd, + Cmd_2: cmd, + }) + if err != nil { + return nil, fmt.Errorf("list delegations for command: %w", err) + } + + results := make([]DelegationResult, len(delegations)) + for i, d := range delegations { + results[i] = *delegationToResult(d) + } + + return results, nil +} + +// IsDelegationRevoked checks if a delegation has been revoked. +func (am *ActionManager) IsDelegationRevoked(ctx context.Context, cid string) (bool, error) { + am.kb.mu.RLock() + defer am.kb.mu.RUnlock() + + revoked, err := am.kb.queries.IsDelegationRevoked(ctx, cid) + if err != nil { + return false, fmt.Errorf("check revocation: %w", err) + } + + return revoked == 1, nil +} + +// RevokeDelegationParams contains parameters for revoking a delegation. +type RevokeDelegationParams struct { + DelegationCID string `json:"delegation_cid"` + RevokedBy string `json:"revoked_by"` + InvocationCID string `json:"invocation_cid,omitempty"` + Reason string `json:"reason,omitempty"` +} + +// RevokeDelegation revokes a delegation by creating a revocation record. +func (am *ActionManager) RevokeDelegation(ctx context.Context, params RevokeDelegationParams) error { am.kb.mu.Lock() defer am.kb.mu.Unlock() - return am.kb.queries.RevokeDelegation(ctx, delegationID) + var invocationCID, reason *string + if params.InvocationCID != "" { + invocationCID = ¶ms.InvocationCID + } + if params.Reason != "" { + reason = ¶ms.Reason + } + + err := am.kb.queries.CreateRevocation(ctx, CreateRevocationParams{ + DelegationCid: params.DelegationCID, + RevokedBy: params.RevokedBy, + InvocationCid: invocationCID, + Reason: reason, + }) + if err != nil { + return fmt.Errorf("create revocation: %w", err) + } + + return nil } -func delegationToResult(d *Delegation) *DelegationResult { - expiresAt := "" - if d.ExpiresAt != nil { - expiresAt = *d.ExpiresAt +// DeleteDelegation deletes a delegation from the database. +func (am *ActionManager) DeleteDelegation(ctx context.Context, cid string) error { + am.kb.mu.Lock() + defer am.kb.mu.Unlock() + + if am.kb.didID == 0 { + return fmt.Errorf("DID not initialized") + } + + err := am.kb.queries.DeleteDelegation(ctx, DeleteDelegationParams{ + Cid: cid, + DidID: am.kb.didID, + }) + if err != nil { + return fmt.Errorf("delete delegation: %w", err) + } + + return nil +} + +// CleanExpiredDelegations removes delegations expired more than 30 days ago. +func (am *ActionManager) CleanExpiredDelegations(ctx context.Context) error { + am.kb.mu.Lock() + defer am.kb.mu.Unlock() + + if err := am.kb.queries.CleanExpiredDelegations(ctx); err != nil { + return fmt.Errorf("clean expired delegations: %w", err) + } + + return nil +} + +// delegationToResult converts a UcanDelegation to DelegationResult. +func delegationToResult(d UcanDelegation) *DelegationResult { + subject := "" + if d.Sub != nil { + subject = *d.Sub + } + policy := "" + if d.Pol != nil { + policy = *d.Pol + } + notBefore := "" + if d.Nbf != nil { + notBefore = *d.Nbf + } + expiration := "" + if d.Exp != nil { + expiration = *d.Exp } return &DelegationResult{ - ID: d.ID, - UcanID: d.UcanID, - Delegator: d.Delegator, - Delegate: d.Delegate, - Resource: d.Resource, - Action: d.Action, - Caveats: d.Caveats, - Depth: d.Depth, - Status: d.Status, - CreatedAt: d.CreatedAt, - ExpiresAt: expiresAt, + ID: d.ID, + CID: d.Cid, + Issuer: d.Iss, + Audience: d.Aud, + Subject: subject, + Command: d.Cmd, + Policy: policy, + NotBefore: notBefore, + Expiration: expiration, + IsRoot: d.IsRoot == 1, + IsPowerline: d.IsPowerline == 1, + CreatedAt: d.CreatedAt, } } diff --git a/internal/keybase/actions_grant.go b/internal/keybase/actions_grant.go index dbf9949..904c1c1 100644 --- a/internal/keybase/actions_grant.go +++ b/internal/keybase/actions_grant.go @@ -7,11 +7,11 @@ import ( ) type NewGrantInput struct { - ServiceID int64 `json:"service_id"` - UcanID int64 `json:"ucan_id,omitempty"` - Scopes json.RawMessage `json:"scopes"` - Accounts json.RawMessage `json:"accounts"` - ExpiresAt string `json:"expires_at,omitempty"` + ServiceID int64 `json:"service_id"` + DelegationCID string `json:"delegation_cid,omitempty"` + Scopes json.RawMessage `json:"scopes"` + Accounts json.RawMessage `json:"accounts"` + ExpiresAt string `json:"expires_at,omitempty"` } func (am *ActionManager) CreateGrant(ctx context.Context, params NewGrantInput) (*GrantResult, error) { @@ -22,9 +22,9 @@ func (am *ActionManager) CreateGrant(ctx context.Context, params NewGrantInput) return nil, fmt.Errorf("DID not initialized") } - var ucanID *int64 - if params.UcanID != 0 { - ucanID = ¶ms.UcanID + var delegationCID *string + if params.DelegationCID != "" { + delegationCID = ¶ms.DelegationCID } var expiresAt *string @@ -42,12 +42,12 @@ func (am *ActionManager) CreateGrant(ctx context.Context, params NewGrantInput) } g, err := am.kb.queries.CreateGrant(ctx, CreateGrantParams{ - DidID: am.kb.didID, - ServiceID: params.ServiceID, - UcanID: ucanID, - Scopes: scopes, - Accounts: accounts, - ExpiresAt: expiresAt, + DidID: am.kb.didID, + ServiceID: params.ServiceID, + DelegationCid: delegationCID, + Scopes: scopes, + Accounts: accounts, + ExpiresAt: expiresAt, }) if err != nil { return nil, fmt.Errorf("create grant: %w", err) diff --git a/internal/keybase/actions_ucan.go b/internal/keybase/actions_ucan.go deleted file mode 100644 index eb367e9..0000000 --- a/internal/keybase/actions_ucan.go +++ /dev/null @@ -1,205 +0,0 @@ -package keybase - -import ( - "context" - "encoding/json" - "fmt" -) - -type UCANResult struct { - ID int64 `json:"id"` - CID string `json:"cid"` - Issuer string `json:"issuer"` - Audience string `json:"audience"` - Subject string `json:"subject,omitempty"` - Capabilities json.RawMessage `json:"capabilities"` - NotBefore string `json:"not_before,omitempty"` - ExpiresAt string `json:"expires_at"` - IsRevoked bool `json:"is_revoked"` - CreatedAt string `json:"created_at"` -} - -type NewUCANInput struct { - CID string `json:"cid"` - Issuer string `json:"issuer"` - Audience string `json:"audience"` - Subject string `json:"subject,omitempty"` - Capabilities json.RawMessage `json:"capabilities"` - ProofChain json.RawMessage `json:"proof_chain,omitempty"` - NotBefore string `json:"not_before,omitempty"` - ExpiresAt string `json:"expires_at"` - Nonce string `json:"nonce,omitempty"` - Facts json.RawMessage `json:"facts,omitempty"` - Signature string `json:"signature"` - RawToken string `json:"raw_token"` -} - -func (am *ActionManager) CreateUCAN(ctx context.Context, params NewUCANInput) (*UCANResult, error) { - am.kb.mu.Lock() - defer am.kb.mu.Unlock() - - if am.kb.didID == 0 { - return nil, fmt.Errorf("DID not initialized") - } - - var subject, notBefore, nonce *string - if params.Subject != "" { - subject = ¶ms.Subject - } - if params.NotBefore != "" { - notBefore = ¶ms.NotBefore - } - if params.Nonce != "" { - nonce = ¶ms.Nonce - } - - proofChain := params.ProofChain - if proofChain == nil { - proofChain = json.RawMessage(`[]`) - } - facts := params.Facts - if facts == nil { - facts = json.RawMessage(`{}`) - } - - ucan, err := am.kb.queries.CreateUCAN(ctx, CreateUCANParams{ - DidID: am.kb.didID, - Cid: params.CID, - Issuer: params.Issuer, - Audience: params.Audience, - Subject: subject, - Capabilities: params.Capabilities, - ProofChain: proofChain, - NotBefore: notBefore, - ExpiresAt: params.ExpiresAt, - Nonce: nonce, - Facts: facts, - Signature: params.Signature, - RawToken: params.RawToken, - }) - if err != nil { - return nil, fmt.Errorf("create ucan: %w", err) - } - - return ucanToResult(&ucan), nil -} - -func (am *ActionManager) ListUCANs(ctx context.Context) ([]UCANResult, error) { - am.kb.mu.RLock() - defer am.kb.mu.RUnlock() - - if am.kb.didID == 0 { - return []UCANResult{}, nil - } - - ucans, err := am.kb.queries.ListUCANsByDID(ctx, am.kb.didID) - if err != nil { - return nil, fmt.Errorf("list ucans: %w", err) - } - - results := make([]UCANResult, len(ucans)) - for i, u := range ucans { - results[i] = *ucanToResult(&u) - } - - return results, nil -} - -func (am *ActionManager) GetUCANByCID(ctx context.Context, cid string) (*UCANResult, error) { - am.kb.mu.RLock() - defer am.kb.mu.RUnlock() - - ucan, err := am.kb.queries.GetUCANByCID(ctx, cid) - if err != nil { - return nil, fmt.Errorf("get ucan: %w", err) - } - - return ucanToResult(&ucan), nil -} - -func (am *ActionManager) ListUCANsByAudience(ctx context.Context, audience string) ([]UCANResult, error) { - am.kb.mu.RLock() - defer am.kb.mu.RUnlock() - - ucans, err := am.kb.queries.ListUCANsByAudience(ctx, audience) - if err != nil { - return nil, fmt.Errorf("list ucans by audience: %w", err) - } - - results := make([]UCANResult, len(ucans)) - for i, u := range ucans { - results[i] = *ucanToResult(&u) - } - - return results, nil -} - -func (am *ActionManager) RevokeUCAN(ctx context.Context, cid string) error { - am.kb.mu.Lock() - defer am.kb.mu.Unlock() - - return am.kb.queries.RevokeUCAN(ctx, cid) -} - -func (am *ActionManager) IsUCANRevoked(ctx context.Context, cid string) (bool, error) { - am.kb.mu.RLock() - defer am.kb.mu.RUnlock() - - revoked, err := am.kb.queries.IsUCANRevoked(ctx, cid) - if err != nil { - return false, fmt.Errorf("check ucan revocation: %w", err) - } - - return revoked == 1, nil -} - -func (am *ActionManager) CreateRevocation(ctx context.Context, ucanCID string, revokedBy string, reason string) error { - am.kb.mu.Lock() - defer am.kb.mu.Unlock() - - var reasonPtr *string - if reason != "" { - reasonPtr = &reason - } - - if err := am.kb.queries.RevokeUCAN(ctx, ucanCID); err != nil { - return fmt.Errorf("revoke ucan token: %w", err) - } - - return am.kb.queries.CreateRevocation(ctx, CreateRevocationParams{ - UcanCid: ucanCID, - RevokedBy: revokedBy, - Reason: reasonPtr, - }) -} - -func (am *ActionManager) CleanExpiredUCANs(ctx context.Context) error { - am.kb.mu.Lock() - defer am.kb.mu.Unlock() - - return am.kb.queries.CleanExpiredUCANs(ctx) -} - -func ucanToResult(u *UcanToken) *UCANResult { - subject := "" - if u.Subject != nil { - subject = *u.Subject - } - notBefore := "" - if u.NotBefore != nil { - notBefore = *u.NotBefore - } - - return &UCANResult{ - ID: u.ID, - CID: u.Cid, - Issuer: u.Issuer, - Audience: u.Audience, - Subject: subject, - Capabilities: u.Capabilities, - NotBefore: notBefore, - ExpiresAt: u.ExpiresAt, - IsRevoked: u.IsRevoked == 1, - CreatedAt: u.CreatedAt, - } -} diff --git a/internal/keybase/db.go b/internal/keybase/db.go index 111c37b..cf16c1f 100644 --- a/internal/keybase/db.go +++ b/internal/keybase/db.go @@ -10,10 +10,10 @@ import ( ) type DBTX interface { - ExecContext(context.Context, string, ...any) (sql.Result, error) + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) PrepareContext(context.Context, string) (*sql.Stmt, error) - QueryContext(context.Context, string, ...any) (*sql.Rows, error) - QueryRowContext(context.Context, string, ...any) *sql.Row + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row } func New(db DBTX) *Queries { diff --git a/internal/keybase/models.go b/internal/keybase/models.go index 2f44611..65b3e81 100644 --- a/internal/keybase/models.go +++ b/internal/keybase/models.go @@ -40,22 +40,6 @@ type Credential struct { LastUsed string `json:"last_used"` } -type Delegation struct { - ID int64 `json:"id"` - DidID int64 `json:"did_id"` - UcanID int64 `json:"ucan_id"` - Delegator string `json:"delegator"` - Delegate string `json:"delegate"` - Resource string `json:"resource"` - Action string `json:"action"` - Caveats json.RawMessage `json:"caveats"` - ParentID *int64 `json:"parent_id"` - Depth int64 `json:"depth"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - ExpiresAt *string `json:"expires_at"` -} - type DidDocument struct { ID int64 `json:"id"` Did string `json:"did"` @@ -68,16 +52,16 @@ type DidDocument struct { } type Grant struct { - ID int64 `json:"id"` - DidID int64 `json:"did_id"` - ServiceID int64 `json:"service_id"` - UcanID *int64 `json:"ucan_id"` - Scopes json.RawMessage `json:"scopes"` - Accounts json.RawMessage `json:"accounts"` - Status string `json:"status"` - GrantedAt string `json:"granted_at"` - LastUsed *string `json:"last_used"` - ExpiresAt *string `json:"expires_at"` + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + ServiceID int64 `json:"service_id"` + DelegationCid *string `json:"delegation_cid"` + Scopes json.RawMessage `json:"scopes"` + Accounts json.RawMessage `json:"accounts"` + Status string `json:"status"` + GrantedAt string `json:"granted_at"` + LastUsed *string `json:"last_used"` + ExpiresAt *string `json:"expires_at"` } type KeyShare struct { @@ -131,31 +115,47 @@ type SyncCheckpoint struct { LastSynced string `json:"last_synced"` } -type UcanRevocation struct { - ID int64 `json:"id"` - UcanCid string `json:"ucan_cid"` - RevokedBy string `json:"revoked_by"` - Reason *string `json:"reason"` - RevokedAt string `json:"revoked_at"` +type UcanDelegation struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + Cid string `json:"cid"` + Envelope []byte `json:"envelope"` + Iss string `json:"iss"` + Aud string `json:"aud"` + Sub *string `json:"sub"` + Cmd string `json:"cmd"` + Pol *string `json:"pol"` + Nbf *string `json:"nbf"` + Exp *string `json:"exp"` + IsRoot int64 `json:"is_root"` + IsPowerline int64 `json:"is_powerline"` + CreatedAt string `json:"created_at"` } -type UcanToken struct { - ID int64 `json:"id"` - DidID int64 `json:"did_id"` - Cid string `json:"cid"` - Issuer string `json:"issuer"` - Audience string `json:"audience"` - Subject *string `json:"subject"` - Capabilities json.RawMessage `json:"capabilities"` - ProofChain json.RawMessage `json:"proof_chain"` - NotBefore *string `json:"not_before"` - ExpiresAt string `json:"expires_at"` - Nonce *string `json:"nonce"` - Facts json.RawMessage `json:"facts"` - Signature string `json:"signature"` - RawToken string `json:"raw_token"` - IsRevoked int64 `json:"is_revoked"` - CreatedAt string `json:"created_at"` +type UcanInvocation struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + Cid string `json:"cid"` + Envelope []byte `json:"envelope"` + Iss string `json:"iss"` + Sub string `json:"sub"` + Aud *string `json:"aud"` + Cmd string `json:"cmd"` + Prf string `json:"prf"` + Exp *string `json:"exp"` + Iat *string `json:"iat"` + ExecutedAt *string `json:"executed_at"` + ResultCid *string `json:"result_cid"` + CreatedAt string `json:"created_at"` +} + +type UcanRevocation struct { + ID int64 `json:"id"` + DelegationCid string `json:"delegation_cid"` + RevokedBy string `json:"revoked_by"` + InvocationCid *string `json:"invocation_cid"` + Reason *string `json:"reason"` + RevokedAt string `json:"revoked_at"` } type VerificationMethod struct { diff --git a/internal/keybase/querier.go b/internal/keybase/querier.go index c04bbb9..43e960b 100644 --- a/internal/keybase/querier.go +++ b/internal/keybase/querier.go @@ -10,22 +10,27 @@ import ( type Querier interface { ArchiveKeyShare(ctx context.Context, id int64) error - CleanExpiredUCANs(ctx context.Context) error + CleanExpiredDelegations(ctx context.Context) error + CleanOldInvocations(ctx context.Context) error CountActiveGrants(ctx context.Context, didID int64) (int64, error) CountCredentialsByDID(ctx context.Context, didID int64) (int64, error) CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error) CreateCredential(ctx context.Context, arg CreateCredentialParams) (Credential, error) CreateDID(ctx context.Context, arg CreateDIDParams) (DidDocument, error) - CreateDelegation(ctx context.Context, arg CreateDelegationParams) (Delegation, error) + CreateDelegation(ctx context.Context, arg CreateDelegationParams) (UcanDelegation, error) CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant, error) + CreateInvocation(ctx context.Context, arg CreateInvocationParams) (UcanInvocation, error) CreateKeyShare(ctx context.Context, arg CreateKeyShareParams) (KeyShare, error) + // ============================================================================= + // UCAN REVOCATION QUERIES + // ============================================================================= CreateRevocation(ctx context.Context, arg CreateRevocationParams) error CreateService(ctx context.Context, arg CreateServiceParams) (Service, error) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) - CreateUCAN(ctx context.Context, arg CreateUCANParams) (UcanToken, error) CreateVerificationMethod(ctx context.Context, arg CreateVerificationMethodParams) (VerificationMethod, error) DeleteAccount(ctx context.Context, arg DeleteAccountParams) error DeleteCredential(ctx context.Context, arg DeleteCredentialParams) error + DeleteDelegation(ctx context.Context, arg DeleteDelegationParams) error DeleteExpiredSessions(ctx context.Context) error DeleteKeyShare(ctx context.Context, arg DeleteKeyShareParams) error DeleteSession(ctx context.Context, id int64) error @@ -39,10 +44,20 @@ type Querier interface { GetDIDByDID(ctx context.Context, did string) (DidDocument, error) GetDIDByID(ctx context.Context, id int64) (DidDocument, error) GetDefaultAccount(ctx context.Context, arg GetDefaultAccountParams) (Account, error) - GetDelegationChain(ctx context.Context, arg GetDelegationChainParams) ([]Delegation, error) + // ============================================================================= + // UCAN DELEGATION QUERIES (v1.0.0-rc.1) + // ============================================================================= + GetDelegationByCID(ctx context.Context, cid string) (UcanDelegation, error) + GetDelegationEnvelopeByCID(ctx context.Context, cid string) ([]byte, error) GetGrantByService(ctx context.Context, arg GetGrantByServiceParams) (Grant, error) + // ============================================================================= + // UCAN INVOCATION QUERIES (v1.0.0-rc.1) + // ============================================================================= + GetInvocationByCID(ctx context.Context, cid string) (UcanInvocation, error) + GetInvocationEnvelopeByCID(ctx context.Context, cid string) ([]byte, error) GetKeyShareByID(ctx context.Context, shareID string) (KeyShare, error) GetKeyShareByKeyID(ctx context.Context, arg GetKeyShareByKeyIDParams) (KeyShare, error) + GetRevocation(ctx context.Context, delegationCid string) (UcanRevocation, error) GetServiceByID(ctx context.Context, id int64) (Service, error) // ============================================================================= // SERVICE QUERIES @@ -53,9 +68,8 @@ type Querier interface { // SYNC QUERIES // ============================================================================= GetSyncCheckpoint(ctx context.Context, arg GetSyncCheckpointParams) (SyncCheckpoint, error) - GetUCANByCID(ctx context.Context, cid string) (UcanToken, error) GetVerificationMethod(ctx context.Context, arg GetVerificationMethodParams) (VerificationMethod, error) - IsUCANRevoked(ctx context.Context, ucanCid string) (int64, error) + IsDelegationRevoked(ctx context.Context, delegationCid string) (int64, error) ListAccountsByChain(ctx context.Context, arg ListAccountsByChainParams) ([]Account, error) // ============================================================================= // ACCOUNT QUERIES @@ -66,41 +80,41 @@ type Querier interface { // CREDENTIAL QUERIES // ============================================================================= ListCredentialsByDID(ctx context.Context, didID int64) ([]Credential, error) - ListDelegationsByDelegate(ctx context.Context, delegate string) ([]Delegation, error) - // ============================================================================= - // DELEGATION QUERIES - // ============================================================================= - ListDelegationsByDelegator(ctx context.Context, delegator string) ([]Delegation, error) - ListDelegationsForResource(ctx context.Context, arg ListDelegationsForResourceParams) ([]Delegation, error) + ListDelegationsByAudience(ctx context.Context, aud string) ([]UcanDelegation, error) + ListDelegationsByDID(ctx context.Context, didID int64) ([]UcanDelegation, error) + ListDelegationsByIssuer(ctx context.Context, iss string) ([]UcanDelegation, error) + ListDelegationsBySubject(ctx context.Context, sub *string) ([]UcanDelegation, error) + ListDelegationsForCommand(ctx context.Context, arg ListDelegationsForCommandParams) ([]UcanDelegation, error) // ============================================================================= // GRANT QUERIES // ============================================================================= ListGrantsByDID(ctx context.Context, didID int64) ([]ListGrantsByDIDRow, error) + ListInvocationsByDID(ctx context.Context, arg ListInvocationsByDIDParams) ([]UcanInvocation, error) + ListInvocationsByIssuer(ctx context.Context, arg ListInvocationsByIssuerParams) ([]UcanInvocation, error) + ListInvocationsBySubject(ctx context.Context, arg ListInvocationsBySubjectParams) ([]UcanInvocation, error) + ListInvocationsForCommand(ctx context.Context, arg ListInvocationsForCommandParams) ([]UcanInvocation, error) // ============================================================================= // KEY SHARE QUERIES // ============================================================================= ListKeySharesByDID(ctx context.Context, didID int64) ([]KeyShare, error) + ListPendingInvocations(ctx context.Context, didID int64) ([]UcanInvocation, error) + ListPowerlineDelegations(ctx context.Context, didID int64) ([]UcanDelegation, error) + ListRevocationsByRevoker(ctx context.Context, revokedBy string) ([]UcanRevocation, error) + ListRootDelegations(ctx context.Context, didID int64) ([]UcanDelegation, error) // ============================================================================= // SESSION QUERIES // ============================================================================= ListSessionsByDID(ctx context.Context, didID int64) ([]ListSessionsByDIDRow, error) ListSyncCheckpoints(ctx context.Context, didID int64) ([]SyncCheckpoint, error) - ListUCANsByAudience(ctx context.Context, audience string) ([]UcanToken, error) - // ============================================================================= - // UCAN TOKEN QUERIES - // ============================================================================= - ListUCANsByDID(ctx context.Context, didID int64) ([]UcanToken, error) // ============================================================================= // VERIFICATION METHOD QUERIES // ============================================================================= ListVerificationMethods(ctx context.Context, didID int64) ([]VerificationMethod, error) ListVerifiedServices(ctx context.Context) ([]Service, error) + MarkInvocationExecuted(ctx context.Context, arg MarkInvocationExecutedParams) error ReactivateGrant(ctx context.Context, id int64) error RenameCredential(ctx context.Context, arg RenameCredentialParams) error - RevokeDelegation(ctx context.Context, id int64) error - RevokeDelegationChain(ctx context.Context, arg RevokeDelegationChainParams) error RevokeGrant(ctx context.Context, id int64) error - RevokeUCAN(ctx context.Context, cid string) error RotateKeyShare(ctx context.Context, id int64) error SetCurrentSession(ctx context.Context, arg SetCurrentSessionParams) error SetDefaultAccount(ctx context.Context, arg SetDefaultAccountParams) error diff --git a/internal/keybase/query.sql.go b/internal/keybase/query.sql.go index 09086ce..289f411 100644 --- a/internal/keybase/query.sql.go +++ b/internal/keybase/query.sql.go @@ -19,12 +19,21 @@ func (q *Queries) ArchiveKeyShare(ctx context.Context, id int64) error { return err } -const cleanExpiredUCANs = `-- name: CleanExpiredUCANs :exec -DELETE FROM ucan_tokens WHERE expires_at < datetime('now', '-30 days') +const cleanExpiredDelegations = `-- name: CleanExpiredDelegations :exec +DELETE FROM ucan_delegations WHERE exp < datetime('now', '-30 days') ` -func (q *Queries) CleanExpiredUCANs(ctx context.Context) error { - _, err := q.db.ExecContext(ctx, cleanExpiredUCANs) +func (q *Queries) CleanExpiredDelegations(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, cleanExpiredDelegations) + return err +} + +const cleanOldInvocations = `-- name: CleanOldInvocations :exec +DELETE FROM ucan_invocations WHERE created_at < datetime('now', '-90 days') +` + +func (q *Queries) CleanOldInvocations(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, cleanOldInvocations) return err } @@ -188,78 +197,83 @@ func (q *Queries) CreateDID(ctx context.Context, arg CreateDIDParams) (DidDocume } const createDelegation = `-- name: CreateDelegation :one -INSERT INTO delegations ( - did_id, ucan_id, delegator, delegate, resource, action, caveats, parent_id, depth, expires_at +INSERT INTO ucan_delegations ( + did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline ) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) -RETURNING id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at ` type CreateDelegationParams struct { - DidID int64 `json:"did_id"` - UcanID int64 `json:"ucan_id"` - Delegator string `json:"delegator"` - Delegate string `json:"delegate"` - Resource string `json:"resource"` - Action string `json:"action"` - Caveats json.RawMessage `json:"caveats"` - ParentID *int64 `json:"parent_id"` - Depth int64 `json:"depth"` - ExpiresAt *string `json:"expires_at"` + DidID int64 `json:"did_id"` + Cid string `json:"cid"` + Envelope []byte `json:"envelope"` + Iss string `json:"iss"` + Aud string `json:"aud"` + Sub *string `json:"sub"` + Cmd string `json:"cmd"` + Pol *string `json:"pol"` + Nbf *string `json:"nbf"` + Exp *string `json:"exp"` + IsRoot int64 `json:"is_root"` + IsPowerline int64 `json:"is_powerline"` } -func (q *Queries) CreateDelegation(ctx context.Context, arg CreateDelegationParams) (Delegation, error) { +func (q *Queries) CreateDelegation(ctx context.Context, arg CreateDelegationParams) (UcanDelegation, error) { row := q.db.QueryRowContext(ctx, createDelegation, arg.DidID, - arg.UcanID, - arg.Delegator, - arg.Delegate, - arg.Resource, - arg.Action, - arg.Caveats, - arg.ParentID, - arg.Depth, - arg.ExpiresAt, + arg.Cid, + arg.Envelope, + arg.Iss, + arg.Aud, + arg.Sub, + arg.Cmd, + arg.Pol, + arg.Nbf, + arg.Exp, + arg.IsRoot, + arg.IsPowerline, ) - var i Delegation + var i UcanDelegation err := row.Scan( &i.ID, &i.DidID, - &i.UcanID, - &i.Delegator, - &i.Delegate, - &i.Resource, - &i.Action, - &i.Caveats, - &i.ParentID, - &i.Depth, - &i.Status, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, &i.CreatedAt, - &i.ExpiresAt, ) return i, err } const createGrant = `-- name: CreateGrant :one -INSERT INTO grants (did_id, service_id, ucan_id, scopes, accounts, expires_at) +INSERT INTO grants (did_id, service_id, delegation_cid, scopes, accounts, expires_at) VALUES (?, ?, ?, ?, ?, ?) -RETURNING id, did_id, service_id, ucan_id, scopes, accounts, status, granted_at, last_used, expires_at +RETURNING id, did_id, service_id, delegation_cid, scopes, accounts, status, granted_at, last_used, expires_at ` type CreateGrantParams struct { - DidID int64 `json:"did_id"` - ServiceID int64 `json:"service_id"` - UcanID *int64 `json:"ucan_id"` - Scopes json.RawMessage `json:"scopes"` - Accounts json.RawMessage `json:"accounts"` - ExpiresAt *string `json:"expires_at"` + DidID int64 `json:"did_id"` + ServiceID int64 `json:"service_id"` + DelegationCid *string `json:"delegation_cid"` + Scopes json.RawMessage `json:"scopes"` + Accounts json.RawMessage `json:"accounts"` + ExpiresAt *string `json:"expires_at"` } func (q *Queries) CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant, error) { row := q.db.QueryRowContext(ctx, createGrant, arg.DidID, arg.ServiceID, - arg.UcanID, + arg.DelegationCid, arg.Scopes, arg.Accounts, arg.ExpiresAt, @@ -269,7 +283,7 @@ func (q *Queries) CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant &i.ID, &i.DidID, &i.ServiceID, - &i.UcanID, + &i.DelegationCid, &i.Scopes, &i.Accounts, &i.Status, @@ -280,6 +294,60 @@ func (q *Queries) CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant return i, err } +const createInvocation = `-- name: CreateInvocation :one +INSERT INTO ucan_invocations ( + did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id, did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat, executed_at, result_cid, created_at +` + +type CreateInvocationParams struct { + DidID int64 `json:"did_id"` + Cid string `json:"cid"` + Envelope []byte `json:"envelope"` + Iss string `json:"iss"` + Sub string `json:"sub"` + Aud *string `json:"aud"` + Cmd string `json:"cmd"` + Prf string `json:"prf"` + Exp *string `json:"exp"` + Iat *string `json:"iat"` +} + +func (q *Queries) CreateInvocation(ctx context.Context, arg CreateInvocationParams) (UcanInvocation, error) { + row := q.db.QueryRowContext(ctx, createInvocation, + arg.DidID, + arg.Cid, + arg.Envelope, + arg.Iss, + arg.Sub, + arg.Aud, + arg.Cmd, + arg.Prf, + arg.Exp, + arg.Iat, + ) + var i UcanInvocation + err := row.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Sub, + &i.Aud, + &i.Cmd, + &i.Prf, + &i.Exp, + &i.Iat, + &i.ExecutedAt, + &i.ResultCid, + &i.CreatedAt, + ) + return i, err +} + const createKeyShare = `-- name: CreateKeyShare :one INSERT INTO key_shares ( did_id, share_id, key_id, party_index, threshold, total_parties, @@ -339,18 +407,28 @@ func (q *Queries) CreateKeyShare(ctx context.Context, arg CreateKeyShareParams) } const createRevocation = `-- name: CreateRevocation :exec -INSERT INTO ucan_revocations (ucan_cid, revoked_by, reason) -VALUES (?, ?, ?) + +INSERT INTO ucan_revocations (delegation_cid, revoked_by, invocation_cid, reason) +VALUES (?, ?, ?, ?) ` type CreateRevocationParams struct { - UcanCid string `json:"ucan_cid"` - RevokedBy string `json:"revoked_by"` - Reason *string `json:"reason"` + DelegationCid string `json:"delegation_cid"` + RevokedBy string `json:"revoked_by"` + InvocationCid *string `json:"invocation_cid"` + Reason *string `json:"reason"` } +// ============================================================================= +// UCAN REVOCATION QUERIES +// ============================================================================= func (q *Queries) CreateRevocation(ctx context.Context, arg CreateRevocationParams) error { - _, err := q.db.ExecContext(ctx, createRevocation, arg.UcanCid, arg.RevokedBy, arg.Reason) + _, err := q.db.ExecContext(ctx, createRevocation, + arg.DelegationCid, + arg.RevokedBy, + arg.InvocationCid, + arg.Reason, + ) return err } @@ -434,69 +512,6 @@ func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (S return i, err } -const createUCAN = `-- name: CreateUCAN :one -INSERT INTO ucan_tokens ( - did_id, cid, issuer, audience, subject, capabilities, - proof_chain, not_before, expires_at, nonce, facts, signature, raw_token -) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) -RETURNING id, did_id, cid, issuer, audience, subject, capabilities, proof_chain, not_before, expires_at, nonce, facts, signature, raw_token, is_revoked, created_at -` - -type CreateUCANParams struct { - DidID int64 `json:"did_id"` - Cid string `json:"cid"` - Issuer string `json:"issuer"` - Audience string `json:"audience"` - Subject *string `json:"subject"` - Capabilities json.RawMessage `json:"capabilities"` - ProofChain json.RawMessage `json:"proof_chain"` - NotBefore *string `json:"not_before"` - ExpiresAt string `json:"expires_at"` - Nonce *string `json:"nonce"` - Facts json.RawMessage `json:"facts"` - Signature string `json:"signature"` - RawToken string `json:"raw_token"` -} - -func (q *Queries) CreateUCAN(ctx context.Context, arg CreateUCANParams) (UcanToken, error) { - row := q.db.QueryRowContext(ctx, createUCAN, - arg.DidID, - arg.Cid, - arg.Issuer, - arg.Audience, - arg.Subject, - arg.Capabilities, - arg.ProofChain, - arg.NotBefore, - arg.ExpiresAt, - arg.Nonce, - arg.Facts, - arg.Signature, - arg.RawToken, - ) - var i UcanToken - err := row.Scan( - &i.ID, - &i.DidID, - &i.Cid, - &i.Issuer, - &i.Audience, - &i.Subject, - &i.Capabilities, - &i.ProofChain, - &i.NotBefore, - &i.ExpiresAt, - &i.Nonce, - &i.Facts, - &i.Signature, - &i.RawToken, - &i.IsRevoked, - &i.CreatedAt, - ) - return i, err -} - const createVerificationMethod = `-- name: CreateVerificationMethod :one INSERT INTO verification_methods (did_id, method_id, method_type, controller, public_key, purpose) VALUES (?, ?, ?, ?, ?, ?) @@ -563,6 +578,20 @@ func (q *Queries) DeleteCredential(ctx context.Context, arg DeleteCredentialPara return err } +const deleteDelegation = `-- name: DeleteDelegation :exec +DELETE FROM ucan_delegations WHERE cid = ? AND did_id = ? +` + +type DeleteDelegationParams struct { + Cid string `json:"cid"` + DidID int64 `json:"did_id"` +} + +func (q *Queries) DeleteDelegation(ctx context.Context, arg DeleteDelegationParams) error { + _, err := q.db.ExecContext(ctx, deleteDelegation, arg.Cid, arg.DidID) + return err +} + const deleteExpiredSessions = `-- name: DeleteExpiredSessions :exec DELETE FROM sessions WHERE expires_at < datetime('now') ` @@ -747,54 +776,49 @@ func (q *Queries) GetDefaultAccount(ctx context.Context, arg GetDefaultAccountPa return i, err } -const getDelegationChain = `-- name: GetDelegationChain :many -SELECT id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at FROM delegations WHERE id = ? OR parent_id = ? ORDER BY depth DESC +const getDelegationByCID = `-- name: GetDelegationByCID :one + +SELECT id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at FROM ucan_delegations WHERE cid = ? LIMIT 1 ` -type GetDelegationChainParams struct { - ID int64 `json:"id"` - ParentID *int64 `json:"parent_id"` +// ============================================================================= +// UCAN DELEGATION QUERIES (v1.0.0-rc.1) +// ============================================================================= +func (q *Queries) GetDelegationByCID(ctx context.Context, cid string) (UcanDelegation, error) { + row := q.db.QueryRowContext(ctx, getDelegationByCID, cid) + var i UcanDelegation + err := row.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, + &i.CreatedAt, + ) + return i, err } -func (q *Queries) GetDelegationChain(ctx context.Context, arg GetDelegationChainParams) ([]Delegation, error) { - rows, err := q.db.QueryContext(ctx, getDelegationChain, arg.ID, arg.ParentID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []Delegation{} - for rows.Next() { - var i Delegation - if err := rows.Scan( - &i.ID, - &i.DidID, - &i.UcanID, - &i.Delegator, - &i.Delegate, - &i.Resource, - &i.Action, - &i.Caveats, - &i.ParentID, - &i.Depth, - &i.Status, - &i.CreatedAt, - &i.ExpiresAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil +const getDelegationEnvelopeByCID = `-- name: GetDelegationEnvelopeByCID :one +SELECT envelope FROM ucan_delegations WHERE cid = ? LIMIT 1 +` + +func (q *Queries) GetDelegationEnvelopeByCID(ctx context.Context, cid string) ([]byte, error) { + row := q.db.QueryRowContext(ctx, getDelegationEnvelopeByCID, cid) + var envelope []byte + err := row.Scan(&envelope) + return envelope, err } const getGrantByService = `-- name: GetGrantByService :one -SELECT id, did_id, service_id, ucan_id, scopes, accounts, status, granted_at, last_used, expires_at FROM grants WHERE did_id = ? AND service_id = ? LIMIT 1 +SELECT id, did_id, service_id, delegation_cid, scopes, accounts, status, granted_at, last_used, expires_at FROM grants WHERE did_id = ? AND service_id = ? LIMIT 1 ` type GetGrantByServiceParams struct { @@ -809,7 +833,7 @@ func (q *Queries) GetGrantByService(ctx context.Context, arg GetGrantByServicePa &i.ID, &i.DidID, &i.ServiceID, - &i.UcanID, + &i.DelegationCid, &i.Scopes, &i.Accounts, &i.Status, @@ -820,6 +844,47 @@ func (q *Queries) GetGrantByService(ctx context.Context, arg GetGrantByServicePa return i, err } +const getInvocationByCID = `-- name: GetInvocationByCID :one + +SELECT id, did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat, executed_at, result_cid, created_at FROM ucan_invocations WHERE cid = ? LIMIT 1 +` + +// ============================================================================= +// UCAN INVOCATION QUERIES (v1.0.0-rc.1) +// ============================================================================= +func (q *Queries) GetInvocationByCID(ctx context.Context, cid string) (UcanInvocation, error) { + row := q.db.QueryRowContext(ctx, getInvocationByCID, cid) + var i UcanInvocation + err := row.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Sub, + &i.Aud, + &i.Cmd, + &i.Prf, + &i.Exp, + &i.Iat, + &i.ExecutedAt, + &i.ResultCid, + &i.CreatedAt, + ) + return i, err +} + +const getInvocationEnvelopeByCID = `-- name: GetInvocationEnvelopeByCID :one +SELECT envelope FROM ucan_invocations WHERE cid = ? LIMIT 1 +` + +func (q *Queries) GetInvocationEnvelopeByCID(ctx context.Context, cid string) ([]byte, error) { + row := q.db.QueryRowContext(ctx, getInvocationEnvelopeByCID, cid) + var envelope []byte + err := row.Scan(&envelope) + return envelope, err +} + const getKeyShareByID = `-- name: GetKeyShareByID :one SELECT id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at FROM key_shares WHERE share_id = ? LIMIT 1 ` @@ -879,6 +944,24 @@ func (q *Queries) GetKeyShareByKeyID(ctx context.Context, arg GetKeyShareByKeyID return i, err } +const getRevocation = `-- name: GetRevocation :one +SELECT id, delegation_cid, revoked_by, invocation_cid, reason, revoked_at FROM ucan_revocations WHERE delegation_cid = ? LIMIT 1 +` + +func (q *Queries) GetRevocation(ctx context.Context, delegationCid string) (UcanRevocation, error) { + row := q.db.QueryRowContext(ctx, getRevocation, delegationCid) + var i UcanRevocation + err := row.Scan( + &i.ID, + &i.DelegationCid, + &i.RevokedBy, + &i.InvocationCid, + &i.Reason, + &i.RevokedAt, + ) + return i, err +} + const getServiceByID = `-- name: GetServiceByID :one SELECT id, origin, name, description, logo_url, did, is_verified, metadata, created_at FROM services WHERE id = ? LIMIT 1 ` @@ -973,34 +1056,6 @@ func (q *Queries) GetSyncCheckpoint(ctx context.Context, arg GetSyncCheckpointPa return i, err } -const getUCANByCID = `-- name: GetUCANByCID :one -SELECT id, did_id, cid, issuer, audience, subject, capabilities, proof_chain, not_before, expires_at, nonce, facts, signature, raw_token, is_revoked, created_at FROM ucan_tokens WHERE cid = ? LIMIT 1 -` - -func (q *Queries) GetUCANByCID(ctx context.Context, cid string) (UcanToken, error) { - row := q.db.QueryRowContext(ctx, getUCANByCID, cid) - var i UcanToken - err := row.Scan( - &i.ID, - &i.DidID, - &i.Cid, - &i.Issuer, - &i.Audience, - &i.Subject, - &i.Capabilities, - &i.ProofChain, - &i.NotBefore, - &i.ExpiresAt, - &i.Nonce, - &i.Facts, - &i.Signature, - &i.RawToken, - &i.IsRevoked, - &i.CreatedAt, - ) - return i, err -} - const getVerificationMethod = `-- name: GetVerificationMethod :one SELECT id, did_id, method_id, method_type, controller, public_key, purpose, created_at FROM verification_methods WHERE did_id = ? AND method_id = ? LIMIT 1 ` @@ -1026,12 +1081,12 @@ func (q *Queries) GetVerificationMethod(ctx context.Context, arg GetVerification return i, err } -const isUCANRevoked = `-- name: IsUCANRevoked :one -SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE ucan_cid = ?) as revoked +const isDelegationRevoked = `-- name: IsDelegationRevoked :one +SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE delegation_cid = ?) as revoked ` -func (q *Queries) IsUCANRevoked(ctx context.Context, ucanCid string) (int64, error) { - row := q.db.QueryRowContext(ctx, isUCANRevoked, ucanCid) +func (q *Queries) IsDelegationRevoked(ctx context.Context, delegationCid string) (int64, error) { + row := q.db.QueryRowContext(ctx, isDelegationRevoked, delegationCid) var revoked int64 err := row.Scan(&revoked) return revoked, err @@ -1083,7 +1138,7 @@ func (q *Queries) ListAccountsByChain(ctx context.Context, arg ListAccountsByCha const listAccountsByDID = `-- name: ListAccountsByDID :many -SELECT a.id, a.did_id, a.key_share_id, a.address, a.chain_id, a.coin_type, a.account_index, a.address_index, a.label, a.is_default, a.created_at, k.public_key, k.curve +SELECT a.id, a.did_id, a.key_share_id, a.address, a.chain_id, a.coin_type, a.account_index, a.address_index, a.label, a.is_default, a.created_at, k.public_key as share_public_key, k.curve FROM accounts a JOIN key_shares k ON a.key_share_id = k.id WHERE a.did_id = ? @@ -1091,19 +1146,19 @@ ORDER BY a.is_default DESC, a.created_at ` type ListAccountsByDIDRow struct { - ID int64 `json:"id"` - DidID int64 `json:"did_id"` - KeyShareID int64 `json:"key_share_id"` - Address string `json:"address"` - ChainID string `json:"chain_id"` - CoinType int64 `json:"coin_type"` - AccountIndex int64 `json:"account_index"` - AddressIndex int64 `json:"address_index"` - Label *string `json:"label"` - IsDefault int64 `json:"is_default"` - CreatedAt string `json:"created_at"` - PublicKey string `json:"public_key"` - Curve string `json:"curve"` + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + KeyShareID int64 `json:"key_share_id"` + Address string `json:"address"` + ChainID string `json:"chain_id"` + CoinType int64 `json:"coin_type"` + AccountIndex int64 `json:"account_index"` + AddressIndex int64 `json:"address_index"` + Label *string `json:"label"` + IsDefault int64 `json:"is_default"` + CreatedAt string `json:"created_at"` + SharePublicKey string `json:"share_public_key"` + Curve string `json:"curve"` } // ============================================================================= @@ -1130,7 +1185,7 @@ func (q *Queries) ListAccountsByDID(ctx context.Context, didID int64) ([]ListAcc &i.Label, &i.IsDefault, &i.CreatedAt, - &i.PublicKey, + &i.SharePublicKey, &i.Curve, ); err != nil { return nil, err @@ -1229,35 +1284,36 @@ func (q *Queries) ListCredentialsByDID(ctx context.Context, didID int64) ([]Cred return items, nil } -const listDelegationsByDelegate = `-- name: ListDelegationsByDelegate :many -SELECT id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at FROM delegations -WHERE delegate = ? AND status = 'active' AND (expires_at IS NULL OR expires_at > datetime('now')) +const listDelegationsByAudience = `-- name: ListDelegationsByAudience :many +SELECT id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at FROM ucan_delegations +WHERE aud = ? AND (exp IS NULL OR exp > datetime('now')) ORDER BY created_at DESC ` -func (q *Queries) ListDelegationsByDelegate(ctx context.Context, delegate string) ([]Delegation, error) { - rows, err := q.db.QueryContext(ctx, listDelegationsByDelegate, delegate) +func (q *Queries) ListDelegationsByAudience(ctx context.Context, aud string) ([]UcanDelegation, error) { + rows, err := q.db.QueryContext(ctx, listDelegationsByAudience, aud) if err != nil { return nil, err } defer rows.Close() - items := []Delegation{} + items := []UcanDelegation{} for rows.Next() { - var i Delegation + var i UcanDelegation if err := rows.Scan( &i.ID, &i.DidID, - &i.UcanID, - &i.Delegator, - &i.Delegate, - &i.Resource, - &i.Action, - &i.Caveats, - &i.ParentID, - &i.Depth, - &i.Status, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, &i.CreatedAt, - &i.ExpiresAt, ); err != nil { return nil, err } @@ -1272,39 +1328,36 @@ func (q *Queries) ListDelegationsByDelegate(ctx context.Context, delegate string return items, nil } -const listDelegationsByDelegator = `-- name: ListDelegationsByDelegator :many - -SELECT id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at FROM delegations -WHERE delegator = ? AND status = 'active' +const listDelegationsByDID = `-- name: ListDelegationsByDID :many +SELECT id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at FROM ucan_delegations +WHERE did_id = ? AND (exp IS NULL OR exp > datetime('now')) ORDER BY created_at DESC ` -// ============================================================================= -// DELEGATION QUERIES -// ============================================================================= -func (q *Queries) ListDelegationsByDelegator(ctx context.Context, delegator string) ([]Delegation, error) { - rows, err := q.db.QueryContext(ctx, listDelegationsByDelegator, delegator) +func (q *Queries) ListDelegationsByDID(ctx context.Context, didID int64) ([]UcanDelegation, error) { + rows, err := q.db.QueryContext(ctx, listDelegationsByDID, didID) if err != nil { return nil, err } defer rows.Close() - items := []Delegation{} + items := []UcanDelegation{} for rows.Next() { - var i Delegation + var i UcanDelegation if err := rows.Scan( &i.ID, &i.DidID, - &i.UcanID, - &i.Delegator, - &i.Delegate, - &i.Resource, - &i.Action, - &i.Caveats, - &i.ParentID, - &i.Depth, - &i.Status, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, &i.CreatedAt, - &i.ExpiresAt, ); err != nil { return nil, err } @@ -1319,40 +1372,132 @@ func (q *Queries) ListDelegationsByDelegator(ctx context.Context, delegator stri return items, nil } -const listDelegationsForResource = `-- name: ListDelegationsForResource :many -SELECT id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at FROM delegations -WHERE did_id = ? AND resource = ? AND status = 'active' -ORDER BY depth, created_at +const listDelegationsByIssuer = `-- name: ListDelegationsByIssuer :many +SELECT id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at FROM ucan_delegations +WHERE iss = ? AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC ` -type ListDelegationsForResourceParams struct { - DidID int64 `json:"did_id"` - Resource string `json:"resource"` -} - -func (q *Queries) ListDelegationsForResource(ctx context.Context, arg ListDelegationsForResourceParams) ([]Delegation, error) { - rows, err := q.db.QueryContext(ctx, listDelegationsForResource, arg.DidID, arg.Resource) +func (q *Queries) ListDelegationsByIssuer(ctx context.Context, iss string) ([]UcanDelegation, error) { + rows, err := q.db.QueryContext(ctx, listDelegationsByIssuer, iss) if err != nil { return nil, err } defer rows.Close() - items := []Delegation{} + items := []UcanDelegation{} for rows.Next() { - var i Delegation + var i UcanDelegation if err := rows.Scan( &i.ID, &i.DidID, - &i.UcanID, - &i.Delegator, - &i.Delegate, - &i.Resource, - &i.Action, - &i.Caveats, - &i.ParentID, - &i.Depth, - &i.Status, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listDelegationsBySubject = `-- name: ListDelegationsBySubject :many +SELECT id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at FROM ucan_delegations +WHERE sub = ? AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC +` + +func (q *Queries) ListDelegationsBySubject(ctx context.Context, sub *string) ([]UcanDelegation, error) { + rows, err := q.db.QueryContext(ctx, listDelegationsBySubject, sub) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanDelegation{} + for rows.Next() { + var i UcanDelegation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listDelegationsForCommand = `-- name: ListDelegationsForCommand :many +SELECT id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at FROM ucan_delegations +WHERE did_id = ? + AND (cmd = ? OR cmd = '/' OR ? LIKE cmd || '/%') + AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC +` + +type ListDelegationsForCommandParams struct { + DidID int64 `json:"did_id"` + Cmd string `json:"cmd"` + Cmd_2 string `json:"cmd_2"` +} + +func (q *Queries) ListDelegationsForCommand(ctx context.Context, arg ListDelegationsForCommandParams) ([]UcanDelegation, error) { + rows, err := q.db.QueryContext(ctx, listDelegationsForCommand, arg.DidID, arg.Cmd, arg.Cmd_2) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanDelegation{} + for rows.Next() { + var i UcanDelegation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, &i.CreatedAt, - &i.ExpiresAt, ); err != nil { return nil, err } @@ -1369,7 +1514,7 @@ func (q *Queries) ListDelegationsForResource(ctx context.Context, arg ListDelega const listGrantsByDID = `-- name: ListGrantsByDID :many -SELECT g.id, g.did_id, g.service_id, g.ucan_id, g.scopes, g.accounts, g.status, g.granted_at, g.last_used, g.expires_at, s.name as service_name, s.origin as service_origin, s.logo_url as service_logo +SELECT g.id, g.did_id, g.service_id, g.delegation_cid, g.scopes, g.accounts, g.status, g.granted_at, g.last_used, g.expires_at, s.name as service_name, s.origin as service_origin, s.logo_url as service_logo FROM grants g JOIN services s ON g.service_id = s.id WHERE g.did_id = ? AND g.status = 'active' @@ -1380,7 +1525,7 @@ type ListGrantsByDIDRow struct { ID int64 `json:"id"` DidID int64 `json:"did_id"` ServiceID int64 `json:"service_id"` - UcanID *int64 `json:"ucan_id"` + DelegationCid *string `json:"delegation_cid"` Scopes json.RawMessage `json:"scopes"` Accounts json.RawMessage `json:"accounts"` Status string `json:"status"` @@ -1408,7 +1553,7 @@ func (q *Queries) ListGrantsByDID(ctx context.Context, didID int64) ([]ListGrant &i.ID, &i.DidID, &i.ServiceID, - &i.UcanID, + &i.DelegationCid, &i.Scopes, &i.Accounts, &i.Status, @@ -1432,6 +1577,207 @@ func (q *Queries) ListGrantsByDID(ctx context.Context, didID int64) ([]ListGrant return items, nil } +const listInvocationsByDID = `-- name: ListInvocationsByDID :many +SELECT id, did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat, executed_at, result_cid, created_at FROM ucan_invocations +WHERE did_id = ? +ORDER BY created_at DESC +LIMIT ? +` + +type ListInvocationsByDIDParams struct { + DidID int64 `json:"did_id"` + Limit int64 `json:"limit"` +} + +func (q *Queries) ListInvocationsByDID(ctx context.Context, arg ListInvocationsByDIDParams) ([]UcanInvocation, error) { + rows, err := q.db.QueryContext(ctx, listInvocationsByDID, arg.DidID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanInvocation{} + for rows.Next() { + var i UcanInvocation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Sub, + &i.Aud, + &i.Cmd, + &i.Prf, + &i.Exp, + &i.Iat, + &i.ExecutedAt, + &i.ResultCid, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listInvocationsByIssuer = `-- name: ListInvocationsByIssuer :many +SELECT id, did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat, executed_at, result_cid, created_at FROM ucan_invocations +WHERE iss = ? +ORDER BY created_at DESC +LIMIT ? +` + +type ListInvocationsByIssuerParams struct { + Iss string `json:"iss"` + Limit int64 `json:"limit"` +} + +func (q *Queries) ListInvocationsByIssuer(ctx context.Context, arg ListInvocationsByIssuerParams) ([]UcanInvocation, error) { + rows, err := q.db.QueryContext(ctx, listInvocationsByIssuer, arg.Iss, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanInvocation{} + for rows.Next() { + var i UcanInvocation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Sub, + &i.Aud, + &i.Cmd, + &i.Prf, + &i.Exp, + &i.Iat, + &i.ExecutedAt, + &i.ResultCid, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listInvocationsBySubject = `-- name: ListInvocationsBySubject :many +SELECT id, did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat, executed_at, result_cid, created_at FROM ucan_invocations +WHERE sub = ? +ORDER BY created_at DESC +LIMIT ? +` + +type ListInvocationsBySubjectParams struct { + Sub string `json:"sub"` + Limit int64 `json:"limit"` +} + +func (q *Queries) ListInvocationsBySubject(ctx context.Context, arg ListInvocationsBySubjectParams) ([]UcanInvocation, error) { + rows, err := q.db.QueryContext(ctx, listInvocationsBySubject, arg.Sub, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanInvocation{} + for rows.Next() { + var i UcanInvocation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Sub, + &i.Aud, + &i.Cmd, + &i.Prf, + &i.Exp, + &i.Iat, + &i.ExecutedAt, + &i.ResultCid, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listInvocationsForCommand = `-- name: ListInvocationsForCommand :many +SELECT id, did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat, executed_at, result_cid, created_at FROM ucan_invocations +WHERE did_id = ? AND cmd = ? +ORDER BY created_at DESC +LIMIT ? +` + +type ListInvocationsForCommandParams struct { + DidID int64 `json:"did_id"` + Cmd string `json:"cmd"` + Limit int64 `json:"limit"` +} + +func (q *Queries) ListInvocationsForCommand(ctx context.Context, arg ListInvocationsForCommandParams) ([]UcanInvocation, error) { + rows, err := q.db.QueryContext(ctx, listInvocationsForCommand, arg.DidID, arg.Cmd, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanInvocation{} + for rows.Next() { + var i UcanInvocation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Sub, + &i.Aud, + &i.Cmd, + &i.Prf, + &i.Exp, + &i.Iat, + &i.ExecutedAt, + &i.ResultCid, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listKeySharesByDID = `-- name: ListKeySharesByDID :many SELECT id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at FROM key_shares WHERE did_id = ? AND status = 'active' ORDER BY created_at @@ -1479,6 +1825,174 @@ func (q *Queries) ListKeySharesByDID(ctx context.Context, didID int64) ([]KeySha return items, nil } +const listPendingInvocations = `-- name: ListPendingInvocations :many +SELECT id, did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat, executed_at, result_cid, created_at FROM ucan_invocations +WHERE did_id = ? AND executed_at IS NULL AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at ASC +` + +func (q *Queries) ListPendingInvocations(ctx context.Context, didID int64) ([]UcanInvocation, error) { + rows, err := q.db.QueryContext(ctx, listPendingInvocations, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanInvocation{} + for rows.Next() { + var i UcanInvocation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Sub, + &i.Aud, + &i.Cmd, + &i.Prf, + &i.Exp, + &i.Iat, + &i.ExecutedAt, + &i.ResultCid, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listPowerlineDelegations = `-- name: ListPowerlineDelegations :many +SELECT id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at FROM ucan_delegations +WHERE did_id = ? AND is_powerline = 1 AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC +` + +func (q *Queries) ListPowerlineDelegations(ctx context.Context, didID int64) ([]UcanDelegation, error) { + rows, err := q.db.QueryContext(ctx, listPowerlineDelegations, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanDelegation{} + for rows.Next() { + var i UcanDelegation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listRevocationsByRevoker = `-- name: ListRevocationsByRevoker :many +SELECT id, delegation_cid, revoked_by, invocation_cid, reason, revoked_at FROM ucan_revocations +WHERE revoked_by = ? +ORDER BY revoked_at DESC +` + +func (q *Queries) ListRevocationsByRevoker(ctx context.Context, revokedBy string) ([]UcanRevocation, error) { + rows, err := q.db.QueryContext(ctx, listRevocationsByRevoker, revokedBy) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanRevocation{} + for rows.Next() { + var i UcanRevocation + if err := rows.Scan( + &i.ID, + &i.DelegationCid, + &i.RevokedBy, + &i.InvocationCid, + &i.Reason, + &i.RevokedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listRootDelegations = `-- name: ListRootDelegations :many +SELECT id, did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline, created_at FROM ucan_delegations +WHERE did_id = ? AND is_root = 1 AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC +` + +func (q *Queries) ListRootDelegations(ctx context.Context, didID int64) ([]UcanDelegation, error) { + rows, err := q.db.QueryContext(ctx, listRootDelegations, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanDelegation{} + for rows.Next() { + var i UcanDelegation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Envelope, + &i.Iss, + &i.Aud, + &i.Sub, + &i.Cmd, + &i.Pol, + &i.Nbf, + &i.Exp, + &i.IsRoot, + &i.IsPowerline, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listSessionsByDID = `-- name: ListSessionsByDID :many SELECT s.id, s.did_id, s.credential_id, s.session_id, s.device_info, s.is_current, s.last_activity, s.expires_at, s.created_at, c.device_name, c.authenticator @@ -1574,102 +2088,6 @@ func (q *Queries) ListSyncCheckpoints(ctx context.Context, didID int64) ([]SyncC return items, nil } -const listUCANsByAudience = `-- name: ListUCANsByAudience :many -SELECT id, did_id, cid, issuer, audience, subject, capabilities, proof_chain, not_before, expires_at, nonce, facts, signature, raw_token, is_revoked, created_at FROM ucan_tokens -WHERE audience = ? AND is_revoked = 0 AND expires_at > datetime('now') -ORDER BY created_at DESC -` - -func (q *Queries) ListUCANsByAudience(ctx context.Context, audience string) ([]UcanToken, error) { - rows, err := q.db.QueryContext(ctx, listUCANsByAudience, audience) - if err != nil { - return nil, err - } - defer rows.Close() - items := []UcanToken{} - for rows.Next() { - var i UcanToken - if err := rows.Scan( - &i.ID, - &i.DidID, - &i.Cid, - &i.Issuer, - &i.Audience, - &i.Subject, - &i.Capabilities, - &i.ProofChain, - &i.NotBefore, - &i.ExpiresAt, - &i.Nonce, - &i.Facts, - &i.Signature, - &i.RawToken, - &i.IsRevoked, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listUCANsByDID = `-- name: ListUCANsByDID :many - -SELECT id, did_id, cid, issuer, audience, subject, capabilities, proof_chain, not_before, expires_at, nonce, facts, signature, raw_token, is_revoked, created_at FROM ucan_tokens -WHERE did_id = ? AND is_revoked = 0 AND expires_at > datetime('now') -ORDER BY created_at DESC -` - -// ============================================================================= -// UCAN TOKEN QUERIES -// ============================================================================= -func (q *Queries) ListUCANsByDID(ctx context.Context, didID int64) ([]UcanToken, error) { - rows, err := q.db.QueryContext(ctx, listUCANsByDID, didID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []UcanToken{} - for rows.Next() { - var i UcanToken - if err := rows.Scan( - &i.ID, - &i.DidID, - &i.Cid, - &i.Issuer, - &i.Audience, - &i.Subject, - &i.Capabilities, - &i.ProofChain, - &i.NotBefore, - &i.ExpiresAt, - &i.Nonce, - &i.Facts, - &i.Signature, - &i.RawToken, - &i.IsRevoked, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const listVerificationMethods = `-- name: ListVerificationMethods :many SELECT id, did_id, method_id, method_type, controller, public_key, purpose, created_at FROM verification_methods WHERE did_id = ? ORDER BY created_at @@ -1747,6 +2165,22 @@ func (q *Queries) ListVerifiedServices(ctx context.Context) ([]Service, error) { return items, nil } +const markInvocationExecuted = `-- name: MarkInvocationExecuted :exec +UPDATE ucan_invocations +SET executed_at = datetime('now'), result_cid = ? +WHERE cid = ? +` + +type MarkInvocationExecutedParams struct { + ResultCid *string `json:"result_cid"` + Cid string `json:"cid"` +} + +func (q *Queries) MarkInvocationExecuted(ctx context.Context, arg MarkInvocationExecutedParams) error { + _, err := q.db.ExecContext(ctx, markInvocationExecuted, arg.ResultCid, arg.Cid) + return err +} + const reactivateGrant = `-- name: ReactivateGrant :exec UPDATE grants SET status = 'active' WHERE id = ? AND status = 'suspended' ` @@ -1770,29 +2204,6 @@ func (q *Queries) RenameCredential(ctx context.Context, arg RenameCredentialPara return err } -const revokeDelegation = `-- name: RevokeDelegation :exec -UPDATE delegations SET status = 'revoked' WHERE id = ? -` - -func (q *Queries) RevokeDelegation(ctx context.Context, id int64) error { - _, err := q.db.ExecContext(ctx, revokeDelegation, id) - return err -} - -const revokeDelegationChain = `-- name: RevokeDelegationChain :exec -UPDATE delegations SET status = 'revoked' WHERE id = ? OR parent_id = ? -` - -type RevokeDelegationChainParams struct { - ID int64 `json:"id"` - ParentID *int64 `json:"parent_id"` -} - -func (q *Queries) RevokeDelegationChain(ctx context.Context, arg RevokeDelegationChainParams) error { - _, err := q.db.ExecContext(ctx, revokeDelegationChain, arg.ID, arg.ParentID) - return err -} - const revokeGrant = `-- name: RevokeGrant :exec UPDATE grants SET status = 'revoked' WHERE id = ? ` @@ -1802,15 +2213,6 @@ func (q *Queries) RevokeGrant(ctx context.Context, id int64) error { return err } -const revokeUCAN = `-- name: RevokeUCAN :exec -UPDATE ucan_tokens SET is_revoked = 1 WHERE cid = ? -` - -func (q *Queries) RevokeUCAN(ctx context.Context, cid string) error { - _, err := q.db.ExecContext(ctx, revokeUCAN, cid) - return err -} - const rotateKeyShare = `-- name: RotateKeyShare :exec UPDATE key_shares SET status = 'rotating', rotated_at = datetime('now') diff --git a/internal/migrations/query.sql b/internal/migrations/query.sql index 43caf05..26abf31 100644 --- a/internal/migrations/query.sql +++ b/internal/migrations/query.sql @@ -108,7 +108,7 @@ DELETE FROM key_shares WHERE id = ? AND did_id = ?; -- ============================================================================= -- name: ListAccountsByDID :many -SELECT a.*, k.public_key, k.curve +SELECT a.*, k.public_key as share_public_key, k.curve FROM accounts a JOIN key_shares k ON a.key_share_id = k.id WHERE a.did_id = ? @@ -140,42 +140,137 @@ UPDATE accounts SET label = ? WHERE id = ?; DELETE FROM accounts WHERE id = ? AND did_id = ?; -- ============================================================================= --- UCAN TOKEN QUERIES +-- UCAN DELEGATION QUERIES (v1.0.0-rc.1) -- ============================================================================= --- name: ListUCANsByDID :many -SELECT * FROM ucan_tokens -WHERE did_id = ? AND is_revoked = 0 AND expires_at > datetime('now') -ORDER BY created_at DESC; +-- name: GetDelegationByCID :one +SELECT * FROM ucan_delegations WHERE cid = ? LIMIT 1; --- name: ListUCANsByAudience :many -SELECT * FROM ucan_tokens -WHERE audience = ? AND is_revoked = 0 AND expires_at > datetime('now') -ORDER BY created_at DESC; +-- name: GetDelegationEnvelopeByCID :one +SELECT envelope FROM ucan_delegations WHERE cid = ? LIMIT 1; --- name: GetUCANByCID :one -SELECT * FROM ucan_tokens WHERE cid = ? LIMIT 1; - --- name: CreateUCAN :one -INSERT INTO ucan_tokens ( - did_id, cid, issuer, audience, subject, capabilities, - proof_chain, not_before, expires_at, nonce, facts, signature, raw_token +-- name: CreateDelegation :one +INSERT INTO ucan_delegations ( + did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline ) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *; --- name: RevokeUCAN :exec -UPDATE ucan_tokens SET is_revoked = 1 WHERE cid = ?; +-- name: ListDelegationsByDID :many +SELECT * FROM ucan_delegations +WHERE did_id = ? AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC; --- name: IsUCANRevoked :one -SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE ucan_cid = ?) as revoked; +-- name: ListDelegationsByIssuer :many +SELECT * FROM ucan_delegations +WHERE iss = ? AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC; + +-- name: ListDelegationsByAudience :many +SELECT * FROM ucan_delegations +WHERE aud = ? AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC; + +-- name: ListDelegationsBySubject :many +SELECT * FROM ucan_delegations +WHERE sub = ? AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC; + +-- name: ListDelegationsForCommand :many +SELECT * FROM ucan_delegations +WHERE did_id = ? + AND (cmd = ? OR cmd = '/' OR ? LIKE cmd || '/%') + AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC; + +-- name: ListRootDelegations :many +SELECT * FROM ucan_delegations +WHERE did_id = ? AND is_root = 1 AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC; + +-- name: ListPowerlineDelegations :many +SELECT * FROM ucan_delegations +WHERE did_id = ? AND is_powerline = 1 AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at DESC; + +-- name: DeleteDelegation :exec +DELETE FROM ucan_delegations WHERE cid = ? AND did_id = ?; + +-- name: CleanExpiredDelegations :exec +DELETE FROM ucan_delegations WHERE exp < datetime('now', '-30 days'); + +-- ============================================================================= +-- UCAN INVOCATION QUERIES (v1.0.0-rc.1) +-- ============================================================================= + +-- name: GetInvocationByCID :one +SELECT * FROM ucan_invocations WHERE cid = ? LIMIT 1; + +-- name: GetInvocationEnvelopeByCID :one +SELECT envelope FROM ucan_invocations WHERE cid = ? LIMIT 1; + +-- name: CreateInvocation :one +INSERT INTO ucan_invocations ( + did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: ListInvocationsByDID :many +SELECT * FROM ucan_invocations +WHERE did_id = ? +ORDER BY created_at DESC +LIMIT ?; + +-- name: ListInvocationsByIssuer :many +SELECT * FROM ucan_invocations +WHERE iss = ? +ORDER BY created_at DESC +LIMIT ?; + +-- name: ListInvocationsBySubject :many +SELECT * FROM ucan_invocations +WHERE sub = ? +ORDER BY created_at DESC +LIMIT ?; + +-- name: ListInvocationsForCommand :many +SELECT * FROM ucan_invocations +WHERE did_id = ? AND cmd = ? +ORDER BY created_at DESC +LIMIT ?; + +-- name: MarkInvocationExecuted :exec +UPDATE ucan_invocations +SET executed_at = datetime('now'), result_cid = ? +WHERE cid = ?; + +-- name: ListPendingInvocations :many +SELECT * FROM ucan_invocations +WHERE did_id = ? AND executed_at IS NULL AND (exp IS NULL OR exp > datetime('now')) +ORDER BY created_at ASC; + +-- name: CleanOldInvocations :exec +DELETE FROM ucan_invocations WHERE created_at < datetime('now', '-90 days'); + +-- ============================================================================= +-- UCAN REVOCATION QUERIES +-- ============================================================================= -- name: CreateRevocation :exec -INSERT INTO ucan_revocations (ucan_cid, revoked_by, reason) -VALUES (?, ?, ?); +INSERT INTO ucan_revocations (delegation_cid, revoked_by, invocation_cid, reason) +VALUES (?, ?, ?, ?); --- name: CleanExpiredUCANs :exec -DELETE FROM ucan_tokens WHERE expires_at < datetime('now', '-30 days'); +-- name: IsDelegationRevoked :one +SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE delegation_cid = ?) as revoked; + +-- name: GetRevocation :one +SELECT * FROM ucan_revocations WHERE delegation_cid = ? LIMIT 1; + +-- name: ListRevocationsByRevoker :many +SELECT * FROM ucan_revocations +WHERE revoked_by = ? +ORDER BY revoked_at DESC; -- ============================================================================= -- SESSION QUERIES @@ -251,7 +346,7 @@ ORDER BY g.last_used DESC NULLS LAST; SELECT * FROM grants WHERE did_id = ? AND service_id = ? LIMIT 1; -- name: CreateGrant :one -INSERT INTO grants (did_id, service_id, ucan_id, scopes, accounts, expires_at) +INSERT INTO grants (did_id, service_id, delegation_cid, scopes, accounts, expires_at) VALUES (?, ?, ?, ?, ?, ?) RETURNING *; @@ -273,41 +368,6 @@ UPDATE grants SET status = 'active' WHERE id = ? AND status = 'suspended'; -- name: CountActiveGrants :one SELECT COUNT(*) FROM grants WHERE did_id = ? AND status = 'active'; --- ============================================================================= --- DELEGATION QUERIES --- ============================================================================= - --- name: ListDelegationsByDelegator :many -SELECT * FROM delegations -WHERE delegator = ? AND status = 'active' -ORDER BY created_at DESC; - --- name: ListDelegationsByDelegate :many -SELECT * FROM delegations -WHERE delegate = ? AND status = 'active' AND (expires_at IS NULL OR expires_at > datetime('now')) -ORDER BY created_at DESC; - --- name: ListDelegationsForResource :many -SELECT * FROM delegations -WHERE did_id = ? AND resource = ? AND status = 'active' -ORDER BY depth, created_at; - --- name: GetDelegationChain :many -SELECT * FROM delegations WHERE id = ? OR parent_id = ? ORDER BY depth DESC; - --- name: CreateDelegation :one -INSERT INTO delegations ( - did_id, ucan_id, delegator, delegate, resource, action, caveats, parent_id, depth, expires_at -) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) -RETURNING *; - --- name: RevokeDelegation :exec -UPDATE delegations SET status = 'revoked' WHERE id = ?; - --- name: RevokeDelegationChain :exec -UPDATE delegations SET status = 'revoked' WHERE id = ? OR parent_id = ?; - -- ============================================================================= -- SYNC QUERIES -- ============================================================================= diff --git a/internal/migrations/schema.sql b/internal/migrations/schema.sql index cb78c4b..317e255 100644 --- a/internal/migrations/schema.sql +++ b/internal/migrations/schema.sql @@ -1,6 +1,7 @@ -- ============================================================================= -- NEBULA KEY ENCLAVE SCHEMA -- Encrypted SQLite database for sensitive wallet data +-- UCAN v1.0.0-rc.1 compliant -- ============================================================================= PRAGMA foreign_keys = ON; @@ -112,44 +113,97 @@ CREATE INDEX idx_accounts_address ON accounts(address); CREATE INDEX idx_accounts_chain_id ON accounts(chain_id); -- ============================================================================= --- UCAN AUTHORIZATION +-- UCAN AUTHORIZATION (v1.0.0-rc.1) -- ============================================================================= --- UCAN Tokens: Capability authorization tokens -CREATE TABLE IF NOT EXISTS ucan_tokens ( +-- UCAN Delegations: v1.0.0-rc.1 delegation envelopes +-- Stores sealed DAG-CBOR envelopes with extracted fields for indexing +CREATE TABLE IF NOT EXISTS ucan_delegations ( id INTEGER PRIMARY KEY, did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, - cid TEXT NOT NULL UNIQUE, -- Content ID of UCAN (for dedup) - issuer TEXT NOT NULL, -- iss: DID of issuer - audience TEXT NOT NULL, -- aud: DID of recipient - subject TEXT, -- sub: DID token is about (optional) - capabilities TEXT NOT NULL, -- JSON array of capabilities - proof_chain TEXT DEFAULT '[]', -- JSON array of parent UCAN CIDs - not_before TEXT, -- nbf: validity start - expires_at TEXT NOT NULL, -- exp: expiration time - nonce TEXT, -- Replay protection - facts TEXT DEFAULT '{}', -- Additional facts (JSON) - signature TEXT NOT NULL, -- Base64 encoded signature - raw_token TEXT NOT NULL, -- Full encoded UCAN token - is_revoked INTEGER NOT NULL DEFAULT 0, + + -- Content Identifier (immutable, unique) + cid TEXT NOT NULL UNIQUE, + + -- Sealed envelope (DAG-CBOR encoded) + envelope BLOB NOT NULL, + + -- Extracted fields for indexing/queries + iss TEXT NOT NULL, -- Issuer DID + aud TEXT NOT NULL, -- Audience DID + sub TEXT, -- Subject DID (null = powerline) + cmd TEXT NOT NULL, -- Command (e.g., "/vault/read") + + -- Policy stored as JSON for inspection + pol TEXT DEFAULT '[]', -- Policy JSON + + -- Temporal fields + nbf TEXT, -- Not before (ISO8601) + exp TEXT, -- Expiration (ISO8601, null = never) + + -- Metadata + is_root INTEGER NOT NULL DEFAULT 0, -- iss == sub + is_powerline INTEGER NOT NULL DEFAULT 0, -- sub IS NULL created_at TEXT NOT NULL DEFAULT (datetime('now')) ); -CREATE INDEX idx_ucan_tokens_did_id ON ucan_tokens(did_id); -CREATE INDEX idx_ucan_tokens_issuer ON ucan_tokens(issuer); -CREATE INDEX idx_ucan_tokens_audience ON ucan_tokens(audience); -CREATE INDEX idx_ucan_tokens_expires_at ON ucan_tokens(expires_at); +CREATE INDEX idx_ucan_delegations_cid ON ucan_delegations(cid); +CREATE INDEX idx_ucan_delegations_did_id ON ucan_delegations(did_id); +CREATE INDEX idx_ucan_delegations_iss ON ucan_delegations(iss); +CREATE INDEX idx_ucan_delegations_aud ON ucan_delegations(aud); +CREATE INDEX idx_ucan_delegations_sub ON ucan_delegations(sub); +CREATE INDEX idx_ucan_delegations_cmd ON ucan_delegations(cmd); +CREATE INDEX idx_ucan_delegations_exp ON ucan_delegations(exp); --- UCAN Revocations: Revoked UCAN tokens +-- UCAN Invocations: v1.0.0-rc.1 invocation envelopes (audit log) +CREATE TABLE IF NOT EXISTS ucan_invocations ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + + -- Content Identifier + cid TEXT NOT NULL UNIQUE, + + -- Sealed envelope (DAG-CBOR encoded) + envelope BLOB NOT NULL, + + -- Extracted fields for indexing + iss TEXT NOT NULL, -- Invoker DID + sub TEXT NOT NULL, -- Subject DID + aud TEXT, -- Executor DID (if different from sub) + cmd TEXT NOT NULL, -- Command invoked + + -- Proof chain (JSON array of delegation CIDs) + prf TEXT NOT NULL DEFAULT '[]', + + -- Temporal + exp TEXT, -- Expiration + iat TEXT, -- Issued at + + -- Execution tracking + executed_at TEXT, -- When actually executed + result_cid TEXT, -- CID of receipt (if executed) + + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_ucan_invocations_cid ON ucan_invocations(cid); +CREATE INDEX idx_ucan_invocations_did_id ON ucan_invocations(did_id); +CREATE INDEX idx_ucan_invocations_iss ON ucan_invocations(iss); +CREATE INDEX idx_ucan_invocations_sub ON ucan_invocations(sub); +CREATE INDEX idx_ucan_invocations_cmd ON ucan_invocations(cmd); + +-- UCAN Revocations: Track revoked delegations CREATE TABLE IF NOT EXISTS ucan_revocations ( id INTEGER PRIMARY KEY, - ucan_cid TEXT NOT NULL UNIQUE, -- CID of revoked UCAN - revoked_by TEXT NOT NULL, -- DID that revoked + delegation_cid TEXT NOT NULL UNIQUE, -- CID of revoked delegation + revoked_by TEXT NOT NULL, -- Revoker DID + invocation_cid TEXT, -- CID of revocation invocation reason TEXT, revoked_at TEXT NOT NULL DEFAULT (datetime('now')) ); -CREATE INDEX idx_ucan_revocations_cid ON ucan_revocations(ucan_cid); +CREATE INDEX idx_ucan_revocations_delegation_cid ON ucan_revocations(delegation_cid); +CREATE INDEX idx_ucan_revocations_revoked_by ON ucan_revocations(revoked_by); -- ============================================================================= -- DEVICE SESSIONS @@ -191,12 +245,12 @@ CREATE TABLE IF NOT EXISTS services ( CREATE INDEX idx_services_origin ON services(origin); --- Grants: User grants to services +-- Grants: User grants to services (backed by UCAN delegations) CREATE TABLE IF NOT EXISTS grants ( id INTEGER PRIMARY KEY, did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, service_id INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE, - ucan_id INTEGER REFERENCES ucan_tokens(id) ON DELETE SET NULL, + delegation_cid TEXT REFERENCES ucan_delegations(cid) ON DELETE SET NULL, -- v1.0.0-rc.1 delegation scopes TEXT NOT NULL DEFAULT '[]', -- JSON array of granted scopes accounts TEXT NOT NULL DEFAULT '[]', -- JSON array of account IDs exposed status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'revoked')), @@ -209,32 +263,7 @@ CREATE TABLE IF NOT EXISTS grants ( CREATE INDEX idx_grants_did_id ON grants(did_id); CREATE INDEX idx_grants_service_id ON grants(service_id); CREATE INDEX idx_grants_status ON grants(status); - --- ============================================================================= --- CAPABILITY DELEGATIONS --- ============================================================================= - --- Delegations: Capability delegation chains -CREATE TABLE IF NOT EXISTS delegations ( - id INTEGER PRIMARY KEY, - did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, - ucan_id INTEGER NOT NULL REFERENCES ucan_tokens(id) ON DELETE CASCADE, - delegator TEXT NOT NULL, -- DID that delegated - delegate TEXT NOT NULL, -- DID that received delegation - resource TEXT NOT NULL, -- Resource URI (e.g., "sonr://vault/*") - action TEXT NOT NULL, -- Action (e.g., "sign", "read", "write") - caveats TEXT DEFAULT '{}', -- JSON: restrictions/conditions - parent_id INTEGER REFERENCES delegations(id), -- Parent delegation (for chains) - depth INTEGER NOT NULL DEFAULT 0, -- Delegation depth (0 = root) - status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked', 'expired')), - created_at TEXT NOT NULL DEFAULT (datetime('now')), - expires_at TEXT -); - -CREATE INDEX idx_delegations_did_id ON delegations(did_id); -CREATE INDEX idx_delegations_delegator ON delegations(delegator); -CREATE INDEX idx_delegations_delegate ON delegations(delegate); -CREATE INDEX idx_delegations_resource ON delegations(resource); +CREATE INDEX idx_grants_delegation_cid ON grants(delegation_cid); -- ============================================================================= -- SYNC STATE