From 46dce34dd6a47448cfcaee4e49211e2b812c7b7f Mon Sep 17 00:00:00 2001 From: Alex Ababii Date: Mon, 5 Oct 2026 15:09:21 +0200 Subject: [PATCH] feat(authguest): atomic verify pin, rollback mechanism for renew --- services/auth-guest/pkg/server/http/redeem.go | 6 + .../auth-guest/pkg/server/http/redeem_test.go | 16 +++ .../pkg/service/authguest/service.go | 88 ++++++------ .../pkg/service/authguest/service_test.go | 129 ++++++++++++------ services/auth-guest/pkg/service/pin/pin.go | 5 +- .../pkg/service/storage/file_manager.go | 32 +++-- .../pkg/service/storage/file_manager_test.go | 79 ++++++----- .../pkg/service/storage/mocks/manager.go | 40 +++--- .../auth-guest/pkg/service/storage/storage.go | 2 +- 9 files changed, 252 insertions(+), 145 deletions(-) diff --git a/services/auth-guest/pkg/server/http/redeem.go b/services/auth-guest/pkg/server/http/redeem.go index 801291a725..76f0cdee55 100644 --- a/services/auth-guest/pkg/server/http/redeem.go +++ b/services/auth-guest/pkg/server/http/redeem.go @@ -37,6 +37,12 @@ func RedeemHandler(log log.Logger, s authguest.AuthGuest, cfg *config.Config) fu return } + if req.Token == "" { + log.Debug().Msg("request body is missing the token") + writeError(w, http.StatusBadRequest, errorResponse{ErrorType: "invalidRequest", Message: "The request body is missing the token."}) + return + } + result, err := s.Redeem(r.Context(), req.Token) if err != nil { log.Debug().Err(err).Msg("redeem failed") diff --git a/services/auth-guest/pkg/server/http/redeem_test.go b/services/auth-guest/pkg/server/http/redeem_test.go index 2369adfd06..ae74730f34 100644 --- a/services/auth-guest/pkg/server/http/redeem_test.go +++ b/services/auth-guest/pkg/server/http/redeem_test.go @@ -153,3 +153,19 @@ func TestRedeemHandlerMalformedBody(t *testing.T) { require.NoError(t, json.NewDecoder(rr.Body).Decode(&resp)) assert.Equal(t, "invalidRequest", resp.ErrorType) } + +func TestRedeemHandlerMissingToken(t *testing.T) { + svcMock := mocks.NewAuthGuest(t) + + body, err := json.Marshal(RedeemRequest{}) + require.NoError(t, err) + + rr := httptest.NewRecorder() + newRedeemHandler(t, svcMock)(rr, httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(body)))) + + assert.Equal(t, http.StatusBadRequest, rr.Code) + + var resp errorResponse + require.NoError(t, json.NewDecoder(rr.Body).Decode(&resp)) + assert.Equal(t, "invalidRequest", resp.ErrorType) +} diff --git a/services/auth-guest/pkg/service/authguest/service.go b/services/auth-guest/pkg/service/authguest/service.go index f0b903dcb6..461627a05b 100644 --- a/services/auth-guest/pkg/service/authguest/service.go +++ b/services/auth-guest/pkg/service/authguest/service.go @@ -136,10 +136,6 @@ func (s *AuthGuestService) Redeem(ctx context.Context, tokenString string) (*Ses // Renew generates a new guest link token and PIN for the given share. func (s *AuthGuestService) Renew(ctx context.Context, shareID string) error { - if _, err := s.store.Get(token.Hash(shareID)); err != nil { - return &GuestError{ErrorType: err, ShareID: shareID} - } - if s.publisher == nil { return ErrEventsNotConfigured } @@ -166,19 +162,20 @@ func (s *AuthGuestService) Renew(ctx context.Context, shareID string) error { now := time.Now() - if err := s.store.Replace(storage.Record{ - ShareID: shareID, - ShareIDHash: tok.ShareIDHash, - SecretHash: tok.SecretHash(), - PinHash: pinHash, - Expiry: now.Add(guestLinkTokenTTL), - PinExpiry: now.Add(guestPinTTL), - Redeemed: false, + var previous storage.Record + if err := s.store.Update(token.Hash(shareID), func(rec *storage.Record) error { + previous = *rec + rec.SecretHash = tok.SecretHash() + rec.PinHash = pinHash + rec.Expiry = now.Add(guestLinkTokenTTL) + rec.PinExpiry = now.Add(guestPinTTL) + rec.Redeemed = false + return nil }); err != nil { - return err + return &GuestError{ErrorType: err, ShareID: shareID} } - return revaevents.Publish(ctx, s.publisher, events.GuestTokenRenewed{ + if err := revaevents.Publish(ctx, s.publisher, events.GuestTokenRenewed{ ShareID: share.GetId(), Sharer: share.GetCreator(), GranteeEmail: share.GetGrantee().GetUserId().GetOpaqueId(), @@ -186,42 +183,51 @@ func (s *AuthGuestService) Renew(ctx context.Context, shareID string) error { Token: tok.String(), Pin: pinStr, Timestamp: now, - }) + }); err != nil { + if rbErr := s.store.Update(token.Hash(shareID), func(rec *storage.Record) error { + *rec = previous + return nil + }); rbErr != nil { + return fmt.Errorf("publishing guest token renewed event failed: %w (rollback failed: %v)", err, rbErr) + } + return err + } + + return nil } // VerifyPin validates a PIN and its share and exchanges them for a session token // and the share id. func (s *AuthGuestService) VerifyPin(ctx context.Context, shareID, pinValue string) (*SessionResponse, error) { - rec, err := s.store.Get(token.Hash(shareID)) - if err != nil { - if errors.Is(err, storage.ErrNotFound) { - // TODO: should a missing record map to 404 tokenNotFound instead of 401 pinInvalid? - return nil, &GuestError{ErrorType: ErrPinInvalid, ShareID: shareID} - } - return nil, &GuestError{ErrorType: err, ShareID: shareID} - } - - if rec.PinHash == "" { - return nil, &GuestError{ErrorType: ErrPinInvalid, ShareID: shareID} - } - - if !rec.PinExpiry.IsZero() && rec.PinExpiry.Before(time.Now()) { - return nil, &GuestError{ErrorType: ErrPinExpired, ShareID: shareID} - } - - ok, err := pin.Verify(pinValue, rec.PinHash) - if err != nil || !ok { - return nil, &GuestError{ErrorType: ErrPinInvalid, ShareID: shareID} - } - if _, err := s.validateShare(ctx, shareID); err != nil { return nil, err } - rec.PinHash = "" - rec.PinExpiry = time.Time{} - if err := s.store.Replace(rec); err != nil { - return nil, err + err := s.store.Update(token.Hash(shareID), func(rec *storage.Record) error { + if rec.PinHash == "" { + return ErrPinInvalid + } + + if !rec.PinExpiry.IsZero() && rec.PinExpiry.Before(time.Now()) { + return ErrPinExpired + } + + ok, err := pin.Verify(pinValue, rec.PinHash) + if err != nil || !ok { + return ErrPinInvalid + } + + rec.PinHash = "" + rec.PinExpiry = time.Time{} + + return nil + }) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + // TODO: should a missing record map to 404 tokenNotFound instead of 401 pinInvalid? + err = ErrPinInvalid + } + return nil, &GuestError{ErrorType: err, ShareID: shareID} } sessionToken, err := s.jwtService.Sign(shareID) diff --git a/services/auth-guest/pkg/service/authguest/service_test.go b/services/auth-guest/pkg/service/authguest/service_test.go index fc7f810379..379274c0a8 100644 --- a/services/auth-guest/pkg/service/authguest/service_test.go +++ b/services/auth-guest/pkg/service/authguest/service_test.go @@ -7,6 +7,8 @@ import ( "context" "errors" "strings" + "sync" + "sync/atomic" "testing" "time" @@ -307,13 +309,8 @@ func existingRecord() storage.Record { } func TestRenewPersistsRecordAndPublishesEvent(t *testing.T) { - store := storagemocks.NewManager(t) - store.On("Get", token.Hash(testShareID)).Return(existingRecord(), nil) - - var replaced storage.Record - store.On("Replace", mock.Anything).Run(func(args mock.Arguments) { - replaced = args.Get(0).(storage.Record) - }).Return(nil) + store := storage.NewFileManager(t.TempDir()) + require.NoError(t, store.Add(existingRecord())) publisher := &testPublisher{} gwc := newGatewayMock(&collaboration.GetShareResponse{ @@ -325,7 +322,8 @@ func TestRenewPersistsRecordAndPublishesEvent(t *testing.T) { require.NoError(t, s.Renew(context.Background(), testShareID)) - store.AssertCalled(t, "Replace", mock.Anything) + replaced, err := store.Get(token.Hash(testShareID)) + require.NoError(t, err) assert.Equal(t, testShareID, replaced.ShareID) assert.Equal(t, token.Hash(testShareID), replaced.ShareIDHash) assert.NotEmpty(t, replaced.SecretHash) @@ -353,11 +351,13 @@ func TestRenewPersistsRecordAndPublishesEvent(t *testing.T) { } func TestRenewRecordNotFound(t *testing.T) { - store := storagemocks.NewManager(t) - store.On("Get", token.Hash(testShareID)).Return(storage.Record{}, storage.ErrNotFound) + store := storage.NewFileManager(t.TempDir()) publisher := &testPublisher{} - s := newRenewService(t, store, newGatewayMock(&collaboration.GetShareResponse{}), publisher) + s := newRenewService(t, store, newGatewayMock(&collaboration.GetShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_OK}, + Share: newGuestShare(time.Now().Add(time.Hour)), + }), publisher) err := s.Renew(context.Background(), testShareID) require.Error(t, err) @@ -367,25 +367,27 @@ func TestRenewRecordNotFound(t *testing.T) { assert.ErrorIs(t, ge.ErrorType, storage.ErrNotFound) assert.Equal(t, testShareID, ge.ShareID) - store.AssertNotCalled(t, "Replace", mock.Anything) assert.Empty(t, publisher.published) } func TestRenewWithoutPublisher(t *testing.T) { - store := storagemocks.NewManager(t) - store.On("Get", token.Hash(testShareID)).Return(existingRecord(), nil) + store := storage.NewFileManager(t.TempDir()) + require.NoError(t, store.Add(existingRecord())) s := newRenewService(t, store, newGatewayMock(&collaboration.GetShareResponse{}), nil) err := s.Renew(context.Background(), testShareID) require.ErrorIs(t, err, ErrEventsNotConfigured) - store.AssertNotCalled(t, "Replace", mock.Anything) + + got, err := store.Get(token.Hash(testShareID)) + require.NoError(t, err) + assert.Empty(t, got.SecretHash) + assert.Empty(t, got.PinHash) } -func TestRenewPublishFailure(t *testing.T) { - store := storagemocks.NewManager(t) - store.On("Get", token.Hash(testShareID)).Return(existingRecord(), nil) - store.On("Replace", mock.Anything).Return(nil) +func TestRenewRollsBackOnPublishFailure(t *testing.T) { + store := storage.NewFileManager(t.TempDir()) + require.NoError(t, store.Add(existingRecord())) publisher := &testPublisher{err: errors.New("publish failed")} gwc := newGatewayMock(&collaboration.GetShareResponse{ @@ -398,11 +400,18 @@ func TestRenewPublishFailure(t *testing.T) { err := s.Renew(context.Background(), testShareID) require.Error(t, err) assert.Contains(t, err.Error(), "publish failed") + + got, err := store.Get(token.Hash(testShareID)) + require.NoError(t, err) + assert.Empty(t, got.SecretHash) + assert.Empty(t, got.PinHash) + assert.True(t, got.PinExpiry.IsZero()) + assert.False(t, got.Redeemed) } func TestRenewShareNotFound(t *testing.T) { - store := storagemocks.NewManager(t) - store.On("Get", token.Hash(testShareID)).Return(existingRecord(), nil) + store := storage.NewFileManager(t.TempDir()) + require.NoError(t, store.Add(existingRecord())) s := newRenewService(t, store, newGatewayMock(&collaboration.GetShareResponse{ Status: &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}, @@ -418,8 +427,8 @@ func TestRenewShareNotFound(t *testing.T) { } func TestRenewShareExpired(t *testing.T) { - store := storagemocks.NewManager(t) - store.On("Get", token.Hash(testShareID)).Return(existingRecord(), nil) + store := storage.NewFileManager(t.TempDir()) + require.NoError(t, store.Add(existingRecord())) s := newRenewService(t, store, newGatewayMock(&collaboration.GetShareResponse{ Status: &rpc.Status{Code: rpc.Code_CODE_OK}, @@ -439,18 +448,13 @@ func TestVerifyPinSuccess(t *testing.T) { pinHash, err := pin.Hash("123456") require.NoError(t, err) - store := storagemocks.NewManager(t) - store.On("Get", token.Hash(testShareID)).Return(storage.Record{ + store := storage.NewFileManager(t.TempDir()) + require.NoError(t, store.Add(storage.Record{ ShareID: testShareID, ShareIDHash: token.Hash(testShareID), PinHash: pinHash, PinExpiry: time.Now().Add(time.Minute), - }, nil) - - var replaced storage.Record - store.On("Replace", mock.Anything).Run(func(args mock.Arguments) { - replaced = args.Get(0).(storage.Record) - }).Return(nil) + })) s := newRedeemService(t, store, newGatewayMock(&collaboration.GetShareResponse{ Status: &rpc.Status{Code: rpc.Code_CODE_OK}, @@ -462,9 +466,10 @@ func TestVerifyPinSuccess(t *testing.T) { assert.NotEmpty(t, result.SessionToken) assert.Equal(t, testShareID, result.ShareID) - store.AssertCalled(t, "Replace", mock.Anything) - assert.Empty(t, replaced.PinHash) - assert.True(t, replaced.PinExpiry.IsZero()) + got, err := store.Get(token.Hash(testShareID)) + require.NoError(t, err) + assert.Empty(t, got.PinHash) + assert.True(t, got.PinExpiry.IsZero()) } func TestVerifyPinErrors(t *testing.T) { @@ -494,7 +499,7 @@ func TestVerifyPinErrors(t *testing.T) { name string pin string rec storage.Record - getErr error + missing bool response *collaboration.GetShareResponse wantErr error }{ @@ -522,7 +527,7 @@ func TestVerifyPinErrors(t *testing.T) { { name: "record not found", pin: "123456", - getErr: storage.ErrNotFound, + missing: true, response: validShare, wantErr: ErrPinInvalid, }, @@ -549,11 +554,9 @@ func TestVerifyPinErrors(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - store := storagemocks.NewManager(t) - if tt.getErr != nil { - store.On("Get", token.Hash(testShareID)).Return(storage.Record{}, tt.getErr) - } else { - store.On("Get", token.Hash(testShareID)).Return(tt.rec, nil) + store := storage.NewFileManager(t.TempDir()) + if !tt.missing { + require.NoError(t, store.Add(tt.rec)) } s := newRedeemService(t, store, newGatewayMock(tt.response)) @@ -566,7 +569,49 @@ func TestVerifyPinErrors(t *testing.T) { assert.ErrorIs(t, ge.ErrorType, tt.wantErr) assert.Equal(t, testShareID, ge.ShareID) - store.AssertNotCalled(t, "Replace", mock.Anything) + if !tt.missing { + got, err := store.Get(token.Hash(testShareID)) + require.NoError(t, err) + assert.Equal(t, tt.rec.PinHash, got.PinHash) + assert.WithinDuration(t, tt.rec.PinExpiry, got.PinExpiry, time.Second) + assert.Equal(t, tt.rec.Redeemed, got.Redeemed) + } }) } } + +func TestVerifyPinSingleUseConcurrent(t *testing.T) { + pinHash, err := pin.Hash("123456") + require.NoError(t, err) + + store := storage.NewFileManager(t.TempDir()) + require.NoError(t, store.Add(storage.Record{ + ShareID: testShareID, + ShareIDHash: token.Hash(testShareID), + PinHash: pinHash, + PinExpiry: time.Now().Add(time.Minute), + })) + + s := newRedeemService(t, store, newGatewayMock(&collaboration.GetShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_OK}, + Share: newGuestShare(time.Now().Add(time.Hour)), + })) + + const workers = 10 + var ( + wg sync.WaitGroup + success atomic.Int32 + ) + for range workers { + wg.Add(1) + go func() { + defer wg.Done() + if _, err := s.VerifyPin(context.Background(), testShareID, "123456"); err == nil { + success.Add(1) + } + }() + } + wg.Wait() + + assert.Equal(t, int32(1), success.Load()) +} diff --git a/services/auth-guest/pkg/service/pin/pin.go b/services/auth-guest/pkg/service/pin/pin.go index 50e18e581c..f73186bf06 100644 --- a/services/auth-guest/pkg/service/pin/pin.go +++ b/services/auth-guest/pkg/service/pin/pin.go @@ -7,6 +7,7 @@ import ( "crypto/rand" "errors" "fmt" + "math" "math/big" "github.com/alexedwards/argon2id" @@ -18,12 +19,12 @@ var ErrInvalidPin = errors.New("invalid pin") var ErrInvalidHash = errors.New("invalid pin hash") func Generate() (string, error) { - n, err := rand.Int(rand.Reader, big.NewInt(1000000)) + n, err := rand.Int(rand.Reader, big.NewInt(int64(math.Pow10(Length)))) if err != nil { return "", fmt.Errorf("could not generate random pin: %w", err) } - return fmt.Sprintf("%06d", n.Int64()), nil + return fmt.Sprintf("%0*d", Length, n.Int64()), nil } func Hash(p string) (string, error) { diff --git a/services/auth-guest/pkg/service/storage/file_manager.go b/services/auth-guest/pkg/service/storage/file_manager.go index 82931a3dda..6b30c0f9f2 100644 --- a/services/auth-guest/pkg/service/storage/file_manager.go +++ b/services/auth-guest/pkg/service/storage/file_manager.go @@ -53,17 +53,6 @@ func (s *FileManager) Add(rec Record) error { return s.upsert(rec) } -// Replace writes the record, overwriting an existing record for the same share id hash. -func (s *FileManager) Replace(rec Record) error { - lock, err := s.lockRecord(rec.ShareIDHash) - if err != nil { - return err - } - defer func() { _ = lock.Unlock() }() - - return s.upsert(rec) -} - // Get returns the record for the given share id hash. func (s *FileManager) Get(shareIDHash string) (Record, error) { return s.get(shareIDHash) @@ -114,6 +103,27 @@ func (s *FileManager) Redeem(shareIDHash string) error { return s.upsert(rec) } +// Update applies fn to the record under the record lock and writes it back. +// If fn returns an error the record is left unchanged. +func (s *FileManager) Update(shareIDHash string, fn func(*Record) error) error { + lock, err := s.lockRecord(shareIDHash) + if err != nil { + return err + } + defer func() { _ = lock.Unlock() }() + + rec, err := s.get(shareIDHash) + if err != nil { + return err + } + + if err := fn(&rec); err != nil { + return err + } + + return s.upsert(rec) +} + func (s *FileManager) lockRecord(shareIDHash string) (*flock.Flock, error) { p, err := s.path(shareIDHash) if err != nil { diff --git a/services/auth-guest/pkg/service/storage/file_manager_test.go b/services/auth-guest/pkg/service/storage/file_manager_test.go index 169a59dc01..4eadba1006 100644 --- a/services/auth-guest/pkg/service/storage/file_manager_test.go +++ b/services/auth-guest/pkg/service/storage/file_manager_test.go @@ -4,6 +4,7 @@ package storage import ( + "errors" "io/fs" "sync" "sync/atomic" @@ -39,36 +40,6 @@ func TestFileManagerAddGet(t *testing.T) { assert.Equal(t, rec, got) } -func TestFileManagerReplaceCreates(t *testing.T) { - dir := t.TempDir() - s := NewFileManager(dir) - - rec := newRecord("e0123456-7890-abcd-ef01-234567890abc") - require.NoError(t, s.Replace(rec)) - - got, err := s.Get(rec.ShareIDHash) - require.NoError(t, err) - assert.Equal(t, rec, got) -} - -func TestFileManagerReplaceOverwrites(t *testing.T) { - dir := t.TempDir() - s := NewFileManager(dir) - - rec := newRecord("e0123456-7890-abcd-ef01-234567890abc") - require.NoError(t, s.Add(rec)) - - rec.SecretHash = "other" - rec.PinHash = "pinhash" - rec.PinExpiry = time.Date(2026, 12, 31, 23, 59, 59, 0, time.UTC) - rec.Redeemed = true - require.NoError(t, s.Replace(rec)) - - got, err := s.Get(rec.ShareIDHash) - require.NoError(t, err) - assert.Equal(t, rec, got) -} - func TestFileManagerGetMissing(t *testing.T) { dir := t.TempDir() s := NewFileManager(dir) @@ -100,7 +71,7 @@ func TestFileManagerInvalidHash(t *testing.T) { require.ErrorIs(t, s.Remove("ab"), ErrInvalidHash) require.ErrorIs(t, s.Add(Record{ShareIDHash: "ab"}), ErrInvalidHash) - require.ErrorIs(t, s.Replace(Record{ShareIDHash: "ab"}), ErrInvalidHash) + require.ErrorIs(t, s.Update("ab", func(*Record) error { return nil }), ErrInvalidHash) } func TestFileManagerRemove(t *testing.T) { @@ -149,6 +120,52 @@ func TestFileManagerRedeemMissing(t *testing.T) { assert.ErrorIs(t, err, ErrNotFound) } +func TestFileManagerUpdate(t *testing.T) { + dir := t.TempDir() + s := NewFileManager(dir) + + rec := newRecord("e0123456-7890-abcd-ef01-234567890abc") + require.NoError(t, s.Add(rec)) + + require.NoError(t, s.Update(rec.ShareIDHash, func(r *Record) error { + r.PinHash = "pinhash" + r.Redeemed = true + return nil + })) + + got, err := s.Get(rec.ShareIDHash) + require.NoError(t, err) + assert.Equal(t, "pinhash", got.PinHash) + assert.True(t, got.Redeemed) +} + +func TestFileManagerUpdateErrorLeavesRecordUnchanged(t *testing.T) { + dir := t.TempDir() + s := NewFileManager(dir) + + rec := newRecord("e0123456-7890-abcd-ef01-234567890abc") + require.NoError(t, s.Add(rec)) + + errBoom := errors.New("boom") + err := s.Update(rec.ShareIDHash, func(r *Record) error { + r.PinHash = "pinhash" + return errBoom + }) + assert.ErrorIs(t, err, errBoom) + + got, err := s.Get(rec.ShareIDHash) + require.NoError(t, err) + assert.Equal(t, rec, got) +} + +func TestFileManagerUpdateMissing(t *testing.T) { + dir := t.TempDir() + s := NewFileManager(dir) + + err := s.Update("doesnotexist", func(*Record) error { return nil }) + assert.ErrorIs(t, err, ErrNotFound) +} + func TestFileManagerAddConcurrent(t *testing.T) { dir := t.TempDir() s := NewFileManager(dir) diff --git a/services/auth-guest/pkg/service/storage/mocks/manager.go b/services/auth-guest/pkg/service/storage/mocks/manager.go index 13585e4547..aa0c066f69 100644 --- a/services/auth-guest/pkg/service/storage/mocks/manager.go +++ b/services/auth-guest/pkg/service/storage/mocks/manager.go @@ -249,53 +249,59 @@ func (_c *Manager_Remove_Call) RunAndReturn(run func(shareIDHash string) error) return _c } -// Replace provides a mock function for the type Manager -func (_mock *Manager) Replace(rec storage.Record) error { - ret := _mock.Called(rec) +// Update provides a mock function for the type Manager +func (_mock *Manager) Update(shareIDHash string, fn func(*storage.Record) error) error { + ret := _mock.Called(shareIDHash, fn) if len(ret) == 0 { - panic("no return value specified for Replace") + panic("no return value specified for Update") } var r0 error - if returnFunc, ok := ret.Get(0).(func(storage.Record) error); ok { - r0 = returnFunc(rec) + if returnFunc, ok := ret.Get(0).(func(string, func(*storage.Record) error) error); ok { + r0 = returnFunc(shareIDHash, fn) } else { r0 = ret.Error(0) } return r0 } -// Manager_Replace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Replace' -type Manager_Replace_Call struct { +// Manager_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type Manager_Update_Call struct { *mock.Call } -// Replace is a helper method to define mock.On call -// - rec storage.Record -func (_e *Manager_Expecter) Replace(rec any) *Manager_Replace_Call { - return &Manager_Replace_Call{Call: _e.mock.On("Replace", rec)} +// Update is a helper method to define mock.On call +// - shareIDHash string +// - fn func(*storage.Record) error +func (_e *Manager_Expecter) Update(shareIDHash any, fn any) *Manager_Update_Call { + return &Manager_Update_Call{Call: _e.mock.On("Update", shareIDHash, fn)} } -func (_c *Manager_Replace_Call) Run(run func(rec storage.Record)) *Manager_Replace_Call { +func (_c *Manager_Update_Call) Run(run func(shareIDHash string, fn func(*storage.Record) error)) *Manager_Update_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 storage.Record + var arg0 string if args[0] != nil { - arg0 = args[0].(storage.Record) + arg0 = args[0].(string) + } + var arg1 func(*storage.Record) error + if args[1] != nil { + arg1 = args[1].(func(*storage.Record) error) } run( arg0, + arg1, ) }) return _c } -func (_c *Manager_Replace_Call) Return(err error) *Manager_Replace_Call { +func (_c *Manager_Update_Call) Return(err error) *Manager_Update_Call { _c.Call.Return(err) return _c } -func (_c *Manager_Replace_Call) RunAndReturn(run func(rec storage.Record) error) *Manager_Replace_Call { +func (_c *Manager_Update_Call) RunAndReturn(run func(shareIDHash string, fn func(*storage.Record) error) error) *Manager_Update_Call { _c.Call.Return(run) return _c } diff --git a/services/auth-guest/pkg/service/storage/storage.go b/services/auth-guest/pkg/service/storage/storage.go index 1facf03151..58883c7cfd 100644 --- a/services/auth-guest/pkg/service/storage/storage.go +++ b/services/auth-guest/pkg/service/storage/storage.go @@ -25,8 +25,8 @@ type Record struct { type Manager interface { Add(rec Record) error - Replace(rec Record) error Get(shareIDHash string) (Record, error) Remove(shareIDHash string) error Redeem(shareIDHash string) error + Update(shareIDHash string, fn func(*Record) error) error }