Jelajahi Sumber

admin: add change password functionality

Mike 1 tahun lalu
induk
melakukan
e872db6609

+ 1 - 0
cmd/server/factory.go

@@ -69,6 +69,7 @@ func Admin(deps Container) oscar.AdminServer {
 		deps.sqLiteUserStore,
 		deps.inMemorySessionManager,
 		deps.inMemorySessionManager,
+		deps.logger,
 	)
 	authService := foodgroup.NewAuthService(
 		deps.cfg,

+ 43 - 0
foodgroup/admin.go

@@ -3,6 +3,7 @@ package foodgroup
 import (
 	"context"
 	"errors"
+	"log/slog"
 	"net/mail"
 
 	"github.com/mk6i/retro-aim-server/state"
@@ -15,11 +16,13 @@ func NewAdminService(
 	buddyListRetriever BuddyListRetriever,
 	messageRelayer MessageRelayer,
 	sessionRetriever SessionRetriever,
+	logger *slog.Logger,
 ) *AdminService {
 	return &AdminService{
 		accountManager:   accountManager,
 		buddyBroadcaster: newBuddyNotifier(buddyListRetriever, messageRelayer, sessionRetriever),
 		messageRelayer:   messageRelayer,
+		logger:           logger,
 	}
 }
 
@@ -30,6 +33,7 @@ type AdminService struct {
 	accountManager   AccountManager
 	buddyBroadcaster buddyBroadcaster
 	messageRelayer   MessageRelayer
+	logger           *slog.Logger
 }
 
 // ConfirmRequest will mark the user account as confirmed if the user has an email address set
@@ -253,6 +257,45 @@ func (s AdminService) InfoChangeRequest(ctx context.Context, sess *state.Session
 		return getAdminChangeReply(tlvList), nil
 	}
 
+	// change password
+	if newPass, hasPassStatus := body.TLVRestBlock.String(wire.AdminTLVNewPassword); hasPassStatus {
+		tlvList.Append(wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}))
+
+		oldPass, ok := body.TLVRestBlock.String(wire.AdminTLVOldPassword)
+		if !ok {
+			tlvList.Append(wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorNeedOldPassword))
+			return getAdminChangeReply(tlvList), nil
+		}
+
+		u, err := s.accountManager.User(sess.IdentScreenName())
+		if err != nil || u == nil {
+			if err != nil {
+				s.logger.ErrorContext(ctx, "accountManager.User: runtime error", "err", err)
+			} else {
+				s.logger.ErrorContext(ctx, "accountManager.User: can't find user", "err", err)
+			}
+			tlvList.Append(wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorAllOtherErrors))
+			return getAdminChangeReply(tlvList), nil
+		}
+
+		if !u.ValidateHash(wire.StrongMD5PasswordHash(oldPass, u.AuthKey)) {
+			tlvList.Append(wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorValidatePassword))
+			return getAdminChangeReply(tlvList), nil
+		}
+
+		if err := s.accountManager.SetUserPassword(sess.IdentScreenName(), newPass); err != nil {
+			if errors.Is(err, state.ErrPasswordInvalid) {
+				tlvList.Append(wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorInvalidPasswordLength))
+			} else {
+				s.logger.ErrorContext(ctx, "accountManager.SetUserPassword: runtime error", "err", err)
+				tlvList.Append(wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorAllOtherErrors))
+			}
+			return getAdminChangeReply(tlvList), nil
+		}
+
+		return getAdminChangeReply(tlvList), nil
+	}
+
 	return wire.SNACMessage{
 		Frame: wire.SNACFrame{
 			FoodGroup: wire.Admin,

+ 398 - 0
foodgroup/admin_test.go

@@ -1,6 +1,8 @@
 package foodgroup
 
 import (
+	"io"
+	"log/slog"
 	"net/mail"
 	"testing"
 
@@ -962,3 +964,399 @@ func TestAdminService_InfoChangeRequest_RegStatus(t *testing.T) {
 		})
 	}
 }
+
+func TestAdminService_InfoChangeRequest_Password(t *testing.T) {
+	cases := []struct {
+		// name is the unit test name
+		name string
+		// cfg is the app configuration
+		cfg config.Config
+		// inputSNAC is the SNAC sent from the client to the server
+		inputSNAC wire.SNACMessage
+		// mockParams is the list of params sent to mocks that satisfy this
+		// method's dependencies
+		mockParams mockParams
+		// userSession is the session of the user
+		userSession *state.Session
+		// expectOutput is the SNAC sent from the server to client
+		expectOutput wire.SNACMessage
+		// expectErr is the expected error returned
+		expectErr error
+	}{
+		{
+			name:        "user changes password successfully",
+			userSession: newTestSession("me"),
+			mockParams: mockParams{
+				accountManagerParams: accountManagerParams{
+					accountManagerSetUserPasswordParams: accountManagerSetUserPasswordParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							password:   "newpass",
+						},
+					},
+					accountManagerUserParams: accountManagerUserParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							result: func() *state.User {
+								user := &state.User{
+									AuthKey: "auth_key",
+								}
+								assert.NoError(t, user.HashPassword("oldpass"))
+								return user
+							}(),
+						},
+					},
+				},
+			},
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeRequest,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
+							wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeReply,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x05_AdminChangeReply{
+					Permissions: wire.AdminInfoPermissionsReadWrite,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "user changes password, invalid new password length",
+			userSession: newTestSession("me"),
+			mockParams: mockParams{
+				accountManagerParams: accountManagerParams{
+					accountManagerSetUserPasswordParams: accountManagerSetUserPasswordParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							password:   "newpass",
+							err:        state.ErrPasswordInvalid,
+						},
+					},
+					accountManagerUserParams: accountManagerUserParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							result: func() *state.User {
+								user := &state.User{
+									AuthKey: "auth_key",
+								}
+								assert.NoError(t, user.HashPassword("oldpass"))
+								return user
+							}(),
+						},
+					},
+				},
+			},
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeRequest,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
+							wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeReply,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x05_AdminChangeReply{
+					Permissions: wire.AdminInfoPermissionsReadWrite,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
+							wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorInvalidPasswordLength),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "user changes password, set user password runtime error",
+			userSession: newTestSession("me"),
+			mockParams: mockParams{
+				accountManagerParams: accountManagerParams{
+					accountManagerSetUserPasswordParams: accountManagerSetUserPasswordParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							password:   "newpass",
+							err:        io.EOF,
+						},
+					},
+					accountManagerUserParams: accountManagerUserParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							result: func() *state.User {
+								user := &state.User{
+									AuthKey: "auth_key",
+								}
+								assert.NoError(t, user.HashPassword("oldpass"))
+								return user
+							}(),
+						},
+					},
+				},
+			},
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeRequest,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
+							wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeReply,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x05_AdminChangeReply{
+					Permissions: wire.AdminInfoPermissionsReadWrite,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
+							wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorAllOtherErrors),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "user changes password, old password is invalid",
+			userSession: newTestSession("me"),
+			mockParams: mockParams{
+				accountManagerParams: accountManagerParams{
+					accountManagerUserParams: accountManagerUserParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							result: func() *state.User {
+								user := &state.User{
+									AuthKey: "auth_key",
+								}
+								assert.NoError(t, user.HashPassword("oldpass"))
+								return user
+							}(),
+						},
+					},
+				},
+			},
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeRequest,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpassbad"),
+							wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeReply,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x05_AdminChangeReply{
+					Permissions: wire.AdminInfoPermissionsReadWrite,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
+							wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorValidatePassword),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "user changes password, user not found",
+			userSession: newTestSession("me"),
+			mockParams: mockParams{
+				accountManagerParams: accountManagerParams{
+					accountManagerUserParams: accountManagerUserParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							result:     nil,
+						},
+					},
+				},
+			},
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeRequest,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
+							wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeReply,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x05_AdminChangeReply{
+					Permissions: wire.AdminInfoPermissionsReadWrite,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
+							wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorAllOtherErrors),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "user changes password, user lookup runtime error",
+			userSession: newTestSession("me"),
+			mockParams: mockParams{
+				accountManagerParams: accountManagerParams{
+					accountManagerUserParams: accountManagerUserParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+							err:        io.EOF,
+						},
+					},
+				},
+			},
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeRequest,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
+							wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeReply,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x05_AdminChangeReply{
+					Permissions: wire.AdminInfoPermissionsReadWrite,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
+							wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorAllOtherErrors),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "user changes password, missing old password",
+			userSession: newTestSession("me"),
+			mockParams:  mockParams{},
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeRequest,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeReply,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x05_AdminChangeReply{
+					Permissions: wire.AdminInfoPermissionsReadWrite,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
+							wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorNeedOldPassword),
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			accountManager := newMockAccountManager(t)
+			for _, params := range tc.mockParams.accountManagerUserParams {
+				accountManager.EXPECT().
+					User(params.screenName).
+					Return(params.result, params.err)
+			}
+			for _, params := range tc.mockParams.accountManagerSetUserPasswordParams {
+				accountManager.EXPECT().
+					SetUserPassword(params.screenName, params.password).
+					Return(params.err)
+			}
+
+			svc := AdminService{
+				accountManager: accountManager,
+				logger:         slog.Default(),
+			}
+			outputSNAC, err := svc.InfoChangeRequest(nil, tc.userSession, tc.inputSNAC.Frame, tc.inputSNAC.Body.(wire.SNAC_0x07_0x04_AdminInfoChangeRequest))
+			assert.ErrorIs(t, err, tc.expectErr)
+			if tc.expectErr != nil {
+				return
+			}
+			assert.Equal(t, tc.expectOutput, outputSNAC)
+		})
+	}
+}

