From fd266338a459d6707a85f09754d82caf90b15bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Gim=C3=A9nez?= Date: Fri, 28 Jun 2024 18:57:12 +0200 Subject: [PATCH] feat: Credential VC Id is an URN and not a url (#680) * feat: Return credential ID as URN * chore: linter * chore: Clearest implementation of urn package. --- internal/core/services/claims.go | 20 ++++++------- internal/urn/urn.go | 50 ++++++++++++++++++++++++++++++++ internal/urn/urn_test.go | 36 +++++++++++++++++++++++ 3 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 internal/urn/urn.go create mode 100644 internal/urn/urn_test.go diff --git a/internal/core/services/claims.go b/internal/core/services/claims.go index 243970374..df7c9c283 100644 --- a/internal/core/services/claims.go +++ b/internal/core/services/claims.go @@ -32,6 +32,7 @@ import ( "github.com/polygonid/sh-id-platform/internal/loader" "github.com/polygonid/sh-id-platform/internal/log" "github.com/polygonid/sh-id-platform/internal/repositories" + "github.com/polygonid/sh-id-platform/internal/urn" "github.com/polygonid/sh-id-platform/pkg/credentials/revocation_status" "github.com/polygonid/sh-id-platform/pkg/pubsub" "github.com/polygonid/sh-id-platform/pkg/rand" @@ -652,10 +653,13 @@ func (c *claim) getAgentCredential(ctx context.Context, basicMessage *ports.Agen return nil, fmt.Errorf("invalid credential fetch request body: %w", err) } - claimID, err := uuid.Parse(fetchRequestBody.ID) + claimID, err := urn.UUIDFromURNString(fetchRequestBody.ID) if err != nil { - log.Error(ctx, "wrong claimID in agent request body", "err", err) - return nil, fmt.Errorf("invalid claim ID") + claimID, err = uuid.Parse(fetchRequestBody.ID) + if err != nil { + log.Error(ctx, "wrong claimID in agent request body", "err", err) + return nil, fmt.Errorf("invalid claim ID") + } } claim, err := c.icRepo.GetByIdAndIssuer(ctx, c.storage.Pgx, basicMessage.IssuerDID, claimID) @@ -802,7 +806,7 @@ func (c *claim) newVerifiableCredential(ctx context.Context, claimReq *ports.Cre issuanceDate := time.Now().UTC() return verifiable.W3CCredential{ - ID: c.buildCredentialID(*claimReq.DID, vcID, claimReq.SingleIssuer), + ID: string(c.buildCredentialID(vcID)), Context: credentialCtx, Type: credentialType, Expiration: claimReq.Expiration, @@ -819,10 +823,6 @@ func (c *claim) newVerifiableCredential(ctx context.Context, claimReq *ports.Cre }, nil } -func (c *claim) buildCredentialID(issuerDID w3c.DID, credID uuid.UUID, singleIssuer bool) string { - // TODO: review how to build the credential ID - if singleIssuer { - return fmt.Sprintf("%s/v1/credentials/%s", strings.TrimSuffix(c.host, "/"), credID.String()) - } - return fmt.Sprintf("%s/v1/%s/claims/%s", strings.TrimSuffix(c.host, "/"), issuerDID.String(), credID.String()) +func (c *claim) buildCredentialID(credID uuid.UUID) urn.URN { + return urn.FromUUID(credID) } diff --git a/internal/urn/urn.go b/internal/urn/urn.go new file mode 100644 index 000000000..b6fd782fd --- /dev/null +++ b/internal/urn/urn.go @@ -0,0 +1,50 @@ +package urn + +import ( + "errors" + + "github.com/google/uuid" +) + +// URN represents a Uniform Resource Name. +type URN string + +// FromUUID creates a URN from a UUID. +func FromUUID(uuid uuid.UUID) URN { + return URN("urn:uuid:" + uuid.String()) +} + +// UUID returns the UUID from a URN. It can throw an error to prevent bad constructor calls or urns without uuids +func (u URN) UUID() (uuid.UUID, error) { + if err := u.valid(); err != nil { + return uuid.Nil, err + } + return uuid.Parse(string(u[9:])) +} + +func (u URN) valid() error { + if len(u) < len("urn:uuid:") { + return errors.New("invalid uuid URN length") + } + if u[:9] != "urn:uuid:" { + return errors.New("invalid uuid URN prefix") + } + return nil +} + +// Parse creates a URN from a string. +func Parse(u string) (URN, error) { + if err := URN(u).valid(); err != nil { + return "", err + } + return URN(u), nil +} + +// UUIDFromURNString returns the UUID from a URN string. +func UUIDFromURNString(s string) (uuid.UUID, error) { + urn, err := Parse(s) + if err != nil { + return uuid.Nil, err + } + return urn.UUID() +} diff --git a/internal/urn/urn_test.go b/internal/urn/urn_test.go new file mode 100644 index 000000000..db662452d --- /dev/null +++ b/internal/urn/urn_test.go @@ -0,0 +1,36 @@ +package urn + +import ( + "errors" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestFromUUID(t *testing.T) { + id := uuid.New() + urn := FromUUID(id) + assert.Equal(t, "urn:uuid:"+id.String(), string(urn)) +} + +func TestUUIDFromURNString(t *testing.T) { + for _, ts := range []struct { + urn string + err error + }{ + {"", errors.New("invalid uuid URN length")}, + {"urn:uuid:", errors.New("invalid UUID length: 0")}, + {"urn:uuid:1234", errors.New("invalid UUID length: 4")}, + {"urn:uuid:123e4567-e89b-12d3-a456-426614174000", nil}, + } { + t.Run(ts.urn, func(t *testing.T) { + u, err := UUIDFromURNString(ts.urn) + if err == nil { + assert.Equal(t, ts.urn[9:], u.String()) + } else { + assert.Equal(t, ts.err.Error(), err.Error()) + } + }) + } +}