ucan

package module
v0.0.0-...-4feafbe Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2023 License: GPL-3.0 Imports: 15 Imported by: 0

README

UCAN Tokens in Go

UCAN

Originally by @b5 as one of the first from scratch implementations of UCAN outside of the Fission teams initial work in TypeScript / Haskell.

If you're interested in updating this codebase to the 1.0 version of the UCAN spec, get involved in the discussion »

About UCAN Tokens

User Controlled Authorization Networks (UCANs) are a way of doing authorization where users are fully in control. OAuth is designed for a centralized world, UCAN is the distributed user controlled version.

UCAN Gopher

Artwork by Bruno Monts. Thank you Renee French for creating the Go Gopher

Documentation

Overview

Package ucan implements User-Controlled Authorization Network tokens by fission: https://whitepaper.fission.codes/access-control/ucan/ucan-tokens

From the paper: The UCAN format is designed as an authenticated digraph in some larger authorization space. The other way to view this is as a function from a set of authorizations (“UCAN proofs“) to a subset output (“UCAN capabilities”).

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/ucan-wg/go-ucan"
)

func main() {
	source, err := ucan.NewPrivKeySource(keyOne)
	panicIfError(err)

	audienceDID, err := ucan.DIDStringFromPublicKey(keyOne.GetPublic())
	panicIfError(err)

	caps := ucan.NewNestedCapabilities("SUPER_USER", "OVERWRITE", "SOFT_DELETE", "REVISE", "CREATE")
	att := ucan.Attenuations{
		{caps.Cap("SUPER_USER"), ucan.NewStringLengthResource("api", "*")},
		{caps.Cap("SUPER_USER"), ucan.NewStringLengthResource("dataset", "b5:world_bank_population:*")},
	}
	zero := time.Time{}

	// create a root UCAN
	origin, err := source.NewOriginToken(audienceDID, att, nil, zero, zero)
	panicIfError(err)

	id, err := origin.CID()
	panicIfError(err)

	fmt.Printf("cid of root UCAN: %s\n", id.String())

	att = ucan.Attenuations{
		{caps.Cap("SUPER_USER"), ucan.NewStringLengthResource("dataset", "third:resource")},
	}

	if _, err = source.NewAttenuatedToken(origin, audienceDID, att, nil, zero, zero); err != nil {
		fmt.Println(err)
	}

	att = ucan.Attenuations{
		{caps.Cap("OVERWRITE"), ucan.NewStringLengthResource("dataset", "b5:world_bank_population:*")},
	}

	derivedToken, err := source.NewAttenuatedToken(origin, audienceDID, att, nil, zero, zero)
	panicIfError(err)

	id, err = derivedToken.CID()
	panicIfError(err)

	fmt.Printf("cid of derived UCAN: %s\n", id.String())

	p := exampleParser()
	tok, err := p.ParseAndVerify(context.Background(), origin.Raw)
	panicIfError(err)

	fmt.Printf("issuer DID key type: %s\n", tok.Issuer.Type().String())

}

func panicIfError(err error) {
	if err != nil {
		panic(err)
	}
}

func exampleParser() *ucan.TokenParser {
	caps := ucan.NewNestedCapabilities("SUPER_USER", "OVERWRITE", "SOFT_DELETE", "REVISE", "CREATE")

	ac := func(m map[string]interface{}) (ucan.Attenuation, error) {
		var (
			cap string
			rsc ucan.Resource
		)
		for key, vali := range m {
			val, ok := vali.(string)
			if !ok {
				return ucan.Attenuation{}, fmt.Errorf(`expected attenuation value to be a string`)
			}

			if key == ucan.CapKey {
				cap = val
			} else {
				rsc = ucan.NewStringLengthResource(key, val)
			}
		}

		return ucan.Attenuation{
			Rsc: rsc,
			Cap: caps.Cap(cap),
		}, nil
	}

	store := ucan.NewMemTokenStore()
	return ucan.NewTokenParser(ac, ucan.StringDIDPubKeyResolver{}, store.(ucan.CIDBytesResolver))
}
Output:

cid of root UCAN: bafkreihl4b2ncrijeutlkppykgspz6wm3q2o4wiej6njl6tj7k2xa3zcue
scope of ucan attenuations must be less than it's parent
cid of derived UCAN: bafkreifhpoxctmbmvocdevfbmio6cpzltwauesyyjycipnylocoykwghzu
issuer DID key type: RSA

Index

Examples

Constants

