Merge pull request #8 from dholms/audience

Change "subject" to "audience"
This commit is contained in:
Justin Johnson
2022-03-26 06:25:01 -05:00
committed by GitHub
3 changed files with 31 additions and 31 deletions

View File

@@ -12,7 +12,7 @@ func Example() {
source, err := ucan.NewPrivKeySource(keyOne)
panicIfError(err)
subjectDID, err := ucan.DIDStringFromPublicKey(keyOne.GetPublic())
audienceDID, err := ucan.DIDStringFromPublicKey(keyOne.GetPublic())
panicIfError(err)
caps := ucan.NewNestedCapabilities("SUPER_USER", "OVERWRITE", "SOFT_DELETE", "REVISE", "CREATE")
@@ -23,7 +23,7 @@ func Example() {
zero := time.Time{}
// create a root UCAN
origin, err := source.NewOriginToken(subjectDID, att, nil, zero, zero)
origin, err := source.NewOriginToken(audienceDID, att, nil, zero, zero)
panicIfError(err)
id, err := origin.CID()
@@ -35,7 +35,7 @@ func Example() {
{caps.Cap("SUPER_USER"), ucan.NewStringLengthResource("dataset", "third:resource")},
}
if _, err = source.NewAttenuatedToken(origin, subjectDID, att, nil, zero, zero); err != nil {
if _, err = source.NewAttenuatedToken(origin, audienceDID, att, nil, zero, zero); err != nil {
fmt.Println(err)
}
@@ -43,7 +43,7 @@ func Example() {
{caps.Cap("OVERWRITE"), ucan.NewStringLengthResource("dataset", "b5:world_bank_population:*")},
}
derivedToken, err := source.NewAttenuatedToken(origin, subjectDID, att, nil, zero, zero)
derivedToken, err := source.NewAttenuatedToken(origin, audienceDID, att, nil, zero, zero)
panicIfError(err)
id, err = derivedToken.CID()
@@ -58,9 +58,9 @@ func Example() {
fmt.Printf("issuer DID key type: %s\n", tok.Issuer.Type().String())
// Output:
// cid of root UCAN: bafkreid6l4npxfwei2ccpelqkhirthcaa2lpmg3xa4rz4lrsqz2rgujp5m
// cid of root UCAN: bafkreihl4b2ncrijeutlkppykgspz6wm3q2o4wiej6njl6tj7k2xa3zcue
// scope of ucan attenuations must be less than it's parent
// cid of derived UCAN: bafkreibeirwwbnz2cmboq5t47d3rsjxrd4rcnreptuozwcc2lr3pbnzuki
// cid of derived UCAN: bafkreifhpoxctmbmvocdevfbmio6cpzltwauesyyjycipnylocoykwghzu
// issuer DID key type: RSA
}

View File

@@ -46,9 +46,9 @@ const (
// token a UCAN
type Token struct {
// Entire UCAN as a signed JWT string
Raw string
Issuer didkey.ID
Subject didkey.ID
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"`
@@ -118,8 +118,8 @@ type CIDBytesResolver interface {
// implementations of Source must conform to the assertion test defined in the
// spec subpackage
type Source interface {
NewOriginToken(subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*Token, error)
NewAttenuatedToken(parent *Token, subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*Token, error)
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)
}
type pkSource struct {
@@ -196,24 +196,24 @@ func NewPrivKeySource(privKey crypto.PrivKey) (Source, error) {
}, nil
}
func (a *pkSource) NewOriginToken(subjectDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*Token, error) {
return a.newToken(subjectDID, nil, att, fct, nbf, exp)
func (a *pkSource) NewOriginToken(audienceDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*Token, error) {
return a.newToken(audienceDID, nil, att, fct, nbf, exp)
}
func (a *pkSource) NewAttenuatedToken(parent *Token, subjectDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*Token, error) {
func (a *pkSource) NewAttenuatedToken(parent *Token, audienceDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*Token, error) {
if !parent.Attenuations.Contains(att) {
return nil, fmt.Errorf("scope of ucan attenuations must be less than it's parent")
}
return a.newToken(subjectDID, append(parent.Proofs, Proof(parent.Raw)), att, fct, nbf, exp)
return a.newToken(audienceDID, append(parent.Proofs, Proof(parent.Raw)), att, fct, nbf, exp)
}
// CreateToken returns a new JWT token
func (a *pkSource) newToken(subjectDID string, prf []Proof, att Attenuations, fct []Fact, nbf, exp time.Time) (*Token, error) {
func (a *pkSource) newToken(audienceDID string, prf []Proof, att Attenuations, fct []Fact, nbf, exp time.Time) (*Token, error) {
// create a signer for rsa 256
t := jwt.New(a.signingMethod)
// if _, err := did.Parse(subjectDID); err != nil {
// return nil, fmt.Errorf("invalid subject DID: %w", err)
// if _, err := did.Parse(audienceDID); err != nil {
// return nil, fmt.Errorf("invalid audience DID: %w", err)
// }
t.Header[UCANVersionKey] = UCANVersion
@@ -234,7 +234,7 @@ func (a *pkSource) newToken(subjectDID string, prf []Proof, att Attenuations, fc
t.Claims = &Claims{
StandardClaims: &jwt.StandardClaims{
Issuer: a.issuerDID,
Subject: subjectDID,
Audience: audienceDID,
NotBefore: nbfUnix,
// set the expire time
// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
@@ -317,7 +317,7 @@ func (p *TokenParser) parseAndVerify(ctx context.Context, raw string, child *Tok
var iss didkey.ID
// TODO(b5): we're double parsing here b/c the jwt lib we're using doesn't expose
// an API (that I know of) for storing parsed issuer / subjects
// an API (that I know of) for storing parsed issuer / audience
if issStr, ok := mc["iss"].(string); ok {
iss, err = didkey.Parse(issStr)
if err != nil {
@@ -327,16 +327,16 @@ func (p *TokenParser) parseAndVerify(ctx context.Context, raw string, child *Tok
return nil, fmt.Errorf(`"iss" key is not in claims`)
}
var sub didkey.ID
var aud didkey.ID
// TODO(b5): we're double parsing here b/c the jwt lib we're using doesn't expose
// an API (that I know of) for storing parsed issuer / subjects
if subStr, ok := mc["sub"].(string); ok {
sub, err = didkey.Parse(subStr)
// an API (that I know of) for storing parsed issuer / audience
if audStr, ok := mc["aud"].(string); ok {
aud, err = didkey.Parse(audStr)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf(`"sub" key is not in claims`)
return nil, fmt.Errorf(`"aud" key is not in claims`)
}
var att Attenuations
@@ -372,7 +372,7 @@ func (p *TokenParser) parseAndVerify(ctx context.Context, raw string, child *Tok
return &Token{
Raw: raw,
Issuer: iss,
Subject: sub,
Audience: aud,
Attenuations: att,
Proofs: prf,
}, nil

View File

@@ -62,7 +62,7 @@ func TestPrivKeySource(t *testing.T) {
t.Fatal(err)
}
expect := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNy4wIn0.eyJpc3MiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJzdWIiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJhdHQiOlt7ImFwaSI6IioiLCJjYXAiOiJTVVBFUl9VU0VSIn0seyJjYXAiOiJTVVBFUl9VU0VSIiwiZGF0YXNldCI6ImI1OndvcmxkX2JhbmtfcG9wdWxhdGlvbjoqIn1dfQ.ndbCaw73SX-cmGz0tQq16nsQsp3Thw1lyza_W8juvoemGo5aiT-ayOSvgl4oeShoBfBiz7NQ7T6FUDfpiUF8cwfP2WLbW-TXDan9arTKw_KLZ3j-re5ACFRVKDWy6sSfJ1BncX9ADt2nmistxYc5uE4wO0RPl9ulSjWsL4mVq6pf77ajPehUYvDgSerGzcvNOmxibDC760gb-F_2jijnA-TEwYvitFaZ_3MmmKhUiMuPP_XqYVWnmuNhje-N1yQg_tEJCsuj25zqFefW9jLPnvkywgyrPyMcrXj1ZQMCX9n37TQbC2WUZE7KvuBkuK11dAkrtXkBH-Gr9QiDWLsdPw`
expect := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNy4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJpc3MiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJhdHQiOlt7ImFwaSI6IioiLCJjYXAiOiJTVVBFUl9VU0VSIn0seyJjYXAiOiJTVVBFUl9VU0VSIiwiZGF0YXNldCI6ImI1OndvcmxkX2JhbmtfcG9wdWxhdGlvbjoqIn1dfQ.ggJJ8fovwKVbq_hLb0sVKlvkFRcKJWb-55RgIagjzO9D9I0pZ5g3elkOCkf1XA60_32fE-K_Dj97np6-BQx_F5u_PofYi6C0BYfR4YATA5jzymT7nBLrdOzhr0Vh2FChYoyM82CT4tkOGSlnvaoCgB4OLMOzlvpWmbfRi5GB3d6yAfdhBGsDuAJ9L7V_N5WBFpjarSu2xAYhVUhFM8IbILHMipXWRd2sP-_DzxmsneS7qu9fxAk35DDC_8cONPp1ZrFUw-VhGts0jYOVuExl_ZWqE3y2XJLtiperxFWO45q59WdULrn15ciQjRhn2O-QmXHmOxLNZMCI6lbFFiCHWg`
if expect != root.Raw {
t.Errorf("token mismatch. expected: %q.\ngot: %q", expect, root.Raw)
}
@@ -77,7 +77,7 @@ func TestPrivKeySource(t *testing.T) {
}
cidStr := mustCidString(t, derivedToken)
expectCID := "bafkreibeirwwbnz2cmboq5t47d3rsjxrd4rcnreptuozwcc2lr3pbnzuki"
expectCID := "bafkreifhpoxctmbmvocdevfbmio6cpzltwauesyyjycipnylocoykwghzu"
if expectCID != cidStr {
t.Errorf("derived token CID mismatch. expected: %q.\ngot: %q", expectCID, cidStr)
@@ -101,7 +101,7 @@ func TestED25519PrivKeySource(t *testing.T) {
t.Fatal(err)
}
subjectDID, err := ucan.DIDStringFromPublicKey(keyOne.GetPublic())
audienceDID, err := ucan.DIDStringFromPublicKey(keyOne.GetPublic())
if err != nil {
panic(err)
}
@@ -109,7 +109,7 @@ func TestED25519PrivKeySource(t *testing.T) {
zero := time.Time{}
// create a root UCAN
origin, err := source.NewOriginToken(subjectDID, nil, nil, zero, zero)
origin, err := source.NewOriginToken(audienceDID, nil, nil, zero, zero)
if err != nil {
panic(err)
}
@@ -130,7 +130,7 @@ func TestTokenSource(t *testing.T) {
}
func TestTokenParse(t *testing.T) {
raw := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNy4wIn0.eyJpc3MiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJzdWIiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJhdHQiOlt7ImFwaSI6IioiLCJjYXAiOiJTVVBFUl9VU0VSIn0seyJjYXAiOiJTVVBFUl9VU0VSIiwiZGF0YXNldCI6ImI1OndvcmxkX2JhbmtfcG9wdWxhdGlvbjoqIn1dfQ.ndbCaw73SX-cmGz0tQq16nsQsp3Thw1lyza_W8juvoemGo5aiT-ayOSvgl4oeShoBfBiz7NQ7T6FUDfpiUF8cwfP2WLbW-TXDan9arTKw_KLZ3j-re5ACFRVKDWy6sSfJ1BncX9ADt2nmistxYc5uE4wO0RPl9ulSjWsL4mVq6pf77ajPehUYvDgSerGzcvNOmxibDC760gb-F_2jijnA-TEwYvitFaZ_3MmmKhUiMuPP_XqYVWnmuNhje-N1yQg_tEJCsuj25zqFefW9jLPnvkywgyrPyMcrXj1ZQMCX9n37TQbC2WUZE7KvuBkuK11dAkrtXkBH-Gr9QiDWLsdPw`
raw := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNy4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJpc3MiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJhdHQiOlt7ImFwaSI6IioiLCJjYXAiOiJTVVBFUl9VU0VSIn0seyJjYXAiOiJTVVBFUl9VU0VSIiwiZGF0YXNldCI6ImI1OndvcmxkX2JhbmtfcG9wdWxhdGlvbjoqIn1dfQ.ggJJ8fovwKVbq_hLb0sVKlvkFRcKJWb-55RgIagjzO9D9I0pZ5g3elkOCkf1XA60_32fE-K_Dj97np6-BQx_F5u_PofYi6C0BYfR4YATA5jzymT7nBLrdOzhr0Vh2FChYoyM82CT4tkOGSlnvaoCgB4OLMOzlvpWmbfRi5GB3d6yAfdhBGsDuAJ9L7V_N5WBFpjarSu2xAYhVUhFM8IbILHMipXWRd2sP-_DzxmsneS7qu9fxAk35DDC_8cONPp1ZrFUw-VhGts0jYOVuExl_ZWqE3y2XJLtiperxFWO45q59WdULrn15ciQjRhn2O-QmXHmOxLNZMCI6lbFFiCHWg`
caps := ucan.NewNestedCapabilities("SUPER_USER", "OVERWRITE", "SOFT_DELETE", "REVISE", "CREATE")