diff --git a/server/api/services/transaction_service.go b/server/api/services/transaction_service.go index 04bd351..b250dc5 100644 --- a/server/api/services/transaction_service.go +++ b/server/api/services/transaction_service.go @@ -62,6 +62,17 @@ func (ts *transactionService) Initialize(userId string, transaction *models.Tran return nil, echo.NewHTTPError(http.StatusNotFound, "unable to find user") } + foundTransaction, err := ts.transactionPersister.GetByIdentifier(transaction.Identifier, ts.tenant.ID) + if err != nil { + ts.logger.Error(err) + return nil, echo.NewHTTPError(http.StatusInternalServerError, "unable to search for transaction") + } + + if foundTransaction != nil && len(*foundTransaction) > 0 { + ts.logger.Error("transaction already exists") + return nil, echo.NewHTTPError(http.StatusConflict, "transaction already exists") + } + // check for better error handling as BeginLogin can throw a BadRequestError AND normal errors (but same type) if len(webauthnUser.WebauthnCredentials) == 0 { return nil, echo.NewHTTPError( diff --git a/server/persistence/persisters/transaction_persister.go b/server/persistence/persisters/transaction_persister.go index 4fbbb96..3ce14ba 100644 --- a/server/persistence/persisters/transaction_persister.go +++ b/server/persistence/persisters/transaction_persister.go @@ -12,6 +12,7 @@ import ( type TransactionPersister interface { Create(transaction *models.Transaction) error + GetByIdentifier(identifier string, tenantID uuid.UUID) (*models.Transactions, error) ListByUserId(userId uuid.UUID, tenantId uuid.UUID) (*models.Transactions, error) GetByUserId(userId uuid.UUID, tenantId uuid.UUID) (*models.Transaction, error) GetByChallenge(challenge string, tenantId uuid.UUID) (*models.Transaction, error) @@ -65,6 +66,19 @@ func (p *transactionPersister) ListByUserId(userId uuid.UUID, tenantId uuid.UUID return &transactions, nil } +func (p *transactionPersister) GetByIdentifier(identifier string, tenantId uuid.UUID) (*models.Transactions, error) { + transactions := models.Transactions{} + err := p.database.Eager().Where("identifier = ? AND tenant_id = ?", identifier, tenantId).All(&transactions) + if err != nil && errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("failed to list transactions by user id: %w", err) + } + + return &transactions, nil +} + func (p *transactionPersister) GetByChallenge(challenge string, tenantId uuid.UUID) (*models.Transaction, error) { transaction := models.Transaction{} err := p.database.Eager().Where("challenge = ? AND tenant_id = ?", challenge, tenantId).First(&transaction) diff --git a/spec/passkey-server.yaml b/spec/passkey-server.yaml index 6a5a8b0..1ce9f9e 100644 --- a/spec/passkey-server.yaml +++ b/spec/passkey-server.yaml @@ -1,6 +1,6 @@ openapi: 3.1.0 info: - version: '1.1' + version: '1.2' title: passkey-server description: 'This API shall represent the private and public endpoints for passkey registration, management and authentication' termsOfService: 'https://www.hanko.io/terms' @@ -263,6 +263,8 @@ paths: $ref: '#/components/responses/error' '404': $ref: '#/components/responses/error' + '409': + $ref: '#/components/responses/error' '500': $ref: '#/components/responses/error' servers: @@ -578,6 +580,7 @@ components: transaction_id: type: string maxLength: 128 + description: Needs to be a tenant-wide unique identifier transaction_data: type: object required: