Skip to content

Commit

Permalink
feat: Identity display name cannot be duplicated.
Browse files Browse the repository at this point in the history
  • Loading branch information
x1m3 committed Oct 2, 2024
1 parent 4ab6052 commit 54bd9e3
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 10 deletions.
2 changes: 2 additions & 0 deletions api/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ paths:
$ref: '#/components/responses/401'
'403':
$ref: '#/components/responses/403'
'409':
$ref: '#/components/responses/409'
'500':
$ref: '#/components/responses/500'
get:
Expand Down
9 changes: 9 additions & 0 deletions internal/api/api.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion internal/api/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ func (s *Server) CreateIdentity(ctx context.Context, request CreateIdentityReque
},
}, nil
}

if errors.Is(err, kms.ErrPermissionDenied) {
var message string
if s.cfg.KeyStore.VaultUserPassAuthEnabled {
Expand All @@ -94,6 +93,13 @@ func (s *Server) CreateIdentity(ctx context.Context, request CreateIdentityReque
},
}, nil
}
if errors.Is(err, services.ErrIdentityDisplayNameDuplicated) {
return CreateIdentity409JSONResponse{
N409JSONResponse{
Message: fmt.Sprintf("display name field already exists: <%s>", *request.Body.DisplayName),
},
}, nil
}

var customErr *services.PublishingStateError
if errors.As(err, &customErr) {
Expand Down
36 changes: 35 additions & 1 deletion internal/api/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func TestServer_CreateIdentity(t *testing.T) {
Method string `json:"method"`
Network string `json:"network"`
Type CreateIdentityRequestDidMetadataType `json:"type"`
}{Blockchain: blockchain, Method: method, Network: network, Type: BJJ}, DisplayName: common.ToPointer("my display name"),
}{Blockchain: blockchain, Method: method, Network: network, Type: BJJ}, DisplayName: common.ToPointer("blockchain display name"),
CredentialStatusType: &authBJJCredentialStatus,
},
expected: expected{
Expand Down Expand Up @@ -236,6 +236,40 @@ func TestServer_CreateIdentity(t *testing.T) {
}
})
}
t.Run("Duplicated display name", func(t *testing.T) {
bodyRequest := CreateIdentityRequest{
DidMetadata: struct {
Blockchain string `json:"blockchain"`
Method string `json:"method"`
Network string `json:"network"`
Type CreateIdentityRequestDidMetadataType `json:"type"`
}{
Blockchain: blockchain,
Method: method,
Network: network,
Type: BJJ,
},
DisplayName: common.ToPointer("Very common display name"),
}
// First request
req, err := http.NewRequest("POST", "/v2/identities", tests.JSONBody(t, bodyRequest))
req.SetBasicAuth(authOk())
require.NoError(t, err)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusCreated, rr.Code) // First time we expect 201

// Second request
req, err = http.NewRequest("POST", "/v2/identities", tests.JSONBody(t, bodyRequest))
req.SetBasicAuth(authOk())
require.NoError(t, err)
rr = httptest.NewRecorder()
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusConflict, rr.Code) // Second time we expect a conflict 409
var response CreateIdentity409JSONResponse
assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response))
assert.Equal(t, "display name field already exists: <Very common display name>", response.Message)
})
}

