From f17a30f1b8f67a1feed361e4cc904983f364d300 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 20 Nov 2024 12:12:27 -0300 Subject: [PATCH 01/20] chore: add verification tables and repository --- internal/core/domain/verification.go | 30 ++ .../core/ports/verification_repository.go | 17 + ...02411200837121_add_verification_tables.sql | 44 +++ internal/repositories/verification.go | 147 +++++++++ internal/repositories/verification_test.go | 304 ++++++++++++++++++ 5 files changed, 542 insertions(+) create mode 100644 internal/core/domain/verification.go create mode 100644 internal/core/ports/verification_repository.go create mode 100644 internal/db/schema/migrations/202411200837121_add_verification_tables.sql create mode 100644 internal/repositories/verification.go create mode 100644 internal/repositories/verification_test.go diff --git a/internal/core/domain/verification.go b/internal/core/domain/verification.go new file mode 100644 index 000000000..1597c057b --- /dev/null +++ b/internal/core/domain/verification.go @@ -0,0 +1,30 @@ +package domain + +import ( + "time" + + "github.com/google/uuid" + "github.com/jackc/pgtype" +) + +// VerificationQuery holds the verification data +type VerificationQuery struct { + ID uuid.UUID + IssuerDID string + ChainID int + SkipCheckRevocation bool + Scopes []VerificationScope + CreatedAt time.Time +} + +// VerificationScope holds the verification scope data +type VerificationScope struct { + ID uuid.UUID + ScopeID int + CircuitID string + Context string + AllowedIssuers []string + CredentialType string + CredentialSubject pgtype.JSONB `json:"credential_subject"` + CreatedAt time.Time +} diff --git a/internal/core/ports/verification_repository.go b/internal/core/ports/verification_repository.go new file mode 100644 index 000000000..e396f3a55 --- /dev/null +++ b/internal/core/ports/verification_repository.go @@ -0,0 +1,17 @@ +package ports + +import ( + "context" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/core/domain" +) + +// VerificationRepository is a repository for verification queries +type VerificationRepository interface { + Save(ctx context.Context, issuerID w3c.DID, query domain.VerificationQuery) (uuid.UUID, error) + Get(ctx context.Context, issuerID w3c.DID, id uuid.UUID) (*domain.VerificationQuery, error) + GetAll(ctx context.Context, issuerID w3c.DID) ([]domain.VerificationQuery, error) +} diff --git a/internal/db/schema/migrations/202411200837121_add_verification_tables.sql b/internal/db/schema/migrations/202411200837121_add_verification_tables.sql new file mode 100644 index 000000000..07cf53e92 --- /dev/null +++ b/internal/db/schema/migrations/202411200837121_add_verification_tables.sql @@ -0,0 +1,44 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS verification_queries ( + id uuid NOT NULL PRIMARY KEY, + issuer_id text NOT NULL, + chain_id integer NOT NULL, + skip_check_revocation boolean NOT NULL, + created_at timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT verification_queries_indentities_id_fk foreign key (issuer_id) references identities (identifier) +); + +CREATE TABLE IF NOT EXISTS verification_scopes ( + id uuid NOT NULL PRIMARY KEY, + verification_query_id uuid NOT NULL, + scope_id integer NOT NULL, + circuit_id text NOT NULL, + context text NOT NULL, + allowed_issuers text[] NOT NULL, + credential_type text NOT NULL, + credential_subject jsonb NULL, + created_at timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT verification_scopes_verification_query_id_fk foreign key (verification_query_id) references verification_queries (id) +); + +CREATE TABLE IF NOT EXISTS verification_responses ( + id uuid NOT NULL PRIMARY KEY, + verification_scope_id uuid NOT NULL, + user_did text NOT NULL, + response jsonb NOT NULL, + pass boolean NOT NULL, + created_at timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT verification_responses_verification_scopes_id_fk foreign key (verification_scope_id) references verification_scopes (id) +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE verification_responses DROP CONSTRAINT verification_responses_verification_scopes_id_fk; +ALTER TABLE verification_scopes DROP CONSTRAINT verification_scopes_verification_query_id_fk; +ALTER TABLE verification_queries DROP CONSTRAINT verification_queries_indentities_id_fk; +DROP TABLE IF EXISTS verification_queries; +DROP TABLE IF EXISTS verification_responses; +DROP TABLE IF EXISTS verification_scopes; +-- +goose StatementEnd \ No newline at end of file diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go new file mode 100644 index 000000000..14718cad4 --- /dev/null +++ b/internal/repositories/verification.go @@ -0,0 +1,147 @@ +package repositories + +import ( + "context" + "errors" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/core/domain" + "github.com/polygonid/sh-id-platform/internal/db" +) + +// VerificationQueryNotFoundError is returned when a verification query is not found +var VerificationQueryNotFoundError = errors.New("verification query not found") + +// VerificationRepository is a repository for verification queries +type VerificationRepository struct { + conn db.Storage +} + +// NewVerification creates a new VerificationRepository +func NewVerification(conn db.Storage) *VerificationRepository { + return &VerificationRepository{conn: conn} +} + +// Save stores a verification query in the database +func (r *VerificationRepository) Save(ctx context.Context, issuerID w3c.DID, query domain.VerificationQuery) (uuid.UUID, error) { + sql := `INSERT INTO verification_queries (id, issuer_id, chain_id, skip_check_revocation) + VALUES($1, $2, $3, $4) ON CONFLICT (id) DO + UPDATE SET issuer_id=$2, chain_id=$3, skip_check_revocation=$4 + RETURNING id` + + var queryID uuid.UUID + tx, err := r.conn.Pgx.Begin(ctx) + if err != nil { + return uuid.Nil, err + } + err = tx.QueryRow(ctx, sql, query.ID, issuerID.String(), query.ChainID, query.SkipCheckRevocation).Scan(&queryID) + if err != nil { + errIn := tx.Rollback(ctx) + if errIn != nil { + return uuid.Nil, errIn + } + return uuid.Nil, err + } + + for _, scope := range query.Scopes { + sql = `INSERT INTO verification_scopes (id, verification_query_id, scope_id, circuit_id, context, allowed_issuers, credential_type, credential_subject) + VALUES($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (id) DO + UPDATE SET circuit_id=$4, context=$5, allowed_issuers=$6, credential_type=$7, credential_subject=$8` + _, err = tx.Exec(ctx, sql, scope.ID, queryID, scope.ScopeID, scope.CircuitID, scope.Context, scope.AllowedIssuers, scope.CredentialType, scope.CredentialSubject) + if err != nil { + errIn := tx.Rollback(ctx) + if errIn != nil { + return uuid.Nil, errIn + } + return uuid.Nil, err + } + } + + err = tx.Commit(ctx) + if err != nil { + return uuid.Nil, err + } + return queryID, err +} + +// Get returns a verification query by issuer and id +func (r *VerificationRepository) Get(ctx context.Context, issuerID w3c.DID, id uuid.UUID) (*domain.VerificationQuery, error) { + sql := `SELECT id, issuer_id, chain_id, skip_check_revocation, created_at + FROM verification_queries + WHERE issuer_id = $1 and id = $2` + + var query domain.VerificationQuery + err := r.conn.Pgx.QueryRow(ctx, sql, issuerID.String(), id).Scan(&query.ID, &query.IssuerDID, &query.ChainID, &query.SkipCheckRevocation, &query.CreatedAt) + if err != nil { + if err.Error() == "no rows in result set" { + return nil, VerificationQueryNotFoundError + } + return nil, err + } + + sql = `SELECT id, scope_id, circuit_id, context, allowed_issuers, credential_type, credential_subject, created_at + FROM verification_scopes + WHERE verification_query_id = $1` + + rows, err := r.conn.Pgx.Query(ctx, sql, query.ID) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var scope domain.VerificationScope + err = rows.Scan(&scope.ID, &scope.ScopeID, &scope.CircuitID, &scope.Context, &scope.AllowedIssuers, &scope.CredentialType, &scope.CredentialSubject, &scope.CreatedAt) + if err != nil { + return nil, err + } + query.Scopes = append(query.Scopes, scope) + } + + return &query, nil +} + +// GetAll returns all verification queries for a given issuer +func (r *VerificationRepository) GetAll(ctx context.Context, issuerID w3c.DID) ([]domain.VerificationQuery, error) { + sql := `SELECT id, issuer_id, chain_id, skip_check_revocation, created_at + FROM verification_queries + WHERE issuer_id = $1` + + rows, err := r.conn.Pgx.Query(ctx, sql, issuerID.String()) + if err != nil { + return nil, err + } + defer rows.Close() + + var queries []domain.VerificationQuery + for rows.Next() { + var query domain.VerificationQuery + err = rows.Scan(&query.ID, &query.IssuerDID, &query.ChainID, &query.SkipCheckRevocation, &query.CreatedAt) + if err != nil { + return nil, err + } + + sql = `SELECT id, scope_id, circuit_id, context, allowed_issuers, credential_type, credential_subject, created_at + FROM verification_scopes + WHERE verification_query_id = $1` + + scopeRows, err := r.conn.Pgx.Query(ctx, sql, query.ID) + if err != nil { + return nil, err + } + defer scopeRows.Close() + + for scopeRows.Next() { + var scope domain.VerificationScope + err = scopeRows.Scan(&scope.ID, &scope.ScopeID, &scope.CircuitID, &scope.Context, &scope.AllowedIssuers, &scope.CredentialType, &scope.CredentialSubject, &scope.CreatedAt) + if err != nil { + return nil, err + } + query.Scopes = append(query.Scopes, scope) + } + queries = append(queries, query) + } + return queries, nil +} diff --git a/internal/repositories/verification_test.go b/internal/repositories/verification_test.go new file mode 100644 index 000000000..b0e87dfbd --- /dev/null +++ b/internal/repositories/verification_test.go @@ -0,0 +1,304 @@ +package repositories + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/jackc/pgtype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/polygonid/sh-id-platform/internal/core/domain" +) + +func TestSaveVerificationQuery(t *testing.T) { + ctx := context.Background() + didStr := "did:iden3:polygon:amoy:x9b7eWa8k5rTuDBiWPHou4AvCAd1XTRfxx2uQCni8" + verificationRepository := NewVerification(*storage) + + _, err := storage.Pgx.Exec(ctx, "INSERT INTO identities (identifier, keytype) VALUES ($1, $2)", didStr, "BJJ") + assert.NoError(t, err) + + did, err := w3c.ParseDID(didStr) + require.NoError(t, err) + + t.Run("should save the verification", func(t *testing.T) { + credentialSubject := pgtype.JSONB{} + err = credentialSubject.Set(`{ + "birthday": { + "$eq": 19791109 + } + }`) + + credentialSubject2 := pgtype.JSONB{} + err = credentialSubject2.Set(` {"position": {"$eq": 1}}`) + require.NoError(t, err) + verificationQuery := domain.VerificationQuery{ + ID: uuid.New(), + ChainID: 8002, + SkipCheckRevocation: false, + Scopes: []domain.VerificationScope{ + { + ID: uuid.New(), + ScopeID: 1, + CircuitID: "credentialAtomicQuerySigV2", + Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", + AllowedIssuers: []string{"issuer1", "issuer2"}, + CredentialType: "KYCAgeCredential", + CredentialSubject: credentialSubject, + }, + { + ID: uuid.New(), + ScopeID: 2, + CircuitID: "credentialAtomicQuerySigV2", + Context: "ipfs://QmaBJzpoYT2CViDx5ShJiuYLKXizrPEfXo8JqzrXCvG6oc", + AllowedIssuers: []string{"*"}, + CredentialType: "TestInteger01", + CredentialSubject: credentialSubject2, + }, + }, + } + + verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryID) + }) +} + +func TestGetVerification(t *testing.T) { + ctx := context.Background() + didStr := "did:iden3:polygon:amoy:xBdqiqz3yVT79NEAuNaqKSDZ6a5V6q8Ph66i5d2tT" + verificationRepository := NewVerification(*storage) + + _, err := storage.Pgx.Exec(ctx, "INSERT INTO identities (identifier, keytype) VALUES ($1, $2)", didStr, "BJJ") + assert.NoError(t, err) + + did, err := w3c.ParseDID(didStr) + require.NoError(t, err) + + t.Run("should get the verification", func(t *testing.T) { + credentialSubject := pgtype.JSONB{} + err = credentialSubject.Set(`{"birthday": {"$eq": 19791109}}`) + credentialSubject2 := pgtype.JSONB{} + err = credentialSubject2.Set(`{"position": {"$eq": 1}}`) + require.NoError(t, err) + verificationQuery := domain.VerificationQuery{ + ID: uuid.New(), + ChainID: 8002, + SkipCheckRevocation: false, + Scopes: []domain.VerificationScope{ + { + ID: uuid.New(), + ScopeID: 1, + CircuitID: "credentialAtomicQuerySigV2", + Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", + AllowedIssuers: []string{"issuer1", "issuer2"}, + CredentialType: "KYCAgeCredential", + CredentialSubject: credentialSubject, + }, + { + ID: uuid.New(), + ScopeID: 2, + CircuitID: "credentialAtomicQuerySigV2", + Context: "ipfs://QmaBJzpoYT2CViDx5ShJiuYLKXizrPEfXo8JqzrXCvG6oc", + AllowedIssuers: []string{"*"}, + CredentialType: "TestInteger01", + CredentialSubject: credentialSubject2, + }, + }, + } + + verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryID) + + verificationQueryFromDB, err := verificationRepository.Get(ctx, *did, verificationQueryID) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryFromDB.ID) + assert.Equal(t, verificationQuery.ChainID, verificationQueryFromDB.ChainID) + assert.Equal(t, verificationQuery.SkipCheckRevocation, verificationQueryFromDB.SkipCheckRevocation) + assert.Equal(t, verificationQuery.Scopes[0].ID, verificationQueryFromDB.Scopes[0].ID) + assert.Equal(t, verificationQuery.Scopes[0].ScopeID, verificationQueryFromDB.Scopes[0].ScopeID) + assert.Equal(t, verificationQuery.Scopes[0].CircuitID, verificationQueryFromDB.Scopes[0].CircuitID) + assert.Equal(t, verificationQuery.Scopes[0].Context, verificationQueryFromDB.Scopes[0].Context) + assert.Equal(t, verificationQuery.Scopes[0].AllowedIssuers, verificationQueryFromDB.Scopes[0].AllowedIssuers) + assert.Equal(t, verificationQuery.Scopes[0].CredentialType, verificationQueryFromDB.Scopes[0].CredentialType) + assert.Equal(t, verificationQuery.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB.Scopes[0].CredentialSubject.Bytes) + assert.Equal(t, verificationQuery.Scopes[1].ID, verificationQueryFromDB.Scopes[1].ID) + assert.Equal(t, verificationQuery.Scopes[1].ScopeID, verificationQueryFromDB.Scopes[1].ScopeID) + assert.Equal(t, verificationQuery.Scopes[1].CircuitID, verificationQueryFromDB.Scopes[1].CircuitID) + assert.Equal(t, verificationQuery.Scopes[1].Context, verificationQueryFromDB.Scopes[1].Context) + assert.Equal(t, verificationQuery.Scopes[1].AllowedIssuers, verificationQueryFromDB.Scopes[1].AllowedIssuers) + assert.Equal(t, verificationQuery.Scopes[1].CredentialType, verificationQueryFromDB.Scopes[1].CredentialType) + assert.Equal(t, verificationQuery.Scopes[1].CredentialSubject.Bytes, verificationQueryFromDB.Scopes[1].CredentialSubject.Bytes) + }) + + t.Run("should not get the verification", func(t *testing.T) { + verificationQueryFromDB, err := verificationRepository.Get(ctx, *did, uuid.New()) + require.Error(t, err) + require.Equal(t, VerificationQueryNotFoundError, err) + assert.Nil(t, verificationQueryFromDB) + }) +} + +func TestUpdateVerificationQuery(t *testing.T) { + ctx := context.Background() + didStr := "did:iden3:polygon:amoy:x7tz1NB9fy4GJJW1oQV1wGYpuratuApN8FWEQVKZP" + verificationRepository := NewVerification(*storage) + + _, err := storage.Pgx.Exec(ctx, "INSERT INTO identities (identifier, keytype) VALUES ($1, $2)", didStr, "BJJ") + assert.NoError(t, err) + + did, err := w3c.ParseDID(didStr) + require.NoError(t, err) + + t.Run("should update the verification query", func(t *testing.T) { + credentialSubject1 := pgtype.JSONB{} + err = credentialSubject1.Set(`{"birthday": {"$eq": 19791109}}`) + credentialSubject2 := pgtype.JSONB{} + err = credentialSubject2.Set(`{"position": {"$eq": 1}}`) + require.NoError(t, err) + verificationQuery := domain.VerificationQuery{ + ID: uuid.New(), + ChainID: 8002, + SkipCheckRevocation: false, + Scopes: []domain.VerificationScope{ + { + ID: uuid.New(), + ScopeID: 1, + CircuitID: "credentialAtomicQuerySigV2", + Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", + AllowedIssuers: []string{"issuer1", "issuer2"}, + CredentialType: "KYCAgeCredential", + CredentialSubject: credentialSubject1, + }, + }, + } + + verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryID) + + verificationQuery.Scopes = append(verificationQuery.Scopes, domain.VerificationScope{ + ID: uuid.New(), + ScopeID: 2, + CircuitID: "credentialAtomicQuerySigV2", + Context: "ipfs://QmaBJzpoYT2CViDx5ShJiuYLKXizrPEfXo8JqzrXCvG6oc", + AllowedIssuers: []string{"*"}, + CredentialType: "TestInteger01", + CredentialSubject: credentialSubject2, + }) + + verificationQuery.SkipCheckRevocation = true + verificationQuery.ChainID = 137 + _, err = verificationRepository.Save(ctx, *did, verificationQuery) + require.NoError(t, err) + + verificationQueryFromDB, err := verificationRepository.Get(ctx, *did, verificationQueryID) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryFromDB.ID) + assert.Equal(t, verificationQuery.ChainID, verificationQueryFromDB.ChainID) + assert.Equal(t, verificationQuery.SkipCheckRevocation, verificationQueryFromDB.SkipCheckRevocation) + assert.Equal(t, verificationQuery.Scopes[0].ID, verificationQueryFromDB.Scopes[0].ID) + assert.Equal(t, verificationQuery.Scopes[0].ScopeID, verificationQueryFromDB.Scopes[0].ScopeID) + assert.Equal(t, verificationQuery.Scopes[0].CircuitID, verificationQueryFromDB.Scopes[0].CircuitID) + assert.Equal(t, verificationQuery.Scopes[0].Context, verificationQueryFromDB.Scopes[0].Context) + assert.Equal(t, verificationQuery.Scopes[0].AllowedIssuers, verificationQueryFromDB.Scopes[0].AllowedIssuers) + assert.Equal(t, verificationQuery.Scopes[0].CredentialType, verificationQueryFromDB.Scopes[0].CredentialType) + assert.Equal(t, verificationQuery.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB.Scopes[0].CredentialSubject.Bytes) + assert.Equal(t, verificationQuery.Scopes[1].ID, verificationQueryFromDB.Scopes[1].ID) + assert.Equal(t, verificationQuery.Scopes[1].ScopeID, verificationQueryFromDB.Scopes[1].ScopeID) + assert.Equal(t, verificationQuery.Scopes[1].CircuitID, verificationQueryFromDB.Scopes[1].CircuitID) + assert.Equal(t, verificationQuery.Scopes[1].Context, verificationQueryFromDB.Scopes[1].Context) + assert.Equal(t, verificationQuery.Scopes[1].AllowedIssuers, verificationQueryFromDB.Scopes[1].AllowedIssuers) + assert.Equal(t, verificationQuery.Scopes[1].CredentialType, verificationQueryFromDB.Scopes[1].CredentialType) + assert.Equal(t, verificationQuery.Scopes[1].CredentialSubject.Bytes, verificationQueryFromDB.Scopes[1].CredentialSubject.Bytes) + }) +} + +func TestGetAllVerification(t *testing.T) { + ctx := context.Background() + didStr := "did:iden3:polygon:amoy:xCu8Cshrj4oegWRabzGtbKzqUFtXN85x8XkCPdREU" + verificationRepository := NewVerification(*storage) + + _, err := storage.Pgx.Exec(ctx, "INSERT INTO identities (identifier, keytype) VALUES ($1, $2)", didStr, "BJJ") + assert.NoError(t, err) + + did, err := w3c.ParseDID(didStr) + require.NoError(t, err) + + t.Run("GetAll", func(t *testing.T) { + credentialSubject := pgtype.JSONB{} + err = credentialSubject.Set(`{"birthday": {"$eq": 19791109}}`) + credentialSubject2 := pgtype.JSONB{} + err = credentialSubject2.Set(`{"position": {"$eq": 1}}`) + require.NoError(t, err) + verificationQuery1 := domain.VerificationQuery{ + ID: uuid.New(), + ChainID: 8002, + SkipCheckRevocation: false, + Scopes: []domain.VerificationScope{ + { + ID: uuid.New(), + ScopeID: 1, + CircuitID: "credentialAtomicQuerySigV2", + Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", + AllowedIssuers: []string{"issuer1", "issuer2"}, + CredentialType: "KYCAgeCredential", + CredentialSubject: credentialSubject, + }, + }, + } + + verificationQuery2 := domain.VerificationQuery{ + ID: uuid.New(), + ChainID: 8002, + SkipCheckRevocation: false, + Scopes: []domain.VerificationScope{ + { + ID: uuid.New(), + ScopeID: 2, + CircuitID: "credentialAtomicQuerySigV2", + Context: "ipfs://QmaBJzpoYT2CViDx5ShJiuYLKXizrPEfXo8JqzrXCvG6oc", + AllowedIssuers: []string{"*"}, + CredentialType: "TestInteger01", + CredentialSubject: credentialSubject2, + }, + }, + } + + verificationQueryID1, err := verificationRepository.Save(ctx, *did, verificationQuery1) + require.NoError(t, err) + assert.Equal(t, verificationQuery1.ID, verificationQueryID1) + + verificationQueryID2, err := verificationRepository.Save(ctx, *did, verificationQuery2) + require.NoError(t, err) + assert.Equal(t, verificationQuery2.ID, verificationQueryID2) + + verificationQueryFromDB, err := verificationRepository.GetAll(ctx, *did) + require.NoError(t, err) + assert.Equal(t, 2, len(verificationQueryFromDB)) + assert.Equal(t, verificationQuery1.ID, verificationQueryFromDB[0].ID) + assert.Equal(t, verificationQuery1.ChainID, verificationQueryFromDB[0].ChainID) + assert.Equal(t, verificationQuery1.SkipCheckRevocation, verificationQueryFromDB[0].SkipCheckRevocation) + assert.Equal(t, verificationQuery1.Scopes[0].ID, verificationQueryFromDB[0].Scopes[0].ID) + assert.Equal(t, verificationQuery1.Scopes[0].ScopeID, verificationQueryFromDB[0].Scopes[0].ScopeID) + assert.Equal(t, verificationQuery1.Scopes[0].CircuitID, verificationQueryFromDB[0].Scopes[0].CircuitID) + assert.Equal(t, verificationQuery1.Scopes[0].Context, verificationQueryFromDB[0].Scopes[0].Context) + assert.Equal(t, verificationQuery1.Scopes[0].AllowedIssuers, verificationQueryFromDB[0].Scopes[0].AllowedIssuers) + assert.Equal(t, verificationQuery1.Scopes[0].CredentialType, verificationQueryFromDB[0].Scopes[0].CredentialType) + assert.Equal(t, verificationQuery1.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB[0].Scopes[0].CredentialSubject.Bytes) + assert.Equal(t, verificationQuery2.ID, verificationQueryFromDB[1].ID) + assert.Equal(t, verificationQuery2.ChainID, verificationQueryFromDB[1].ChainID) + assert.Equal(t, verificationQuery2.SkipCheckRevocation, verificationQueryFromDB[1].SkipCheckRevocation) + assert.Equal(t, verificationQuery2.Scopes[0].ID, verificationQueryFromDB[1].Scopes[0].ID) + assert.Equal(t, verificationQuery2.Scopes[0].ScopeID, verificationQueryFromDB[1].Scopes[0].ScopeID) + assert.Equal(t, verificationQuery2.Scopes[0].CircuitID, verificationQueryFromDB[1].Scopes[0].CircuitID) + assert.Equal(t, verificationQuery2.Scopes[0].Context, verificationQueryFromDB[1].Scopes[0].Context) + assert.Equal(t, verificationQuery2.Scopes[0].AllowedIssuers, verificationQueryFromDB[1].Scopes[0].AllowedIssuers) + assert.Equal(t, verificationQuery2.Scopes[0].CredentialType, verificationQueryFromDB[1].Scopes[0].CredentialType) + assert.Equal(t, verificationQuery2.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB[1].Scopes[0].CredentialSubject.Bytes) + }) +} From a7b739458c2b3eb6e404c8643e673341362e686a Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 20 Nov 2024 13:26:45 -0300 Subject: [PATCH 02/20] chore: add responses --- internal/core/domain/verification.go | 10 +++ .../core/ports/verification_repository.go | 1 + internal/repositories/verification.go | 34 ++++++++- internal/repositories/verification_test.go | 72 +++++++++++++++++++ 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/internal/core/domain/verification.go b/internal/core/domain/verification.go index 1597c057b..389439afe 100644 --- a/internal/core/domain/verification.go +++ b/internal/core/domain/verification.go @@ -28,3 +28,13 @@ type VerificationScope struct { CredentialSubject pgtype.JSONB `json:"credential_subject"` CreatedAt time.Time } + +// VerificationResponse holds the verification response data +type VerificationResponse struct { + ID uuid.UUID + VerificationScopeID uuid.UUID + UserDID string + Response pgtype.JSONB `json:"response"` + Pass bool + CreatedAt time.Time +} diff --git a/internal/core/ports/verification_repository.go b/internal/core/ports/verification_repository.go index e396f3a55..c7979a81b 100644 --- a/internal/core/ports/verification_repository.go +++ b/internal/core/ports/verification_repository.go @@ -14,4 +14,5 @@ type VerificationRepository interface { Save(ctx context.Context, issuerID w3c.DID, query domain.VerificationQuery) (uuid.UUID, error) Get(ctx context.Context, issuerID w3c.DID, id uuid.UUID) (*domain.VerificationQuery, error) GetAll(ctx context.Context, issuerID w3c.DID) ([]domain.VerificationQuery, error) + AddResponse(ctx context.Context, scopeID uuid.UUID, response domain.VerificationResponse) (uuid.UUID, error) } diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go index 14718cad4..c1126a622 100644 --- a/internal/repositories/verification.go +++ b/internal/repositories/verification.go @@ -3,6 +3,8 @@ package repositories import ( "context" "errors" + "github.com/jackc/pgconn" + "strings" "github.com/google/uuid" "github.com/iden3/go-iden3-core/v2/w3c" @@ -11,8 +13,14 @@ import ( "github.com/polygonid/sh-id-platform/internal/db" ) -// VerificationQueryNotFoundError is returned when a verification query is not found -var VerificationQueryNotFoundError = errors.New("verification query not found") +const foreignKeyViolationErrorCode = "23503" + +var ( + // VerificationQueryNotFoundError is returned when a verification query is not found + VerificationQueryNotFoundError = errors.New("verification query not found") + // VerificationScopeNotFoundError is returned when a verification scope is not found + VerificationScopeNotFoundError = errors.New("verification scope not found") +) // VerificationRepository is a repository for verification queries type VerificationRepository struct { @@ -75,7 +83,8 @@ func (r *VerificationRepository) Get(ctx context.Context, issuerID w3c.DID, id u var query domain.VerificationQuery err := r.conn.Pgx.QueryRow(ctx, sql, issuerID.String(), id).Scan(&query.ID, &query.IssuerDID, &query.ChainID, &query.SkipCheckRevocation, &query.CreatedAt) if err != nil { - if err.Error() == "no rows in result set" { + + if strings.Contains(err.Error(), "no rows in result set") { return nil, VerificationQueryNotFoundError } return nil, err @@ -145,3 +154,22 @@ func (r *VerificationRepository) GetAll(ctx context.Context, issuerID w3c.DID) ( } return queries, nil } + +// AddResponse stores a verification response in the database +func (r *VerificationRepository) AddResponse(ctx context.Context, scopeID uuid.UUID, response domain.VerificationResponse) (uuid.UUID, error) { + sql := `INSERT INTO verification_responses (id, verification_scope_id, user_did, response, pass) + VALUES($1, $2, $3, $4, $5) ON CONFLICT (id) DO + UPDATE SET user_did=$3, response=$4, pass=$5 + RETURNING id` + + var responseID uuid.UUID + err := r.conn.Pgx.QueryRow(ctx, sql, response.ID, scopeID, response.UserDID, response.Response, response.Pass).Scan(&responseID) + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) && pgErr.Code == foreignKeyViolationErrorCode { + return uuid.Nil, VerificationScopeNotFoundError + } + return uuid.Nil, err + } + return responseID, nil +} diff --git a/internal/repositories/verification_test.go b/internal/repositories/verification_test.go index b0e87dfbd..916f3a2e0 100644 --- a/internal/repositories/verification_test.go +++ b/internal/repositories/verification_test.go @@ -2,6 +2,7 @@ package repositories import ( "context" + "errors" "testing" "github.com/google/uuid" @@ -302,3 +303,74 @@ func TestGetAllVerification(t *testing.T) { assert.Equal(t, verificationQuery2.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB[1].Scopes[0].CredentialSubject.Bytes) }) } + +func TestAddVerification(t *testing.T) { + ctx := context.Background() + didStr := "did:iden3:polygon:amoy:xCd1tRmXnqbgiT3QC2CuDddUoHK4S9iXwq5xFDJGb" + verificationRepository := NewVerification(*storage) + + _, err := storage.Pgx.Exec(ctx, "INSERT INTO identities (identifier, keytype) VALUES ($1, $2)", didStr, "BJJ") + assert.NoError(t, err) + + did, err := w3c.ParseDID(didStr) + require.NoError(t, err) + + t.Run("should add a response to verification", func(t *testing.T) { + credentialSubject := pgtype.JSONB{} + err = credentialSubject.Set(`{"birthday": {"$eq": 19791109}}`) + require.NoError(t, err) + verificationQuery := domain.VerificationQuery{ + ID: uuid.New(), + ChainID: 8002, + SkipCheckRevocation: false, + Scopes: []domain.VerificationScope{ + { + ID: uuid.New(), + ScopeID: 1, + CircuitID: "credentialAtomicQuerySigV2", + Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", + AllowedIssuers: []string{"issuer1", "issuer2"}, + CredentialType: "KYCAgeCredential", + CredentialSubject: credentialSubject, + }, + }, + } + + verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryID) + + verificationQueryFromDB, err := verificationRepository.Get(ctx, *did, verificationQueryID) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryFromDB.ID) + + response := pgtype.JSONB{} + err = response.Set(`{"something": {"proof": 1}}`) + require.NoError(t, err) + verificationResponse := domain.VerificationResponse{ + ID: uuid.New(), + VerificationScopeID: verificationQueryFromDB.Scopes[0].ID, + UserDID: "did:iden3:privado:main:2SizDYDWBViKXRfp1VgUAMqhz5SDvP7D1MYiPfwJV3", + Response: response, + } + + responseID, err := verificationRepository.AddResponse(ctx, verificationQueryFromDB.Scopes[0].ID, verificationResponse) + require.NoError(t, err) + assert.Equal(t, verificationResponse.ID, responseID) + }) + + t.Run("should get an error", func(t *testing.T) { + response := pgtype.JSONB{} + err = response.Set(`{"something": {"proof": 1}}`) + require.NoError(t, err) + verificationResponse := domain.VerificationResponse{ + ID: uuid.New(), + UserDID: "did:iden3:privado:main:2SizDYDWBViKXRfp1VgUAMqhz5SDvP7D1MYiPfwJV3", + Response: response, + } + responseID, err := verificationRepository.AddResponse(ctx, uuid.New(), verificationResponse) + require.Error(t, err) + require.True(t, errors.Is(err, VerificationScopeNotFoundError)) + assert.Equal(t, uuid.Nil, responseID) + }) +} From 7431cd27511b59a07096828e5a47996768e04047 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Wed, 20 Nov 2024 13:38:13 -0300 Subject: [PATCH 03/20] fix: linter --- internal/repositories/verification.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go index c1126a622..75d1c060c 100644 --- a/internal/repositories/verification.go +++ b/internal/repositories/verification.go @@ -3,11 +3,11 @@ package repositories import ( "context" "errors" - "github.com/jackc/pgconn" "strings" "github.com/google/uuid" "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/db" From 204b9d2a9c3311646680dd6afd85c17d4ce30126 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Thu, 28 Nov 2024 07:42:34 -0300 Subject: [PATCH 04/20] chore: remove scope table --- internal/core/domain/verification.go | 16 +- ...02411200837121_add_verification_tables.sql | 20 +- internal/repositories/verification.go | 102 ++------ internal/repositories/verification_test.go | 236 ++++++------------ 4 files changed, 99 insertions(+), 275 deletions(-) diff --git a/internal/core/domain/verification.go b/internal/core/domain/verification.go index 389439afe..c7e08ab8e 100644 --- a/internal/core/domain/verification.go +++ b/internal/core/domain/verification.go @@ -13,26 +13,14 @@ type VerificationQuery struct { IssuerDID string ChainID int SkipCheckRevocation bool - Scopes []VerificationScope + Scope pgtype.JSONB `json:"scopes"` CreatedAt time.Time } -// VerificationScope holds the verification scope data -type VerificationScope struct { - ID uuid.UUID - ScopeID int - CircuitID string - Context string - AllowedIssuers []string - CredentialType string - CredentialSubject pgtype.JSONB `json:"credential_subject"` - CreatedAt time.Time -} - // VerificationResponse holds the verification response data type VerificationResponse struct { ID uuid.UUID - VerificationScopeID uuid.UUID + VerificationQueryID uuid.UUID UserDID string Response pgtype.JSONB `json:"response"` Pass bool diff --git a/internal/db/schema/migrations/202411200837121_add_verification_tables.sql b/internal/db/schema/migrations/202411200837121_add_verification_tables.sql index 07cf53e92..a2de10f05 100644 --- a/internal/db/schema/migrations/202411200837121_add_verification_tables.sql +++ b/internal/db/schema/migrations/202411200837121_add_verification_tables.sql @@ -4,41 +4,27 @@ CREATE TABLE IF NOT EXISTS verification_queries ( id uuid NOT NULL PRIMARY KEY, issuer_id text NOT NULL, chain_id integer NOT NULL, + scope jsonb NULL, skip_check_revocation boolean NOT NULL, created_at timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT verification_queries_indentities_id_fk foreign key (issuer_id) references identities (identifier) ); -CREATE TABLE IF NOT EXISTS verification_scopes ( - id uuid NOT NULL PRIMARY KEY, - verification_query_id uuid NOT NULL, - scope_id integer NOT NULL, - circuit_id text NOT NULL, - context text NOT NULL, - allowed_issuers text[] NOT NULL, - credential_type text NOT NULL, - credential_subject jsonb NULL, - created_at timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, - CONSTRAINT verification_scopes_verification_query_id_fk foreign key (verification_query_id) references verification_queries (id) -); - CREATE TABLE IF NOT EXISTS verification_responses ( id uuid NOT NULL PRIMARY KEY, - verification_scope_id uuid NOT NULL, + verification_query_id uuid NOT NULL, user_did text NOT NULL, response jsonb NOT NULL, pass boolean NOT NULL, created_at timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, - CONSTRAINT verification_responses_verification_scopes_id_fk foreign key (verification_scope_id) references verification_scopes (id) + CONSTRAINT verification_responses_verification_queries_id_fk foreign key (verification_query_id) references verification_queries (id) ); -- +goose StatementEnd -- +goose Down -- +goose StatementBegin ALTER TABLE verification_responses DROP CONSTRAINT verification_responses_verification_scopes_id_fk; -ALTER TABLE verification_scopes DROP CONSTRAINT verification_scopes_verification_query_id_fk; ALTER TABLE verification_queries DROP CONSTRAINT verification_queries_indentities_id_fk; DROP TABLE IF EXISTS verification_queries; DROP TABLE IF EXISTS verification_responses; -DROP TABLE IF EXISTS verification_scopes; -- +goose StatementEnd \ No newline at end of file diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go index 75d1c060c..02957b7d0 100644 --- a/internal/repositories/verification.go +++ b/internal/repositories/verification.go @@ -15,12 +15,8 @@ import ( const foreignKeyViolationErrorCode = "23503" -var ( - // VerificationQueryNotFoundError is returned when a verification query is not found - VerificationQueryNotFoundError = errors.New("verification query not found") - // VerificationScopeNotFoundError is returned when a verification scope is not found - VerificationScopeNotFoundError = errors.New("verification scope not found") -) +// VerificationQueryNotFoundError is returned when a verification query is not found +var VerificationQueryNotFoundError = errors.New("verification query not found") // VerificationRepository is a repository for verification queries type VerificationRepository struct { @@ -34,87 +30,38 @@ func NewVerification(conn db.Storage) *VerificationRepository { // Save stores a verification query in the database func (r *VerificationRepository) Save(ctx context.Context, issuerID w3c.DID, query domain.VerificationQuery) (uuid.UUID, error) { - sql := `INSERT INTO verification_queries (id, issuer_id, chain_id, skip_check_revocation) - VALUES($1, $2, $3, $4) ON CONFLICT (id) DO - UPDATE SET issuer_id=$2, chain_id=$3, skip_check_revocation=$4 + sql := `INSERT INTO verification_queries (id, issuer_id, chain_id, scope, skip_check_revocation) + VALUES($1, $2, $3, $4, $5) ON CONFLICT (id) DO + UPDATE SET issuer_id=$2, chain_id=$3, scope=$4, skip_check_revocation=$5 RETURNING id` var queryID uuid.UUID - tx, err := r.conn.Pgx.Begin(ctx) - if err != nil { - return uuid.Nil, err - } - err = tx.QueryRow(ctx, sql, query.ID, issuerID.String(), query.ChainID, query.SkipCheckRevocation).Scan(&queryID) - if err != nil { - errIn := tx.Rollback(ctx) - if errIn != nil { - return uuid.Nil, errIn - } - return uuid.Nil, err - } - - for _, scope := range query.Scopes { - sql = `INSERT INTO verification_scopes (id, verification_query_id, scope_id, circuit_id, context, allowed_issuers, credential_type, credential_subject) - VALUES($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (id) DO - UPDATE SET circuit_id=$4, context=$5, allowed_issuers=$6, credential_type=$7, credential_subject=$8` - _, err = tx.Exec(ctx, sql, scope.ID, queryID, scope.ScopeID, scope.CircuitID, scope.Context, scope.AllowedIssuers, scope.CredentialType, scope.CredentialSubject) - if err != nil { - errIn := tx.Rollback(ctx) - if errIn != nil { - return uuid.Nil, errIn - } - return uuid.Nil, err - } - } - - err = tx.Commit(ctx) - if err != nil { + if err := r.conn.Pgx.QueryRow(ctx, sql, query.ID, issuerID.String(), query.ChainID, query.Scope, query.SkipCheckRevocation).Scan(&queryID); err != nil { return uuid.Nil, err } - return queryID, err + return queryID, nil } // Get returns a verification query by issuer and id func (r *VerificationRepository) Get(ctx context.Context, issuerID w3c.DID, id uuid.UUID) (*domain.VerificationQuery, error) { - sql := `SELECT id, issuer_id, chain_id, skip_check_revocation, created_at + sql := `SELECT id, issuer_id, chain_id, scope, skip_check_revocation, created_at FROM verification_queries WHERE issuer_id = $1 and id = $2` var query domain.VerificationQuery - err := r.conn.Pgx.QueryRow(ctx, sql, issuerID.String(), id).Scan(&query.ID, &query.IssuerDID, &query.ChainID, &query.SkipCheckRevocation, &query.CreatedAt) + err := r.conn.Pgx.QueryRow(ctx, sql, issuerID.String(), id).Scan(&query.ID, &query.IssuerDID, &query.ChainID, &query.Scope, &query.SkipCheckRevocation, &query.CreatedAt) if err != nil { - if strings.Contains(err.Error(), "no rows in result set") { return nil, VerificationQueryNotFoundError } return nil, err } - - sql = `SELECT id, scope_id, circuit_id, context, allowed_issuers, credential_type, credential_subject, created_at - FROM verification_scopes - WHERE verification_query_id = $1` - - rows, err := r.conn.Pgx.Query(ctx, sql, query.ID) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var scope domain.VerificationScope - err = rows.Scan(&scope.ID, &scope.ScopeID, &scope.CircuitID, &scope.Context, &scope.AllowedIssuers, &scope.CredentialType, &scope.CredentialSubject, &scope.CreatedAt) - if err != nil { - return nil, err - } - query.Scopes = append(query.Scopes, scope) - } - return &query, nil } // GetAll returns all verification queries for a given issuer func (r *VerificationRepository) GetAll(ctx context.Context, issuerID w3c.DID) ([]domain.VerificationQuery, error) { - sql := `SELECT id, issuer_id, chain_id, skip_check_revocation, created_at + sql := `SELECT id, issuer_id, chain_id, scope, skip_check_revocation, created_at FROM verification_queries WHERE issuer_id = $1` @@ -127,47 +74,28 @@ func (r *VerificationRepository) GetAll(ctx context.Context, issuerID w3c.DID) ( var queries []domain.VerificationQuery for rows.Next() { var query domain.VerificationQuery - err = rows.Scan(&query.ID, &query.IssuerDID, &query.ChainID, &query.SkipCheckRevocation, &query.CreatedAt) - if err != nil { - return nil, err - } - - sql = `SELECT id, scope_id, circuit_id, context, allowed_issuers, credential_type, credential_subject, created_at - FROM verification_scopes - WHERE verification_query_id = $1` - - scopeRows, err := r.conn.Pgx.Query(ctx, sql, query.ID) + err = rows.Scan(&query.ID, &query.IssuerDID, &query.ChainID, &query.Scope, &query.SkipCheckRevocation, &query.CreatedAt) if err != nil { return nil, err } - defer scopeRows.Close() - - for scopeRows.Next() { - var scope domain.VerificationScope - err = scopeRows.Scan(&scope.ID, &scope.ScopeID, &scope.CircuitID, &scope.Context, &scope.AllowedIssuers, &scope.CredentialType, &scope.CredentialSubject, &scope.CreatedAt) - if err != nil { - return nil, err - } - query.Scopes = append(query.Scopes, scope) - } queries = append(queries, query) } return queries, nil } // AddResponse stores a verification response in the database -func (r *VerificationRepository) AddResponse(ctx context.Context, scopeID uuid.UUID, response domain.VerificationResponse) (uuid.UUID, error) { - sql := `INSERT INTO verification_responses (id, verification_scope_id, user_did, response, pass) +func (r *VerificationRepository) AddResponse(ctx context.Context, queryID uuid.UUID, response domain.VerificationResponse) (uuid.UUID, error) { + sql := `INSERT INTO verification_responses (id, verification_query_id, user_did, response, pass) VALUES($1, $2, $3, $4, $5) ON CONFLICT (id) DO UPDATE SET user_did=$3, response=$4, pass=$5 RETURNING id` var responseID uuid.UUID - err := r.conn.Pgx.QueryRow(ctx, sql, response.ID, scopeID, response.UserDID, response.Response, response.Pass).Scan(&responseID) + err := r.conn.Pgx.QueryRow(ctx, sql, response.ID, queryID, response.UserDID, response.Response, response.Pass).Scan(&responseID) if err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == foreignKeyViolationErrorCode { - return uuid.Nil, VerificationScopeNotFoundError + return uuid.Nil, VerificationQueryNotFoundError } return uuid.Nil, err } diff --git a/internal/repositories/verification_test.go b/internal/repositories/verification_test.go index 916f3a2e0..318b0c2ed 100644 --- a/internal/repositories/verification_test.go +++ b/internal/repositories/verification_test.go @@ -2,6 +2,7 @@ package repositories import ( "context" + "encoding/json" "errors" "testing" @@ -26,40 +27,14 @@ func TestSaveVerificationQuery(t *testing.T) { require.NoError(t, err) t.Run("should save the verification", func(t *testing.T) { - credentialSubject := pgtype.JSONB{} - err = credentialSubject.Set(`{ - "birthday": { - "$eq": 19791109 - } - }`) - - credentialSubject2 := pgtype.JSONB{} - err = credentialSubject2.Set(` {"position": {"$eq": 1}}`) + scope := pgtype.JSONB{} + err = scope.Set(`[{"ID": 1, "circuitID": "credentialAtomicQuerySigV2", "query": {"context": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", "allowedIssuers": ["*"], "type": "KYCAgeCredential", "credentialSubject": {"birthday": {"$eq": 19791109}}}}]`) require.NoError(t, err) verificationQuery := domain.VerificationQuery{ ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scopes: []domain.VerificationScope{ - { - ID: uuid.New(), - ScopeID: 1, - CircuitID: "credentialAtomicQuerySigV2", - Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", - AllowedIssuers: []string{"issuer1", "issuer2"}, - CredentialType: "KYCAgeCredential", - CredentialSubject: credentialSubject, - }, - { - ID: uuid.New(), - ScopeID: 2, - CircuitID: "credentialAtomicQuerySigV2", - Context: "ipfs://QmaBJzpoYT2CViDx5ShJiuYLKXizrPEfXo8JqzrXCvG6oc", - AllowedIssuers: []string{"*"}, - CredentialType: "TestInteger01", - CredentialSubject: credentialSubject2, - }, - }, + Scope: scope, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -68,6 +43,7 @@ func TestSaveVerificationQuery(t *testing.T) { }) } +//nolint:all func TestGetVerification(t *testing.T) { ctx := context.Background() didStr := "did:iden3:polygon:amoy:xBdqiqz3yVT79NEAuNaqKSDZ6a5V6q8Ph66i5d2tT" @@ -80,35 +56,14 @@ func TestGetVerification(t *testing.T) { require.NoError(t, err) t.Run("should get the verification", func(t *testing.T) { - credentialSubject := pgtype.JSONB{} - err = credentialSubject.Set(`{"birthday": {"$eq": 19791109}}`) - credentialSubject2 := pgtype.JSONB{} - err = credentialSubject2.Set(`{"position": {"$eq": 1}}`) + scope := pgtype.JSONB{} + err = scope.Set(`[{"ID": 1, "circuitID": "credentialAtomicQuerySigV2", "query": {"context": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", "allowedIssuers": ["*"], "type": "KYCAgeCredential", "credentialSubject": {"birthday": {"$eq": 19791109}}}}]`) require.NoError(t, err) verificationQuery := domain.VerificationQuery{ ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scopes: []domain.VerificationScope{ - { - ID: uuid.New(), - ScopeID: 1, - CircuitID: "credentialAtomicQuerySigV2", - Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", - AllowedIssuers: []string{"issuer1", "issuer2"}, - CredentialType: "KYCAgeCredential", - CredentialSubject: credentialSubject, - }, - { - ID: uuid.New(), - ScopeID: 2, - CircuitID: "credentialAtomicQuerySigV2", - Context: "ipfs://QmaBJzpoYT2CViDx5ShJiuYLKXizrPEfXo8JqzrXCvG6oc", - AllowedIssuers: []string{"*"}, - CredentialType: "TestInteger01", - CredentialSubject: credentialSubject2, - }, - }, + Scope: scope, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -120,20 +75,17 @@ func TestGetVerification(t *testing.T) { assert.Equal(t, verificationQuery.ID, verificationQueryFromDB.ID) assert.Equal(t, verificationQuery.ChainID, verificationQueryFromDB.ChainID) assert.Equal(t, verificationQuery.SkipCheckRevocation, verificationQueryFromDB.SkipCheckRevocation) - assert.Equal(t, verificationQuery.Scopes[0].ID, verificationQueryFromDB.Scopes[0].ID) - assert.Equal(t, verificationQuery.Scopes[0].ScopeID, verificationQueryFromDB.Scopes[0].ScopeID) - assert.Equal(t, verificationQuery.Scopes[0].CircuitID, verificationQueryFromDB.Scopes[0].CircuitID) - assert.Equal(t, verificationQuery.Scopes[0].Context, verificationQueryFromDB.Scopes[0].Context) - assert.Equal(t, verificationQuery.Scopes[0].AllowedIssuers, verificationQueryFromDB.Scopes[0].AllowedIssuers) - assert.Equal(t, verificationQuery.Scopes[0].CredentialType, verificationQueryFromDB.Scopes[0].CredentialType) - assert.Equal(t, verificationQuery.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB.Scopes[0].CredentialSubject.Bytes) - assert.Equal(t, verificationQuery.Scopes[1].ID, verificationQueryFromDB.Scopes[1].ID) - assert.Equal(t, verificationQuery.Scopes[1].ScopeID, verificationQueryFromDB.Scopes[1].ScopeID) - assert.Equal(t, verificationQuery.Scopes[1].CircuitID, verificationQueryFromDB.Scopes[1].CircuitID) - assert.Equal(t, verificationQuery.Scopes[1].Context, verificationQueryFromDB.Scopes[1].Context) - assert.Equal(t, verificationQuery.Scopes[1].AllowedIssuers, verificationQueryFromDB.Scopes[1].AllowedIssuers) - assert.Equal(t, verificationQuery.Scopes[1].CredentialType, verificationQueryFromDB.Scopes[1].CredentialType) - assert.Equal(t, verificationQuery.Scopes[1].CredentialSubject.Bytes, verificationQueryFromDB.Scopes[1].CredentialSubject.Bytes) + assert.NotNil(t, verificationQueryFromDB.Scope) + + var res []map[string]interface{} + require.NoError(t, json.Unmarshal(verificationQuery.Scope.Bytes, &res)) + assert.Equal(t, 1, len(res)) + assert.Equal(t, 1, int(res[0]["ID"].(float64))) + assert.Equal(t, "credentialAtomicQuerySigV2", res[0]["circuitID"]) + assert.Equal(t, "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", res[0]["query"].(map[string]interface{})["context"]) + assert.Equal(t, []interface{}{"*"}, res[0]["query"].(map[string]interface{})["allowedIssuers"]) + assert.Equal(t, "KYCAgeCredential", res[0]["query"].(map[string]interface{})["type"]) + assert.Equal(t, 19791109, int(res[0]["query"].(map[string]interface{})["credentialSubject"].(map[string]interface{})["birthday"].(map[string]interface{})["$eq"].(float64))) }) t.Run("should not get the verification", func(t *testing.T) { @@ -144,6 +96,7 @@ func TestGetVerification(t *testing.T) { }) } +//nolint:all func TestUpdateVerificationQuery(t *testing.T) { ctx := context.Background() didStr := "did:iden3:polygon:amoy:x7tz1NB9fy4GJJW1oQV1wGYpuratuApN8FWEQVKZP" @@ -161,37 +114,25 @@ func TestUpdateVerificationQuery(t *testing.T) { credentialSubject2 := pgtype.JSONB{} err = credentialSubject2.Set(`{"position": {"$eq": 1}}`) require.NoError(t, err) + + scope := pgtype.JSONB{} + err = scope.Set(`[{"ID": 1, "circuitID": "credentialAtomicQuerySigV2", "query": {"context": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", "allowedIssuers": ["*"], "type": "KYCAgeCredential", "credentialSubject": {"birthday": {"$eq": 19791109}}}}]`) + require.NoError(t, err) + scope2 := pgtype.JSONB{} + err = scope2.Set(`[{"ID": 1,"circuitID": "credentialAtomicQueryV3-beta.1","query": {"context": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld","allowedIssuers": ["*"],"type": "KYCAgeCredential","credentialSubject": {"birthday": {"$eq": 19791109}}}}]`) + require.NoError(t, err) verificationQuery := domain.VerificationQuery{ ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scopes: []domain.VerificationScope{ - { - ID: uuid.New(), - ScopeID: 1, - CircuitID: "credentialAtomicQuerySigV2", - Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", - AllowedIssuers: []string{"issuer1", "issuer2"}, - CredentialType: "KYCAgeCredential", - CredentialSubject: credentialSubject1, - }, - }, + Scope: scope, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) require.NoError(t, err) assert.Equal(t, verificationQuery.ID, verificationQueryID) - verificationQuery.Scopes = append(verificationQuery.Scopes, domain.VerificationScope{ - ID: uuid.New(), - ScopeID: 2, - CircuitID: "credentialAtomicQuerySigV2", - Context: "ipfs://QmaBJzpoYT2CViDx5ShJiuYLKXizrPEfXo8JqzrXCvG6oc", - AllowedIssuers: []string{"*"}, - CredentialType: "TestInteger01", - CredentialSubject: credentialSubject2, - }) - + verificationQuery.Scope = scope2 verificationQuery.SkipCheckRevocation = true verificationQuery.ChainID = 137 _, err = verificationRepository.Save(ctx, *did, verificationQuery) @@ -202,23 +143,21 @@ func TestUpdateVerificationQuery(t *testing.T) { assert.Equal(t, verificationQuery.ID, verificationQueryFromDB.ID) assert.Equal(t, verificationQuery.ChainID, verificationQueryFromDB.ChainID) assert.Equal(t, verificationQuery.SkipCheckRevocation, verificationQueryFromDB.SkipCheckRevocation) - assert.Equal(t, verificationQuery.Scopes[0].ID, verificationQueryFromDB.Scopes[0].ID) - assert.Equal(t, verificationQuery.Scopes[0].ScopeID, verificationQueryFromDB.Scopes[0].ScopeID) - assert.Equal(t, verificationQuery.Scopes[0].CircuitID, verificationQueryFromDB.Scopes[0].CircuitID) - assert.Equal(t, verificationQuery.Scopes[0].Context, verificationQueryFromDB.Scopes[0].Context) - assert.Equal(t, verificationQuery.Scopes[0].AllowedIssuers, verificationQueryFromDB.Scopes[0].AllowedIssuers) - assert.Equal(t, verificationQuery.Scopes[0].CredentialType, verificationQueryFromDB.Scopes[0].CredentialType) - assert.Equal(t, verificationQuery.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB.Scopes[0].CredentialSubject.Bytes) - assert.Equal(t, verificationQuery.Scopes[1].ID, verificationQueryFromDB.Scopes[1].ID) - assert.Equal(t, verificationQuery.Scopes[1].ScopeID, verificationQueryFromDB.Scopes[1].ScopeID) - assert.Equal(t, verificationQuery.Scopes[1].CircuitID, verificationQueryFromDB.Scopes[1].CircuitID) - assert.Equal(t, verificationQuery.Scopes[1].Context, verificationQueryFromDB.Scopes[1].Context) - assert.Equal(t, verificationQuery.Scopes[1].AllowedIssuers, verificationQueryFromDB.Scopes[1].AllowedIssuers) - assert.Equal(t, verificationQuery.Scopes[1].CredentialType, verificationQueryFromDB.Scopes[1].CredentialType) - assert.Equal(t, verificationQuery.Scopes[1].CredentialSubject.Bytes, verificationQueryFromDB.Scopes[1].CredentialSubject.Bytes) + assert.NotNil(t, verificationQueryFromDB.Scope) + + var res []map[string]interface{} + require.NoError(t, json.Unmarshal(verificationQueryFromDB.Scope.Bytes, &res)) + assert.Equal(t, 1, len(res)) + assert.Equal(t, 1, int(res[0]["ID"].(float64))) + assert.Equal(t, "credentialAtomicQueryV3-beta.1", res[0]["circuitID"]) + assert.Equal(t, "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", res[0]["query"].(map[string]interface{})["context"]) + assert.Equal(t, []interface{}{"*"}, res[0]["query"].(map[string]interface{})["allowedIssuers"]) + assert.Equal(t, "KYCAgeCredential", res[0]["query"].(map[string]interface{})["type"]) + assert.Equal(t, 19791109, int(res[0]["query"].(map[string]interface{})["credentialSubject"].(map[string]interface{})["birthday"].(map[string]interface{})["$eq"].(float64))) }) } +//nolint:all func TestGetAllVerification(t *testing.T) { ctx := context.Background() didStr := "did:iden3:polygon:amoy:xCu8Cshrj4oegWRabzGtbKzqUFtXN85x8XkCPdREU" @@ -231,45 +170,26 @@ func TestGetAllVerification(t *testing.T) { require.NoError(t, err) t.Run("GetAll", func(t *testing.T) { - credentialSubject := pgtype.JSONB{} - err = credentialSubject.Set(`{"birthday": {"$eq": 19791109}}`) - credentialSubject2 := pgtype.JSONB{} - err = credentialSubject2.Set(`{"position": {"$eq": 1}}`) + scope := pgtype.JSONB{} + err = scope.Set(`[{"ID": 1, "circuitID": "credentialAtomicQuerySigV2", "query": {"context": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", "allowedIssuers": ["*"], "type": "KYCAgeCredential", "credentialSubject": {"birthday": {"$eq": 19791109}}}}]`) require.NoError(t, err) + + scope2 := pgtype.JSONB{} + err = scope2.Set(`[{"ID": 2,"circuitID": "credentialAtomicQueryV3-beta.1","query": {"context": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld","allowedIssuers": ["*"],"type": "KYCAgeCredential","credentialSubject": {"birthday": {"$eq": 19791109}}}}]`) + verificationQuery1 := domain.VerificationQuery{ ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scopes: []domain.VerificationScope{ - { - ID: uuid.New(), - ScopeID: 1, - CircuitID: "credentialAtomicQuerySigV2", - Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", - AllowedIssuers: []string{"issuer1", "issuer2"}, - CredentialType: "KYCAgeCredential", - CredentialSubject: credentialSubject, - }, - }, + Scope: scope, } verificationQuery2 := domain.VerificationQuery{ ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scopes: []domain.VerificationScope{ - { - ID: uuid.New(), - ScopeID: 2, - CircuitID: "credentialAtomicQuerySigV2", - Context: "ipfs://QmaBJzpoYT2CViDx5ShJiuYLKXizrPEfXo8JqzrXCvG6oc", - AllowedIssuers: []string{"*"}, - CredentialType: "TestInteger01", - CredentialSubject: credentialSubject2, - }, - }, + Scope: scope2, } - verificationQueryID1, err := verificationRepository.Save(ctx, *did, verificationQuery1) require.NoError(t, err) assert.Equal(t, verificationQuery1.ID, verificationQueryID1) @@ -281,26 +201,34 @@ func TestGetAllVerification(t *testing.T) { verificationQueryFromDB, err := verificationRepository.GetAll(ctx, *did) require.NoError(t, err) assert.Equal(t, 2, len(verificationQueryFromDB)) + + var resVerificationQuery1 []map[string]interface{} + require.NoError(t, json.Unmarshal(verificationQueryFromDB[0].Scope.Bytes, &resVerificationQuery1)) + assert.Equal(t, verificationQuery1.ID, verificationQueryFromDB[0].ID) assert.Equal(t, verificationQuery1.ChainID, verificationQueryFromDB[0].ChainID) assert.Equal(t, verificationQuery1.SkipCheckRevocation, verificationQueryFromDB[0].SkipCheckRevocation) - assert.Equal(t, verificationQuery1.Scopes[0].ID, verificationQueryFromDB[0].Scopes[0].ID) - assert.Equal(t, verificationQuery1.Scopes[0].ScopeID, verificationQueryFromDB[0].Scopes[0].ScopeID) - assert.Equal(t, verificationQuery1.Scopes[0].CircuitID, verificationQueryFromDB[0].Scopes[0].CircuitID) - assert.Equal(t, verificationQuery1.Scopes[0].Context, verificationQueryFromDB[0].Scopes[0].Context) - assert.Equal(t, verificationQuery1.Scopes[0].AllowedIssuers, verificationQueryFromDB[0].Scopes[0].AllowedIssuers) - assert.Equal(t, verificationQuery1.Scopes[0].CredentialType, verificationQueryFromDB[0].Scopes[0].CredentialType) - assert.Equal(t, verificationQuery1.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB[0].Scopes[0].CredentialSubject.Bytes) + assert.Equal(t, 1, len(resVerificationQuery1)) + assert.Equal(t, 1, int(resVerificationQuery1[0]["ID"].(float64))) + assert.Equal(t, "credentialAtomicQuerySigV2", resVerificationQuery1[0]["circuitID"]) + assert.Equal(t, "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", resVerificationQuery1[0]["query"].(map[string]interface{})["context"]) + assert.Equal(t, []interface{}{"*"}, resVerificationQuery1[0]["query"].(map[string]interface{})["allowedIssuers"]) + assert.Equal(t, "KYCAgeCredential", resVerificationQuery1[0]["query"].(map[string]interface{})["type"]) + assert.Equal(t, 19791109, int(resVerificationQuery1[0]["query"].(map[string]interface{})["credentialSubject"].(map[string]interface{})["birthday"].(map[string]interface{})["$eq"].(float64))) + + var resVerificationQuery2 []map[string]interface{} + require.NoError(t, json.Unmarshal(verificationQueryFromDB[1].Scope.Bytes, &resVerificationQuery2)) + assert.Equal(t, verificationQuery2.ID, verificationQueryFromDB[1].ID) assert.Equal(t, verificationQuery2.ChainID, verificationQueryFromDB[1].ChainID) assert.Equal(t, verificationQuery2.SkipCheckRevocation, verificationQueryFromDB[1].SkipCheckRevocation) - assert.Equal(t, verificationQuery2.Scopes[0].ID, verificationQueryFromDB[1].Scopes[0].ID) - assert.Equal(t, verificationQuery2.Scopes[0].ScopeID, verificationQueryFromDB[1].Scopes[0].ScopeID) - assert.Equal(t, verificationQuery2.Scopes[0].CircuitID, verificationQueryFromDB[1].Scopes[0].CircuitID) - assert.Equal(t, verificationQuery2.Scopes[0].Context, verificationQueryFromDB[1].Scopes[0].Context) - assert.Equal(t, verificationQuery2.Scopes[0].AllowedIssuers, verificationQueryFromDB[1].Scopes[0].AllowedIssuers) - assert.Equal(t, verificationQuery2.Scopes[0].CredentialType, verificationQueryFromDB[1].Scopes[0].CredentialType) - assert.Equal(t, verificationQuery2.Scopes[0].CredentialSubject.Bytes, verificationQueryFromDB[1].Scopes[0].CredentialSubject.Bytes) + assert.Equal(t, 1, len(resVerificationQuery2)) + assert.Equal(t, 2, int(resVerificationQuery2[0]["ID"].(float64))) + assert.Equal(t, "credentialAtomicQueryV3-beta.1", resVerificationQuery2[0]["circuitID"]) + assert.Equal(t, "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", resVerificationQuery2[0]["query"].(map[string]interface{})["context"]) + assert.Equal(t, []interface{}{"*"}, resVerificationQuery2[0]["query"].(map[string]interface{})["allowedIssuers"]) + assert.Equal(t, "KYCAgeCredential", resVerificationQuery2[0]["query"].(map[string]interface{})["type"]) + assert.Equal(t, 19791109, int(resVerificationQuery2[0]["query"].(map[string]interface{})["credentialSubject"].(map[string]interface{})["birthday"].(map[string]interface{})["$eq"].(float64))) }) } @@ -319,21 +247,15 @@ func TestAddVerification(t *testing.T) { credentialSubject := pgtype.JSONB{} err = credentialSubject.Set(`{"birthday": {"$eq": 19791109}}`) require.NoError(t, err) + + scope := pgtype.JSONB{} + err = scope.Set(`[{"ID": 1, "circuitID": "credentialAtomicQuerySigV2", "query": {"context": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", "allowedIssuers": ["*"], "type": "KYCAgeCredential", "credentialSubject": {"birthday": {"$eq": 19791109}}}}]`) + require.NoError(t, err) verificationQuery := domain.VerificationQuery{ ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scopes: []domain.VerificationScope{ - { - ID: uuid.New(), - ScopeID: 1, - CircuitID: "credentialAtomicQuerySigV2", - Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", - AllowedIssuers: []string{"issuer1", "issuer2"}, - CredentialType: "KYCAgeCredential", - CredentialSubject: credentialSubject, - }, - }, + Scope: scope, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -349,12 +271,12 @@ func TestAddVerification(t *testing.T) { require.NoError(t, err) verificationResponse := domain.VerificationResponse{ ID: uuid.New(), - VerificationScopeID: verificationQueryFromDB.Scopes[0].ID, + VerificationQueryID: verificationQueryFromDB.ID, UserDID: "did:iden3:privado:main:2SizDYDWBViKXRfp1VgUAMqhz5SDvP7D1MYiPfwJV3", Response: response, } - responseID, err := verificationRepository.AddResponse(ctx, verificationQueryFromDB.Scopes[0].ID, verificationResponse) + responseID, err := verificationRepository.AddResponse(ctx, verificationQueryFromDB.ID, verificationResponse) require.NoError(t, err) assert.Equal(t, verificationResponse.ID, responseID) }) @@ -370,7 +292,7 @@ func TestAddVerification(t *testing.T) { } responseID, err := verificationRepository.AddResponse(ctx, uuid.New(), verificationResponse) require.Error(t, err) - require.True(t, errors.Is(err, VerificationScopeNotFoundError)) + require.True(t, errors.Is(err, VerificationQueryNotFoundError)) assert.Equal(t, uuid.Nil, responseID) }) } From fd653a752d7d2a5998c8aaf3a4a53dac0f4de27d Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Wed, 27 Nov 2024 10:43:30 -0800 Subject: [PATCH 05/20] initial verification service layer --- api/api.yaml | 161 +++++++++ cmd/platform/main.go | 5 +- internal/api/api.gen.go | 316 ++++++++++++++++++ internal/api/main_test.go | 19 +- internal/api/server.go | 6 +- .../core/ports/verification_repository.go | 1 + internal/core/ports/verification_service.go | 15 + internal/core/services/verification.go | 78 +++++ internal/repositories/verification.go | 32 +- 9 files changed, 628 insertions(+), 5 deletions(-) create mode 100644 internal/core/ports/verification_service.go create mode 100644 internal/core/services/verification.go diff --git a/api/api.yaml b/api/api.yaml index eeeafc590..639a30667 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -1989,6 +1989,77 @@ paths: '500': $ref: '#/components/responses/500' + # Verification + + /v2/verification/{identifier}/check: + get: + summary: Check Verification Response or Provide Query + operationId: CheckVerification + description: | + Checks if a verification response already exists for the given identifier. + If no response is found, it returns the verification query request to be completed by the user. + tags: + - Verification + parameters: + - name: identifier + in: path + required: true + description: User's DID identifier + schema: + type: string + - $ref: '#/components/parameters/verificationQueryId' + responses: + '200': + description: Verification Response or Query Request + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/VerificationResponse' + - $ref: '#/components/schemas/VerificationQueryRequest' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + '500': + $ref: '#/components/responses/500' + + /v2/verification/{identifier}/callback: + post: + summary: Submit Verification Response + operationId: SubmitVerificationResponse + description: | + Endpoint to submit a verification response for the given verification query request. + The response will be validated and stored in the verification_responses table. + tags: + - Verification + parameters: + - name: identifier + in: path + required: true + description: User's DID identifier + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationResponseRequest' + responses: + '200': + description: Verification response submitted successfully + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationResponseStatus' + '400': + $ref: '#/components/responses/400' + '500': + $ref: '#/components/responses/500' + + + components: securitySchemes: basicAuth: @@ -3163,6 +3234,85 @@ components: x-go-type-import: name: protocol path: github.com/iden3/iden3comm/v2/protocol + + VerificationResponse: + type: object + required: + - verification_scope_id + - user_did + - response + - pass + properties: + verification_scope_id: + type: string + description: Scope ID for the verification query. + user_did: + type: string + description: Decentralized identifier of the user. + response: + type: object + description: The response from the user as a JSON object. + pass: + type: boolean + description: Indicates if the verification passed. + + VerificationQueryRequest: + type: object + required: + - verification_query_id + - scopes + properties: + verification_query_id: + type: string + description: The ID of the verification query. + scopes: + type: array + items: + $ref: '#/components/schemas/VerificationScope' + + VerificationScope: + type: object + required: + - scope_id + - query_type + - context + properties: + scope_id: + type: string + description: ID of the scope. + query_type: + type: string + description: Type of query for the verification. + context: + type: string + description: Additional context for the query. + + VerificationResponseRequest: + type: object + required: + - verification_scope_id + - user_did + - response + properties: + verification_scope_id: + type: string + description: Scope ID of the verification query. + user_did: + type: string + description: User's DID identifier. + response: + type: object + description: Response to the verification query request. + + VerificationResponseStatus: + type: object + required: + - status + properties: + status: + type: string + enum: [ submitted, validated, error ] + description: The status of the submitted verification response. CreateDisplayMethodRequest: type: object @@ -3367,6 +3517,17 @@ components: schema: type: string + verificationQueryId: + name: verificationQueryId + in: query + required: true + description: The verification query ID to check for a response + schema: + type: string + x-go-type: uuid.UUID + x-go-type-import: + name: uuid + path: github.com/google/uuid responses: '400': diff --git a/cmd/platform/main.go b/cmd/platform/main.go index 0193638a2..5827f77c3 100644 --- a/cmd/platform/main.go +++ b/cmd/platform/main.go @@ -115,6 +115,7 @@ func main() { sessionRepository := repositories.NewSessionCached(cachex) keyRepository := repositories.NewKey(*storage) paymentsRepo := repositories.NewPayment(*storage) + verificationRepository := repositories.NewVerification(*storage) // services initialization mtService := services.NewIdentityMerkleTrees(mtRepository) @@ -170,6 +171,8 @@ func main() { } accountService := services.NewAccountService(*networkResolver) + verificationService := services.NewVerificationService(verifier, verificationRepository) + publisherGateway, err := gateways.NewPublisherEthGateway(*networkResolver, keyStore, cfg.PublishingKeyPath) if err != nil { log.Error(ctx, "error creating publish gateway", "err", err) @@ -205,7 +208,7 @@ func main() { api.HandlerWithOptions( api.NewStrictHandlerWithOptions( - api.NewServer(cfg, identityService, accountService, connectionsService, claimsService, qrService, publisher, packageManager, *networkResolver, serverHealth, schemaService, linkService, displayMethodService, keyService, paymentService), + api.NewServer(cfg, identityService, accountService, connectionsService, claimsService, qrService, publisher, packageManager, *networkResolver, serverHealth, schemaService, linkService, displayMethodService, keyService, paymentService, verificationService, mediaTypeManager), middlewares(ctx, cfg.HTTPBasicAuth), api.StrictHTTPServerOptions{ RequestErrorHandlerFunc: errors.RequestErrorHandlerFunc, diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 99caa0726..83e89680e 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -125,6 +125,13 @@ const ( StateTransactionStatusPublished StateTransactionStatus = "published" ) +// Defines values for VerificationResponseStatusStatus. +const ( + Error VerificationResponseStatusStatus = "error" + Submitted VerificationResponseStatusStatus = "submitted" + Validated VerificationResponseStatusStatus = "validated" +) + // Defines values for GetConnectionsParamsSort. const ( GetConnectionsParamsSortCreatedAt GetConnectionsParamsSort = "createdAt" @@ -753,6 +760,62 @@ type UUIDResponse struct { // UUIDString defines model for UUIDString. type UUIDString = string +// VerificationQueryRequest defines model for VerificationQueryRequest. +type VerificationQueryRequest struct { + Scopes []VerificationScope `json:"scopes"` + + // VerificationQueryId The ID of the verification query. + VerificationQueryId string `json:"verification_query_id"` +} + +// VerificationResponse defines model for VerificationResponse. +type VerificationResponse struct { + // Pass Indicates if the verification passed. + Pass bool `json:"pass"` + + // Response The response from the user as a JSON object. + Response map[string]interface{} `json:"response"` + + // UserDid Decentralized identifier of the user. + UserDid string `json:"user_did"` + + // VerificationScopeId Scope ID for the verification query. + VerificationScopeId string `json:"verification_scope_id"` +} + +// VerificationResponseRequest defines model for VerificationResponseRequest. +type VerificationResponseRequest struct { + // Response Response to the verification query request. + Response map[string]interface{} `json:"response"` + + // UserDid User's DID identifier. + UserDid string `json:"user_did"` + + // VerificationScopeId Scope ID of the verification query. + VerificationScopeId string `json:"verification_scope_id"` +} + +// VerificationResponseStatus defines model for VerificationResponseStatus. +type VerificationResponseStatus struct { + // Status The status of the submitted verification response. + Status VerificationResponseStatusStatus `json:"status"` +} + +// VerificationResponseStatusStatus The status of the submitted verification response. +type VerificationResponseStatusStatus string + +// VerificationScope defines model for VerificationScope. +type VerificationScope struct { + // Context Additional context for the query. + Context string `json:"context"` + + // QueryType Type of query for the verification. + QueryType string `json:"query_type"` + + // ScopeId ID of the scope. + ScopeId string `json:"scope_id"` +} + // Id defines model for id. type Id = uuid.UUID @@ -777,6 +840,9 @@ type PathNonce = int64 // SessionID defines model for sessionID. type SessionID = uuid.UUID +// VerificationQueryId defines model for verificationQueryId. +type VerificationQueryId = uuid.UUID + // N400 defines model for 400. type N400 = GenericErrorMessage @@ -998,6 +1064,12 @@ type GetQrFromStoreParams struct { Issuer *string `form:"issuer,omitempty" json:"issuer,omitempty"` } +// CheckVerificationParams defines parameters for CheckVerification. +type CheckVerificationParams struct { + // VerificationQueryId The verification query ID to check for a response + VerificationQueryId VerificationQueryId `form:"verificationQueryId" json:"verificationQueryId"` +} + // AuthenticationParams defines parameters for Authentication. type AuthenticationParams struct { // Type Type: @@ -1069,6 +1141,9 @@ type ImportSchemaJSONRequestBody = ImportSchemaRequest // UpdateSchemaJSONRequestBody defines body for UpdateSchema for application/json ContentType. type UpdateSchemaJSONRequestBody UpdateSchemaJSONBody +// SubmitVerificationResponseJSONRequestBody defines body for SubmitVerificationResponse for application/json ContentType. +type SubmitVerificationResponseJSONRequestBody = VerificationResponseRequest + // ServerInterface represents all server handlers. type ServerInterface interface { // Healthcheck @@ -1254,6 +1329,12 @@ type ServerInterface interface { // Get Supported Networks // (GET /v2/supported-networks) GetSupportedNetworks(w http.ResponseWriter, r *http.Request) + // Submit Verification Response + // (POST /v2/verification/{identifier}/callback) + SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string) + // Check Verification Response or Provide Query + // (GET /v2/verification/{identifier}/check) + CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) // Get Authentication Message // (POST /v2/{identifier}/authentication) Authentication(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params AuthenticationParams) @@ -1629,6 +1710,18 @@ func (_ Unimplemented) GetSupportedNetworks(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusNotImplemented) } +// Submit Verification Response +// (POST /v2/verification/{identifier}/callback) +func (_ Unimplemented) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Check Verification Response or Provide Query +// (GET /v2/verification/{identifier}/check) +func (_ Unimplemented) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // Get Authentication Message // (POST /v2/{identifier}/authentication) func (_ Unimplemented) Authentication(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params AuthenticationParams) { @@ -3927,6 +4020,74 @@ func (siw *ServerInterfaceWrapper) GetSupportedNetworks(w http.ResponseWriter, r handler.ServeHTTP(w, r) } +// SubmitVerificationResponse operation middleware +func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier string + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.SubmitVerificationResponse(w, r, identifier) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CheckVerification operation middleware +func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier string + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params CheckVerificationParams + + // ------------- Required query parameter "verificationQueryId" ------------- + + if paramValue := r.URL.Query().Get("verificationQueryId"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "verificationQueryId"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "verificationQueryId", r.URL.Query(), ¶ms.VerificationQueryId) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "verificationQueryId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CheckVerification(w, r, identifier, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // Authentication operation middleware func (siw *ServerInterfaceWrapper) Authentication(w http.ResponseWriter, r *http.Request) { @@ -4259,6 +4420,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/supported-networks", wrapper.GetSupportedNetworks) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/v2/verification/{identifier}/callback", wrapper.SubmitVerificationResponse) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/v2/verification/{identifier}/check", wrapper.CheckVerification) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/v2/{identifier}/authentication", wrapper.Authentication) }) @@ -6880,6 +7047,89 @@ func (response GetSupportedNetworks500JSONResponse) VisitGetSupportedNetworksRes return json.NewEncoder(w).Encode(response) } +type SubmitVerificationResponseRequestObject struct { + Identifier string `json:"identifier"` + Body *SubmitVerificationResponseJSONRequestBody +} + +type SubmitVerificationResponseResponseObject interface { + VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error +} + +type SubmitVerificationResponse200JSONResponse VerificationResponseStatus + +func (response SubmitVerificationResponse200JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type SubmitVerificationResponse400JSONResponse struct{ N400JSONResponse } + +func (response SubmitVerificationResponse400JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type SubmitVerificationResponse500JSONResponse struct{ N500JSONResponse } + +func (response SubmitVerificationResponse500JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type CheckVerificationRequestObject struct { + Identifier string `json:"identifier"` + Params CheckVerificationParams +} + +type CheckVerificationResponseObject interface { + VisitCheckVerificationResponse(w http.ResponseWriter) error +} + +type CheckVerification200JSONResponse struct { + union json.RawMessage +} + +func (response CheckVerification200JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.union) +} + +type CheckVerification400JSONResponse struct{ N400JSONResponse } + +func (response CheckVerification400JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CheckVerification404JSONResponse struct{ N404JSONResponse } + +func (response CheckVerification404JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type CheckVerification500JSONResponse struct{ N500JSONResponse } + +func (response CheckVerification500JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type AuthenticationRequestObject struct { Identifier PathIdentifier `json:"identifier"` Params AuthenticationParams @@ -7101,6 +7351,12 @@ type StrictServerInterface interface { // Get Supported Networks // (GET /v2/supported-networks) GetSupportedNetworks(ctx context.Context, request GetSupportedNetworksRequestObject) (GetSupportedNetworksResponseObject, error) + // Submit Verification Response + // (POST /v2/verification/{identifier}/callback) + SubmitVerificationResponse(ctx context.Context, request SubmitVerificationResponseRequestObject) (SubmitVerificationResponseResponseObject, error) + // Check Verification Response or Provide Query + // (GET /v2/verification/{identifier}/check) + CheckVerification(ctx context.Context, request CheckVerificationRequestObject) (CheckVerificationResponseObject, error) // Get Authentication Message // (POST /v2/{identifier}/authentication) Authentication(ctx context.Context, request AuthenticationRequestObject) (AuthenticationResponseObject, error) @@ -8888,6 +9144,66 @@ func (sh *strictHandler) GetSupportedNetworks(w http.ResponseWriter, r *http.Req } } +// SubmitVerificationResponse operation middleware +func (sh *strictHandler) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string) { + var request SubmitVerificationResponseRequestObject + + request.Identifier = identifier + + var body SubmitVerificationResponseJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.SubmitVerificationResponse(ctx, request.(SubmitVerificationResponseRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "SubmitVerificationResponse") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(SubmitVerificationResponseResponseObject); ok { + if err := validResponse.VisitSubmitVerificationResponseResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// CheckVerification operation middleware +func (sh *strictHandler) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { + var request CheckVerificationRequestObject + + request.Identifier = identifier + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CheckVerification(ctx, request.(CheckVerificationRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CheckVerification") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CheckVerificationResponseObject); ok { + if err := validResponse.VisitCheckVerificationResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // Authentication operation middleware func (sh *strictHandler) Authentication(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params AuthenticationParams) { var request AuthenticationRequestObject diff --git a/internal/api/main_test.go b/internal/api/main_test.go index 84f8adc22..c04134679 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -33,6 +33,10 @@ import ( "github.com/polygonid/sh-id-platform/internal/repositories" "github.com/polygonid/sh-id-platform/internal/reversehash" "github.com/polygonid/sh-id-platform/internal/revocationstatus" + + auth "github.com/iden3/go-iden3-auth/v2" + authLoaders "github.com/iden3/go-iden3-auth/v2/loaders" + "github.com/polygonid/sh-id-platform/internal/packagemanager" ) var ( @@ -239,6 +243,7 @@ type repos struct { revocation ports.RevocationRepository displayMethod ports.DisplayMethodRepository keyRepository ports.KeyRepository + verification ports.VerificationRepository } type servicex struct { @@ -282,6 +287,7 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { revocation: repositories.NewRevocation(), displayMethod: repositories.NewDisplayMethod(*st), keyRepository: repositories.NewKey(*st), + verification: repositories.NewVerification(*st), } pubSub := pubsub.NewMock() @@ -367,7 +373,18 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { accountService := services.NewAccountService(*networkResolver) linkService := services.NewLinkService(storage, claimsService, qrService, repos.claims, repos.links, repos.schemas, schemaLoader, repos.sessions, pubSub, identityService, *networkResolver, cfg.UniversalLinks) keyService := services.NewKey(keyStore, claimsService, repos.keyRepository) - server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService, displayMethodService, keyService, paymentService) + + universalDIDResolverUrl := auth.UniversalResolverURL + if cfg.UniversalDIDResolver.UniversalResolverURL != nil && *cfg.UniversalDIDResolver.UniversalResolverURL != "" { + universalDIDResolverUrl = *cfg.UniversalDIDResolver.UniversalResolverURL + } + universalDIDResolverHandler := packagemanager.NewUniversalDIDResolverHandler(universalDIDResolverUrl) + verificationKeyLoader := &authLoaders.FSKeyLoader{Dir: cfg.Circuit.Path + "/authV2"} + verifier, err := auth.NewVerifier(verificationKeyLoader, networkResolver.GetStateResolvers(), auth.WithDIDResolver(universalDIDResolverHandler)) + + verificationService := services.NewVerificationService(verifier, repos.verification) + + server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService, displayMethodService, keyService, paymentService, verificationService, mediaTypeManager) return &testServer{ Server: server, diff --git a/internal/api/server.go b/internal/api/server.go index ebc41877c..8518c1969 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -32,10 +32,12 @@ type Server struct { paymentService ports.PaymentService displayMethodService ports.DisplayMethodService keyService ports.KeyService + verificationService ports.VerificationService + mediaTypeManager ports.MediaTypeManager } // NewServer is a Server constructor -func NewServer(cfg *config.Configuration, identityService ports.IdentityService, accountService ports.AccountService, connectionsService ports.ConnectionService, claimsService ports.ClaimService, qrService ports.QrStoreService, publisherGateway ports.Publisher, packageManager *iden3comm.PackageManager, networkResolver network.Resolver, health *health.Status, schemaService ports.SchemaService, linkService ports.LinkService, displayMethodService ports.DisplayMethodService, keyService ports.KeyService, paymentService ports.PaymentService) *Server { +func NewServer(cfg *config.Configuration, identityService ports.IdentityService, accountService ports.AccountService, connectionsService ports.ConnectionService, claimsService ports.ClaimService, qrService ports.QrStoreService, publisherGateway ports.Publisher, packageManager *iden3comm.PackageManager, networkResolver network.Resolver, health *health.Status, schemaService ports.SchemaService, linkService ports.LinkService, displayMethodService ports.DisplayMethodService, keyService ports.KeyService, paymentService ports.PaymentService, verificationService ports.VerificationService, mediaTypeManager ports.MediaTypeManager) *Server { return &Server{ cfg: cfg, accountService: accountService, @@ -52,6 +54,8 @@ func NewServer(cfg *config.Configuration, identityService ports.IdentityService, displayMethodService: displayMethodService, keyService: keyService, paymentService: paymentService, + verificationService: verificationService, + mediaTypeManager: mediaTypeManager, } } diff --git a/internal/core/ports/verification_repository.go b/internal/core/ports/verification_repository.go index c7979a81b..7fa181f9e 100644 --- a/internal/core/ports/verification_repository.go +++ b/internal/core/ports/verification_repository.go @@ -15,4 +15,5 @@ type VerificationRepository interface { Get(ctx context.Context, issuerID w3c.DID, id uuid.UUID) (*domain.VerificationQuery, error) GetAll(ctx context.Context, issuerID w3c.DID) ([]domain.VerificationQuery, error) AddResponse(ctx context.Context, scopeID uuid.UUID, response domain.VerificationResponse) (uuid.UUID, error) + GetVerificationResponse(ctx context.Context, scopeID uuid.UUID, userDID string) (*domain.VerificationResponse, error) } diff --git a/internal/core/ports/verification_service.go b/internal/core/ports/verification_service.go new file mode 100644 index 000000000..fc88147cb --- /dev/null +++ b/internal/core/ports/verification_service.go @@ -0,0 +1,15 @@ +package ports + +import ( + "context" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/polygonid/sh-id-platform/internal/core/domain" +) + +// VerificationService interface. +type VerificationService interface { + CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID, userDID string) (*domain.VerificationResponse, *domain.VerificationQuery, error) + SubmitVerificationResponse(ctx context.Context, verificationScopeID uuid.UUID, userDID string, response domain.VerificationResponse) (*domain.VerificationResponse, error) +} diff --git a/internal/core/services/verification.go b/internal/core/services/verification.go new file mode 100644 index 000000000..bc133f529 --- /dev/null +++ b/internal/core/services/verification.go @@ -0,0 +1,78 @@ +package services + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" + auth "github.com/iden3/go-iden3-auth/v2" + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/jackc/pgtype" + "github.com/polygonid/sh-id-platform/internal/core/domain" + "github.com/polygonid/sh-id-platform/internal/core/ports" + "github.com/polygonid/sh-id-platform/internal/repositories" +) + +type VerificationService struct { + verifier *auth.Verifier + repo ports.VerificationRepository +} + +// NewVerificationService creates a new instance of VerificationService +func NewVerificationService(verifier *auth.Verifier, repo ports.VerificationRepository) *VerificationService { + return &VerificationService{verifier: verifier, repo: repo} +} + +// CheckVerification checks if a verification response already exists for a given verification query ID and userDID. +// If no response exists, it returns the verification query. +func (vs *VerificationService) CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID, userDID string) (*domain.VerificationResponse, *domain.VerificationQuery, error) { + query, err := vs.repo.Get(ctx, issuerID, verificationQueryID) + if err != nil { + if err == repositories.VerificationQueryNotFoundError { + return nil, nil, fmt.Errorf("verification query not found: %w", err) + } + return nil, nil, fmt.Errorf("failed to get verification query: %w", err) + } + + for _, scope := range query.Scopes { + response, err := vs.repo.GetVerificationResponse(ctx, scope.ID, userDID) + if err == nil && response != nil { + return response, nil, nil + } + } + + return nil, query, nil +} + +// SubmitVerificationResponse submits a verification response for a given verification scope ID and userDID +func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, verificationScopeID uuid.UUID, userDID string, responseData domain.VerificationResponse) (*domain.VerificationResponse, error) { + responseJSON, err := json.Marshal(responseData) + if err != nil { + return nil, fmt.Errorf("failed to marshal response data to JSON: %w", err) + } + + var jsonbResponse pgtype.JSONB + err = jsonbResponse.Set(responseJSON) + if err != nil { + return nil, fmt.Errorf("failed to set JSONB value: %w", err) + } + + response := domain.VerificationResponse{ + ID: uuid.New(), + VerificationScopeID: verificationScopeID, + UserDID: userDID, + Response: jsonbResponse, + Pass: true, + CreatedAt: time.Now(), + } + + responseID, err := vs.repo.AddResponse(ctx, verificationScopeID, response) + if err != nil { + return nil, fmt.Errorf("failed to add verification response: %w", err) + } + + response.ID = responseID + return &response, nil +} diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go index 02957b7d0..1814e5ed2 100644 --- a/internal/repositories/verification.go +++ b/internal/repositories/verification.go @@ -15,8 +15,11 @@ import ( const foreignKeyViolationErrorCode = "23503" -// VerificationQueryNotFoundError is returned when a verification query is not found -var VerificationQueryNotFoundError = errors.New("verification query not found") +var ( + // VerificationQueryNotFoundError is returned when a verification query is not found + VerificationQueryNotFoundError = errors.New("verification query not found") + VerificationResponseNotFoundError = errors.New("verification response not found") +) // VerificationRepository is a repository for verification queries type VerificationRepository struct { @@ -101,3 +104,28 @@ func (r *VerificationRepository) AddResponse(ctx context.Context, queryID uuid.U } return responseID, nil } + +// GetVerificationResponse returns a verification response by scopeID and userDID +func (r *VerificationRepository) GetVerificationResponse(ctx context.Context, queryID uuid.UUID, userDID string) (*domain.VerificationResponse, error) { + sql := `SELECT id, verification_query_id, user_did, response, pass, created_at + FROM verification_responses + WHERE verification_query_id = $1 AND user_did = $2` + + var response domain.VerificationResponse + err := r.conn.Pgx.QueryRow(ctx, sql, queryID, userDID).Scan( + &response.ID, + &response.VerificationQueryID, + &response.UserDID, + &response.Response, + &response.Pass, + &response.CreatedAt, + ) + if err != nil { + if strings.Contains(err.Error(), "no rows in result set") { + return nil, VerificationResponseNotFoundError + } + return nil, err + } + + return &response, nil +} From 8923f563516fe7f027323040fe65b38041d57b5a Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Tue, 10 Dec 2024 06:30:56 -0800 Subject: [PATCH 06/20] Cleanup paths and params and add controller --- api/api.yaml | 21 ++- internal/api/api.gen.go | 281 +++++++++++++++++++---------------- internal/api/verification.go | 77 ++++++++++ 3 files changed, 244 insertions(+), 135 deletions(-) create mode 100644 internal/api/verification.go diff --git a/api/api.yaml b/api/api.yaml index 639a30667..71a6e98a2 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -2024,7 +2024,7 @@ paths: '500': $ref: '#/components/responses/500' - /v2/verification/{identifier}/callback: + /v2/identities/{identifier}/verification/callback: post: summary: Submit Verification Response operationId: SubmitVerificationResponse @@ -2040,12 +2040,14 @@ paths: description: User's DID identifier schema: type: string + - $ref: '#/components/parameters/verificationQueryId' requestBody: required: true content: - application/json: + text/plain: schema: - $ref: '#/components/schemas/VerificationResponseRequest' + type: string + example: jwz-token responses: '200': description: Verification response submitted successfully @@ -3269,7 +3271,8 @@ components: type: array items: $ref: '#/components/schemas/VerificationScope' - + + VerificationScope: type: object required: @@ -3296,6 +3299,10 @@ components: properties: verification_scope_id: type: string + x-go-type: uuid.UUID + x-go-type-import: + name: uuid + path: github.com/google/uuid description: Scope ID of the verification query. user_did: type: string @@ -3308,11 +3315,15 @@ components: type: object required: - status + - pass properties: status: type: string enum: [ submitted, validated, error ] description: The status of the submitted verification response. + pass: + type: boolean + description: Whether the query response passed the check CreateDisplayMethodRequest: type: object @@ -3518,7 +3529,7 @@ components: type: string verificationQueryId: - name: verificationQueryId + name: id in: query required: true description: The verification query ID to check for a response diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 83e89680e..f4a165c18 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -783,20 +783,11 @@ type VerificationResponse struct { VerificationScopeId string `json:"verification_scope_id"` } -// VerificationResponseRequest defines model for VerificationResponseRequest. -type VerificationResponseRequest struct { - // Response Response to the verification query request. - Response map[string]interface{} `json:"response"` - - // UserDid User's DID identifier. - UserDid string `json:"user_did"` - - // VerificationScopeId Scope ID of the verification query. - VerificationScopeId string `json:"verification_scope_id"` -} - // VerificationResponseStatus defines model for VerificationResponseStatus. type VerificationResponseStatus struct { + // Pass Whether the query response passed the check + Pass bool `json:"pass"` + // Status The status of the submitted verification response. Status VerificationResponseStatusStatus `json:"status"` } @@ -1058,6 +1049,15 @@ type GetStateTransactionsParamsFilter string // GetStateTransactionsParamsSort defines parameters for GetStateTransactions. type GetStateTransactionsParamsSort string +// SubmitVerificationResponseTextBody defines parameters for SubmitVerificationResponse. +type SubmitVerificationResponseTextBody = string + +// SubmitVerificationResponseParams defines parameters for SubmitVerificationResponse. +type SubmitVerificationResponseParams struct { + // Id The verification query ID to check for a response + Id VerificationQueryId `form:"id" json:"id"` +} + // GetQrFromStoreParams defines parameters for GetQrFromStore. type GetQrFromStoreParams struct { Id *uuid.UUID `form:"id,omitempty" json:"id,omitempty"` @@ -1066,8 +1066,8 @@ type GetQrFromStoreParams struct { // CheckVerificationParams defines parameters for CheckVerification. type CheckVerificationParams struct { - // VerificationQueryId The verification query ID to check for a response - VerificationQueryId VerificationQueryId `form:"verificationQueryId" json:"verificationQueryId"` + // Id The verification query ID to check for a response + Id VerificationQueryId `form:"id" json:"id"` } // AuthenticationParams defines parameters for Authentication. @@ -1141,8 +1141,8 @@ type ImportSchemaJSONRequestBody = ImportSchemaRequest // UpdateSchemaJSONRequestBody defines body for UpdateSchema for application/json ContentType. type UpdateSchemaJSONRequestBody UpdateSchemaJSONBody -// SubmitVerificationResponseJSONRequestBody defines body for SubmitVerificationResponse for application/json ContentType. -type SubmitVerificationResponseJSONRequestBody = VerificationResponseRequest +// SubmitVerificationResponseTextRequestBody defines body for SubmitVerificationResponse for text/plain ContentType. +type SubmitVerificationResponseTextRequestBody = SubmitVerificationResponseTextBody // ServerInterface represents all server handlers. type ServerInterface interface { @@ -1320,6 +1320,9 @@ type ServerInterface interface { // Get Identity State Transactions // (GET /v2/identities/{identifier}/state/transactions) GetStateTransactions(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetStateTransactionsParams) + // Submit Verification Response + // (POST /v2/identities/{identifier}/verification/callback) + SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) // Payments Configuration // (GET /v2/payment/settings) GetPaymentSettings(w http.ResponseWriter, r *http.Request) @@ -1329,9 +1332,6 @@ type ServerInterface interface { // Get Supported Networks // (GET /v2/supported-networks) GetSupportedNetworks(w http.ResponseWriter, r *http.Request) - // Submit Verification Response - // (POST /v2/verification/{identifier}/callback) - SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string) // Check Verification Response or Provide Query // (GET /v2/verification/{identifier}/check) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) @@ -1692,6 +1692,12 @@ func (_ Unimplemented) GetStateTransactions(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusNotImplemented) } +// Submit Verification Response +// (POST /v2/identities/{identifier}/verification/callback) +func (_ Unimplemented) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // Payments Configuration // (GET /v2/payment/settings) func (_ Unimplemented) GetPaymentSettings(w http.ResponseWriter, r *http.Request) { @@ -1710,12 +1716,6 @@ func (_ Unimplemented) GetSupportedNetworks(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusNotImplemented) } -// Submit Verification Response -// (POST /v2/verification/{identifier}/callback) -func (_ Unimplemented) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string) { - w.WriteHeader(http.StatusNotImplemented) -} - // Check Verification Response or Provide Query // (GET /v2/verification/{identifier}/check) func (_ Unimplemented) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { @@ -3945,6 +3945,49 @@ func (siw *ServerInterfaceWrapper) GetStateTransactions(w http.ResponseWriter, r handler.ServeHTTP(w, r) } +// SubmitVerificationResponse operation middleware +func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier string + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params SubmitVerificationResponseParams + + // ------------- Required query parameter "id" ------------- + + if paramValue := r.URL.Query().Get("id"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "id"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "id", r.URL.Query(), ¶ms.Id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.SubmitVerificationResponse(w, r, identifier, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetPaymentSettings operation middleware func (siw *ServerInterfaceWrapper) GetPaymentSettings(w http.ResponseWriter, r *http.Request) { @@ -4020,31 +4063,6 @@ func (siw *ServerInterfaceWrapper) GetSupportedNetworks(w http.ResponseWriter, r handler.ServeHTTP(w, r) } -// SubmitVerificationResponse operation middleware -func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "identifier" ------------- - var identifier string - - err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) - return - } - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.SubmitVerificationResponse(w, r, identifier) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - // CheckVerification operation middleware func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *http.Request) { @@ -4062,18 +4080,18 @@ func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *h // Parameter object where we will unmarshal all parameters from the context var params CheckVerificationParams - // ------------- Required query parameter "verificationQueryId" ------------- + // ------------- Required query parameter "id" ------------- - if paramValue := r.URL.Query().Get("verificationQueryId"); paramValue != "" { + if paramValue := r.URL.Query().Get("id"); paramValue != "" { } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "verificationQueryId"}) + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "id"}) return } - err = runtime.BindQueryParameter("form", true, true, "verificationQueryId", r.URL.Query(), ¶ms.VerificationQueryId) + err = runtime.BindQueryParameter("form", true, true, "id", r.URL.Query(), ¶ms.Id) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "verificationQueryId", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } @@ -4411,6 +4429,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/identities/{identifier}/state/transactions", wrapper.GetStateTransactions) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/v2/identities/{identifier}/verification/callback", wrapper.SubmitVerificationResponse) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/payment/settings", wrapper.GetPaymentSettings) }) @@ -4420,9 +4441,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/supported-networks", wrapper.GetSupportedNetworks) }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/v2/verification/{identifier}/callback", wrapper.SubmitVerificationResponse) - }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/verification/{identifier}/check", wrapper.CheckVerification) }) @@ -6926,6 +6944,43 @@ func (response GetStateTransactions500JSONResponse) VisitGetStateTransactionsRes return json.NewEncoder(w).Encode(response) } +type SubmitVerificationResponseRequestObject struct { + Identifier string `json:"identifier"` + Params SubmitVerificationResponseParams + Body *SubmitVerificationResponseTextRequestBody +} + +type SubmitVerificationResponseResponseObject interface { + VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error +} + +type SubmitVerificationResponse200JSONResponse VerificationResponseStatus + +func (response SubmitVerificationResponse200JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type SubmitVerificationResponse400JSONResponse struct{ N400JSONResponse } + +func (response SubmitVerificationResponse400JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type SubmitVerificationResponse500JSONResponse struct{ N500JSONResponse } + +func (response SubmitVerificationResponse500JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type GetPaymentSettingsRequestObject struct { } @@ -7047,42 +7102,6 @@ func (response GetSupportedNetworks500JSONResponse) VisitGetSupportedNetworksRes return json.NewEncoder(w).Encode(response) } -type SubmitVerificationResponseRequestObject struct { - Identifier string `json:"identifier"` - Body *SubmitVerificationResponseJSONRequestBody -} - -type SubmitVerificationResponseResponseObject interface { - VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error -} - -type SubmitVerificationResponse200JSONResponse VerificationResponseStatus - -func (response SubmitVerificationResponse200JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type SubmitVerificationResponse400JSONResponse struct{ N400JSONResponse } - -func (response SubmitVerificationResponse400JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type SubmitVerificationResponse500JSONResponse struct{ N500JSONResponse } - -func (response SubmitVerificationResponse500JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - type CheckVerificationRequestObject struct { Identifier string `json:"identifier"` Params CheckVerificationParams @@ -7342,6 +7361,9 @@ type StrictServerInterface interface { // Get Identity State Transactions // (GET /v2/identities/{identifier}/state/transactions) GetStateTransactions(ctx context.Context, request GetStateTransactionsRequestObject) (GetStateTransactionsResponseObject, error) + // Submit Verification Response + // (POST /v2/identities/{identifier}/verification/callback) + SubmitVerificationResponse(ctx context.Context, request SubmitVerificationResponseRequestObject) (SubmitVerificationResponseResponseObject, error) // Payments Configuration // (GET /v2/payment/settings) GetPaymentSettings(ctx context.Context, request GetPaymentSettingsRequestObject) (GetPaymentSettingsResponseObject, error) @@ -7351,9 +7373,6 @@ type StrictServerInterface interface { // Get Supported Networks // (GET /v2/supported-networks) GetSupportedNetworks(ctx context.Context, request GetSupportedNetworksRequestObject) (GetSupportedNetworksResponseObject, error) - // Submit Verification Response - // (POST /v2/verification/{identifier}/callback) - SubmitVerificationResponse(ctx context.Context, request SubmitVerificationResponseRequestObject) (SubmitVerificationResponseResponseObject, error) // Check Verification Response or Provide Query // (GET /v2/verification/{identifier}/check) CheckVerification(ctx context.Context, request CheckVerificationRequestObject) (CheckVerificationResponseObject, error) @@ -9070,6 +9089,41 @@ func (sh *strictHandler) GetStateTransactions(w http.ResponseWriter, r *http.Req } } +// SubmitVerificationResponse operation middleware +func (sh *strictHandler) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) { + var request SubmitVerificationResponseRequestObject + + request.Identifier = identifier + request.Params = params + + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) + return + } + body := SubmitVerificationResponseTextRequestBody(data) + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.SubmitVerificationResponse(ctx, request.(SubmitVerificationResponseRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "SubmitVerificationResponse") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(SubmitVerificationResponseResponseObject); ok { + if err := validResponse.VisitSubmitVerificationResponseResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetPaymentSettings operation middleware func (sh *strictHandler) GetPaymentSettings(w http.ResponseWriter, r *http.Request) { var request GetPaymentSettingsRequestObject @@ -9144,39 +9198,6 @@ func (sh *strictHandler) GetSupportedNetworks(w http.ResponseWriter, r *http.Req } } -// SubmitVerificationResponse operation middleware -func (sh *strictHandler) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string) { - var request SubmitVerificationResponseRequestObject - - request.Identifier = identifier - - var body SubmitVerificationResponseJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.SubmitVerificationResponse(ctx, request.(SubmitVerificationResponseRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "SubmitVerificationResponse") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(SubmitVerificationResponseResponseObject); ok { - if err := validResponse.VisitSubmitVerificationResponseResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - // CheckVerification operation middleware func (sh *strictHandler) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { var request CheckVerificationRequestObject diff --git a/internal/api/verification.go b/internal/api/verification.go new file mode 100644 index 000000000..917678e44 --- /dev/null +++ b/internal/api/verification.go @@ -0,0 +1,77 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + + "github.com/polygonid/sh-id-platform/internal/log" +) + +// CheckVerification returns CheckVerificationResponse or Provide Query +func (s *Server) CheckVerification(ctx context.Context, request CheckVerificationRequestObject) (CheckVerificationResponseObject, error) { + //validation + issuerDID, err := w3c.ParseDID(request.Identifier) + if err != nil { + log.Error(ctx, "parsing issuer did", "err", err, "did", request.Identifier) + return CheckVerification400JSONResponse{N400JSONResponse{Message: "invalid issuer did"}}, nil + } + + verificationQueryID, err := uuid.Parse(request.Params.Id.String()) + if err != nil { + log.Error(ctx, "invalid verification query ID", "err", err) + return CheckVerification400JSONResponse{N400JSONResponse{Message: "invalid verification query ID"}}, nil + } + + // Use the VerificationService to check if there's an existing response or get the query + response, query, err := s.verificationService.CheckVerification(ctx, *issuerDID, verificationQueryID) + if err != nil { + log.Error(ctx, "checking verification", "err", err) + return CheckVerification500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + + if response != nil { + return CheckVerification200JSONResponse{union: json.RawMessage(fmt.Sprintf(`{"status": "validated", "response": %v}`, response))}, nil + } + + if query != nil { + return CheckVerification200JSONResponse{union: json.RawMessage(fmt.Sprintf(`{"query": %v}`, query))}, nil + } + + return CheckVerification404JSONResponse{N404JSONResponse{Message: "Verification query not found"}}, nil +} + +// SubmitVerificationResponse returns a VerificationResponse +func (s *Server) SubmitVerificationResponse(ctx context.Context, request SubmitVerificationResponseRequestObject) (SubmitVerificationResponseResponseObject, error) { + // Unmarshal user DID and response data + issuerDID, err := w3c.ParseDID(request.Identifier) + if err != nil { + log.Error(ctx, "submitting verification query response.. Parsing did", "err", err) + return SubmitVerificationResponse400JSONResponse{ + N400JSONResponse{ + Message: "invalid did", + }, + }, err + } + token := request.Body + + // Submit the verification response using VerificationService + response, err := s.verificationService.SubmitVerificationResponse(ctx, request.Params.Id, *issuerDID, *token, s.cfg.ServerUrl) + if err != nil { + log.Error(ctx, "failed to submit verification response", "err", err) + return SubmitVerificationResponse500JSONResponse{ + N500JSONResponse{ + Message: "failed to submit verification response", + }, + }, nil + } + + // Construct and return the response status + return SubmitVerificationResponse200JSONResponse{ + Status: "submitted", + Pass: response.Pass, + }, nil +} From d430ee7f5bf62300699b1308bec36559e8f5582c Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Tue, 10 Dec 2024 06:31:30 -0800 Subject: [PATCH 07/20] Add repository method to get verification response --- internal/core/ports/verification_repository.go | 4 ++-- internal/repositories/verification.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/core/ports/verification_repository.go b/internal/core/ports/verification_repository.go index 7fa181f9e..6dc291edc 100644 --- a/internal/core/ports/verification_repository.go +++ b/internal/core/ports/verification_repository.go @@ -14,6 +14,6 @@ type VerificationRepository interface { Save(ctx context.Context, issuerID w3c.DID, query domain.VerificationQuery) (uuid.UUID, error) Get(ctx context.Context, issuerID w3c.DID, id uuid.UUID) (*domain.VerificationQuery, error) GetAll(ctx context.Context, issuerID w3c.DID) ([]domain.VerificationQuery, error) - AddResponse(ctx context.Context, scopeID uuid.UUID, response domain.VerificationResponse) (uuid.UUID, error) - GetVerificationResponse(ctx context.Context, scopeID uuid.UUID, userDID string) (*domain.VerificationResponse, error) + AddResponse(ctx context.Context, queryID uuid.UUID, response domain.VerificationResponse) (uuid.UUID, error) + GetVerificationResponse(ctx context.Context, queryID uuid.UUID) (*domain.VerificationResponse, error) } diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go index 1814e5ed2..17b8649c5 100644 --- a/internal/repositories/verification.go +++ b/internal/repositories/verification.go @@ -106,13 +106,13 @@ func (r *VerificationRepository) AddResponse(ctx context.Context, queryID uuid.U } // GetVerificationResponse returns a verification response by scopeID and userDID -func (r *VerificationRepository) GetVerificationResponse(ctx context.Context, queryID uuid.UUID, userDID string) (*domain.VerificationResponse, error) { +func (r *VerificationRepository) GetVerificationResponse(ctx context.Context, queryID uuid.UUID) (*domain.VerificationResponse, error) { sql := `SELECT id, verification_query_id, user_did, response, pass, created_at FROM verification_responses - WHERE verification_query_id = $1 AND user_did = $2` + WHERE verification_query_id = $1` var response domain.VerificationResponse - err := r.conn.Pgx.QueryRow(ctx, sql, queryID, userDID).Scan( + err := r.conn.Pgx.QueryRow(ctx, sql, queryID).Scan( &response.ID, &response.VerificationQueryID, &response.UserDID, From 0f963ca0b1f99131e848497fa2f137aca2bd2d63 Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Tue, 10 Dec 2024 06:32:37 -0800 Subject: [PATCH 08/20] Fixes for scope changes and method to submit verification response --- internal/core/ports/verification_service.go | 4 +- internal/core/services/verification.go | 131 +++++++++++++++++--- 2 files changed, 117 insertions(+), 18 deletions(-) diff --git a/internal/core/ports/verification_service.go b/internal/core/ports/verification_service.go index fc88147cb..4a9d775aa 100644 --- a/internal/core/ports/verification_service.go +++ b/internal/core/ports/verification_service.go @@ -10,6 +10,6 @@ import ( // VerificationService interface. type VerificationService interface { - CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID, userDID string) (*domain.VerificationResponse, *domain.VerificationQuery, error) - SubmitVerificationResponse(ctx context.Context, verificationScopeID uuid.UUID, userDID string, response domain.VerificationResponse) (*domain.VerificationResponse, error) + CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) + SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) } diff --git a/internal/core/services/verification.go b/internal/core/services/verification.go index bc133f529..8b3077df1 100644 --- a/internal/core/services/verification.go +++ b/internal/core/services/verification.go @@ -3,18 +3,44 @@ package services import ( "context" "encoding/json" + "errors" "fmt" + "math/big" "time" "github.com/google/uuid" auth "github.com/iden3/go-iden3-auth/v2" + "github.com/iden3/go-iden3-auth/v2/pubsignals" "github.com/iden3/go-iden3-core/v2/w3c" + protocol "github.com/iden3/iden3comm/v2/protocol" "github.com/jackc/pgtype" "github.com/polygonid/sh-id-platform/internal/core/domain" "github.com/polygonid/sh-id-platform/internal/core/ports" + "github.com/polygonid/sh-id-platform/internal/log" "github.com/polygonid/sh-id-platform/internal/repositories" ) +type Scope struct { + CircuitId string `json:"circuitId"` + Id uint32 `json:"id"` + Params *map[string]interface{} `json:"params,omitempty"` + Query map[string]interface{} `json:"query"` + TransactionData *TransactionData `json:"transactionData,omitempty"` +} + +type TransactionData struct { + ChainID int `json:"chainID"` + ContractAddress string `json:"contractAddress"` + MethodID string `json:"methodID"` + Network string `json:"network"` +} + +const ( + stateTransitionDelay = time.Minute * 5 + defaultReason = "for testing purposes" + defaultBigIntBase = 10 +) + type VerificationService struct { verifier *auth.Verifier repo ports.VerificationRepository @@ -27,7 +53,7 @@ func NewVerificationService(verifier *auth.Verifier, repo ports.VerificationRepo // CheckVerification checks if a verification response already exists for a given verification query ID and userDID. // If no response exists, it returns the verification query. -func (vs *VerificationService) CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID, userDID string) (*domain.VerificationResponse, *domain.VerificationQuery, error) { +func (vs *VerificationService) CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) { query, err := vs.repo.Get(ctx, issuerID, verificationQueryID) if err != nil { if err == repositories.VerificationQueryNotFoundError { @@ -36,39 +62,51 @@ func (vs *VerificationService) CheckVerification(ctx context.Context, issuerID w return nil, nil, fmt.Errorf("failed to get verification query: %w", err) } - for _, scope := range query.Scopes { - response, err := vs.repo.GetVerificationResponse(ctx, scope.ID, userDID) - if err == nil && response != nil { - return response, nil, nil - } + response, err := vs.repo.GetVerificationResponse(ctx, verificationQueryID) + if err == nil && response != nil { + return response, nil, nil } return nil, query, nil } -// SubmitVerificationResponse submits a verification response for a given verification scope ID and userDID -func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, verificationScopeID uuid.UUID, userDID string, responseData domain.VerificationResponse) (*domain.VerificationResponse, error) { - responseJSON, err := json.Marshal(responseData) +func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) { + // check db for existing verification query + var authRequest = protocol.AuthorizationRequestMessage{} + verificationQuery, err := vs.repo.Get(ctx, issuerID, verificationQueryID) + authRequest, err = getAuthRequestOffChain(verificationQuery, serverURL) if err != nil { - return nil, fmt.Errorf("failed to marshal response data to JSON: %w", err) + return nil, fmt.Errorf("failed to get authorization request: %w", err) } + // perform verification + authRespMsg, err := vs.verifier.FullVerify(ctx, token, + authRequest, + pubsignals.WithAcceptedStateTransitionDelay(stateTransitionDelay)) + + if err != nil { + log.Error(ctx, "failed to verify", "verificationQueryID", verificationQueryID, "err", err) + return nil, fmt.Errorf("failed to verify token: %w", err) + } + + // prepare to save result var jsonbResponse pgtype.JSONB - err = jsonbResponse.Set(responseJSON) + err = jsonbResponse.Set(authRespMsg) if err != nil { return nil, fmt.Errorf("failed to set JSONB value: %w", err) } response := domain.VerificationResponse{ - ID: uuid.New(), - VerificationScopeID: verificationScopeID, - UserDID: userDID, + ID: uuid.New(), // Generate a new UUID for the response + VerificationQueryID: verificationQueryID, + UserDID: authRespMsg.From, Response: jsonbResponse, - Pass: true, + Pass: true, // Assuming this field is present to indicate verification success CreatedAt: time.Now(), } - responseID, err := vs.repo.AddResponse(ctx, verificationScopeID, response) + // Save the verification response to the repository + responseID, err := vs.repo.AddResponse(ctx, verificationQueryID, response) if err != nil { return nil, fmt.Errorf("failed to add verification response: %w", err) } @@ -76,3 +114,64 @@ func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, v response.ID = responseID return &response, nil } + +func getAuthRequestOffChain(req *domain.VerificationQuery, serverURL string) (protocol.AuthorizationRequestMessage, error) { + + id := uuid.NewString() + authReq := auth.CreateAuthorizationRequest(getReason(nil), req.IssuerDID, getUri(serverURL, req.IssuerDID, req.ID)) + authReq.ID = id + authReq.ThreadID = id + authReq.To = "" + + var scopes []Scope + if req.Scope.Status == pgtype.Present { + err := json.Unmarshal(req.Scope.Bytes, &scopes) + if err != nil { + return protocol.AuthorizationRequestMessage{}, err + } + } + + for _, scope := range scopes { + mtpProofRequest := protocol.ZeroKnowledgeProofRequest{ + ID: scope.Id, + CircuitID: scope.CircuitId, + Query: scope.Query, + } + if scope.Params != nil { + params, err := getParams(*scope.Params) + if err != nil { + return protocol.AuthorizationRequestMessage{}, err + } + + mtpProofRequest.Params = params + } + authReq.Body.Scope = append(authReq.Body.Scope, mtpProofRequest) + } + return authReq, nil +} + +func getReason(reason *string) string { + if reason == nil { + return "for testing purposes" + } + return *reason +} + +func getParams(params map[string]interface{}) (map[string]interface{}, error) { + val, ok := params["nullifierSessionID"] + if !ok { + return nil, errors.New("nullifierSessionID is empty") + } + + nullifierSessionID := new(big.Int) + if _, ok := nullifierSessionID.SetString(val.(string), defaultBigIntBase); !ok { + return nil, errors.New("nullifierSessionID is not a valid big integer") + } + + return map[string]interface{}{"nullifierSessionId": nullifierSessionID.String()}, nil +} + +func getUri(serverURL string, issuerDID string, verificationQueryID uuid.UUID) string { + path := fmt.Sprintf(`/v2/identities/%s/verification/callback?id=%s`, issuerDID, verificationQueryID) + return fmt.Sprintf("%s%s", serverURL, path) +} From b2e5cac3802764447209d9392a2b2a02491942e1 Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Tue, 10 Dec 2024 06:49:28 -0800 Subject: [PATCH 09/20] Fixes for linter --- internal/core/ports/verification_service.go | 2 +- internal/core/services/verification.go | 4 ++++ internal/repositories/verification.go | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/core/ports/verification_service.go b/internal/core/ports/verification_service.go index 4a9d775aa..45d205baa 100644 --- a/internal/core/ports/verification_service.go +++ b/internal/core/ports/verification_service.go @@ -8,7 +8,7 @@ import ( "github.com/polygonid/sh-id-platform/internal/core/domain" ) -// VerificationService interface. +// VerificationService interface type VerificationService interface { CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) diff --git a/internal/core/services/verification.go b/internal/core/services/verification.go index 8b3077df1..504239650 100644 --- a/internal/core/services/verification.go +++ b/internal/core/services/verification.go @@ -20,6 +20,7 @@ import ( "github.com/polygonid/sh-id-platform/internal/repositories" ) +// Scope is a property of VerificationQuery, and it's required for the authRequest type Scope struct { CircuitId string `json:"circuitId"` Id uint32 `json:"id"` @@ -28,6 +29,7 @@ type Scope struct { TransactionData *TransactionData `json:"transactionData,omitempty"` } +// TransactionData is a property of Scope, and it's required for the authRequest type TransactionData struct { ChainID int `json:"chainID"` ContractAddress string `json:"contractAddress"` @@ -41,6 +43,7 @@ const ( defaultBigIntBase = 10 ) +// VerificationService can verify responses to verification queries type VerificationService struct { verifier *auth.Verifier repo ports.VerificationRepository @@ -70,6 +73,7 @@ func (vs *VerificationService) CheckVerification(ctx context.Context, issuerID w return nil, query, nil } +// SubmitVerificationResponse checks if a verification response passes a verify check and saves result func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) { // check db for existing verification query var authRequest = protocol.AuthorizationRequestMessage{} diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go index 17b8649c5..8914cd358 100644 --- a/internal/repositories/verification.go +++ b/internal/repositories/verification.go @@ -17,7 +17,8 @@ const foreignKeyViolationErrorCode = "23503" var ( // VerificationQueryNotFoundError is returned when a verification query is not found - VerificationQueryNotFoundError = errors.New("verification query not found") + VerificationQueryNotFoundError = errors.New("verification query not found") + // VerificationResponseNotFoundError is returned when a verification query is not found VerificationResponseNotFoundError = errors.New("verification response not found") ) From 5c118beba2dfc148b9f10801aeddd0fdd52bede2 Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Mon, 16 Dec 2024 07:07:51 -0800 Subject: [PATCH 10/20] Various fixes, add create endpoint --- api/api.yaml | 100 +++-- cmd/platform/main.go | 2 +- internal/api/api.gen.go | 412 +++++++++++++------- internal/api/main_test.go | 11 +- internal/api/verification.go | 27 +- internal/core/ports/verification_service.go | 6 +- internal/core/services/verification.go | 83 +++- 7 files changed, 415 insertions(+), 226 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 71a6e98a2..d0d82cc4c 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -1991,7 +1991,40 @@ paths: # Verification - /v2/verification/{identifier}/check: + /v2/identities/{identifier}/verification/create: + post: + summary: Create a Verification query + operationId: CreateVerification + description: | + Endpoint to create a verification query. + tags: + - Verification + parameters: + - name: identifier + in: path + required: true + description: Issuer's DID Identifier + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateVerificationQueryRequest' + responses: + '200': + description: Verification query created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GenericMessage' + '400': + $ref: '#/components/responses/400' + '500': + $ref: '#/components/responses/500' + + /v2/identities/{identifier}/verification/check: get: summary: Check Verification Response or Provide Query operationId: CheckVerification @@ -3236,7 +3269,8 @@ components: x-go-type-import: name: protocol path: github.com/iden3/iden3comm/v2/protocol - + + #Verification VerificationResponse: type: object required: @@ -3268,48 +3302,9 @@ components: type: string description: The ID of the verification query. scopes: - type: array - items: - $ref: '#/components/schemas/VerificationScope' - - - VerificationScope: - type: object - required: - - scope_id - - query_type - - context - properties: - scope_id: - type: string - description: ID of the scope. - query_type: - type: string - description: Type of query for the verification. - context: - type: string - description: Additional context for the query. - - VerificationResponseRequest: - type: object - required: - - verification_scope_id - - user_did - - response - properties: - verification_scope_id: - type: string - x-go-type: uuid.UUID - x-go-type-import: - name: uuid - path: github.com/google/uuid - description: Scope ID of the verification query. - user_did: - type: string - description: User's DID identifier. - response: type: object - description: Response to the verification query request. + additionalProperties: true + description: "Dynamic JSON object for scopes" VerificationResponseStatus: type: object @@ -3434,6 +3429,25 @@ components: example: "Iden3ReverseSparseMerkleTreeProof" enum: [ Iden3commRevocationStatusV1.0, Iden3ReverseSparseMerkleTreeProof, Iden3OnchainSparseMerkleTreeProof2023 ] + CreateVerificationQueryRequest: + type: object + required: + - chain_id + - skip_revocation_check + - scopes + properties: + chain_id: + type: integer + example: 1 + skip_revocation_check: + type: boolean + example: false + scopes: + type: object + additionalProperties: true + description: "Dynamic JSON object for scopes" + + parameters: credentialStatusType: name: credentialStatusType diff --git a/cmd/platform/main.go b/cmd/platform/main.go index 5827f77c3..4a76f1f1f 100644 --- a/cmd/platform/main.go +++ b/cmd/platform/main.go @@ -171,7 +171,7 @@ func main() { } accountService := services.NewAccountService(*networkResolver) - verificationService := services.NewVerificationService(verifier, verificationRepository) + verificationService := services.NewVerificationService(networkResolver, cachex, verificationRepository, verifier) publisherGateway, err := gateways.NewPublisherEthGateway(*networkResolver, keyStore, cfg.PublishingKeyPath) if err != nil { diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index f4a165c18..1e5c00eca 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -379,6 +379,15 @@ type CreatePaymentRequestResponse struct { UserDID string `json:"userDID"` } +// CreateVerificationQueryRequest defines model for CreateVerificationQueryRequest. +type CreateVerificationQueryRequest struct { + ChainId int `json:"chain_id"` + + // Scopes Dynamic JSON object for scopes + Scopes map[string]interface{} `json:"scopes"` + SkipRevocationCheck bool `json:"skip_revocation_check"` +} + // Credential defines model for Credential. type Credential struct { Id string `json:"id"` @@ -762,7 +771,8 @@ type UUIDString = string // VerificationQueryRequest defines model for VerificationQueryRequest. type VerificationQueryRequest struct { - Scopes []VerificationScope `json:"scopes"` + // Scopes Dynamic JSON object for scopes + Scopes map[string]interface{} `json:"scopes"` // VerificationQueryId The ID of the verification query. VerificationQueryId string `json:"verification_query_id"` @@ -795,18 +805,6 @@ type VerificationResponseStatus struct { // VerificationResponseStatusStatus The status of the submitted verification response. type VerificationResponseStatusStatus string -// VerificationScope defines model for VerificationScope. -type VerificationScope struct { - // Context Additional context for the query. - Context string `json:"context"` - - // QueryType Type of query for the verification. - QueryType string `json:"query_type"` - - // ScopeId ID of the scope. - ScopeId string `json:"scope_id"` -} - // Id defines model for id. type Id = uuid.UUID @@ -1058,18 +1056,18 @@ type SubmitVerificationResponseParams struct { Id VerificationQueryId `form:"id" json:"id"` } -// GetQrFromStoreParams defines parameters for GetQrFromStore. -type GetQrFromStoreParams struct { - Id *uuid.UUID `form:"id,omitempty" json:"id,omitempty"` - Issuer *string `form:"issuer,omitempty" json:"issuer,omitempty"` -} - // CheckVerificationParams defines parameters for CheckVerification. type CheckVerificationParams struct { // Id The verification query ID to check for a response Id VerificationQueryId `form:"id" json:"id"` } +// GetQrFromStoreParams defines parameters for GetQrFromStore. +type GetQrFromStoreParams struct { + Id *uuid.UUID `form:"id,omitempty" json:"id,omitempty"` + Issuer *string `form:"issuer,omitempty" json:"issuer,omitempty"` +} + // AuthenticationParams defines parameters for Authentication. type AuthenticationParams struct { // Type Type: @@ -1144,6 +1142,9 @@ type UpdateSchemaJSONRequestBody UpdateSchemaJSONBody // SubmitVerificationResponseTextRequestBody defines body for SubmitVerificationResponse for text/plain ContentType. type SubmitVerificationResponseTextRequestBody = SubmitVerificationResponseTextBody +// CreateVerificationJSONRequestBody defines body for CreateVerification for application/json ContentType. +type CreateVerificationJSONRequestBody = CreateVerificationQueryRequest + // ServerInterface represents all server handlers. type ServerInterface interface { // Healthcheck @@ -1323,6 +1324,12 @@ type ServerInterface interface { // Submit Verification Response // (POST /v2/identities/{identifier}/verification/callback) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) + // Check Verification Response or Provide Query + // (GET /v2/identities/{identifier}/verification/check) + CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) + // Create a Verification query + // (POST /v2/identities/{identifier}/verification/create) + CreateVerification(w http.ResponseWriter, r *http.Request, identifier string) // Payments Configuration // (GET /v2/payment/settings) GetPaymentSettings(w http.ResponseWriter, r *http.Request) @@ -1332,9 +1339,6 @@ type ServerInterface interface { // Get Supported Networks // (GET /v2/supported-networks) GetSupportedNetworks(w http.ResponseWriter, r *http.Request) - // Check Verification Response or Provide Query - // (GET /v2/verification/{identifier}/check) - CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) // Get Authentication Message // (POST /v2/{identifier}/authentication) Authentication(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params AuthenticationParams) @@ -1698,6 +1702,18 @@ func (_ Unimplemented) SubmitVerificationResponse(w http.ResponseWriter, r *http w.WriteHeader(http.StatusNotImplemented) } +// Check Verification Response or Provide Query +// (GET /v2/identities/{identifier}/verification/check) +func (_ Unimplemented) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Create a Verification query +// (POST /v2/identities/{identifier}/verification/create) +func (_ Unimplemented) CreateVerification(w http.ResponseWriter, r *http.Request, identifier string) { + w.WriteHeader(http.StatusNotImplemented) +} + // Payments Configuration // (GET /v2/payment/settings) func (_ Unimplemented) GetPaymentSettings(w http.ResponseWriter, r *http.Request) { @@ -1716,12 +1732,6 @@ func (_ Unimplemented) GetSupportedNetworks(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusNotImplemented) } -// Check Verification Response or Provide Query -// (GET /v2/verification/{identifier}/check) -func (_ Unimplemented) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { - w.WriteHeader(http.StatusNotImplemented) -} - // Get Authentication Message // (POST /v2/{identifier}/authentication) func (_ Unimplemented) Authentication(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params AuthenticationParams) { @@ -3988,6 +3998,74 @@ func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWri handler.ServeHTTP(w, r) } +// CheckVerification operation middleware +func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier string + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params CheckVerificationParams + + // ------------- Required query parameter "id" ------------- + + if paramValue := r.URL.Query().Get("id"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "id"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "id", r.URL.Query(), ¶ms.Id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CheckVerification(w, r, identifier, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateVerification operation middleware +func (siw *ServerInterfaceWrapper) CreateVerification(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "identifier" ------------- + var identifier string + + err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateVerification(w, r, identifier) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetPaymentSettings operation middleware func (siw *ServerInterfaceWrapper) GetPaymentSettings(w http.ResponseWriter, r *http.Request) { @@ -4063,49 +4141,6 @@ func (siw *ServerInterfaceWrapper) GetSupportedNetworks(w http.ResponseWriter, r handler.ServeHTTP(w, r) } -// CheckVerification operation middleware -func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "identifier" ------------- - var identifier string - - err = runtime.BindStyledParameterWithOptions("simple", "identifier", chi.URLParam(r, "identifier"), &identifier, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identifier", Err: err}) - return - } - - // Parameter object where we will unmarshal all parameters from the context - var params CheckVerificationParams - - // ------------- Required query parameter "id" ------------- - - if paramValue := r.URL.Query().Get("id"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "id"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "id", r.URL.Query(), ¶ms.Id) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CheckVerification(w, r, identifier, params) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - // Authentication operation middleware func (siw *ServerInterfaceWrapper) Authentication(w http.ResponseWriter, r *http.Request) { @@ -4432,6 +4467,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/v2/identities/{identifier}/verification/callback", wrapper.SubmitVerificationResponse) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/v2/identities/{identifier}/verification/check", wrapper.CheckVerification) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/v2/identities/{identifier}/verification/create", wrapper.CreateVerification) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/payment/settings", wrapper.GetPaymentSettings) }) @@ -4441,9 +4482,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/supported-networks", wrapper.GetSupportedNetworks) }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/v2/verification/{identifier}/check", wrapper.CheckVerification) - }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/v2/{identifier}/authentication", wrapper.Authentication) }) @@ -6981,6 +7019,89 @@ func (response SubmitVerificationResponse500JSONResponse) VisitSubmitVerificatio return json.NewEncoder(w).Encode(response) } +type CheckVerificationRequestObject struct { + Identifier string `json:"identifier"` + Params CheckVerificationParams +} + +type CheckVerificationResponseObject interface { + VisitCheckVerificationResponse(w http.ResponseWriter) error +} + +type CheckVerification200JSONResponse struct { + union json.RawMessage +} + +func (response CheckVerification200JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.union) +} + +type CheckVerification400JSONResponse struct{ N400JSONResponse } + +func (response CheckVerification400JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CheckVerification404JSONResponse struct{ N404JSONResponse } + +func (response CheckVerification404JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type CheckVerification500JSONResponse struct{ N500JSONResponse } + +func (response CheckVerification500JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVerificationRequestObject struct { + Identifier string `json:"identifier"` + Body *CreateVerificationJSONRequestBody +} + +type CreateVerificationResponseObject interface { + VisitCreateVerificationResponse(w http.ResponseWriter) error +} + +type CreateVerification200JSONResponse GenericMessage + +func (response CreateVerification200JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVerification400JSONResponse struct{ N400JSONResponse } + +func (response CreateVerification400JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVerification500JSONResponse struct{ N500JSONResponse } + +func (response CreateVerification500JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type GetPaymentSettingsRequestObject struct { } @@ -7102,53 +7223,6 @@ func (response GetSupportedNetworks500JSONResponse) VisitGetSupportedNetworksRes return json.NewEncoder(w).Encode(response) } -type CheckVerificationRequestObject struct { - Identifier string `json:"identifier"` - Params CheckVerificationParams -} - -type CheckVerificationResponseObject interface { - VisitCheckVerificationResponse(w http.ResponseWriter) error -} - -type CheckVerification200JSONResponse struct { - union json.RawMessage -} - -func (response CheckVerification200JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response.union) -} - -type CheckVerification400JSONResponse struct{ N400JSONResponse } - -func (response CheckVerification400JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type CheckVerification404JSONResponse struct{ N404JSONResponse } - -func (response CheckVerification404JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(404) - - return json.NewEncoder(w).Encode(response) -} - -type CheckVerification500JSONResponse struct{ N500JSONResponse } - -func (response CheckVerification500JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - type AuthenticationRequestObject struct { Identifier PathIdentifier `json:"identifier"` Params AuthenticationParams @@ -7364,6 +7438,12 @@ type StrictServerInterface interface { // Submit Verification Response // (POST /v2/identities/{identifier}/verification/callback) SubmitVerificationResponse(ctx context.Context, request SubmitVerificationResponseRequestObject) (SubmitVerificationResponseResponseObject, error) + // Check Verification Response or Provide Query + // (GET /v2/identities/{identifier}/verification/check) + CheckVerification(ctx context.Context, request CheckVerificationRequestObject) (CheckVerificationResponseObject, error) + // Create a Verification query + // (POST /v2/identities/{identifier}/verification/create) + CreateVerification(ctx context.Context, request CreateVerificationRequestObject) (CreateVerificationResponseObject, error) // Payments Configuration // (GET /v2/payment/settings) GetPaymentSettings(ctx context.Context, request GetPaymentSettingsRequestObject) (GetPaymentSettingsResponseObject, error) @@ -7373,9 +7453,6 @@ type StrictServerInterface interface { // Get Supported Networks // (GET /v2/supported-networks) GetSupportedNetworks(ctx context.Context, request GetSupportedNetworksRequestObject) (GetSupportedNetworksResponseObject, error) - // Check Verification Response or Provide Query - // (GET /v2/verification/{identifier}/check) - CheckVerification(ctx context.Context, request CheckVerificationRequestObject) (CheckVerificationResponseObject, error) // Get Authentication Message // (POST /v2/{identifier}/authentication) Authentication(ctx context.Context, request AuthenticationRequestObject) (AuthenticationResponseObject, error) @@ -9124,6 +9201,66 @@ func (sh *strictHandler) SubmitVerificationResponse(w http.ResponseWriter, r *ht } } +// CheckVerification operation middleware +func (sh *strictHandler) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { + var request CheckVerificationRequestObject + + request.Identifier = identifier + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CheckVerification(ctx, request.(CheckVerificationRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CheckVerification") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CheckVerificationResponseObject); ok { + if err := validResponse.VisitCheckVerificationResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// CreateVerification operation middleware +func (sh *strictHandler) CreateVerification(w http.ResponseWriter, r *http.Request, identifier string) { + var request CreateVerificationRequestObject + + request.Identifier = identifier + + var body CreateVerificationJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CreateVerification(ctx, request.(CreateVerificationRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateVerification") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CreateVerificationResponseObject); ok { + if err := validResponse.VisitCreateVerificationResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetPaymentSettings operation middleware func (sh *strictHandler) GetPaymentSettings(w http.ResponseWriter, r *http.Request) { var request GetPaymentSettingsRequestObject @@ -9198,33 +9335,6 @@ func (sh *strictHandler) GetSupportedNetworks(w http.ResponseWriter, r *http.Req } } -// CheckVerification operation middleware -func (sh *strictHandler) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { - var request CheckVerificationRequestObject - - request.Identifier = identifier - request.Params = params - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.CheckVerification(ctx, request.(CheckVerificationRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "CheckVerification") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(CheckVerificationResponseObject); ok { - if err := validResponse.VisitCheckVerificationResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - // Authentication operation middleware func (sh *strictHandler) Authentication(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params AuthenticationParams) { var request AuthenticationRequestObject diff --git a/internal/api/main_test.go b/internal/api/main_test.go index c04134679..7cdfb02a6 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -8,6 +8,8 @@ import ( "github.com/go-chi/chi/v5" "github.com/hashicorp/vault/api" + auth "github.com/iden3/go-iden3-auth/v2" + authLoaders "github.com/iden3/go-iden3-auth/v2/loaders" "github.com/iden3/go-iden3-core/v2/w3c" "github.com/iden3/iden3comm/v2" "github.com/iden3/iden3comm/v2/packers" @@ -27,16 +29,13 @@ import ( "github.com/polygonid/sh-id-platform/internal/loader" "github.com/polygonid/sh-id-platform/internal/log" "github.com/polygonid/sh-id-platform/internal/network" + "github.com/polygonid/sh-id-platform/internal/packagemanager" "github.com/polygonid/sh-id-platform/internal/payments" "github.com/polygonid/sh-id-platform/internal/providers" "github.com/polygonid/sh-id-platform/internal/pubsub" "github.com/polygonid/sh-id-platform/internal/repositories" "github.com/polygonid/sh-id-platform/internal/reversehash" "github.com/polygonid/sh-id-platform/internal/revocationstatus" - - auth "github.com/iden3/go-iden3-auth/v2" - authLoaders "github.com/iden3/go-iden3-auth/v2/loaders" - "github.com/polygonid/sh-id-platform/internal/packagemanager" ) var ( @@ -380,9 +379,9 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { } universalDIDResolverHandler := packagemanager.NewUniversalDIDResolverHandler(universalDIDResolverUrl) verificationKeyLoader := &authLoaders.FSKeyLoader{Dir: cfg.Circuit.Path + "/authV2"} - verifier, err := auth.NewVerifier(verificationKeyLoader, networkResolver.GetStateResolvers(), auth.WithDIDResolver(universalDIDResolverHandler)) + verifier, _ := auth.NewVerifier(verificationKeyLoader, networkResolver.GetStateResolvers(), auth.WithDIDResolver(universalDIDResolverHandler)) - verificationService := services.NewVerificationService(verifier, repos.verification) + verificationService := services.NewVerificationService(networkResolver, cachex, repos.verification, verifier) server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService, displayMethodService, keyService, paymentService, verificationService, mediaTypeManager) diff --git a/internal/api/verification.go b/internal/api/verification.go index 917678e44..703be75d2 100644 --- a/internal/api/verification.go +++ b/internal/api/verification.go @@ -11,9 +11,28 @@ import ( "github.com/polygonid/sh-id-platform/internal/log" ) -// CheckVerification returns CheckVerificationResponse or Provide Query +// CreateVerification a VerificationQuery using verificationService +func (s *Server) CreateVerification(ctx context.Context, request CreateVerificationRequestObject) (CreateVerificationResponseObject, error) { + issuerDID, err := w3c.ParseDID(request.Identifier) + if err != nil { + log.Error(ctx, "parsing issuer did", "err", err, "did", request.Identifier) + return CreateVerification400JSONResponse{N400JSONResponse{Message: "invalid issuer did"}}, nil + } + + verificationQuery, err := s.verificationService.Create(ctx, *issuerDID, request.Body.ChainId, request.Body.SkipRevocationCheck, request.Body.Scopes, s.cfg.ServerUrl) + if err != nil { + log.Error(ctx, "creating verification query", "err", err) + return CreateVerification500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + + return CreateVerification200JSONResponse{ + Message: verificationQuery.ID.String(), + }, nil +} + +// CheckVerification returns CheckVerificationResponse or Provided Query func (s *Server) CheckVerification(ctx context.Context, request CheckVerificationRequestObject) (CheckVerificationResponseObject, error) { - //validation + // validation issuerDID, err := w3c.ParseDID(request.Identifier) if err != nil { log.Error(ctx, "parsing issuer did", "err", err, "did", request.Identifier) @@ -27,7 +46,7 @@ func (s *Server) CheckVerification(ctx context.Context, request CheckVerificatio } // Use the VerificationService to check if there's an existing response or get the query - response, query, err := s.verificationService.CheckVerification(ctx, *issuerDID, verificationQueryID) + response, query, err := s.verificationService.Check(ctx, *issuerDID, verificationQueryID) if err != nil { log.Error(ctx, "checking verification", "err", err) return CheckVerification500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil @@ -59,7 +78,7 @@ func (s *Server) SubmitVerificationResponse(ctx context.Context, request SubmitV token := request.Body // Submit the verification response using VerificationService - response, err := s.verificationService.SubmitVerificationResponse(ctx, request.Params.Id, *issuerDID, *token, s.cfg.ServerUrl) + response, err := s.verificationService.Submit(ctx, request.Params.Id, *issuerDID, *token, s.cfg.ServerUrl) if err != nil { log.Error(ctx, "failed to submit verification response", "err", err) return SubmitVerificationResponse500JSONResponse{ diff --git a/internal/core/ports/verification_service.go b/internal/core/ports/verification_service.go index 45d205baa..0f5c13ff3 100644 --- a/internal/core/ports/verification_service.go +++ b/internal/core/ports/verification_service.go @@ -5,11 +5,13 @@ import ( "github.com/google/uuid" "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/polygonid/sh-id-platform/internal/core/domain" ) // VerificationService interface type VerificationService interface { - CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) - SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) + Create(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) + Check(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) + Submit(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) } diff --git a/internal/core/services/verification.go b/internal/core/services/verification.go index 504239650..a24f567ad 100644 --- a/internal/core/services/verification.go +++ b/internal/core/services/verification.go @@ -14,9 +14,12 @@ import ( "github.com/iden3/go-iden3-core/v2/w3c" protocol "github.com/iden3/iden3comm/v2/protocol" "github.com/jackc/pgtype" + + "github.com/polygonid/sh-id-platform/internal/cache" "github.com/polygonid/sh-id-platform/internal/core/domain" "github.com/polygonid/sh-id-platform/internal/core/ports" "github.com/polygonid/sh-id-platform/internal/log" + "github.com/polygonid/sh-id-platform/internal/network" "github.com/polygonid/sh-id-platform/internal/repositories" ) @@ -38,25 +41,70 @@ type TransactionData struct { } const ( - stateTransitionDelay = time.Minute * 5 - defaultReason = "for testing purposes" - defaultBigIntBase = 10 + stateTransitionDelay = time.Minute * 5 + defaultReason = "for testing purposes" + defaultBigIntBase = 10 + defaultExpiration time.Duration = 0 ) // VerificationService can verify responses to verification queries type VerificationService struct { - verifier *auth.Verifier - repo ports.VerificationRepository + networkResolver *network.Resolver + store cache.Cache + repo ports.VerificationRepository + verifier *auth.Verifier } // NewVerificationService creates a new instance of VerificationService -func NewVerificationService(verifier *auth.Verifier, repo ports.VerificationRepository) *VerificationService { - return &VerificationService{verifier: verifier, repo: repo} +func NewVerificationService(networkResolver *network.Resolver, store cache.Cache, repo ports.VerificationRepository, verifier *auth.Verifier) *VerificationService { + return &VerificationService{ + networkResolver: networkResolver, + store: store, + verifier: verifier, + repo: repo, + } +} + +// Create creates and saves a new verification query in the database. +func (vs *VerificationService) Create(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) { + var scopeJSON pgtype.JSONB + err := scopeJSON.Set(scopes) + if err != nil { + return nil, fmt.Errorf("failed to set scope JSON: %w", err) + } + + verificationQuery := domain.VerificationQuery{ + ID: uuid.New(), + IssuerDID: issuerID.String(), + ChainID: chainID, + SkipCheckRevocation: skipCheckRevocation, + Scope: scopeJSON, + CreatedAt: time.Now(), + } + + queryID, err := vs.repo.Save(ctx, issuerID, verificationQuery) + if err != nil { + return nil, fmt.Errorf("failed to save verification query: %w", err) + } + + verificationQuery.ID = queryID + + authRequest, err := getAuthRequestOffChain(&verificationQuery, serverURL) + if err != nil { + return nil, fmt.Errorf("failed to generate auth request: %w", err) + } + + if err := vs.store.Set(ctx, queryID.String(), authRequest, defaultExpiration); err != nil { + log.Error(ctx, "error storing verification query request", "id", queryID.String(), "error", err) + return nil, err + } + + return &verificationQuery, nil } -// CheckVerification checks if a verification response already exists for a given verification query ID and userDID. +// Check checks if a verification response already exists for a given verification query ID and userDID. // If no response exists, it returns the verification query. -func (vs *VerificationService) CheckVerification(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) { +func (vs *VerificationService) Check(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) { query, err := vs.repo.Get(ctx, issuerID, verificationQueryID) if err != nil { if err == repositories.VerificationQueryNotFoundError { @@ -73,21 +121,19 @@ func (vs *VerificationService) CheckVerification(ctx context.Context, issuerID w return nil, query, nil } -// SubmitVerificationResponse checks if a verification response passes a verify check and saves result -func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) { - // check db for existing verification query - var authRequest = protocol.AuthorizationRequestMessage{} - verificationQuery, err := vs.repo.Get(ctx, issuerID, verificationQueryID) - authRequest, err = getAuthRequestOffChain(verificationQuery, serverURL) - if err != nil { - return nil, fmt.Errorf("failed to get authorization request: %w", err) +// Submit checks if a verification response passes a verify check and saves result +func (vs *VerificationService) Submit(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) { + // check cache for existing authRequest + var authRequest protocol.AuthorizationRequestMessage + if found := vs.store.Get(ctx, verificationQueryID.String(), &authRequest); !found { + log.Error(ctx, "authRequest not found in the cache", "id", verificationQueryID.String()) + return nil, ErrQRCodeLinkNotFound } // perform verification authRespMsg, err := vs.verifier.FullVerify(ctx, token, authRequest, pubsignals.WithAcceptedStateTransitionDelay(stateTransitionDelay)) - if err != nil { log.Error(ctx, "failed to verify", "verificationQueryID", verificationQueryID, "err", err) return nil, fmt.Errorf("failed to verify token: %w", err) @@ -120,7 +166,6 @@ func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, v } func getAuthRequestOffChain(req *domain.VerificationQuery, serverURL string) (protocol.AuthorizationRequestMessage, error) { - id := uuid.NewString() authReq := auth.CreateAuthorizationRequest(getReason(nil), req.IssuerDID, getUri(serverURL, req.IssuerDID, req.ID)) authReq.ID = id From 0bbfcad381fc868a694ab9b7cf05ce2ba6834a15 Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Tue, 17 Dec 2024 02:54:56 -0800 Subject: [PATCH 11/20] Cleanup method names, requested changes --- api/api.yaml | 112 ++++---- internal/api/api.gen.go | 278 ++++++++++---------- internal/api/main_test.go | 3 +- internal/api/verification.go | 80 ++++-- internal/core/ports/verification_service.go | 6 +- internal/core/services/verification.go | 44 ++-- 6 files changed, 300 insertions(+), 223 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index d0d82cc4c..db16e900a 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -1991,71 +1991,68 @@ paths: # Verification - /v2/identities/{identifier}/verification/create: - post: - summary: Create a Verification query - operationId: CreateVerification + /v2/identities/{identifier}/verification: + get: + summary: Check Verification Response or Provide Query + operationId: CheckVerification description: | - Endpoint to create a verification query. + Checks if a verification response already exists for the given identifier. + If no response is found, it returns the verification query request to be completed by the user. tags: - Verification parameters: - name: identifier in: path required: true - description: Issuer's DID Identifier + description: User's DID identifier schema: type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateVerificationQueryRequest' + - $ref: '#/components/parameters/verificationQueryId' responses: '200': - description: Verification query created successfully + description: Verification Response or Query Request content: application/json: schema: - $ref: '#/components/schemas/GenericMessage' + $ref: '#/components/schemas/CheckVerificationResponse' '400': $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' '500': $ref: '#/components/responses/500' - /v2/identities/{identifier}/verification/check: - get: - summary: Check Verification Response or Provide Query - operationId: CheckVerification + post: + summary: Create a Verification query + operationId: CreateVerification description: | - Checks if a verification response already exists for the given identifier. - If no response is found, it returns the verification query request to be completed by the user. + Endpoint to create a verification query. tags: - Verification parameters: - name: identifier in: path required: true - description: User's DID identifier + description: Issuer's DID Identifier schema: type: string - - $ref: '#/components/parameters/verificationQueryId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateVerificationQueryRequest' responses: - '200': - description: Verification Response or Query Request + '201': + description: Verification query created successfully content: application/json: schema: - oneOf: - - $ref: '#/components/schemas/VerificationResponse' - - $ref: '#/components/schemas/VerificationQueryRequest' + $ref: '#/components/schemas/CreateVerificationQueryResponse' '400': $ref: '#/components/responses/400' - '404': - $ref: '#/components/responses/404' '500': - $ref: '#/components/responses/500' + $ref: '#/components/responses/500' /v2/identities/{identifier}/verification/callback: post: @@ -3271,6 +3268,33 @@ components: path: github.com/iden3/iden3comm/v2/protocol #Verification + CreateVerificationQueryRequest: + type: object + required: + - chain_id + - skip_revocation_check + - scopes + properties: + chain_id: + type: integer + example: 1 + skip_revocation_check: + type: boolean + example: false + scopes: + type: object + additionalProperties: true + description: "Dynamic JSON object for scopes" + + CreateVerificationQueryResponse: + type: object + required: + - verificationQueryId + properties: + verificationQueryId: + type: string + description: The ID of the created verification query. + VerificationResponse: type: object required: @@ -3306,6 +3330,16 @@ components: additionalProperties: true description: "Dynamic JSON object for scopes" + CheckVerificationResponse: + type: object + description: Response that includes either a verification response or a verification query request. + properties: + verification_response: + $ref: '#/components/schemas/VerificationResponse' + verification_query_request: + $ref: '#/components/schemas/VerificationQueryRequest' + additionalProperties: false + VerificationResponseStatus: type: object required: @@ -3429,24 +3463,6 @@ components: example: "Iden3ReverseSparseMerkleTreeProof" enum: [ Iden3commRevocationStatusV1.0, Iden3ReverseSparseMerkleTreeProof, Iden3OnchainSparseMerkleTreeProof2023 ] - CreateVerificationQueryRequest: - type: object - required: - - chain_id - - skip_revocation_check - - scopes - properties: - chain_id: - type: integer - example: 1 - skip_revocation_check: - type: boolean - example: false - scopes: - type: object - additionalProperties: true - description: "Dynamic JSON object for scopes" - parameters: credentialStatusType: diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 1e5c00eca..d09202388 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -239,6 +239,12 @@ type BasicMessage struct { Type string `json:"type"` } +// CheckVerificationResponse Response that includes either a verification response or a verification query request. +type CheckVerificationResponse struct { + VerificationQueryRequest *VerificationQueryRequest `json:"verification_query_request,omitempty"` + VerificationResponse *VerificationResponse `json:"verification_response,omitempty"` +} + // ConnectionsPaginated defines model for ConnectionsPaginated. type ConnectionsPaginated struct { Items GetConnectionsResponse `json:"items"` @@ -388,6 +394,12 @@ type CreateVerificationQueryRequest struct { SkipRevocationCheck bool `json:"skip_revocation_check"` } +// CreateVerificationQueryResponse defines model for CreateVerificationQueryResponse. +type CreateVerificationQueryResponse struct { + // VerificationQueryId The ID of the created verification query. + VerificationQueryId string `json:"verificationQueryId"` +} + // Credential defines model for Credential. type Credential struct { Id string `json:"id"` @@ -1047,6 +1059,12 @@ type GetStateTransactionsParamsFilter string // GetStateTransactionsParamsSort defines parameters for GetStateTransactions. type GetStateTransactionsParamsSort string +// CheckVerificationParams defines parameters for CheckVerification. +type CheckVerificationParams struct { + // Id The verification query ID to check for a response + Id VerificationQueryId `form:"id" json:"id"` +} + // SubmitVerificationResponseTextBody defines parameters for SubmitVerificationResponse. type SubmitVerificationResponseTextBody = string @@ -1056,12 +1074,6 @@ type SubmitVerificationResponseParams struct { Id VerificationQueryId `form:"id" json:"id"` } -// CheckVerificationParams defines parameters for CheckVerification. -type CheckVerificationParams struct { - // Id The verification query ID to check for a response - Id VerificationQueryId `form:"id" json:"id"` -} - // GetQrFromStoreParams defines parameters for GetQrFromStore. type GetQrFromStoreParams struct { Id *uuid.UUID `form:"id,omitempty" json:"id,omitempty"` @@ -1139,12 +1151,12 @@ type ImportSchemaJSONRequestBody = ImportSchemaRequest // UpdateSchemaJSONRequestBody defines body for UpdateSchema for application/json ContentType. type UpdateSchemaJSONRequestBody UpdateSchemaJSONBody -// SubmitVerificationResponseTextRequestBody defines body for SubmitVerificationResponse for text/plain ContentType. -type SubmitVerificationResponseTextRequestBody = SubmitVerificationResponseTextBody - // CreateVerificationJSONRequestBody defines body for CreateVerification for application/json ContentType. type CreateVerificationJSONRequestBody = CreateVerificationQueryRequest +// SubmitVerificationResponseTextRequestBody defines body for SubmitVerificationResponse for text/plain ContentType. +type SubmitVerificationResponseTextRequestBody = SubmitVerificationResponseTextBody + // ServerInterface represents all server handlers. type ServerInterface interface { // Healthcheck @@ -1321,15 +1333,15 @@ type ServerInterface interface { // Get Identity State Transactions // (GET /v2/identities/{identifier}/state/transactions) GetStateTransactions(w http.ResponseWriter, r *http.Request, identifier PathIdentifier, params GetStateTransactionsParams) - // Submit Verification Response - // (POST /v2/identities/{identifier}/verification/callback) - SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) // Check Verification Response or Provide Query - // (GET /v2/identities/{identifier}/verification/check) + // (GET /v2/identities/{identifier}/verification) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) // Create a Verification query - // (POST /v2/identities/{identifier}/verification/create) + // (POST /v2/identities/{identifier}/verification) CreateVerification(w http.ResponseWriter, r *http.Request, identifier string) + // Submit Verification Response + // (POST /v2/identities/{identifier}/verification/callback) + SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) // Payments Configuration // (GET /v2/payment/settings) GetPaymentSettings(w http.ResponseWriter, r *http.Request) @@ -1696,24 +1708,24 @@ func (_ Unimplemented) GetStateTransactions(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusNotImplemented) } -// Submit Verification Response -// (POST /v2/identities/{identifier}/verification/callback) -func (_ Unimplemented) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) { - w.WriteHeader(http.StatusNotImplemented) -} - // Check Verification Response or Provide Query -// (GET /v2/identities/{identifier}/verification/check) +// (GET /v2/identities/{identifier}/verification) func (_ Unimplemented) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { w.WriteHeader(http.StatusNotImplemented) } // Create a Verification query -// (POST /v2/identities/{identifier}/verification/create) +// (POST /v2/identities/{identifier}/verification) func (_ Unimplemented) CreateVerification(w http.ResponseWriter, r *http.Request, identifier string) { w.WriteHeader(http.StatusNotImplemented) } +// Submit Verification Response +// (POST /v2/identities/{identifier}/verification/callback) +func (_ Unimplemented) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // Payments Configuration // (GET /v2/payment/settings) func (_ Unimplemented) GetPaymentSettings(w http.ResponseWriter, r *http.Request) { @@ -3955,8 +3967,8 @@ func (siw *ServerInterfaceWrapper) GetStateTransactions(w http.ResponseWriter, r handler.ServeHTTP(w, r) } -// SubmitVerificationResponse operation middleware -func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request) { +// CheckVerification operation middleware +func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *http.Request) { var err error @@ -3970,7 +3982,7 @@ func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWri } // Parameter object where we will unmarshal all parameters from the context - var params SubmitVerificationResponseParams + var params CheckVerificationParams // ------------- Required query parameter "id" ------------- @@ -3988,7 +4000,7 @@ func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWri } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.SubmitVerificationResponse(w, r, identifier, params) + siw.Handler.CheckVerification(w, r, identifier, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -3998,8 +4010,8 @@ func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWri handler.ServeHTTP(w, r) } -// CheckVerification operation middleware -func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *http.Request) { +// CreateVerification operation middleware +func (siw *ServerInterfaceWrapper) CreateVerification(w http.ResponseWriter, r *http.Request) { var err error @@ -4012,26 +4024,8 @@ func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *h return } - // Parameter object where we will unmarshal all parameters from the context - var params CheckVerificationParams - - // ------------- Required query parameter "id" ------------- - - if paramValue := r.URL.Query().Get("id"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "id"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "id", r.URL.Query(), ¶ms.Id) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CheckVerification(w, r, identifier, params) + siw.Handler.CreateVerification(w, r, identifier) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4041,8 +4035,8 @@ func (siw *ServerInterfaceWrapper) CheckVerification(w http.ResponseWriter, r *h handler.ServeHTTP(w, r) } -// CreateVerification operation middleware -func (siw *ServerInterfaceWrapper) CreateVerification(w http.ResponseWriter, r *http.Request) { +// SubmitVerificationResponse operation middleware +func (siw *ServerInterfaceWrapper) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request) { var err error @@ -4055,8 +4049,26 @@ func (siw *ServerInterfaceWrapper) CreateVerification(w http.ResponseWriter, r * return } + // Parameter object where we will unmarshal all parameters from the context + var params SubmitVerificationResponseParams + + // ------------- Required query parameter "id" ------------- + + if paramValue := r.URL.Query().Get("id"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "id"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "id", r.URL.Query(), ¶ms.Id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateVerification(w, r, identifier) + siw.Handler.SubmitVerificationResponse(w, r, identifier, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4465,13 +4477,13 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/v2/identities/{identifier}/state/transactions", wrapper.GetStateTransactions) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/v2/identities/{identifier}/verification/callback", wrapper.SubmitVerificationResponse) + r.Get(options.BaseURL+"/v2/identities/{identifier}/verification", wrapper.CheckVerification) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/v2/identities/{identifier}/verification/check", wrapper.CheckVerification) + r.Post(options.BaseURL+"/v2/identities/{identifier}/verification", wrapper.CreateVerification) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/v2/identities/{identifier}/verification/create", wrapper.CreateVerification) + r.Post(options.BaseURL+"/v2/identities/{identifier}/verification/callback", wrapper.SubmitVerificationResponse) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/v2/payment/settings", wrapper.GetPaymentSettings) @@ -6982,120 +6994,118 @@ func (response GetStateTransactions500JSONResponse) VisitGetStateTransactionsRes return json.NewEncoder(w).Encode(response) } -type SubmitVerificationResponseRequestObject struct { +type CheckVerificationRequestObject struct { Identifier string `json:"identifier"` - Params SubmitVerificationResponseParams - Body *SubmitVerificationResponseTextRequestBody + Params CheckVerificationParams } -type SubmitVerificationResponseResponseObject interface { - VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error +type CheckVerificationResponseObject interface { + VisitCheckVerificationResponse(w http.ResponseWriter) error } -type SubmitVerificationResponse200JSONResponse VerificationResponseStatus +type CheckVerification200JSONResponse CheckVerificationResponse -func (response SubmitVerificationResponse200JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { +func (response CheckVerification200JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type SubmitVerificationResponse400JSONResponse struct{ N400JSONResponse } +type CheckVerification400JSONResponse struct{ N400JSONResponse } -func (response SubmitVerificationResponse400JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { +func (response CheckVerification400JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type SubmitVerificationResponse500JSONResponse struct{ N500JSONResponse } +type CheckVerification404JSONResponse struct{ N404JSONResponse } -func (response SubmitVerificationResponse500JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { +func (response CheckVerification404JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) + w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } -type CheckVerificationRequestObject struct { - Identifier string `json:"identifier"` - Params CheckVerificationParams -} +type CheckVerification500JSONResponse struct{ N500JSONResponse } -type CheckVerificationResponseObject interface { - VisitCheckVerificationResponse(w http.ResponseWriter) error -} +func (response CheckVerification500JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) -type CheckVerification200JSONResponse struct { - union json.RawMessage + return json.NewEncoder(w).Encode(response) } -func (response CheckVerification200JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) +type CreateVerificationRequestObject struct { + Identifier string `json:"identifier"` + Body *CreateVerificationJSONRequestBody +} - return json.NewEncoder(w).Encode(response.union) +type CreateVerificationResponseObject interface { + VisitCreateVerificationResponse(w http.ResponseWriter) error } -type CheckVerification400JSONResponse struct{ N400JSONResponse } +type CreateVerification201JSONResponse CreateVerificationQueryResponse -func (response CheckVerification400JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { +func (response CreateVerification201JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) + w.WriteHeader(201) return json.NewEncoder(w).Encode(response) } -type CheckVerification404JSONResponse struct{ N404JSONResponse } +type CreateVerification400JSONResponse struct{ N400JSONResponse } -func (response CheckVerification404JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { +func (response CreateVerification400JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(404) + w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type CheckVerification500JSONResponse struct{ N500JSONResponse } +type CreateVerification500JSONResponse struct{ N500JSONResponse } -func (response CheckVerification500JSONResponse) VisitCheckVerificationResponse(w http.ResponseWriter) error { +func (response CreateVerification500JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type CreateVerificationRequestObject struct { +type SubmitVerificationResponseRequestObject struct { Identifier string `json:"identifier"` - Body *CreateVerificationJSONRequestBody + Params SubmitVerificationResponseParams + Body *SubmitVerificationResponseTextRequestBody } -type CreateVerificationResponseObject interface { - VisitCreateVerificationResponse(w http.ResponseWriter) error +type SubmitVerificationResponseResponseObject interface { + VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error } -type CreateVerification200JSONResponse GenericMessage +type SubmitVerificationResponse200JSONResponse VerificationResponseStatus -func (response CreateVerification200JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { +func (response SubmitVerificationResponse200JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type CreateVerification400JSONResponse struct{ N400JSONResponse } +type SubmitVerificationResponse400JSONResponse struct{ N400JSONResponse } -func (response CreateVerification400JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { +func (response SubmitVerificationResponse400JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type CreateVerification500JSONResponse struct{ N500JSONResponse } +type SubmitVerificationResponse500JSONResponse struct{ N500JSONResponse } -func (response CreateVerification500JSONResponse) VisitCreateVerificationResponse(w http.ResponseWriter) error { +func (response SubmitVerificationResponse500JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) @@ -7435,15 +7445,15 @@ type StrictServerInterface interface { // Get Identity State Transactions // (GET /v2/identities/{identifier}/state/transactions) GetStateTransactions(ctx context.Context, request GetStateTransactionsRequestObject) (GetStateTransactionsResponseObject, error) - // Submit Verification Response - // (POST /v2/identities/{identifier}/verification/callback) - SubmitVerificationResponse(ctx context.Context, request SubmitVerificationResponseRequestObject) (SubmitVerificationResponseResponseObject, error) // Check Verification Response or Provide Query - // (GET /v2/identities/{identifier}/verification/check) + // (GET /v2/identities/{identifier}/verification) CheckVerification(ctx context.Context, request CheckVerificationRequestObject) (CheckVerificationResponseObject, error) // Create a Verification query - // (POST /v2/identities/{identifier}/verification/create) + // (POST /v2/identities/{identifier}/verification) CreateVerification(ctx context.Context, request CreateVerificationRequestObject) (CreateVerificationResponseObject, error) + // Submit Verification Response + // (POST /v2/identities/{identifier}/verification/callback) + SubmitVerificationResponse(ctx context.Context, request SubmitVerificationResponseRequestObject) (SubmitVerificationResponseResponseObject, error) // Payments Configuration // (GET /v2/payment/settings) GetPaymentSettings(ctx context.Context, request GetPaymentSettingsRequestObject) (GetPaymentSettingsResponseObject, error) @@ -9166,34 +9176,26 @@ func (sh *strictHandler) GetStateTransactions(w http.ResponseWriter, r *http.Req } } -// SubmitVerificationResponse operation middleware -func (sh *strictHandler) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) { - var request SubmitVerificationResponseRequestObject +// CheckVerification operation middleware +func (sh *strictHandler) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { + var request CheckVerificationRequestObject request.Identifier = identifier request.Params = params - data, err := io.ReadAll(r.Body) - if err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) - return - } - body := SubmitVerificationResponseTextRequestBody(data) - request.Body = &body - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.SubmitVerificationResponse(ctx, request.(SubmitVerificationResponseRequestObject)) + return sh.ssi.CheckVerification(ctx, request.(CheckVerificationRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "SubmitVerificationResponse") + handler = middleware(handler, "CheckVerification") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(SubmitVerificationResponseResponseObject); ok { - if err := validResponse.VisitSubmitVerificationResponseResponse(w); err != nil { + } else if validResponse, ok := response.(CheckVerificationResponseObject); ok { + if err := validResponse.VisitCheckVerificationResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -9201,26 +9203,32 @@ func (sh *strictHandler) SubmitVerificationResponse(w http.ResponseWriter, r *ht } } -// CheckVerification operation middleware -func (sh *strictHandler) CheckVerification(w http.ResponseWriter, r *http.Request, identifier string, params CheckVerificationParams) { - var request CheckVerificationRequestObject +// CreateVerification operation middleware +func (sh *strictHandler) CreateVerification(w http.ResponseWriter, r *http.Request, identifier string) { + var request CreateVerificationRequestObject request.Identifier = identifier - request.Params = params + + var body CreateVerificationJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.CheckVerification(ctx, request.(CheckVerificationRequestObject)) + return sh.ssi.CreateVerification(ctx, request.(CreateVerificationRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "CheckVerification") + handler = middleware(handler, "CreateVerification") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(CheckVerificationResponseObject); ok { - if err := validResponse.VisitCheckVerificationResponse(w); err != nil { + } else if validResponse, ok := response.(CreateVerificationResponseObject); ok { + if err := validResponse.VisitCreateVerificationResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -9228,32 +9236,34 @@ func (sh *strictHandler) CheckVerification(w http.ResponseWriter, r *http.Reques } } -// CreateVerification operation middleware -func (sh *strictHandler) CreateVerification(w http.ResponseWriter, r *http.Request, identifier string) { - var request CreateVerificationRequestObject +// SubmitVerificationResponse operation middleware +func (sh *strictHandler) SubmitVerificationResponse(w http.ResponseWriter, r *http.Request, identifier string, params SubmitVerificationResponseParams) { + var request SubmitVerificationResponseRequestObject request.Identifier = identifier + request.Params = params - var body CreateVerificationJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) return } + body := SubmitVerificationResponseTextRequestBody(data) request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.CreateVerification(ctx, request.(CreateVerificationRequestObject)) + return sh.ssi.SubmitVerificationResponse(ctx, request.(SubmitVerificationResponseRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "CreateVerification") + handler = middleware(handler, "SubmitVerificationResponse") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(CreateVerificationResponseObject); ok { - if err := validResponse.VisitCreateVerificationResponse(w); err != nil { + } else if validResponse, ok := response.(SubmitVerificationResponseResponseObject); ok { + if err := validResponse.VisitSubmitVerificationResponseResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { diff --git a/internal/api/main_test.go b/internal/api/main_test.go index 7cdfb02a6..ef0b0d1de 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -379,7 +379,8 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { } universalDIDResolverHandler := packagemanager.NewUniversalDIDResolverHandler(universalDIDResolverUrl) verificationKeyLoader := &authLoaders.FSKeyLoader{Dir: cfg.Circuit.Path + "/authV2"} - verifier, _ := auth.NewVerifier(verificationKeyLoader, networkResolver.GetStateResolvers(), auth.WithDIDResolver(universalDIDResolverHandler)) + verifier, err := auth.NewVerifier(verificationKeyLoader, networkResolver.GetStateResolvers(), auth.WithDIDResolver(universalDIDResolverHandler)) + require.NoError(t, err) verificationService := services.NewVerificationService(networkResolver, cachex, repos.verification, verifier) diff --git a/internal/api/verification.go b/internal/api/verification.go index 703be75d2..1069d50f1 100644 --- a/internal/api/verification.go +++ b/internal/api/verification.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" - "github.com/google/uuid" "github.com/iden3/go-iden3-core/v2/w3c" "github.com/polygonid/sh-id-platform/internal/log" @@ -19,47 +18,92 @@ func (s *Server) CreateVerification(ctx context.Context, request CreateVerificat return CreateVerification400JSONResponse{N400JSONResponse{Message: "invalid issuer did"}}, nil } - verificationQuery, err := s.verificationService.Create(ctx, *issuerDID, request.Body.ChainId, request.Body.SkipRevocationCheck, request.Body.Scopes, s.cfg.ServerUrl) + verificationQuery, err := s.verificationService.CreateVerificationQuery(ctx, *issuerDID, request.Body.ChainId, request.Body.SkipRevocationCheck, request.Body.Scopes, s.cfg.ServerUrl) if err != nil { log.Error(ctx, "creating verification query", "err", err) return CreateVerification500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil } - return CreateVerification200JSONResponse{ - Message: verificationQuery.ID.String(), + return CreateVerification201JSONResponse{ + VerificationQueryId: verificationQuery.ID.String(), }, nil } // CheckVerification returns CheckVerificationResponse or Provided Query func (s *Server) CheckVerification(ctx context.Context, request CheckVerificationRequestObject) (CheckVerificationResponseObject, error) { - // validation + // Parse and validate issuer DID issuerDID, err := w3c.ParseDID(request.Identifier) if err != nil { log.Error(ctx, "parsing issuer did", "err", err, "did", request.Identifier) return CheckVerification400JSONResponse{N400JSONResponse{Message: "invalid issuer did"}}, nil } - verificationQueryID, err := uuid.Parse(request.Params.Id.String()) - if err != nil { - log.Error(ctx, "invalid verification query ID", "err", err) - return CheckVerification400JSONResponse{N400JSONResponse{Message: "invalid verification query ID"}}, nil - } + verificationQueryID := request.Params.Id - // Use the VerificationService to check if there's an existing response or get the query - response, query, err := s.verificationService.Check(ctx, *issuerDID, verificationQueryID) + // Use the VerificationService to check for existing response or query + response, query, err := s.verificationService.GetVerificationStatus(ctx, *issuerDID, verificationQueryID) if err != nil { - log.Error(ctx, "checking verification", "err", err) + log.Error(ctx, "checking verification", "err", err, "issuerDID", issuerDID, "id", verificationQueryID) return CheckVerification500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil } + // Check if a response exists if response != nil { - return CheckVerification200JSONResponse{union: json.RawMessage(fmt.Sprintf(`{"status": "validated", "response": %v}`, response))}, nil + var jsonResponse map[string]interface{} + + // Marshal the interface{} value to JSON bytes + responseBytes, err := json.Marshal(response.Response.Get()) + if err != nil { + return CheckVerification500JSONResponse{ + N500JSONResponse{Message: fmt.Sprintf("failed to marshal response: %v", err)}, + }, nil + } + + // Unmarshal the JSON bytes into map[string]interface{} + if err := json.Unmarshal(responseBytes, &jsonResponse); err != nil { + return CheckVerification500JSONResponse{ + N500JSONResponse{Message: fmt.Sprintf("failed to unmarshal response: %v", err)}, + }, nil + } + + return CheckVerification200JSONResponse{ + VerificationResponse: &VerificationResponse{ + VerificationScopeId: response.VerificationQueryID.String(), + UserDid: response.UserDID, + Response: jsonResponse, // Safely populated JSON object + Pass: response.Pass, + }, + }, nil } + // Check if a query exists if query != nil { - return CheckVerification200JSONResponse{union: json.RawMessage(fmt.Sprintf(`{"query": %v}`, query))}, nil + var scopeJSON map[string]interface{} + + // Marshal the interface{} value to JSON bytes + scopeBytes, err := json.Marshal(query.Scope.Get()) + if err != nil { + return CheckVerification500JSONResponse{ + N500JSONResponse{Message: fmt.Sprintf("failed to marshal scope: %v", err)}, + }, nil + } + + // Unmarshal the JSON bytes into map[string]interface{} + if err := json.Unmarshal(scopeBytes, &scopeJSON); err != nil { + return CheckVerification500JSONResponse{ + N500JSONResponse{Message: fmt.Sprintf("failed to unmarshal scope: %v", err)}, + }, nil + } + + return CheckVerification200JSONResponse{ + VerificationQueryRequest: &VerificationQueryRequest{ + VerificationQueryId: query.ID.String(), + Scopes: scopeJSON, // Safely populated JSON object + }, + }, nil } + // Return 404 if neither response nor query exists return CheckVerification404JSONResponse{N404JSONResponse{Message: "Verification query not found"}}, nil } @@ -68,7 +112,7 @@ func (s *Server) SubmitVerificationResponse(ctx context.Context, request SubmitV // Unmarshal user DID and response data issuerDID, err := w3c.ParseDID(request.Identifier) if err != nil { - log.Error(ctx, "submitting verification query response.. Parsing did", "err", err) + log.Error(ctx, "submitting verification query response.. Parsing did", "err", err, "issuerDID", issuerDID) return SubmitVerificationResponse400JSONResponse{ N400JSONResponse{ Message: "invalid did", @@ -78,7 +122,7 @@ func (s *Server) SubmitVerificationResponse(ctx context.Context, request SubmitV token := request.Body // Submit the verification response using VerificationService - response, err := s.verificationService.Submit(ctx, request.Params.Id, *issuerDID, *token, s.cfg.ServerUrl) + response, err := s.verificationService.SubmitVerificationResponse(ctx, request.Params.Id, *issuerDID, *token, s.cfg.ServerUrl) if err != nil { log.Error(ctx, "failed to submit verification response", "err", err) return SubmitVerificationResponse500JSONResponse{ @@ -90,7 +134,7 @@ func (s *Server) SubmitVerificationResponse(ctx context.Context, request SubmitV // Construct and return the response status return SubmitVerificationResponse200JSONResponse{ - Status: "submitted", + Status: Submitted, Pass: response.Pass, }, nil } diff --git a/internal/core/ports/verification_service.go b/internal/core/ports/verification_service.go index 0f5c13ff3..115b331fc 100644 --- a/internal/core/ports/verification_service.go +++ b/internal/core/ports/verification_service.go @@ -11,7 +11,7 @@ import ( // VerificationService interface type VerificationService interface { - Create(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) - Check(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) - Submit(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) + CreateVerificationQuery(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) + GetVerificationStatus(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) + SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) } diff --git a/internal/core/services/verification.go b/internal/core/services/verification.go index a24f567ad..549df9d2a 100644 --- a/internal/core/services/verification.go +++ b/internal/core/services/verification.go @@ -24,16 +24,16 @@ import ( ) // Scope is a property of VerificationQuery, and it's required for the authRequest -type Scope struct { +type scope struct { CircuitId string `json:"circuitId"` Id uint32 `json:"id"` Params *map[string]interface{} `json:"params,omitempty"` Query map[string]interface{} `json:"query"` - TransactionData *TransactionData `json:"transactionData,omitempty"` + TransactionData *transactionData `json:"transactionData,omitempty"` } // TransactionData is a property of Scope, and it's required for the authRequest -type TransactionData struct { +type transactionData struct { ChainID int `json:"chainID"` ContractAddress string `json:"contractAddress"` MethodID string `json:"methodID"` @@ -47,6 +47,8 @@ const ( defaultExpiration time.Duration = 0 ) +var errVerificationKeyNotFound = errors.New("authRequest not found in the cache") + // VerificationService can verify responses to verification queries type VerificationService struct { networkResolver *network.Resolver @@ -65,8 +67,8 @@ func NewVerificationService(networkResolver *network.Resolver, store cache.Cache } } -// Create creates and saves a new verification query in the database. -func (vs *VerificationService) Create(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) { +// CreateVerificationQuery creates and saves a new verification query in the database. +func (vs *VerificationService) CreateVerificationQuery(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) { var scopeJSON pgtype.JSONB err := scopeJSON.Set(scopes) if err != nil { @@ -89,12 +91,12 @@ func (vs *VerificationService) Create(ctx context.Context, issuerID w3c.DID, cha verificationQuery.ID = queryID - authRequest, err := getAuthRequestOffChain(&verificationQuery, serverURL) + authRequest, err := vs.getAuthRequestOffChain(&verificationQuery, serverURL) if err != nil { return nil, fmt.Errorf("failed to generate auth request: %w", err) } - if err := vs.store.Set(ctx, queryID.String(), authRequest, defaultExpiration); err != nil { + if err := vs.store.Set(ctx, vs.key(queryID), authRequest, defaultExpiration); err != nil { log.Error(ctx, "error storing verification query request", "id", queryID.String(), "error", err) return nil, err } @@ -102,9 +104,9 @@ func (vs *VerificationService) Create(ctx context.Context, issuerID w3c.DID, cha return &verificationQuery, nil } -// Check checks if a verification response already exists for a given verification query ID and userDID. +// GetVerificationStatus checks if a verification response already exists for a given verification query ID and userDID. // If no response exists, it returns the verification query. -func (vs *VerificationService) Check(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) { +func (vs *VerificationService) GetVerificationStatus(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) { query, err := vs.repo.Get(ctx, issuerID, verificationQueryID) if err != nil { if err == repositories.VerificationQueryNotFoundError { @@ -121,13 +123,13 @@ func (vs *VerificationService) Check(ctx context.Context, issuerID w3c.DID, veri return nil, query, nil } -// Submit checks if a verification response passes a verify check and saves result -func (vs *VerificationService) Submit(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) { +// SubmitVerificationResponse checks if a verification response passes a verify check and saves result +func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) { // check cache for existing authRequest var authRequest protocol.AuthorizationRequestMessage if found := vs.store.Get(ctx, verificationQueryID.String(), &authRequest); !found { log.Error(ctx, "authRequest not found in the cache", "id", verificationQueryID.String()) - return nil, ErrQRCodeLinkNotFound + return nil, errVerificationKeyNotFound } // perform verification @@ -165,14 +167,18 @@ func (vs *VerificationService) Submit(ctx context.Context, verificationQueryID u return &response, nil } -func getAuthRequestOffChain(req *domain.VerificationQuery, serverURL string) (protocol.AuthorizationRequestMessage, error) { +func (s *VerificationService) key(id uuid.UUID) string { + return "issuer-node:qr-code:" + id.String() +} + +func (vs *VerificationService) getAuthRequestOffChain(req *domain.VerificationQuery, serverURL string) (protocol.AuthorizationRequestMessage, error) { id := uuid.NewString() - authReq := auth.CreateAuthorizationRequest(getReason(nil), req.IssuerDID, getUri(serverURL, req.IssuerDID, req.ID)) + authReq := auth.CreateAuthorizationRequest(vs.getReason(nil), req.IssuerDID, vs.getUri(serverURL, req.IssuerDID, req.ID)) authReq.ID = id authReq.ThreadID = id authReq.To = "" - var scopes []Scope + var scopes []scope if req.Scope.Status == pgtype.Present { err := json.Unmarshal(req.Scope.Bytes, &scopes) if err != nil { @@ -187,7 +193,7 @@ func getAuthRequestOffChain(req *domain.VerificationQuery, serverURL string) (pr Query: scope.Query, } if scope.Params != nil { - params, err := getParams(*scope.Params) + params, err := vs.getParams(*scope.Params) if err != nil { return protocol.AuthorizationRequestMessage{}, err } @@ -199,14 +205,14 @@ func getAuthRequestOffChain(req *domain.VerificationQuery, serverURL string) (pr return authReq, nil } -func getReason(reason *string) string { +func (vs *VerificationService) getReason(reason *string) string { if reason == nil { return "for testing purposes" } return *reason } -func getParams(params map[string]interface{}) (map[string]interface{}, error) { +func (vs *VerificationService) getParams(params map[string]interface{}) (map[string]interface{}, error) { val, ok := params["nullifierSessionID"] if !ok { return nil, errors.New("nullifierSessionID is empty") @@ -220,7 +226,7 @@ func getParams(params map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{"nullifierSessionId": nullifierSessionID.String()}, nil } -func getUri(serverURL string, issuerDID string, verificationQueryID uuid.UUID) string { +func (vs *VerificationService) getUri(serverURL string, issuerDID string, verificationQueryID uuid.UUID) string { path := fmt.Sprintf(`/v2/identities/%s/verification/callback?id=%s`, issuerDID, verificationQueryID) return fmt.Sprintf("%s%s", serverURL, path) } From f6c84198e61bdecbf7d225889c60d2a7e3b125d6 Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Thu, 19 Dec 2024 03:48:19 -0800 Subject: [PATCH 12/20] Fixes for Scope types --- api/api.yaml | 9 ++++--- internal/api/api.gen.go | 6 ++--- internal/core/ports/verification_service.go | 2 +- internal/core/services/verification.go | 29 ++++++++++++++------- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index db16e900a..44a133eb7 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -3282,9 +3282,12 @@ components: type: boolean example: false scopes: - type: object - additionalProperties: true - description: "Dynamic JSON object for scopes" + type: array + items: + type: object + additionalProperties: true + description: A dynamic JSON object representing a scope. + description: An array of dynamic JSON objects for scopes. CreateVerificationQueryResponse: type: object diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index d09202388..eb240fb62 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -389,9 +389,9 @@ type CreatePaymentRequestResponse struct { type CreateVerificationQueryRequest struct { ChainId int `json:"chain_id"` - // Scopes Dynamic JSON object for scopes - Scopes map[string]interface{} `json:"scopes"` - SkipRevocationCheck bool `json:"skip_revocation_check"` + // Scopes An array of dynamic JSON objects for scopes. + Scopes []map[string]interface{} `json:"scopes"` + SkipRevocationCheck bool `json:"skip_revocation_check"` } // CreateVerificationQueryResponse defines model for CreateVerificationQueryResponse. diff --git a/internal/core/ports/verification_service.go b/internal/core/ports/verification_service.go index 115b331fc..698f75192 100644 --- a/internal/core/ports/verification_service.go +++ b/internal/core/ports/verification_service.go @@ -11,7 +11,7 @@ import ( // VerificationService interface type VerificationService interface { - CreateVerificationQuery(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) + CreateVerificationQuery(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes []map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) GetVerificationStatus(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) } diff --git a/internal/core/services/verification.go b/internal/core/services/verification.go index 549df9d2a..31e421e00 100644 --- a/internal/core/services/verification.go +++ b/internal/core/services/verification.go @@ -25,11 +25,11 @@ import ( // Scope is a property of VerificationQuery, and it's required for the authRequest type scope struct { - CircuitId string `json:"circuitId"` - Id uint32 `json:"id"` - Params *map[string]interface{} `json:"params,omitempty"` - Query map[string]interface{} `json:"query"` - TransactionData *transactionData `json:"transactionData,omitempty"` + CircuitId string `json:"circuitId"` + Id uint32 `json:"id"` + Params json.RawMessage `json:"params,omitempty"` + Query json.RawMessage `json:"query"` + TransactionData *transactionData `json:"transactionData,omitempty"` } // TransactionData is a property of Scope, and it's required for the authRequest @@ -68,7 +68,7 @@ func NewVerificationService(networkResolver *network.Resolver, store cache.Cache } // CreateVerificationQuery creates and saves a new verification query in the database. -func (vs *VerificationService) CreateVerificationQuery(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) { +func (vs *VerificationService) CreateVerificationQuery(ctx context.Context, issuerID w3c.DID, chainID int, skipCheckRevocation bool, scopes []map[string]interface{}, serverURL string) (*domain.VerificationQuery, error) { var scopeJSON pgtype.JSONB err := scopeJSON.Set(scopes) if err != nil { @@ -127,7 +127,7 @@ func (vs *VerificationService) GetVerificationStatus(ctx context.Context, issuer func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, verificationQueryID uuid.UUID, issuerID w3c.DID, token string, serverURL string) (*domain.VerificationResponse, error) { // check cache for existing authRequest var authRequest protocol.AuthorizationRequestMessage - if found := vs.store.Get(ctx, verificationQueryID.String(), &authRequest); !found { + if found := vs.store.Get(ctx, vs.key(verificationQueryID), &authRequest); !found { log.Error(ctx, "authRequest not found in the cache", "id", verificationQueryID.String()) return nil, errVerificationKeyNotFound } @@ -187,13 +187,24 @@ func (vs *VerificationService) getAuthRequestOffChain(req *domain.VerificationQu } for _, scope := range scopes { + var query map[string]interface{} + err := json.Unmarshal(scope.Query, &query) + if err != nil { + return protocol.AuthorizationRequestMessage{}, err + } + mtpProofRequest := protocol.ZeroKnowledgeProofRequest{ ID: scope.Id, CircuitID: scope.CircuitId, - Query: scope.Query, + Query: query, } if scope.Params != nil { - params, err := vs.getParams(*scope.Params) + var paramsObj map[string]interface{} + err := json.Unmarshal(scope.Params, ¶msObj) + if err != nil { + return protocol.AuthorizationRequestMessage{}, err + } + params, err := vs.getParams(paramsObj) if err != nil { return protocol.AuthorizationRequestMessage{}, err } From 3a53cffe7a0bca462ffe189021e2988adece0f8a Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Thu, 19 Dec 2024 09:52:54 -0800 Subject: [PATCH 13/20] Use 500 default response for checkVerification --- internal/api/verification.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/verification.go b/internal/api/verification.go index 1069d50f1..097b7b43b 100644 --- a/internal/api/verification.go +++ b/internal/api/verification.go @@ -104,7 +104,7 @@ func (s *Server) CheckVerification(ctx context.Context, request CheckVerificatio } // Return 404 if neither response nor query exists - return CheckVerification404JSONResponse{N404JSONResponse{Message: "Verification query not found"}}, nil + return CheckVerification500JSONResponse{N500JSONResponse{Message: "Verification query not found"}}, nil } // SubmitVerificationResponse returns a VerificationResponse From 487786caafe4f1717fadbd1d7e449f6d51bba445 Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Thu, 19 Dec 2024 12:56:48 -0800 Subject: [PATCH 14/20] Adding circuits/verification_keys directory --- cmd/platform/main.go | 2 +- .../circuits/verification_keys/authV2.json | 104 ++++ .../credentialAtomicQuerySigV2.json | 474 ++++++++++++++++++ 3 files changed, 579 insertions(+), 1 deletion(-) create mode 100644 pkg/credentials/circuits/verification_keys/authV2.json create mode 100644 pkg/credentials/circuits/verification_keys/credentialAtomicQuerySigV2.json diff --git a/cmd/platform/main.go b/cmd/platform/main.go index 4a76f1f1f..79a0b2dd2 100644 --- a/cmd/platform/main.go +++ b/cmd/platform/main.go @@ -142,7 +142,7 @@ func main() { return } - verificationKeyLoader := &authLoaders.FSKeyLoader{Dir: cfg.Circuit.Path + "/authV2"} + verificationKeyLoader := &authLoaders.FSKeyLoader{Dir: cfg.Circuit.Path + "/verification_keys"} verifier, err := auth.NewVerifier(verificationKeyLoader, networkResolver.GetStateResolvers(), auth.WithDIDResolver(universalDIDResolverHandler)) if err != nil { log.Error(ctx, "failed init verifier", "err", err) diff --git a/pkg/credentials/circuits/verification_keys/authV2.json b/pkg/credentials/circuits/verification_keys/authV2.json new file mode 100644 index 000000000..b229c431b --- /dev/null +++ b/pkg/credentials/circuits/verification_keys/authV2.json @@ -0,0 +1,104 @@ +{ + "protocol": "groth16", + "curve": "bn128", + "nPublic": 3, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "15934125614912710821614323121670433574627734468332981610453472911976383177228", + "13386788725021602198567425385006899728203544659933593917276469726154154017730" + ], + [ + "8759505107016263108323717548646403750748432711908544803866765373342463765424", + "13205305607413475134301212820100793870092003365382735436692046794406857938024" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "12385314984359904314257455036963499193805822249900169493212773820637861017270", + "13455871848617958073752171682190449799364399689372987044617812281838570851280", + "1" + ], + [ + "1493564767784757620464057507283285365409721187164502463730502309417194080296", + "6377944811748764752279954590131952700069491229367911408873461121555475171995", + "1" + ], + [ + "17810471156883173964067651564103955395454521925125801510057769541384109536787", + "5548963437503981062668882632052452068705295424483999545932010198708798592260", + "1" + ], + [ + "13853274336731202523728826661915506795333516652854674163618978302237601632434", + "15420320918214290109713867361085955935385737854012308761626909938871786338011", + "1" + ] + ] +} \ No newline at end of file diff --git a/pkg/credentials/circuits/verification_keys/credentialAtomicQuerySigV2.json b/pkg/credentials/circuits/verification_keys/credentialAtomicQuerySigV2.json new file mode 100644 index 000000000..65fc8b5aa --- /dev/null +++ b/pkg/credentials/circuits/verification_keys/credentialAtomicQuerySigV2.json @@ -0,0 +1,474 @@ +{ + "protocol": "groth16", + "curve": "bn128", + "nPublic": 77, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "94086484245612286241184073311986077918425211631674599706523220655149342939", + "9066276963657894725757757487225537372173116727614372653696187043001221518998" + ], + [ + "3327364283265612114403912214795791325281389316262923243431510926287241284864", + "14359594691603603310505775002171568388983231776478551485379790891637661560036" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "82858741371846418102390975654111682337499684659569272071403747441178775094", + "4286912121744489803335872688967541084060786471335097977275283322008748340788", + "1" + ], + [ + "2949292283195261203307481545407020289663288337904117332935641624369320632240", + "4895868961372318516427313051068589418900672425971040539274101100343980810774", + "1" + ], + [ + "4283138832985371893627053916321257875901863688069679108552360851257151597412", + "12301445626126932387280175924125479080740113735715055950924670609729124190207", + "1" + ], + [ + "11403809675918608249249090476998815481360885938257614779638377601415472359725", + "10370383889744288167730128292551983273910856935841190320403487139321945926331", + "1" + ], + [ + "146046474957242547231947902105279535658945942556306365105970828598652582527", + "19316391077135032736180617854925909709408388622932141258157985352356274532963", + "1" + ], + [ + "19392288121313736624150032390760791125928224365133434019677559994823666881089", + "16678649776488360981633823784602747916355420844155287963893744273416034228821", + "1" + ], + [ + "17700244988293823646570920639274788254338288455367989082935279790186368535035", + "9321729693794775981089306312375631163270588069455137620052333218103779355408", + "1" + ], + [ + "6304242739428065891345661929109377623420995931327428546798256748327063014748", + "10780954993779202198998690148500166760839127869483280185479097694857479543720", + "1" + ], + [ + "15890615372155272172271387761362992284949685750813721764642195772457152019165", + "16926364370539068089248210833120023205713972276708964692683476649740531300907", + "1" + ], + [ + "8982718515991384638670171335434300220939254007691698916481977516501727365353", + "19933274338973273645600209826788464024915126306902861441259660444347103717011", + "1" + ], + [ + "13624813441697027269419893255557978801536200641544726966141640956028191608323", + "14568380736652140809608442081925020674177705472939839035342120687477258992462", + "1" + ], + [ + "7287001243757652147041978755465708957323122739823909998863775376965394099650", + "8194529377789992862935644002017654759465911480466382895596098893998234618850", + "1" + ], + [ + "15672609491810685215081462552663762421452118634631233092085621737595234267652", + "2904325643405367825992406778236164206511504644734317609359066533215328475457", + "1" + ], + [ + "915419396354435358079156433856200252628133349566107753168383098995762031654", + "7640503160883357453422046063266912192061137337928989362670501928245645451659", + "1" + ], + [ + "14233801682464959754511413505122573904411742927995284652060293502655728201014", + "16894634587173766457107177463154201142416235583146961458016001995514241600797", + "1" + ], + [ + "865637081385889540821011982382409477264012478881380726208863605069542609927", + "2851681816679478967076297609298375717665401236464133109800669778140378240569", + "1" + ], + [ + "14583623854715049637997176036159291974860261686624791798735868439140771963929", + "6184396208904580798818208852802256520753949645935054330532118256917410168729", + "1" + ], + [ + "7340832411020963493191281149327227244123195086114916154649774645956361514808", + "15074654768397897175094344665304877372038748718786237401937087336643412854581", + "1" + ], + [ + "19226322861612796019283465663964339481922670365980090908059633666493003526920", + "4784769513565224451763088600366892575717723263716353119873721504223139128187", + "1" + ], + [ + "4012352361946387705819729346828208464133226668583289635600033112445603056095", + "16057116501864906353475966636435162596350251241825748835463929049616401925621", + "1" + ], + [ + "17699015327677133433762523603786186670660923725134988336625372092121404781352", + "20779678450565816831584776337126804401686084032081969015904070067714797666579", + "1" + ], + [ + "7110239219642171000502622847102158151023059843711231146792904825853085995446", + "13160112334708882478456144620242292286930388110897910022916805337362636596223", + "1" + ], + [ + "18572966839469907925154251914990082086889869375548359485092736092666901883458", + "2717317841156606824568938974067202962925196229862100442399526546853487033983", + "1" + ], + [ + "18220496879414149572832879223762015215122990853285228408008689887370853115800", + "20936442965528445732757109001578889091199378707564445483449611217812774527300", + "1" + ], + [ + "12232077851023924549383437896602730279522249237260024995597446142400396114435", + "6563047663035805798395574221466154696714942622556424013116465603874844109521", + "1" + ], + [ + "12109591576870421145669126698637125260126292886167173093152618373092533930060", + "4838150475568627509687345029028244688244087639289765225238388542584247749631", + "1" + ], + [ + "11686953332615374166413400118993933874772588790879032818717674616364110119003", + "997777557714243682580030314729391031075903187098529888297775331753993942129", + "1" + ], + [ + "17856019272291166215824899590072473034447333075695359997293931405627546425641", + "20847076087560230989541971055803462020430493691416327989998676442287723694850", + "1" + ], + [ + "3719233446259090602031823947643646288707755415354997947878759420203354178997", + "8365343103859542659965068947238411148203250330874262347086737251125778319916", + "1" + ], + [ + "11510913293380810758607591029811272398780473934028209478267236534219159061926", + "21487940007144748529535209675246355785366971140524949831759963463815763342086", + "1" + ], + [ + "1320866581863043338512589290197401952944129485739535713341560521800016476945", + "2493558738654724256478579784099858256156921863307444062868905210334526715644", + "1" + ], + [ + "17921519492985568040647785518984679305231500205599581370398502174410641627915", + "7509747881493316986520702491460493363464636273816319210975041613994864176359", + "1" + ], + [ + "3430712548343353484542829031470422149014035558383953986112467142255149482517", + "14550495557052814428641743686592474039195116676265817238933308829823014113648", + "1" + ], + [ + "15404982231804436812797545928371130848106957647851884888070752463417657014850", + "4611196330294175143659018144962441564350289800068036864557333922145119754928", + "1" + ], + [ + "15263701692315698223820596911784671235943493678301007311780033091137667408294", + "604902718763398072835765087427553474161374630152901938069702903322739720901", + "1" + ], + [ + "20244489449718281224771972382454688977944613519370226595741543942193818865707", + "19005325000779061572105038900407210855167193186325179384744370456674531846752", + "1" + ], + [ + "17905734409676470389691612532757732246970469909527519932162818478447075527708", + "14786147027096263915511297996292826847659087062694857723516642286999030404099", + "1" + ], + [ + "13170413525057177027118218806689559566136892856728098698757124284101508780041", + "21138656039297587744247525334341823578219773225144796878223492790449265984236", + "1" + ], + [ + "17336985422666489495917434298152259228601087998778158909593038947037047437034", + "17640592272124615371604215593775167886611551365178888179149933534238084536035", + "1" + ], + [ + "13115497681355919008843671294553575734811415225866638680245661303800809177454", + "11101396909544211706087319187611419845994320778819031170997345351820871301500", + "1" + ], + [ + "5062223967757742841418833629304894504824711871486969640422734949713212037564", + "5995745391558201393058938510743066155455293405187491263678494062578649392537", + "1" + ], + [ + "10769448662035944503588756967807107248531839233480897019708757162346507533856", + "17683311668907780400377940051769789887732541265829205574046296993672760170234", + "1" + ], + [ + "18909222506084760520118400904387222253545995580483849260301107047056625024809", + "4739939423481558802886855387063931149245914588665635776355445541037716874191", + "1" + ], + [ + "237258354423629009139604512345017099458104648744590151045267949108836136046", + "8398477677610482726525801716151367352299183871166696265021289782422497361573", + "1" + ], + [ + "21614899156845209369731734601334455382216240765307343061569649838763541939674", + "19737392631718395415895070591908882100913238716433370918711613904255554425863", + "1" + ], + [ + "11583018052272400568802079643913527066888050377899679393339799714495380661286", + "18731342198059952174455684263813783627914005022231316888089150078895916324047", + "1" + ], + [ + "1800355436595083773914109207841543211385162586213879300974211146987134123202", + "13948211457427522096477007682338343714710607448417503368661334375180794438646", + "1" + ], + [ + "12380151495516055715423974838724751018509187196044814610299179374420946317150", + "15604417067169603747798399311766223987091027902169069371143436781729299657321", + "1" + ], + [ + "15586796314575179431439068400563064768255110456857949233401991221744972388290", + "6869029658475871438252091552567541171158916510785162784566374960655529514541", + "1" + ], + [ + "9454259555969046405835114554840573800894460654638881697884905397762877591671", + "15649504506074833196199868121136622617218927420004842806076140919475722131828", + "1" + ], + [ + "10896479167947716665298047581223743449173173843441842077252741245744005867502", + "19466843112239886558867945418277977036987004293914397183376561940538353431523", + "1" + ], + [ + "21093729272413526398302027094496286062183285233963676469751711501691030254808", + "5616302988725848953380515455651462166436866513193168038457367334604897142643", + "1" + ], + [ + "9162526928276135317390642844528140151219934473125754385250619863694467527200", + "2704506477496217427068410968125378104897220198385366379780167188967641057115", + "1" + ], + [ + "2611703957607573998187347531547663551663457287415444888228409918854708173921", + "16503183711195222950599750913705688322318608987024302010766325034010085416942", + "1" + ], + [ + "11144191723062275260143093129552752703141874383079896857211309008338415817157", + "16781709265403451651508663495722707593571471440254748275969368360531033950981", + "1" + ], + [ + "16812964624232834362158259778644306304990898865384873094141859868591196349911", + "13922581405000464856568387775868339864861250228661168312914123793152293400444", + "1" + ], + [ + "7757513072166428505849658731723708923254564692050157984152317397804988815683", + "10596938418787218858764915380828895440118767426036644517687291298143168844310", + "1" + ], + [ + "776963606617916676505305527193906634173028085645966206654629945039284611047", + "10912266632936528293222094364131226803541316295737642239229481058897029295382", + "1" + ], + [ + "16373521723700098997684769703648529905132042031831109132237638798191456108024", + "11103461808206383275179890639579495748275557033587467729454195698739957928818", + "1" + ], + [ + "12824183511411995663633068443784430488868221252426812112737116307753322360649", + "16623259457237877334961209654722086328478084586497591770992246134732053248864", + "1" + ], + [ + "4440204567609318598244879167103963411704795015851735842148385354806120614776", + "439254538132916792300814825054431063060942088144912111780551400772555518726", + "1" + ], + [ + "12550163747027957708679416133057345303366416863124571412695635212042254660231", + "8088733548936346769418714493856651195585281586259855084725506021681259615995", + "1" + ], + [ + "601695970176955369889380617598670451586934521316364158397268786862817318324", + "8177591911722905772853175277543921812590123868553367351838538360889195534445", + "1" + ], + [ + "21420783745411266284334793892673128470907336626528172424997282710543407678562", + "815896668203600154924756799739505300427469299180041621997534085009087797462", + "1" + ], + [ + "5985108217900335996495740885137329434596043615956659372441003017431913541049", + "12400136587102116035870838109370183374536730415523630490626751552251898583723", + "1" + ], + [ + "19164864123547614924145477596845278488377820092261133057871046801697026504830", + "9058096638083409870599642989053208885699231157685473105338357045834902352657", + "1" + ], + [ + "5625208601612291266473410363212052500521534296123171244974430101397304128598", + "20032223272677310984797975395155189489741584704900355338539941675727215575834", + "1" + ], + [ + "19722119258760509259002736097441021306020824312273305874398252765749858625383", + "21476284183336273518938142350498253130780475874465538560639647841713873409967", + "1" + ], + [ + "16214582901276753122992657437576487460434296014671145602237720787074011098320", + "14638014587762532725758011227519785515588402732574136404578917712064567856285", + "1" + ], + [ + "21242009997783861714463438145352831066649780964520311892324662965942254722206", + "17000102442647549832409557151546317734552217997144722436132435910128720474199", + "1" + ], + [ + "3901540629460292660569097126645101425134163883949632616895230751480848213369", + "15462697656409662538566017032586328247938424512432827105504267029364730924622", + "1" + ], + [ + "13589690411326512053318122778970095131969288693012374077657080517788789407345", + "3256080065084142457753800788670092375263661907651111479712628200718368447047", + "1" + ], + [ + "80867775087231075346193177024922594793617447013874226020788299753946699495", + "18692751692376633351750143580480221941277275632746814828053833446777751678844", + "1" + ], + [ + "12208564838188569361021574820173956567515251256529477811689323941001064824558", + "14719028828276200987004700519998864320546393568625959778339171591168779343802", + "1" + ], + [ + "11487733918945328878091426687312208948265605735490434820651127086706421059773", + "12884027668625422693735810500338290593888443327897978424106732856151809328547", + "1" + ], + [ + "20632552305904865953323352410960265689821616276406781145263037394521803318607", + "2807465385289781642965528502072388424070031841212619812619243814164168949956", + "1" + ], + [ + "15762698488851251645720011145875054676705544433397646684075263620983096891945", + "1298669502852138604153414592011225832117552495360099510102598347062068301160", + "1" + ], + [ + "11129488032579072454261600806944244717606891316794143410356402368489165589130", + "17038312956321424279807397639106394935887287800511404015727988253317547071041", + "1" + ] + ] +} \ No newline at end of file From 793286c192925f75637a2286f3af90bb95466f65 Mon Sep 17 00:00:00 2001 From: martinsaporiti Date: Mon, 23 Dec 2024 14:58:25 -0300 Subject: [PATCH 15/20] fix: path --- internal/api/main_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/api/main_test.go b/internal/api/main_test.go index ef0b0d1de..8edc87776 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -125,6 +125,7 @@ func TestMain(m *testing.M) { cfg.ServerUrl = "https://testing.env" cfg.Ethereum = cfgForTesting.Ethereum cfg.UniversalLinks = config.UniversalLinks{BaseUrl: "https://testing.env"} + cfg.Circuit.Path = "../../pkg/credentials/circuits" schemaLoader = loader.NewDocumentLoader(ipfsGatewayURL, false) m.Run() } From d5dd46b7e7224db01461404d6a225b5e3d5f799d Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Thu, 2 Jan 2025 10:01:02 -0500 Subject: [PATCH 16/20] Add api tests and various fixes to get them passing --- api/api.yaml | 2 + internal/api/api.gen.go | 9 + internal/api/verification.go | 13 +- internal/api/verification_test.go | 217 ++++++++++++++++++ internal/core/domain/verification.go | 4 +- internal/core/services/verification.go | 21 +- internal/repositories/fixture.go | 2 + internal/repositories/verification.go | 28 ++- internal/repositories/verification_test.go | 22 +- .../repositories/verification_test_fixture.go | 27 +++ 10 files changed, 312 insertions(+), 33 deletions(-) create mode 100644 internal/api/verification_test.go create mode 100644 internal/repositories/verification_test_fixture.go diff --git a/api/api.yaml b/api/api.yaml index 44a133eb7..f27bbce8d 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -2087,6 +2087,8 @@ paths: $ref: '#/components/schemas/VerificationResponseStatus' '400': $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' '500': $ref: '#/components/responses/500' diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index eb240fb62..fb10b8c4d 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -7103,6 +7103,15 @@ func (response SubmitVerificationResponse400JSONResponse) VisitSubmitVerificatio return json.NewEncoder(w).Encode(response) } +type SubmitVerificationResponse404JSONResponse struct{ N404JSONResponse } + +func (response SubmitVerificationResponse404JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + type SubmitVerificationResponse500JSONResponse struct{ N500JSONResponse } func (response SubmitVerificationResponse500JSONResponse) VisitSubmitVerificationResponseResponse(w http.ResponseWriter) error { diff --git a/internal/api/verification.go b/internal/api/verification.go index 097b7b43b..4dd224f08 100644 --- a/internal/api/verification.go +++ b/internal/api/verification.go @@ -43,8 +43,15 @@ func (s *Server) CheckVerification(ctx context.Context, request CheckVerificatio // Use the VerificationService to check for existing response or query response, query, err := s.verificationService.GetVerificationStatus(ctx, *issuerDID, verificationQueryID) if err != nil { - log.Error(ctx, "checking verification", "err", err, "issuerDID", issuerDID, "id", verificationQueryID) - return CheckVerification500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + //if error is not found, return 404 + if err.Error() == "verification query not found" { + log.Error(ctx, "checking verification, not found", "err", err, "issuerDID", issuerDID, "id", verificationQueryID) + return CheckVerification404JSONResponse{N404JSONResponse{Message: "Verification query not found"}}, nil + } else { + log.Error(ctx, "checking verification", "err", err, "issuerDID", issuerDID, "id", verificationQueryID) + return CheckVerification500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil + } + } // Check if a response exists @@ -104,7 +111,7 @@ func (s *Server) CheckVerification(ctx context.Context, request CheckVerificatio } // Return 404 if neither response nor query exists - return CheckVerification500JSONResponse{N500JSONResponse{Message: "Verification query not found"}}, nil + return CheckVerification404JSONResponse{N404JSONResponse{Message: "Verification query not found"}}, nil } // SubmitVerificationResponse returns a VerificationResponse diff --git a/internal/api/verification_test.go b/internal/api/verification_test.go new file mode 100644 index 000000000..d5f61b368 --- /dev/null +++ b/internal/api/verification_test.go @@ -0,0 +1,217 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/google/uuid" + "github.com/jackc/pgtype" + + core "github.com/iden3/go-iden3-core/v2" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/polygonid/sh-id-platform/internal/common" + "github.com/polygonid/sh-id-platform/internal/core/domain" + "github.com/polygonid/sh-id-platform/internal/db/tests" + "github.com/polygonid/sh-id-platform/internal/repositories" +) + +func TestServer_CreateVerification(t *testing.T) { + server := newTestServer(t, nil) + handler := getHandler(context.Background(), server) + + type expected struct { + httpCode int + message *string + } + type testConfig struct { + name string + auth func() (string, string) + identifier string + body CreateVerificationQueryRequest + expected expected + } + + idFromString, err := core.IDFromString("x2Uw18ATvY7mEsgfrrDipBmQQdPWAao4NmF56wGvp") + require.NoError(t, err) + + did, err := core.ParseDIDFromID(idFromString) + require.NoError(t, err) + + identity := &domain.Identity{ + Identifier: did.String(), + } + fixture := repositories.NewFixture(storage) + fixture.CreateIdentity(t, identity) + + for _, tc := range []testConfig{ + { + name: "Valid request", + auth: authOk, + identifier: did.String(), + body: CreateVerificationQueryRequest{ + ChainId: 137, + SkipRevocationCheck: false, + Scopes: []map[string]interface{}{ + { + "circuitId": "credentialAtomicQuerySigV2", + "id": 1, + "params": map[string]interface{}{ + "nullifierSessionID": "123456789", + }, + "query": nil, + }, + }, + }, + expected: expected{ + httpCode: http.StatusCreated, + }, + }, + { + name: "Invalid identifier", + auth: authOk, + identifier: "invalid-identifier", + expected: expected{ + httpCode: http.StatusBadRequest, + message: common.ToPointer("invalid issuer did"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/verification", tc.identifier) + req, err := http.NewRequest("POST", url, tests.JSONBody(t, tc.body)) + req.SetBasicAuth(tc.auth()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + + require.Equal(t, tc.expected.httpCode, rr.Code) + if tc.expected.httpCode == http.StatusCreated { + var response CreateVerificationQueryResponse + assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.NotNil(t, response.VerificationQueryId) + } else if tc.expected.httpCode == http.StatusBadRequest { + var response CreateVerification400JSONResponse + assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + assert.Equal(t, *tc.expected.message, response.Message) + } + }) + } +} + +func TestServer_CheckVerification(t *testing.T) { + server := newTestServer(t, nil) + handler := getHandler(context.Background(), server) + + idFromString, err := core.IDFromString("x2Uw18ATvY7mEsgfrrDipBmQQdPWAao4NmF56wGvp") + require.NoError(t, err) + + did, err := core.ParseDIDFromID(idFromString) + require.NoError(t, err) + + // Mock data setup + queryWithoutResponse := domain.VerificationQuery{ + ID: uuid.New(), + IssuerDID: did.String(), + ChainID: 137, + SkipCheckRevocation: false, + // CreatedAt: time.Now(), + } + queryWithResponse := domain.VerificationQuery{ + ID: uuid.New(), + IssuerDID: did.String(), + ChainID: 137, + SkipCheckRevocation: false, + // CreatedAt: time.Now(), + } + + responseJson := pgtype.JSONB{ + Bytes: []byte(`{"foo":"bar"}`), + Status: pgtype.Present, + } + + response := domain.VerificationResponse{ + ID: uuid.New(), + VerificationQueryID: queryWithResponse.ID, + UserDID: did.String(), + Response: &responseJson, + Pass: true, + // CreatedAt: time.Now(), + } + + fixture := repositories.NewFixture(storage) + fixture.CreateVerificationQuery(t, *did, queryWithoutResponse) + fixture.CreateVerificationQuery(t, *did, queryWithResponse) + fixture.CreateVerificationResponse(t, queryWithResponse.ID, response) + + type expected struct { + httpCode int + responseType string + } + type testConfig struct { + name string + auth func() (string, string) + identifier string + queryID string + expected expected + } + + for _, tc := range []testConfig{ + { + name: "Verification response exists", + auth: authOk, + identifier: did.String(), + queryID: queryWithResponse.ID.String(), + expected: expected{ + httpCode: http.StatusOK, + responseType: "VerificationResponse", + }, + }, + { + name: "Verification query exists without response", + auth: authOk, + identifier: did.String(), + queryID: queryWithoutResponse.ID.String(), + expected: expected{ + httpCode: http.StatusOK, + responseType: "VerificationQueryRequest", + }, + }, + { + name: "Query not found", + auth: authOk, + identifier: did.String(), + queryID: uuid.New().String(), + expected: expected{ + httpCode: http.StatusNotFound, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rr := httptest.NewRecorder() + url := fmt.Sprintf("/v2/identities/%s/verification?id=%s", tc.identifier, tc.queryID) + req, err := http.NewRequest("GET", url, nil) + req.SetBasicAuth(tc.auth()) + require.NoError(t, err) + handler.ServeHTTP(rr, req) + require.Equal(t, tc.expected.httpCode, rr.Code) + if tc.expected.httpCode == http.StatusOK { + var response CheckVerificationResponse + assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &response)) + if tc.expected.responseType == "VerificationResponse" { + assert.NotNil(t, response.VerificationResponse) + assert.Nil(t, response.VerificationQueryRequest) + } else if tc.expected.responseType == "VerificationQueryRequest" { + assert.NotNil(t, response.VerificationQueryRequest) + assert.Nil(t, response.VerificationResponse) + } + } + }) + } +} diff --git a/internal/core/domain/verification.go b/internal/core/domain/verification.go index c7e08ab8e..ba883e179 100644 --- a/internal/core/domain/verification.go +++ b/internal/core/domain/verification.go @@ -13,7 +13,7 @@ type VerificationQuery struct { IssuerDID string ChainID int SkipCheckRevocation bool - Scope pgtype.JSONB `json:"scopes"` + Scope *pgtype.JSONB `json:"scopes"` CreatedAt time.Time } @@ -22,7 +22,7 @@ type VerificationResponse struct { ID uuid.UUID VerificationQueryID uuid.UUID UserDID string - Response pgtype.JSONB `json:"response"` + Response *pgtype.JSONB `json:"response"` Pass bool CreatedAt time.Time } diff --git a/internal/core/services/verification.go b/internal/core/services/verification.go index 31e421e00..c66659bf6 100644 --- a/internal/core/services/verification.go +++ b/internal/core/services/verification.go @@ -25,11 +25,11 @@ import ( // Scope is a property of VerificationQuery, and it's required for the authRequest type scope struct { - CircuitId string `json:"circuitId"` - Id uint32 `json:"id"` - Params json.RawMessage `json:"params,omitempty"` - Query json.RawMessage `json:"query"` - TransactionData *transactionData `json:"transactionData,omitempty"` + CircuitId string `json:"circuitId"` + Id uint32 `json:"id"` + Params json.RawMessage `json:"params,omitempty"` + Query json.RawMessage `json:"query"` + // TransactionData *transactionData `json:"transactionData,omitempty"` } // TransactionData is a property of Scope, and it's required for the authRequest @@ -80,7 +80,7 @@ func (vs *VerificationService) CreateVerificationQuery(ctx context.Context, issu IssuerDID: issuerID.String(), ChainID: chainID, SkipCheckRevocation: skipCheckRevocation, - Scope: scopeJSON, + Scope: &scopeJSON, CreatedAt: time.Now(), } @@ -109,8 +109,8 @@ func (vs *VerificationService) CreateVerificationQuery(ctx context.Context, issu func (vs *VerificationService) GetVerificationStatus(ctx context.Context, issuerID w3c.DID, verificationQueryID uuid.UUID) (*domain.VerificationResponse, *domain.VerificationQuery, error) { query, err := vs.repo.Get(ctx, issuerID, verificationQueryID) if err != nil { - if err == repositories.VerificationQueryNotFoundError { - return nil, nil, fmt.Errorf("verification query not found: %w", err) + if err == repositories.ErrVerificationQueryNotFound { + return nil, nil, err } return nil, nil, fmt.Errorf("failed to get verification query: %w", err) } @@ -152,7 +152,7 @@ func (vs *VerificationService) SubmitVerificationResponse(ctx context.Context, v ID: uuid.New(), // Generate a new UUID for the response VerificationQueryID: verificationQueryID, UserDID: authRespMsg.From, - Response: jsonbResponse, + Response: &jsonbResponse, Pass: true, // Assuming this field is present to indicate verification success CreatedAt: time.Now(), } @@ -182,7 +182,8 @@ func (vs *VerificationService) getAuthRequestOffChain(req *domain.VerificationQu if req.Scope.Status == pgtype.Present { err := json.Unmarshal(req.Scope.Bytes, &scopes) if err != nil { - return protocol.AuthorizationRequestMessage{}, err + log.Error(context.Background(), "failed to unmarshal scope", "error", err) + return protocol.AuthorizationRequestMessage{}, fmt.Errorf("failed to unmarshal scope: %w", err) } } diff --git a/internal/repositories/fixture.go b/internal/repositories/fixture.go index 0a631d836..197c033c1 100644 --- a/internal/repositories/fixture.go +++ b/internal/repositories/fixture.go @@ -19,6 +19,7 @@ type Fixture struct { schemaRepository ports.SchemaRepository identityStateRepository ports.IdentityStateRepository paymentRepository ports.PaymentRepository + verificationRepository ports.VerificationRepository } // NewFixture - constructor @@ -31,6 +32,7 @@ func NewFixture(storage *db.Storage) *Fixture { schemaRepository: NewSchema(*storage), identityStateRepository: NewIdentityState(), paymentRepository: NewPayment(*storage), + verificationRepository: NewVerification(*storage), } } diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go index 8914cd358..4b312b30f 100644 --- a/internal/repositories/verification.go +++ b/internal/repositories/verification.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "github.com/iden3/go-iden3-core/v2/w3c" "github.com/jackc/pgconn" + "github.com/jackc/pgtype" "github.com/polygonid/sh-id-platform/internal/core/domain" "github.com/polygonid/sh-id-platform/internal/db" @@ -16,10 +17,10 @@ import ( const foreignKeyViolationErrorCode = "23503" var ( - // VerificationQueryNotFoundError is returned when a verification query is not found - VerificationQueryNotFoundError = errors.New("verification query not found") - // VerificationResponseNotFoundError is returned when a verification query is not found - VerificationResponseNotFoundError = errors.New("verification response not found") + // ErrVerificationQueryNotFound is returned when a verification query is not found + ErrVerificationQueryNotFound = errors.New("verification query not found") + // ErrVerificationResponseNotFound is returned when a verification query is not found + ErrVerificationResponseNotFound = errors.New("verification response not found") ) // VerificationRepository is a repository for verification queries @@ -39,6 +40,12 @@ func (r *VerificationRepository) Save(ctx context.Context, issuerID w3c.DID, que UPDATE SET issuer_id=$2, chain_id=$3, scope=$4, skip_check_revocation=$5 RETURNING id` + if query.Scope == nil { + query.Scope = &pgtype.JSONB{ + Status: pgtype.Null, + } + } + var queryID uuid.UUID if err := r.conn.Pgx.QueryRow(ctx, sql, query.ID, issuerID.String(), query.ChainID, query.Scope, query.SkipCheckRevocation).Scan(&queryID); err != nil { return uuid.Nil, err @@ -56,10 +63,17 @@ func (r *VerificationRepository) Get(ctx context.Context, issuerID w3c.DID, id u err := r.conn.Pgx.QueryRow(ctx, sql, issuerID.String(), id).Scan(&query.ID, &query.IssuerDID, &query.ChainID, &query.Scope, &query.SkipCheckRevocation, &query.CreatedAt) if err != nil { if strings.Contains(err.Error(), "no rows in result set") { - return nil, VerificationQueryNotFoundError + return nil, ErrVerificationQueryNotFound } return nil, err } + + if query.Scope == nil { + query.Scope = &pgtype.JSONB{ + Status: pgtype.Null, + } + } + return &query, nil } @@ -99,7 +113,7 @@ func (r *VerificationRepository) AddResponse(ctx context.Context, queryID uuid.U if err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == foreignKeyViolationErrorCode { - return uuid.Nil, VerificationQueryNotFoundError + return uuid.Nil, ErrVerificationQueryNotFound } return uuid.Nil, err } @@ -123,7 +137,7 @@ func (r *VerificationRepository) GetVerificationResponse(ctx context.Context, qu ) if err != nil { if strings.Contains(err.Error(), "no rows in result set") { - return nil, VerificationResponseNotFoundError + return nil, ErrVerificationResponseNotFound } return nil, err } diff --git a/internal/repositories/verification_test.go b/internal/repositories/verification_test.go index 318b0c2ed..d036368e6 100644 --- a/internal/repositories/verification_test.go +++ b/internal/repositories/verification_test.go @@ -34,7 +34,7 @@ func TestSaveVerificationQuery(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: scope, + Scope: &scope, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -63,7 +63,7 @@ func TestGetVerification(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: scope, + Scope: nil, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -91,7 +91,7 @@ func TestGetVerification(t *testing.T) { t.Run("should not get the verification", func(t *testing.T) { verificationQueryFromDB, err := verificationRepository.Get(ctx, *did, uuid.New()) require.Error(t, err) - require.Equal(t, VerificationQueryNotFoundError, err) + require.Equal(t, ErrVerificationQueryNotFound, err) assert.Nil(t, verificationQueryFromDB) }) } @@ -125,14 +125,14 @@ func TestUpdateVerificationQuery(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: scope, + Scope: nil, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) require.NoError(t, err) assert.Equal(t, verificationQuery.ID, verificationQueryID) - verificationQuery.Scope = scope2 + verificationQuery.Scope = &scope2 verificationQuery.SkipCheckRevocation = true verificationQuery.ChainID = 137 _, err = verificationRepository.Save(ctx, *did, verificationQuery) @@ -181,14 +181,14 @@ func TestGetAllVerification(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: scope, + Scope: nil, } verificationQuery2 := domain.VerificationQuery{ ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: scope2, + Scope: &scope2, } verificationQueryID1, err := verificationRepository.Save(ctx, *did, verificationQuery1) require.NoError(t, err) @@ -255,7 +255,7 @@ func TestAddVerification(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: scope, + Scope: nil, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -273,7 +273,7 @@ func TestAddVerification(t *testing.T) { ID: uuid.New(), VerificationQueryID: verificationQueryFromDB.ID, UserDID: "did:iden3:privado:main:2SizDYDWBViKXRfp1VgUAMqhz5SDvP7D1MYiPfwJV3", - Response: response, + Response: &response, } responseID, err := verificationRepository.AddResponse(ctx, verificationQueryFromDB.ID, verificationResponse) @@ -288,11 +288,11 @@ func TestAddVerification(t *testing.T) { verificationResponse := domain.VerificationResponse{ ID: uuid.New(), UserDID: "did:iden3:privado:main:2SizDYDWBViKXRfp1VgUAMqhz5SDvP7D1MYiPfwJV3", - Response: response, + Response: &response, } responseID, err := verificationRepository.AddResponse(ctx, uuid.New(), verificationResponse) require.Error(t, err) - require.True(t, errors.Is(err, VerificationQueryNotFoundError)) + require.True(t, errors.Is(err, ErrVerificationQueryNotFound)) assert.Equal(t, uuid.Nil, responseID) }) } diff --git a/internal/repositories/verification_test_fixture.go b/internal/repositories/verification_test_fixture.go new file mode 100644 index 000000000..c9c36ede3 --- /dev/null +++ b/internal/repositories/verification_test_fixture.go @@ -0,0 +1,27 @@ +package repositories + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/stretchr/testify/assert" + + "github.com/polygonid/sh-id-platform/internal/core/domain" +) + +// CreateVerificationQuery creates a new query +func (f *Fixture) CreateVerificationQuery(t *testing.T, issuerID w3c.DID, query domain.VerificationQuery) { + t.Helper() + _, err := f.verificationRepository.Save(context.Background(), issuerID, query) + assert.NoError(t, err, "Failed to create verification query") +} + +// CreateVerificationResponse creates a new response for a query +func (f *Fixture) CreateVerificationResponse(t *testing.T, queryID uuid.UUID, response domain.VerificationResponse) { + t.Helper() + responseID, err := f.verificationRepository.AddResponse(context.Background(), queryID, response) + assert.NoError(t, err, "Failed to create verification response") + assert.NotEmpty(t, responseID, "Response ID should not be empty") +} From a1ee6f3997bb713faedeec4d1b4429732554aa68 Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Thu, 2 Jan 2025 10:05:58 -0500 Subject: [PATCH 17/20] Linter fixes --- internal/api/verification.go | 3 +-- internal/api/verification_test.go | 4 +--- internal/core/services/verification.go | 15 +++------------ 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/internal/api/verification.go b/internal/api/verification.go index 4dd224f08..a1ed5061d 100644 --- a/internal/api/verification.go +++ b/internal/api/verification.go @@ -43,7 +43,7 @@ func (s *Server) CheckVerification(ctx context.Context, request CheckVerificatio // Use the VerificationService to check for existing response or query response, query, err := s.verificationService.GetVerificationStatus(ctx, *issuerDID, verificationQueryID) if err != nil { - //if error is not found, return 404 + // if error is not found, return 404 if err.Error() == "verification query not found" { log.Error(ctx, "checking verification, not found", "err", err, "issuerDID", issuerDID, "id", verificationQueryID) return CheckVerification404JSONResponse{N404JSONResponse{Message: "Verification query not found"}}, nil @@ -51,7 +51,6 @@ func (s *Server) CheckVerification(ctx context.Context, request CheckVerificatio log.Error(ctx, "checking verification", "err", err, "issuerDID", issuerDID, "id", verificationQueryID) return CheckVerification500JSONResponse{N500JSONResponse{Message: err.Error()}}, nil } - } // Check if a response exists diff --git a/internal/api/verification_test.go b/internal/api/verification_test.go index d5f61b368..69075b2c1 100644 --- a/internal/api/verification_test.go +++ b/internal/api/verification_test.go @@ -9,10 +9,8 @@ import ( "testing" "github.com/google/uuid" - "github.com/jackc/pgtype" - core "github.com/iden3/go-iden3-core/v2" - + "github.com/jackc/pgtype" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/core/services/verification.go b/internal/core/services/verification.go index c66659bf6..53aaee46e 100644 --- a/internal/core/services/verification.go +++ b/internal/core/services/verification.go @@ -29,15 +29,6 @@ type scope struct { Id uint32 `json:"id"` Params json.RawMessage `json:"params,omitempty"` Query json.RawMessage `json:"query"` - // TransactionData *transactionData `json:"transactionData,omitempty"` -} - -// TransactionData is a property of Scope, and it's required for the authRequest -type transactionData struct { - ChainID int `json:"chainID"` - ContractAddress string `json:"contractAddress"` - MethodID string `json:"methodID"` - Network string `json:"network"` } const ( @@ -91,7 +82,7 @@ func (vs *VerificationService) CreateVerificationQuery(ctx context.Context, issu verificationQuery.ID = queryID - authRequest, err := vs.getAuthRequestOffChain(&verificationQuery, serverURL) + authRequest, err := vs.getAuthRequestOffChain(ctx, &verificationQuery, serverURL) if err != nil { return nil, fmt.Errorf("failed to generate auth request: %w", err) } @@ -171,7 +162,7 @@ func (s *VerificationService) key(id uuid.UUID) string { return "issuer-node:qr-code:" + id.String() } -func (vs *VerificationService) getAuthRequestOffChain(req *domain.VerificationQuery, serverURL string) (protocol.AuthorizationRequestMessage, error) { +func (vs *VerificationService) getAuthRequestOffChain(ctx context.Context, req *domain.VerificationQuery, serverURL string) (protocol.AuthorizationRequestMessage, error) { id := uuid.NewString() authReq := auth.CreateAuthorizationRequest(vs.getReason(nil), req.IssuerDID, vs.getUri(serverURL, req.IssuerDID, req.ID)) authReq.ID = id @@ -182,7 +173,7 @@ func (vs *VerificationService) getAuthRequestOffChain(req *domain.VerificationQu if req.Scope.Status == pgtype.Present { err := json.Unmarshal(req.Scope.Bytes, &scopes) if err != nil { - log.Error(context.Background(), "failed to unmarshal scope", "error", err) + log.Error(ctx, "failed to unmarshal scope", "error", err) return protocol.AuthorizationRequestMessage{}, fmt.Errorf("failed to unmarshal scope: %w", err) } } From eb0b06c7a77a25c71a67c79763db7618fb7d0e70 Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Fri, 3 Jan 2025 10:24:11 -0500 Subject: [PATCH 18/20] Add test for new repo method and fix scope pointers --- internal/repositories/verification_test.go | 61 ++++++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/internal/repositories/verification_test.go b/internal/repositories/verification_test.go index d036368e6..487d58bdf 100644 --- a/internal/repositories/verification_test.go +++ b/internal/repositories/verification_test.go @@ -63,7 +63,7 @@ func TestGetVerification(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: nil, + Scope: &scope, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -125,7 +125,7 @@ func TestUpdateVerificationQuery(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: nil, + Scope: &scope, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -181,7 +181,7 @@ func TestGetAllVerification(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: nil, + Scope: &scope, } verificationQuery2 := domain.VerificationQuery{ @@ -255,7 +255,7 @@ func TestAddVerification(t *testing.T) { ID: uuid.New(), ChainID: 8002, SkipCheckRevocation: false, - Scope: nil, + Scope: &scope, } verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) @@ -296,3 +296,56 @@ func TestAddVerification(t *testing.T) { assert.Equal(t, uuid.Nil, responseID) }) } + +func TestGetVerificationResponse(t *testing.T) { + ctx := context.Background() + didStr := "did:iden3:polygon:amoy:xCd1tRmXnqbgiT3QC2CuDddUoHK4S9iXwq5xFDJGb" + verificationRepository := NewVerification(*storage) + + did, err := w3c.ParseDID(didStr) + require.NoError(t, err) + + t.Run("should get a response to verification", func(t *testing.T) { + credentialSubject := pgtype.JSONB{} + err = credentialSubject.Set(`{"birthday": {"$eq": 19791109}}`) + require.NoError(t, err) + + scope := pgtype.JSONB{} + err = scope.Set(`[{"ID": 1, "circuitID": "credentialAtomicQuerySigV2", "query": {"context": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", "allowedIssuers": ["*"], "type": "KYCAgeCredential", "credentialSubject": {"birthday": {"$eq": 19791109}}}}]`) + require.NoError(t, err) + verificationQuery := domain.VerificationQuery{ + ID: uuid.New(), + ChainID: 8002, + SkipCheckRevocation: false, + Scope: nil, + } + + verificationQueryID, err := verificationRepository.Save(ctx, *did, verificationQuery) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryID) + + verificationQueryFromDB, err := verificationRepository.Get(ctx, *did, verificationQueryID) + require.NoError(t, err) + assert.Equal(t, verificationQuery.ID, verificationQueryFromDB.ID) + + response := pgtype.JSONB{} + err = response.Set(`{"something": {"proof": 1}}`) + require.NoError(t, err) + verificationResponse := domain.VerificationResponse{ + ID: uuid.New(), + VerificationQueryID: verificationQueryFromDB.ID, + UserDID: "did:iden3:privado:main:2SizDYDWBVi", + Response: &response, + } + + responseID, err := verificationRepository.AddResponse(ctx, verificationQueryFromDB.ID, verificationResponse) + require.NoError(t, err) + assert.Equal(t, verificationResponse.ID, responseID) + + verificationResponseFromDB, err := verificationRepository.GetVerificationResponse(ctx, verificationQueryFromDB.ID) + require.NoError(t, err) + assert.Equal(t, verificationResponse.ID, verificationResponseFromDB.ID) + assert.Equal(t, verificationResponse.UserDID, verificationResponseFromDB.UserDID) + assert.NotNil(t, verificationResponseFromDB.Response) + }) +} From 30a15f7809139ae5133c2a09c60d434e3c4ac68d Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Tue, 7 Jan 2025 00:41:29 -0500 Subject: [PATCH 19/20] Removing duplicated error code const --- internal/repositories/verification.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/repositories/verification.go b/internal/repositories/verification.go index 4b312b30f..6b1e7d817 100644 --- a/internal/repositories/verification.go +++ b/internal/repositories/verification.go @@ -14,8 +14,6 @@ import ( "github.com/polygonid/sh-id-platform/internal/db" ) -const foreignKeyViolationErrorCode = "23503" - var ( // ErrVerificationQueryNotFound is returned when a verification query is not found ErrVerificationQueryNotFound = errors.New("verification query not found") From 19b2aeb04ed86b666e118d6ae71cec2b5c62245a Mon Sep 17 00:00:00 2001 From: Aldi Gjoka Date: Tue, 7 Jan 2025 00:51:04 -0500 Subject: [PATCH 20/20] Undo unnecessary mediaTypeManager param --- cmd/platform/main.go | 2 +- internal/api/main_test.go | 2 +- internal/api/server.go | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cmd/platform/main.go b/cmd/platform/main.go index 79a0b2dd2..893cc9f59 100644 --- a/cmd/platform/main.go +++ b/cmd/platform/main.go @@ -208,7 +208,7 @@ func main() { api.HandlerWithOptions( api.NewStrictHandlerWithOptions( - api.NewServer(cfg, identityService, accountService, connectionsService, claimsService, qrService, publisher, packageManager, *networkResolver, serverHealth, schemaService, linkService, displayMethodService, keyService, paymentService, verificationService, mediaTypeManager), + api.NewServer(cfg, identityService, accountService, connectionsService, claimsService, qrService, publisher, packageManager, *networkResolver, serverHealth, schemaService, linkService, displayMethodService, keyService, paymentService, verificationService), middlewares(ctx, cfg.HTTPBasicAuth), api.StrictHTTPServerOptions{ RequestErrorHandlerFunc: errors.RequestErrorHandlerFunc, diff --git a/internal/api/main_test.go b/internal/api/main_test.go index 8edc87776..f7c4f89e1 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -385,7 +385,7 @@ func newTestServer(t *testing.T, st *db.Storage) *testServer { verificationService := services.NewVerificationService(networkResolver, cachex, repos.verification, verifier) - server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService, displayMethodService, keyService, paymentService, verificationService, mediaTypeManager) + server := NewServer(&cfg, identityService, accountService, connectionService, claimsService, qrService, NewPublisherMock(), NewPackageManagerMock(), *networkResolver, nil, schemaService, linkService, displayMethodService, keyService, paymentService, verificationService) return &testServer{ Server: server, diff --git a/internal/api/server.go b/internal/api/server.go index 8518c1969..2b41321eb 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -33,11 +33,10 @@ type Server struct { displayMethodService ports.DisplayMethodService keyService ports.KeyService verificationService ports.VerificationService - mediaTypeManager ports.MediaTypeManager } // NewServer is a Server constructor -func NewServer(cfg *config.Configuration, identityService ports.IdentityService, accountService ports.AccountService, connectionsService ports.ConnectionService, claimsService ports.ClaimService, qrService ports.QrStoreService, publisherGateway ports.Publisher, packageManager *iden3comm.PackageManager, networkResolver network.Resolver, health *health.Status, schemaService ports.SchemaService, linkService ports.LinkService, displayMethodService ports.DisplayMethodService, keyService ports.KeyService, paymentService ports.PaymentService, verificationService ports.VerificationService, mediaTypeManager ports.MediaTypeManager) *Server { +func NewServer(cfg *config.Configuration, identityService ports.IdentityService, accountService ports.AccountService, connectionsService ports.ConnectionService, claimsService ports.ClaimService, qrService ports.QrStoreService, publisherGateway ports.Publisher, packageManager *iden3comm.PackageManager, networkResolver network.Resolver, health *health.Status, schemaService ports.SchemaService, linkService ports.LinkService, displayMethodService ports.DisplayMethodService, keyService ports.KeyService, paymentService ports.PaymentService, verificationService ports.VerificationService) *Server { return &Server{ cfg: cfg, accountService: accountService, @@ -55,7 +54,6 @@ func NewServer(cfg *config.Configuration, identityService ports.IdentityService, keyService: keyService, paymentService: paymentService, verificationService: verificationService, - mediaTypeManager: mediaTypeManager, } }