Skip to content

Commit

Permalink
Add verify_passport_v2 endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaptoss committed Jul 16, 2024
2 parents a5c43ed + ccfe03c commit 06d73aa
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs/spec/components/schemas/VerifyPassport.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ allOf:
format: types.ZKProof
description: |
Query ZK passport verification proof.
Required for endpoint `/v2/balances/{nullifier}/verifypassport`.
Required for endpoint `/v1/balances/{nullifier}/verifypassport`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
post:
tags:
- Points balance
summary: Verify passport V2
description: |
Verify passport with JWT (verified: true).
JWT with this parameter can be obtained from the auth v1 and v2 service.
One passport can't be verified twice.
operationId: verifyPassportV2
parameters:
- $ref: '#/components/parameters/pathNullifier'
- in: header
name: Signature
description: Signature of the request
required: true
schema:
type: string
pattern: '^[a-f0-9]{64}$'
requestBody:
required: true
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/VerifyPassport'
responses:
200:
description: Success
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/EventClaimingState'
400:
$ref: '#/components/responses/invalidParameter'
401:
$ref: '#/components/responses/invalidAuth'
404:
description: Balance not exists.
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
409:
description: Passport already verified or event absent for user.
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
500:
$ref: '#/components/responses/internalError'
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/google/jsonapi v1.0.0
github.com/iden3/go-rapidsnark/types v0.0.3
github.com/labstack/gommon v0.4.0
github.com/rarimo/geo-auth-svc v0.2.0
github.com/rarimo/geo-auth-svc v1.1.0
github.com/rarimo/saver-grpc-lib v1.0.0
github.com/rarimo/zkverifier-kit v1.1.1
github.com/rubenv/sql-migrate v1.6.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,8 @@ github.com/rarimo/cosmos-sdk v0.46.7 h1:jU2PiWzc+19SF02cXM0O0puKPeH1C6Q6t2lzJ9s1
github.com/rarimo/cosmos-sdk v0.46.7/go.mod h1:fqKqz39U5IlEFb4nbQ72951myztsDzFKKDtffYJ63nk=
github.com/rarimo/geo-auth-svc v0.2.0 h1:yQvcIBNx+Tc1jJdtpWDfyLc0HogU+okA08HEZ55wv5U=
github.com/rarimo/geo-auth-svc v0.2.0/go.mod h1:SB4bo1xHYDAsBaQGX2+FoEgD3xxqYmcgr4XTTjy4/OM=
github.com/rarimo/geo-auth-svc v1.1.0 h1:3k1tTWAjtCBsnzlMb3aB+xgsFLEPUSmB3woME+q6tfk=
github.com/rarimo/geo-auth-svc v1.1.0/go.mod h1:JrpCGdT0xtAcWIKgPhxPHf7QCW4h845BXuh6M7NdQFw=
github.com/rarimo/saver-grpc-lib v1.0.0 h1:MGUVjYg7unmodYczVsLqlqZNkT4CIgKqdo6aQtL1qdE=
github.com/rarimo/saver-grpc-lib v1.0.0/go.mod h1:DpugWK5B7Hi0bdC3MPe/9FD2zCxaRwsyykdwxtF1Zgg=
github.com/rarimo/zkverifier-kit v1.1.0-rc.1 h1:xtmrFEl7eLAE6mi7IQYOOMKFdwXC3gbe39fYQdvKVZg=
Expand Down
123 changes: 123 additions & 0 deletions internal/service/handlers/verify_passport_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package handlers

import (
"fmt"
"net/http"

validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/rarimo/geo-auth-svc/pkg/auth"
"github.com/rarimo/geo-points-svc/internal/data"
"github.com/rarimo/geo-points-svc/internal/data/evtypes/models"
"github.com/rarimo/geo-points-svc/internal/service/requests"
"gitlab.com/distributed_lab/ape"
"gitlab.com/distributed_lab/ape/problems"
)