+ 115 - 10
foodgroup/mock_account_manager_test.go

@@ -22,9 +22,9 @@ func (_m *mockAccountManager) EXPECT() *mockAccountManager_Expecter {
 	return &mockAccountManager_Expecter{mock: &_m.Mock}
 }
 
-// ConfirmStatusByName provides a mock function with given fields: screnName
-func (_m *mockAccountManager) ConfirmStatusByName(screnName state.IdentScreenName) (bool, error) {
-	ret := _m.Called(screnName)
+// ConfirmStatusByName provides a mock function with given fields: screenName
+func (_m *mockAccountManager) ConfirmStatusByName(screenName state.IdentScreenName) (bool, error) {
+	ret := _m.Called(screenName)
 
 	if len(ret) == 0 {
 		panic("no return value specified for ConfirmStatusByName")
@@ -33,16 +33,16 @@ func (_m *mockAccountManager) ConfirmStatusByName(screnName state.IdentScreenNam
 	var r0 bool
 	var r1 error
 	if rf, ok := ret.Get(0).(func(state.IdentScreenName) (bool, error)); ok {
-		return rf(screnName)
+		return rf(screenName)
 	}
 	if rf, ok := ret.Get(0).(func(state.IdentScreenName) bool); ok {
-		r0 = rf(screnName)
+		r0 = rf(screenName)
 	} else {
 		r0 = ret.Get(0).(bool)
 	}
 
 	if rf, ok := ret.Get(1).(func(state.IdentScreenName) error); ok {
-		r1 = rf(screnName)
+		r1 = rf(screenName)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -56,12 +56,12 @@ type mockAccountManager_ConfirmStatusByName_Call struct {
 }
 
 // ConfirmStatusByName is a helper method to define mock.On call
-//   - screnName state.IdentScreenName
-func (_e *mockAccountManager_Expecter) ConfirmStatusByName(screnName interface{}) *mockAccountManager_ConfirmStatusByName_Call {
-	return &mockAccountManager_ConfirmStatusByName_Call{Call: _e.mock.On("ConfirmStatusByName", screnName)}
+//   - screenName state.IdentScreenName
+func (_e *mockAccountManager_Expecter) ConfirmStatusByName(screenName interface{}) *mockAccountManager_ConfirmStatusByName_Call {
+	return &mockAccountManager_ConfirmStatusByName_Call{Call: _e.mock.On("ConfirmStatusByName", screenName)}
 }
 
-func (_c *mockAccountManager_ConfirmStatusByName_Call) Run(run func(screnName state.IdentScreenName)) *mockAccountManager_ConfirmStatusByName_Call {
+func (_c *mockAccountManager_ConfirmStatusByName_Call) Run(run func(screenName state.IdentScreenName)) *mockAccountManager_ConfirmStatusByName_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		run(args[0].(state.IdentScreenName))
 	})
@@ -192,6 +192,53 @@ func (_c *mockAccountManager_RegStatusByName_Call) RunAndReturn(run func(state.I
 	return _c
 }
 
+// SetUserPassword provides a mock function with given fields: screenName, newPassword
+func (_m *mockAccountManager) SetUserPassword(screenName state.IdentScreenName, newPassword string) error {
+	ret := _m.Called(screenName, newPassword)
+
+	if len(ret) == 0 {
+		panic("no return value specified for SetUserPassword")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(state.IdentScreenName, string) error); ok {
+		r0 = rf(screenName, newPassword)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// mockAccountManager_SetUserPassword_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUserPassword'
+type mockAccountManager_SetUserPassword_Call struct {
+	*mock.Call
+}
+
+// SetUserPassword is a helper method to define mock.On call
+//   - screenName state.IdentScreenName
+//   - newPassword string
+func (_e *mockAccountManager_Expecter) SetUserPassword(screenName interface{}, newPassword interface{}) *mockAccountManager_SetUserPassword_Call {
+	return &mockAccountManager_SetUserPassword_Call{Call: _e.mock.On("SetUserPassword", screenName, newPassword)}
+}
+
+func (_c *mockAccountManager_SetUserPassword_Call) Run(run func(screenName state.IdentScreenName, newPassword string)) *mockAccountManager_SetUserPassword_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(state.IdentScreenName), args[1].(string))
+	})
+	return _c
+}
+
+func (_c *mockAccountManager_SetUserPassword_Call) Return(_a0 error) *mockAccountManager_SetUserPassword_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *mockAccountManager_SetUserPassword_Call) RunAndReturn(run func(state.IdentScreenName, string) error) *mockAccountManager_SetUserPassword_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // UpdateConfirmStatus provides a mock function with given fields: confirmStatus, screenName
 func (_m *mockAccountManager) UpdateConfirmStatus(confirmStatus bool, screenName state.IdentScreenName) error {
 	ret := _m.Called(confirmStatus, screenName)
@@ -379,6 +426,64 @@ func (_c *mockAccountManager_UpdateRegStatus_Call) RunAndReturn(run func(uint16,
 	return _c
 }
 
+// User provides a mock function with given fields: screenName
+func (_m *mockAccountManager) User(screenName state.IdentScreenName) (*state.User, error) {
+	ret := _m.Called(screenName)
+
+	if len(ret) == 0 {
+		panic("no return value specified for User")
+	}
+
+	var r0 *state.User
+	var r1 error
+	if rf, ok := ret.Get(0).(func(state.IdentScreenName) (*state.User, error)); ok {
+		return rf(screenName)
+	}
+	if rf, ok := ret.Get(0).(func(state.IdentScreenName) *state.User); ok {
+		r0 = rf(screenName)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*state.User)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(state.IdentScreenName) error); ok {
+		r1 = rf(screenName)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// mockAccountManager_User_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'User'
+type mockAccountManager_User_Call struct {
+	*mock.Call
+}
+
+// User is a helper method to define mock.On call
+//   - screenName state.IdentScreenName
+func (_e *mockAccountManager_Expecter) User(screenName interface{}) *mockAccountManager_User_Call {
+	return &mockAccountManager_User_Call{Call: _e.mock.On("User", screenName)}
+}
+
+func (_c *mockAccountManager_User_Call) Run(run func(screenName state.IdentScreenName)) *mockAccountManager_User_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(state.IdentScreenName))
+	})
+	return _c
+}
+
+func (_c *mockAccountManager_User_Call) Return(_a0 *state.User, _a1 error) *mockAccountManager_User_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *mockAccountManager_User_Call) RunAndReturn(run func(state.IdentScreenName) (*state.User, error)) *mockAccountManager_User_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // newMockAccountManager creates a new instance of mockAccountManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
 // The first argument is typically a *testing.T value.
 func newMockAccountManager(t interface {

+ 18 - 0
foodgroup/test_helpers.go

@@ -571,6 +571,8 @@ type accountManagerParams struct {
 	accountManagerRegStatusByNameParams
 	accountManagerUpdateConfirmStatusParams
 	accountManagerConfirmStatusByNameParams
+	accountManagerUserParams
+	accountManagerSetUserPasswordParams
 }
 
 // accountManagerUpdateDisplayScreenNameParams is the list of parameters passed at the mock
@@ -628,6 +630,22 @@ type accountManagerConfirmStatusByNameParams []struct {
 	err           error
 }
 
+// accountManagerUserParams is the list of parameters passed at the mock
+// accountManager.User call site
+type accountManagerUserParams []struct {
+	screenName state.IdentScreenName
+	result     *state.User
+	err        error
+}
+
+// accountManagerSetUserPasswordParams is the list of parameters passed at the mock
+// accountManager.SetUserPassword call site
+type accountManagerSetUserPasswordParams []struct {
+	screenName state.IdentScreenName
+	password   string
+	err        error
+}
+
 // buddyBroadcasterParams is a helper struct that contains mock parameters for
 // buddyBroadcaster methods
 type buddyBroadcasterParams struct {

+ 3 - 1
foodgroup/types.go

@@ -49,7 +49,9 @@ type AccountManager interface {
 	UpdateRegStatus(regStatus uint16, screenName state.IdentScreenName) error
 	RegStatusByName(screenName state.IdentScreenName) (uint16, error)
 	UpdateConfirmStatus(confirmStatus bool, screenName state.IdentScreenName) error
-	ConfirmStatusByName(screnName state.IdentScreenName) (bool, error)
+	ConfirmStatusByName(screenName state.IdentScreenName) (bool, error)
+	SetUserPassword(screenName state.IdentScreenName, newPassword string) error
+	User(screenName state.IdentScreenName) (*state.User, error)
 }
 
 type BARTManager interface {