func TestServer_GetIdentities(t *testing.T) {
Expand Down
16 changes: 14 additions & 2 deletions internal/core/services/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/polygonid/sh-id-platform/internal/kms"
"github.com/polygonid/sh-id-platform/internal/log"
"github.com/polygonid/sh-id-platform/internal/qrlink"
"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/credentials/signature/circuit/signer"
Expand All @@ -53,12 +54,17 @@ const (

// ErrWrongDIDMetada - represents an error in the identity metadata
var (
// ErrWrongDIDMetada - represents an error in the identity metadata
ErrWrongDIDMetada = errors.New("wrong DID Metadata")
// ErrAssigningMTPProof - represents an error in the identity metadata
ErrAssigningMTPProof = errors.New("error assigning the MTP Proof from Auth Claim. If this identity has keyType=ETH you must to publish the state first")

// ErrIdentityDisplayNameDuplicated - returned when trying to create an identity with a duplicated display name
ErrIdentityDisplayNameDuplicated = errors.New("duplicated identity display name")

// ErrNoClaimsFoundToProcess - means that there are no claims to process
ErrNoClaimsFoundToProcess = errors.New("no MTP or revoked claims found to process")

// ErrWrongDIDMetada - represents an error in the identity metadata
ErrWrongDIDMetada = errors.New("wrong DID Metadata")
)

type identity struct {
Expand Down Expand Up @@ -676,6 +682,9 @@ func (i *identity) createEthIdentity(ctx context.Context, tx db.Querier, hostURL
identity.DisplayName = didOptions.DisplayName

if err = i.identityRepository.Save(ctx, tx, identity); err != nil {
if errors.Is(err, repositories.ErrDisplayNameDuplicated) {
return nil, nil, ErrIdentityDisplayNameDuplicated
}
log.Error(ctx, "saving identity", "err", err)
return nil, nil, errors.Join(err, errors.New("can't save identity"))
}
Expand Down Expand Up @@ -784,6 +793,9 @@ func (i *identity) createIdentity(ctx context.Context, tx db.Querier, hostURL st
}

if err = i.identityRepository.Save(ctx, tx, identity); err != nil {
if errors.Is(err, repositories.ErrDisplayNameDuplicated) {
return nil, nil, ErrIdentityDisplayNameDuplicated
}
return nil, nil, fmt.Errorf("can't save identity: %w", err)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- +goose Up
-- +goose StatementBegin
WITH cte AS (SELECT identifier,
display_name,
ROW_NUMBER() OVER (PARTITION BY display_name ORDER BY identifier) AS rn
FROM identities
WHERE display_name IS NOT NULL)
UPDATE identities
SET display_name = identities.display_name || '_' || rn
FROM cte
WHERE identities.identifier = cte.identifier
AND cte.rn > 1;

ALTER TABLE identities
ADD CONSTRAINT identities_display_name_unique UNIQUE (display_name);
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
ALTER TABLE identities
DROP CONSTRAINT identities_display_name_unique;
-- +goose StatementEnd
11 changes: 11 additions & 0 deletions internal/repositories/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/iden3/go-iden3-core/v2/w3c"
"github.com/jackc/pgconn"

"github.com/polygonid/sh-id-platform/internal/core/domain"
"github.com/polygonid/sh-id-platform/internal/core/ports"
Expand All @@ -16,6 +17,9 @@ import (
// ErrIdentityNotFound - identity not found error
var ErrIdentityNotFound = errors.New("identity not found")

// ErrDisplayNameDuplicated - display name already exists error
var ErrDisplayNameDuplicated = errors.New("display name already exists")

type identity struct{}

// NewIdentity TODO
Expand All @@ -26,6 +30,13 @@ func NewIdentity() ports.IndentityRepository {
// Save - Create new identity
func (i *identity) Save(ctx context.Context, conn db.Querier, identity *domain.Identity) error {
_, err := conn.Exec(ctx, `INSERT INTO identities (identifier, address, keyType, display_name) VALUES ($1, $2, $3, $4)`, identity.Identifier, identity.Address, identity.KeyType, identity.DisplayName)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == duplicateViolationErrorCode {
return ErrDisplayNameDuplicated
}
return err
}
return err
}

Expand Down
8 changes: 3 additions & 5 deletions internal/repositories/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ import (
"github.com/polygonid/sh-id-platform/internal/db"
)

const duplicatedEntryPGCode = "23505"

var (
ErrSchemaDoesNotExist = errors.New("schema does not exist") // ErrSchemaDoesNotExist schema does not exist
ErrSchemaDuplicated = errors.New("schema already imported") // ErrSchemaDuplicated schema duplicated
ErrDuplicated = errors.New("schema already imported") // ErrDuplicated schema duplicated
)

type dbSchema struct {
Expand Down Expand Up @@ -68,8 +66,8 @@ func (r *schema) Save(ctx context.Context, s *domain.Schema) error {
s.Description)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == duplicatedEntryPGCode {
return ErrSchemaDuplicated
if errors.As(err, &pgErr) && pgErr.Code == duplicateViolationErrorCode {
return ErrDuplicated
}

return err
Expand Down
2 changes: 1 addition & 1 deletion internal/repositories/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestCreateSchema(t *testing.T) {
}

require.NoError(t, store.Save(ctx, schema1))
assert.ErrorIs(t, ErrSchemaDuplicated, store.Save(ctx, schema1), "cannot have duplicated schemas with the same version for the same issuer and type")
assert.ErrorIs(t, ErrDuplicated, store.Save(ctx, schema1), "cannot have duplicated schemas with the same version for the same issuer and type")

schema2 := schema1
schema2.Version = uuid.NewString()
Expand Down

0 comments on commit 54bd9e3

Please sign in to comment.