func VerifyPassportV2(w http.ResponseWriter, r *http.Request) {
req, err := requests.NewVerifyPassportV2(r)
if err != nil {
Log(r).WithError(err).Debug("Bad request")
ape.RenderErr(w, problems.BadRequest(err)...)
return
}

var (
nullifier = req.Data.ID
anonymousID = req.Data.Attributes.AnonymousId
log = Log(r).WithFields(map[string]any{
"balance.nullifier": nullifier,
"balance.anonymous_id": anonymousID,
})

gotSig = r.Header.Get("Signature")
)

if !auth.Authenticates(UserClaims(r), auth.UserGrant(nullifier)) {
ape.RenderErr(w, problems.Unauthorized())
return
}

wantSig, err := SigCalculator(r).PassportVerificationSignature(req.Data.ID, anonymousID)
if err != nil { // must never happen due to preceding validation
Log(r).WithError(err).Error("Failed to calculate HMAC signature")
ape.RenderErr(w, problems.InternalError())
return
}

if gotSig != wantSig {
log.Warnf("Passport verification unauthorized access: HMAC signature mismatch: got %s, want %s", gotSig, wantSig)
ape.RenderErr(w, problems.Forbidden())
return
}

byNullifier, err := BalancesQ(r).FilterByNullifier(nullifier).FilterDisabled().Get()
if err != nil {
Log(r).WithError(err).Error("Failed to get balance by nullifier")
ape.RenderErr(w, problems.InternalError())
return
}

if byNullifier == nil {
Log(r).Debugf("Balance not found: nullifier=%s", nullifier)
ape.RenderErr(w, problems.NotFound())
return
}

if byNullifier.SharedHash != nil {
Log(r).Debugf("Already verified: nullifier=%s, AID=%s", nullifier, anonymousID)
ape.RenderErr(w, problems.Conflict())
return
}

byAnonymousID, err := BalancesQ(r).FilterByAnonymousID(anonymousID).Get()
if err != nil {
log.WithError(err).Error("Failed to get balance by anonymous ID")
ape.RenderErr(w, problems.InternalError())
return
}

if byAnonymousID != nil && byAnonymousID.Nullifier != byNullifier.Nullifier {
Log(r).Debugf("AnonymousID already used: nullifier=%s, AID=%s, AIDBalance=%+v", nullifier, anonymousID, byAnonymousID)
ape.RenderErr(w, problems.Conflict())
return
}

// UserClaims(r)[0] will not panic because of authorization validation
sharedHash := UserClaims(r)[0].SharedHash
if sharedHash == nil {
ape.RenderErr(w, problems.BadRequest(validation.Errors{
"shared_hash": fmt.Errorf("not provided in JWT"),
})...)
return
}

err = EventsQ(r).Transaction(func() error {
err = BalancesQ(r).FilterByNullifier(byNullifier.Nullifier).Update(map[string]any{
data.ColSharedHash: *sharedHash,
data.ColAnonymousID: anonymousID,
data.ColIsVerified: true,
})
if err != nil {
return fmt.Errorf("failed to update balance: %w", err)
}

return doPassportScanUpdates(r, *byNullifier, anonymousID, sharedHash)
})
if err != nil {
log.WithError(err).Error("Failed to execute transaction")
ape.RenderErr(w, problems.InternalError())
return
}

event, err := EventsQ(r).FilterByNullifier(byNullifier.Nullifier).
FilterByType(models.TypePassportScan).
FilterByStatus(data.EventClaimed).
Get()
if err != nil {
log.WithError(err).Error("Failed to get claimed event")
ape.RenderErr(w, problems.InternalError())
return
}

ape.Render(w, newEventClaimingStateResponse(req.Data.ID, event != nil))
}
30 changes: 30 additions & 0 deletions internal/service/requests/verify_passport_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package requests

import (
"encoding/json"
"net/http"
"strings"

"github.com/go-chi/chi"
val "github.com/go-ozzo/ozzo-validation/v4"
"github.com/rarimo/geo-points-svc/resources"
)

func NewVerifyPassportV2(r *http.Request) (req resources.VerifyPassportRequest, err error) {
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
return req, newDecodeError("body", err)
}

req.Data.ID = strings.ToLower(req.Data.ID)

return req, val.Errors{
"data/id": val.Validate(req.Data.ID,
val.Required,
val.In(strings.ToLower(chi.URLParam(r, "nullifier"))),
val.Match(nullifierRegexp)),
"data/type": val.Validate(req.Data.Type,
val.Required,
val.In(resources.VERIFY_PASSPORT)),
"data/attributes/anonymous_id": val.Validate(req.Data.Attributes.AnonymousId, val.Required, val.Match(hex32bRegexp)),
}.Filter()
}
1 change: 1 addition & 0 deletions internal/service/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func Run(ctx context.Context, cfg config.Config) {
r.Use(authMW)
r.Post("/", handlers.CreateBalanceV2)
r.Patch("/{nullifier}", handlers.ActivateBalance)
r.Post("/{nullifier}/verifypassport", handlers.VerifyPassportV2)
})

cfg.Log().Info("Service started")
Expand Down
2 changes: 1 addition & 1 deletion resources/model_verify_passport_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ import "github.com/iden3/go-rapidsnark/types"
type VerifyPassportAttributes struct {
// Unique identifier of the passport.
AnonymousId string `json:"anonymous_id"`
// Query ZK passport verification proof. Required for endpoint `/v2/balances/{nullifier}/verifypassport`.
// Query ZK passport verification proof. Required for endpoint `/v1/balances/{nullifier}/verifypassport`.
Proof *types.ZKProof `json:"proof,omitempty"`
}

0 comments on commit 06d73aa

Please sign in to comment.