Skip to content

Commit

Permalink
Add error codes
Browse files Browse the repository at this point in the history
  • Loading branch information
DJAndries committed Nov 13, 2024
1 parent 303af72 commit 213eabc
Show file tree
Hide file tree
Showing 16 changed files with 151 additions and 91 deletions.
11 changes: 4 additions & 7 deletions controllers/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ import (
"github.com/go-playground/validator/v10"
)

var ErrEmailNotVerified = errors.New("email not verified")
var ErrIncorrectVerificationIntent = errors.New("incorrect verification intent")

type AccountsController struct {
opaqueService *services.OpaqueService
validate *validator.Validate
Expand Down Expand Up @@ -166,12 +163,12 @@ func (ac *AccountsController) Router(verificationMiddleware func(http.Handler) h

func checkVerificationStatusAndIntent(w http.ResponseWriter, r *http.Request, verification *datastore.Verification) bool {
if !verification.Verified {
util.RenderErrorResponse(w, r, http.StatusForbidden, ErrEmailNotVerified)
util.RenderErrorResponse(w, r, http.StatusForbidden, util.ErrEmailNotVerified)
return false
}

if verification.Intent != datastore.RegistrationIntent && verification.Intent != datastore.SetPasswordIntent {
util.RenderErrorResponse(w, r, http.StatusForbidden, ErrIncorrectVerificationIntent)
util.RenderErrorResponse(w, r, http.StatusForbidden, util.ErrIncorrectVerificationIntent)
return false
}
return true
Expand Down Expand Up @@ -277,9 +274,9 @@ func (ac *AccountsController) SetupPasswordFinalize(w http.ResponseWriter, r *ht
account, err := ac.opaqueService.SetupPasswordFinalize(verification.Email, opaqueRecord)
if err != nil {
switch {
case errors.Is(err, datastore.ErrRegistrationStateNotFound):
case errors.Is(err, util.ErrRegistrationStateNotFound):
util.RenderErrorResponse(w, r, http.StatusNotFound, err)
case errors.Is(err, datastore.ErrRegistrationStateExpired):
case errors.Is(err, util.ErrRegistrationStateExpired):
util.RenderErrorResponse(w, r, http.StatusBadRequest, err)
default:
util.RenderErrorResponse(w, r, http.StatusInternalServerError, err)
Expand Down
8 changes: 4 additions & 4 deletions controllers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func (ac *AuthController) LoginInit(w http.ResponseWriter, r *http.Request) {

ke2, akeState, err := ac.opaqueService.LoginInit(requestData.Email, opaqueReq)
if err != nil {
if errors.Is(err, services.ErrIncorrectCredentials) {
if errors.Is(err, util.ErrIncorrectCredentials) {
util.RenderErrorResponse(w, r, http.StatusUnauthorized, err)
return
}
Expand Down Expand Up @@ -324,9 +324,9 @@ func (ac *AuthController) LoginFinalize(w http.ResponseWriter, r *http.Request)

accountID, err := ac.opaqueService.LoginFinalize(akeStateID, opaqueReq)
if err != nil {
if errors.Is(err, services.ErrIncorrectCredentials) ||
errors.Is(err, datastore.ErrAKEStateNotFound) ||
errors.Is(err, datastore.ErrAKEStateExpired) {
if errors.Is(err, util.ErrIncorrectCredentials) ||
errors.Is(err, util.ErrAKEStateNotFound) ||
errors.Is(err, util.ErrAKEStateExpired) {
util.RenderErrorResponse(w, r, http.StatusUnauthorized, err)
return
}
Expand Down
2 changes: 1 addition & 1 deletion controllers/user_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (uc *UserKeysController) GetKey(w http.ResponseWriter, r *http.Request) {

key, err := uc.ds.GetUserKey(session.AccountID, name)
if err != nil {
if errors.Is(err, datastore.ErrKeyNotFound) {
if errors.Is(err, util.ErrKeyNotFound) {
util.RenderErrorResponse(w, r, http.StatusNotFound, err)
return
}
Expand Down
14 changes: 5 additions & 9 deletions controllers/verification.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ const (
localStackSESEndpoint = "http://localhost:4566/_aws/ses"
)

var ErrIntentNotAllowed = errors.New("intent not allowed")
var ErrAccountExists = errors.New("account already exists")
var ErrAccountDoesNotExist = errors.New("account does not exist")

type VerificationController struct {
datastore *datastore.Datastore
validate *validator.Validate
Expand Down Expand Up @@ -185,7 +181,7 @@ func (vc *VerificationController) VerifyInit(w http.ResponseWriter, r *http.Requ
intentAllowed = false
}
if !intentAllowed {
util.RenderErrorResponse(w, r, http.StatusBadRequest, ErrIntentNotAllowed)
util.RenderErrorResponse(w, r, http.StatusBadRequest, util.ErrIntentNotAllowed)
return
}

Expand All @@ -196,18 +192,18 @@ func (vc *VerificationController) VerifyInit(w http.ResponseWriter, r *http.Requ
return
}
if requestData.Intent == datastore.RegistrationIntent && accountExists {
util.RenderErrorResponse(w, r, http.StatusBadRequest, ErrAccountExists)
util.RenderErrorResponse(w, r, http.StatusBadRequest, util.ErrAccountExists)
return
}
if requestData.Intent == datastore.SetPasswordIntent && !accountExists {
util.RenderErrorResponse(w, r, http.StatusBadRequest, ErrAccountDoesNotExist)
util.RenderErrorResponse(w, r, http.StatusBadRequest, util.ErrAccountDoesNotExist)
return
}
}

verification, err := vc.datastore.CreateVerification(requestData.Email, requestData.Service, requestData.Intent)
if err != nil {
if errors.Is(err, datastore.ErrTooManyVerifications) {
if errors.Is(err, util.ErrTooManyVerifications) {
util.RenderErrorResponse(w, r, http.StatusBadRequest, err)
return
}
Expand Down Expand Up @@ -264,7 +260,7 @@ func (vc *VerificationController) VerifyComplete(w http.ResponseWriter, r *http.
// Update verification status
verification, err := vc.datastore.UpdateAndGetVerificationStatus(requestData.ID, requestData.Code)
if err != nil {
if errors.Is(err, datastore.ErrVerificationNotFound) {
if errors.Is(err, util.ErrVerificationNotFound) {
util.RenderErrorResponse(w, r, http.StatusNotFound, err)
return
}
Expand Down
8 changes: 3 additions & 5 deletions datastore/ake_states.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ import (
"fmt"
"time"

"github.com/brave-experiments/accounts/util"
"github.com/google/uuid"
"gorm.io/gorm"
)

const AkeStateExpiration = 30 * time.Second

var ErrAKEStateNotFound = errors.New("AKE state not found")
var ErrAKEStateExpired = errors.New("AKE state has expired")

type AKEState struct {
ID uuid.UUID `json:"id"`
AccountID *uuid.UUID `json:"-"`
Expand Down Expand Up @@ -46,15 +44,15 @@ func (d *Datastore) GetAKEState(akeStateID uuid.UUID) (*AKEState, error) {
var akeState AKEState
if err := d.db.First(&akeState, "id = ?", akeStateID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAKEStateNotFound
return nil, util.ErrAKEStateNotFound
}
return nil, fmt.Errorf("failed to get AKE state: %w", err)
}

var err error
// Check if AKE state has expired
if time.Since(akeState.CreatedAt) > AkeStateExpiration {
err = ErrAKEStateExpired
err = util.ErrAKEStateExpired
}

if dbErr := d.db.Delete(&AKEState{}, "id = ?", akeStateID).Error; dbErr != nil {
Expand Down
5 changes: 4 additions & 1 deletion datastore/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/jackc/pgx/v5"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)

const databaseURLEnv = "DATABASE_URL"
Expand Down Expand Up @@ -52,7 +53,9 @@ func NewDatastore(minSessionVersion int) (*Datastore, error) {
err = nil
}

db, err := gorm.Open(postgres.Open(dbURL), &gorm.Config{})
db, err := gorm.Open(postgres.Open(dbURL), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
Expand Down
8 changes: 3 additions & 5 deletions datastore/registration_states.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import (
"fmt"
"time"

"github.com/brave-experiments/accounts/util"
"gorm.io/gorm"
)

const registrationStateExpiration = 30 * time.Second

var ErrRegistrationStateNotFound = errors.New("Registration state not found")
var ErrRegistrationStateExpired = errors.New("Registration state has expired")

type RegistrationState struct {
Email string `gorm:"primaryKey"`
OprfSeedID int
Expand All @@ -23,14 +21,14 @@ func (d *Datastore) GetRegistrationStateSeedID(email string) (int, error) {
var state RegistrationState
if err := d.db.First(&state, "email = ?", email).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, ErrRegistrationStateNotFound
return 0, util.ErrRegistrationStateNotFound
}
return 0, fmt.Errorf("failed to get registration state: %w", err)
}

var err error
if time.Since(state.CreatedAt) > registrationStateExpiration {
err = ErrRegistrationStateExpired
err = util.ErrRegistrationStateExpired
}

if dbErr := d.db.Delete(&state).Error; dbErr != nil {
Expand Down
6 changes: 2 additions & 4 deletions datastore/user_keys.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package datastore

import (
"errors"
"fmt"
"time"

"github.com/brave-experiments/accounts/util"
"github.com/google/uuid"
"gorm.io/gorm"
)

var ErrKeyNotFound = errors.New("key not found")

// DBUserKey represents a key in the database
type DBUserKey struct {
// AccountID is the UUID of the account that owns this key
Expand Down Expand Up @@ -43,7 +41,7 @@ func (d *Datastore) GetUserKey(accountID uuid.UUID, name string) (*DBUserKey, er
result := d.db.Where("account_id = ? AND name = ?", accountID, name).First(&key)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, ErrKeyNotFound
return nil, util.ErrKeyNotFound
}
return nil, fmt.Errorf("failed to retrieve user key: %w", result.Error)
}
Expand Down
13 changes: 5 additions & 8 deletions datastore/verifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ const (
maxPendingVerifications = 3
)

var ErrVerificationNotFound = errors.New("verification not found or invalid token")
var ErrTooManyVerifications = errors.New("too many pending verification requests for email")

func generateNotificationChannel(id uuid.UUID) string {
return fmt.Sprintf("verification_%s", id.String())
}
Expand Down Expand Up @@ -70,7 +67,7 @@ func (d *Datastore) CreateVerification(email string, service string, intent stri
}

if existingCount >= maxPendingVerifications {
return nil, ErrTooManyVerifications
return nil, util.ErrTooManyVerifications
}
if err := d.db.Create(&verification).Error; err != nil {
return nil, fmt.Errorf("error creating verification: %w", err)
Expand All @@ -90,7 +87,7 @@ func (d *Datastore) UpdateAndGetVerificationStatus(id uuid.UUID, code string) (*
}

if result.RowsAffected == 0 {
return nil, ErrVerificationNotFound
return nil, util.ErrVerificationNotFound
}

// Send notification
Expand All @@ -110,7 +107,7 @@ func (d *Datastore) GetVerificationStatus(id uuid.UUID) (*Verification, error) {
var verification Verification
if err := d.db.First(&verification, "id = ?", id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrVerificationNotFound
return nil, util.ErrVerificationNotFound
}
return nil, fmt.Errorf("error fetching verification: %w", err)
}
Expand All @@ -119,7 +116,7 @@ func (d *Datastore) GetVerificationStatus(id uuid.UUID) (*Verification, error) {
if err := d.db.Delete(&verification).Error; err != nil {
return nil, fmt.Errorf("error deleting expired verification: %w", err)
}
return nil, ErrVerificationNotFound
return nil, util.ErrVerificationNotFound
}

return &verification, nil
Expand Down Expand Up @@ -173,7 +170,7 @@ func (d *Datastore) DeleteVerification(id uuid.UUID) error {
}

if result.RowsAffected == 0 {
return ErrVerificationNotFound
return util.ErrVerificationNotFound
}

return nil
Expand Down
4 changes: 4 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,10 @@ const docTemplate = `{
"description": "Standard error response",
"type": "object",
"properties": {
"code": {
"description": "Error code",
"type": "integer"
},
"error": {
"description": "Error message",
"type": "string"
Expand Down
4 changes: 4 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,10 @@
"description": "Standard error response",
"type": "object",
"properties": {
"code": {
"description": "Error code",
"type": "integer"
},
"error": {
"description": "Error message",
"type": "string"
Expand Down
3 changes: 3 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ definitions:
util.ErrorResponse:
description: Standard error response
properties:
code:
description: Error code
type: integer
error:
description: Error message
type: string
Expand Down
6 changes: 3 additions & 3 deletions middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func AuthMiddleware(jwtService *services.JWTService, ds *datastore.Datastore, mi
}

if session.Version < minSessionVersion {
util.RenderErrorResponse(w, r, http.StatusForbidden, errors.New("outdated session"))
util.RenderErrorResponse(w, r, http.StatusForbidden, util.ErrOutdatedSession)
return
}

Expand Down Expand Up @@ -73,7 +73,7 @@ func VerificationAuthMiddleware(jwtService *services.JWTService, ds *datastore.D

verification, err := ds.GetVerificationStatus(verificationID)
if err != nil {
if errors.Is(err, datastore.ErrVerificationNotFound) {
if errors.Is(err, util.ErrVerificationNotFound) {
util.RenderErrorResponse(w, r, http.StatusNotFound, err)
return
}
Expand All @@ -96,7 +96,7 @@ func ServicesKeyMiddleware() func(http.Handler) http.Handler {
if servicesKey != "" {
headerKey := r.Header.Get(braveServicesKeyHeader)
if headerKey != servicesKey {
util.RenderErrorResponse(w, r, http.StatusUnauthorized, errors.New("services key does not match"))
util.RenderErrorResponse(w, r, http.StatusUnauthorized, util.ErrInvalidServicesKey)
return
}
}
Expand Down
9 changes: 4 additions & 5 deletions services/opaque.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"

"github.com/brave-experiments/accounts/datastore"
"github.com/brave-experiments/accounts/util"
"github.com/bytemare/crypto"
"github.com/bytemare/opaque"
opaqueMsg "github.com/bytemare/opaque/message"
Expand All @@ -25,8 +26,6 @@ const (

var opaqueArgon2Salt = make([]byte, 16)

var ErrIncorrectCredentials = errors.New("incorrect credentials")

type OpaqueService struct {
ds *datastore.Datastore
oprfSeeds map[int][]byte
Expand Down Expand Up @@ -157,7 +156,7 @@ func (o *OpaqueService) LoginInit(email string, ke1 *opaqueMsg.KE1) (*opaqueMsg.
}

if account == nil && !o.fakeRecordEnabled {
return nil, nil, ErrIncorrectCredentials
return nil, nil, util.ErrIncorrectCredentials
}

useFakeRecord := account == nil || account.OpaqueRegistration == nil || account.OprfSeedID == nil
Expand Down Expand Up @@ -229,11 +228,11 @@ func (o *OpaqueService) LoginFinalize(akeStateID uuid.UUID, ke3 *opaqueMsg.KE3)
}

if err = server.LoginFinish(ke3); err != nil {
return nil, ErrIncorrectCredentials
return nil, util.ErrIncorrectCredentials
}

if akeState.AccountID == nil {
return nil, ErrIncorrectCredentials
return nil, util.ErrIncorrectCredentials
}

return akeState.AccountID, nil
Expand Down
Loading

0 comments on commit 213eabc

Please sign in to comment.