View Source
const (
	// UCANVersion is the current version of the UCAN spec
	UCANVersion = "0.7.0"
	// UCANVersionKey is the key used in version headers for the UCAN spec
	UCANVersionKey = "ucv"
	// PrfKey denotes "Proofs" in a UCAN. Stored in JWT Claims
	PrfKey = "prf"
	// FctKey denotes "Facts" in a UCAN. Stored in JWT Claims
	FctKey = "fct"
	// AttKey denotes "Attenuations" in a UCAN. Stored in JWT Claims
	AttKey = "att"
	// CapKey indicates a resource Capability. Used in an attenuation
	CapKey = "cap"
)

Variables

View Source
var ErrInvalidToken = errors.New("invalid access token")

ErrInvalidToken indicates an access token is invalid

View Source
var ErrTokenNotFound = errors.New("access token not found")

ErrTokenNotFound is returned by stores that cannot find an access token for a given key

Functions

func CtxWithToken

func CtxWithToken(ctx context.Context, t Token) context.Context

CtxWithToken adds a UCAN value to a context

func DIDStringFromPublicKey

func DIDStringFromPublicKey(pub crypto.PubKey) (string, error)

DIDStringFromPublicKey creates a did:key identifier string from a public key

Types

type Attenuation

type Attenuation struct {
	Cap Capability
	Rsc Resource
}

Attenuation is a capability on a resource

func (Attenuation) Contains

func (a Attenuation) Contains(b Attenuation) bool

Contains returns true if both

func (Attenuation) MarshalJSON

func (a Attenuation) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaller interface

func (Attenuation) String

func (a Attenuation) String() string

String formats an attenuation as a string

type AttenuationConstructorFunc

type AttenuationConstructorFunc func(v map[string]interface{}) (Attenuation, error)

AttenuationConstructorFunc is a function that creates an attenuation from a map Users of this package provide an Attenuation Constructor to the parser to bind attenuation logic to a UCAN

type Attenuations

type Attenuations []Attenuation

Attenuations is a list of attenuations

func (Attenuations) Contains

func (att Attenuations) Contains(b Attenuations) bool

Contains is true if all attenuations in b are contained

func (Attenuations) String

func (att Attenuations) String() string

type CIDBytesResolver

type CIDBytesResolver interface {
	ResolveCIDBytes(ctx context.Context, id cid.Cid) ([]byte, error)
}

CIDBytesResolver is a small interface for turning a CID into the bytes they reference. In practice this may be backed by a network connection that can fetch CIDs, eg: IPFS.

type Capability

type Capability interface {
	// A Capability must be expressable as a string
	String() string
	// Capabilities must be comparable to other same-type capabilities
	Contains(b Capability) bool
}

Capability is an action users can perform

type Claims

type Claims struct {
	*jwt.StandardClaims
	// the "inputs" to this token, a chain UCAN tokens with broader scopes &
	// deadlines than this token
	// Proofs are UCAN chains, leading back to a self-evident origin token
	Proofs []Proof `json:"prf,omitempty"`
	// the "outputs" of this token, an array of heterogenous resources &
	// capabilities
	Attenuations Attenuations `json:"att,omitempty"`
	// Facts are facts, jack.
	Facts []Fact `json:"fct,omitempty"`
}

Claims is the claims component of a UCAN token. UCAN claims are expressed as a standard JWT claims object with additional special fields

type CtxKey

type CtxKey string

CtxKey defines a distinct type for context keys used by the access package

const TokenCtxKey CtxKey = "UCAN"

TokenCtxKey is the key for adding an access UCAN to a context.Context

type DIDPubKeyResolver

type DIDPubKeyResolver interface {
	ResolveDIDKey(ctx context.Context, did string) (didkey.ID, error)
}

DIDPubKeyResolver turns did:key Decentralized IDentifiers into a public key, possibly using a network request

type Fact

type Fact struct {
	// contains filtered or unexported fields
}

Fact is self-evident statement

type NestedCapabilities

type NestedCapabilities struct {
	// contains filtered or unexported fields
}

NestedCapabilities is a basic implementation of the Capabilities interface based on a hierarchal list of strings ordered from most to least capable It is both a capability and a capability factory with the .Cap method

func NewNestedCapabilities

func NewNestedCapabilities(strs ...string) NestedCapabilities

NewNestedCapabilities creates a set of NestedCapabilities

func (NestedCapabilities) Cap

func (nc NestedCapabilities) Cap(str string) Capability

Cap creates a new capability from the hierarchy

func (NestedCapabilities) Contains

func (nc NestedCapabilities) Contains(cap Capability) bool

Contains returns true if cap is equal or less than the NestedCapability value

func (NestedCapabilities) String

func (nc NestedCapabilities) String() string

String returns the Capability value as a string

type Proof

type Proof string

Proof is a string representing a fact. Expected to be either a raw UCAN token or the CID of a raw UCAN token

func (Proof) IsCID

func (prf Proof) IsCID() bool

IsCID returns true if the Proof string is a CID

type RawToken

type RawToken struct {
	Key string
	Raw string
}

RawToken is a struct that binds a key to a raw token string

type RawTokens

type RawTokens []RawToken

RawTokens is a list of tokens that implements sorting by keys

func (RawTokens) Len

func (rts RawTokens) Len() int

func (RawTokens) Less

func (rts RawTokens) Less(a, b int) bool

func (RawTokens) Swap

func (rts RawTokens) Swap(i, j int)

type Resource

type Resource interface {
	Type() string
	Value() string
	Contains(b Resource) bool
}

Resource is a unique identifier for a thing, usually stored state. Resources are organized by string types

func NewStringLengthResource

func NewStringLengthResource(typ, val string) Resource

NewStringLengthResource is a silly implementation of resource to use while I figure out what an OR filter on strings is. Don't use this.

type Source

type Source interface {
	NewOriginToken(audienceDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*Token, error)
	NewAttenuatedToken(parent *Token, audienceDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*Token, error)
}

Source creates tokens, and provides a verification key for all tokens it creates

implementations of Source must conform to the assertion test defined in the spec subpackage

func NewPrivKeySource

func NewPrivKeySource(privKey crypto.PrivKey) (Source, error)

NewPrivKeySource creates an authentication interface backed by a single private key. Intended for a node running as remote, or providing a public API

type StringDIDPubKeyResolver

type StringDIDPubKeyResolver struct{}

StringDIDPubKeyResolver implements the DIDPubKeyResolver interface without any network backing. Works if the key string given contains the public key itself

func (StringDIDPubKeyResolver) ResolveDIDKey

func (StringDIDPubKeyResolver) ResolveDIDKey(ctx context.Context, didStr string) (didkey.ID, error)

ResolveDIDKey extracts a public key from a did:key string

type Token

type Token struct {
	// Entire UCAN as a signed JWT string
	Raw      string
	Issuer   didkey.ID
	Audience didkey.ID
	// the "inputs" to this token, a chain UCAN tokens with broader scopes &
	// deadlines than this token
	Proofs []Proof `json:"prf,omitempty"`
	// the "outputs" of this token, an array of heterogenous resources &
	// capabilities
	Attenuations Attenuations `json:"att,omitempty"`
	// Facts are facts, jack.
	Facts []Fact `json:"fct,omitempty"`
}

Token is a JSON Web Token (JWT) that contains special keys that make the token a UCAN

func FromCtx

func FromCtx(ctx context.Context) *Token

FromCtx extracts a token from a given context if one is set, returning nil otherwise

func (*Token) CID

func (t *Token) CID() (cid.Cid, error)

CID calculates the cid of a UCAN using the default prefix

func (*Token) PrefixCID

func (t *Token) PrefixCID(pref cid.Prefix) (cid.Cid, error)

PrefixCID calculates the CID of a token with a supplied prefix

type TokenParser

type TokenParser struct {
	// contains filtered or unexported fields
}

TokenParser parses a raw string into a Token

func NewTokenParser

NewTokenParser constructs a token parser

func (*TokenParser) ParseAndVerify

func (p *TokenParser) ParseAndVerify(ctx context.Context, raw string) (*Token, error)

ParseAndVerify will parse, validate and return a token

type TokenStore

type TokenStore interface {
	PutToken(ctx context.Context, key, rawToken string) error
	RawToken(ctx context.Context, key string) (rawToken string, err error)
	DeleteToken(ctx context.Context, key string) (err error)
	ListTokens(ctx context.Context, offset, limit int) (results []RawToken, err error)
}

TokenStore is a store intended for clients, who need to persist jwts. It deals in raw, string-formatted json web tokens, which are more useful when working with APIs, but validates the tokens are well-formed when placed in the store

implementations of TokenStore must conform to the assertion test defined in the spec subpackage

func NewMemTokenStore

func NewMemTokenStore() TokenStore

NewMemTokenStore creates an in-memory token store

Directories

Path Synopsis
Package didkey implements the did:key method.
Package didkey implements the did:key method.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL