Jelajahi Sumber

centralize account creation logic

Mike 5 bulan lalu
induk
melakukan
e78710f8bd

+ 26 - 3
cmd/server/factory.go

@@ -236,6 +236,7 @@ func OSCAR(deps Container) *oscar.Server {
 		deps.sqLiteUserStore,
 		deps.sqLiteUserStore,
 		deps.rateLimitClasses,
+		state.NewAccountCreator(deps.sqLiteUserStore.InsertUser),
 		logger,
 	)
 	bartService := foodgroup.NewBARTService(
@@ -269,8 +270,14 @@ func OSCAR(deps Container) *oscar.Server {
 		deps.inMemorySessionManager,
 		deps.inMemorySessionManager,
 	)
-	icqService := foodgroup.NewICQService(deps.inMemorySessionManager, deps.sqLiteUserStore, deps.sqLiteUserStore,
-		logger, deps.inMemorySessionManager, deps.sqLiteUserStore)
+	icqService := foodgroup.NewICQService(
+		deps.inMemorySessionManager,
+		deps.sqLiteUserStore,
+		deps.sqLiteUserStore,
+		logger,
+		deps.inMemorySessionManager,
+		deps.sqLiteUserStore,
+	)
 	locateService := foodgroup.NewLocateService(
 		deps.sqLiteUserStore,
 		deps.inMemorySessionManager,
@@ -339,7 +346,20 @@ func OSCAR(deps Container) *oscar.Server {
 // KerberosAPI creates an HTTP server for the Kerberos server.
 func KerberosAPI(deps Container) *kerberos.Server {
 	logger := deps.logger.With("svc", "Kerberos")
-	authService := foodgroup.NewAuthService(deps.cfg, deps.inMemorySessionManager, deps.inMemorySessionManager, deps.chatSessionManager, deps.sqLiteUserStore, deps.hmacCookieBaker, deps.chatSessionManager, deps.sqLiteUserStore, deps.sqLiteUserStore, deps.rateLimitClasses, logger)
+	authService := foodgroup.NewAuthService(
+		deps.cfg,
+		deps.inMemorySessionManager,
+		deps.inMemorySessionManager,
+		deps.chatSessionManager,
+		deps.sqLiteUserStore,
+		deps.hmacCookieBaker,
+		deps.chatSessionManager,
+		deps.sqLiteUserStore,
+		deps.sqLiteUserStore,
+		deps.rateLimitClasses,
+		state.NewAccountCreator(deps.sqLiteUserStore.InsertUser),
+		logger,
+	)
 	return kerberos.NewKerberosServer(deps.Listeners, logger, authService)
 }
 
@@ -376,6 +396,7 @@ func MgmtAPI(deps Container) *http.Server {
 		deps.sqLiteUserStore,        // accountManager
 		deps.sqLiteUserStore,        // profileRetriever
 		deps.sqLiteUserStore,        // webAPIKeyManager
+		state.NewAccountCreator(deps.sqLiteUserStore.InsertUser),
 		logger,
 	)
 }
@@ -407,6 +428,7 @@ func TOC(deps Container) *toc.Server {
 				deps.sqLiteUserStore,
 				deps.sqLiteUserStore,
 				deps.rateLimitClasses,
+				state.NewAccountCreator(deps.sqLiteUserStore.InsertUser),
 				logger,
 			),
 			BuddyListRegistry: deps.sqLiteUserStore,
@@ -508,6 +530,7 @@ func WebAPI(deps Container) *webapi.Server {
 			deps.sqLiteUserStore,
 			deps.sqLiteUserStore,
 			deps.rateLimitClasses,
+			state.NewAccountCreator(deps.sqLiteUserStore.InsertUser),
 			logger,
 		),
 		BuddyListRegistry: deps.sqLiteUserStore,

+ 13 - 27
foodgroup/auth.go

@@ -33,6 +33,7 @@ func NewAuthService(
 	accountManager AccountManager,
 	bartItemManager BARTItemManager,
 	classes wire.RateLimitClasses,
+	createAccount state.CreateAccountFunc,
 	logger *slog.Logger,
 ) *AuthService {
 	return &AuthService{
@@ -48,6 +49,7 @@ func NewAuthService(
 		rateLimitClasses:           classes,
 		timeNow:                    time.Now,
 		maxConcurrentLoginsPerUser: MaxConcurrentLoginsPerUser,
+		createAccount:              createAccount,
 		logger:                     logger,
 	}
 }
@@ -69,6 +71,7 @@ type AuthService struct {
 	rateLimitClasses           wire.RateLimitClasses
 	timeNow                    func() time.Time
 	maxConcurrentLoginsPerUser int
+	createAccount              state.CreateAccountFunc
 }
 
 // RegisterChatSession adds a user to a chat room. The authCookie param is an
@@ -282,9 +285,9 @@ func (s AuthService) BUCPChallenge(ctx context.Context, inBody wire.SNAC_0x17_0x
 // (wire.LoginTLVTagsReconnectHere) and an authorization cookie
 // (wire.LoginTLVTagsAuthorizationCookie). Else, an error code is set
 // (wire.LoginTLVTagsErrorSubcode).
-func (s AuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error) {
+func (s AuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error) {
 
-	block, err := s.login(ctx, inBody.TLVList, newUserFn, advertisedHost)
+	block, err := s.login(ctx, inBody.TLVList, advertisedHost)
 	if err != nil {
 		return wire.SNACMessage{}, err
 	}
@@ -310,8 +313,8 @@ func (s AuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_B
 // (wire.LoginTLVTagsReconnectHere) and an authorization cookie
 // (wire.LoginTLVTagsAuthorizationCookie). Else, an error code is set
 // (wire.LoginTLVTagsErrorSubcode).
-func (s AuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error) {
-	return s.login(ctx, inFrame.TLVList, newUserFn, advertisedHost)
+func (s AuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error) {
+	return s.login(ctx, inFrame.TLVList, advertisedHost)
 }
 
 // KerberosLogin handles AIM-style Kerberos authentication for AIM 6.0+.
@@ -322,7 +325,7 @@ func (s AuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame
 //
 // Several values in the response are poorly understood but necessary for proper
 // processing on the client side.
-func (s AuthService) KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error) {
+func (s AuthService) KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string) (wire.SNACMessage, error) {
 
 	b, ok := inBody.TicketRequestMetadata.Bytes(wire.KerberosTLVTicketRequest)
 	if !ok {
@@ -345,7 +348,7 @@ func (s AuthService) KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_
 		list = append(list, wire.NewTLVBE(wire.LoginTLVTagsPlaintextPassword, info.Password))
 	}
 
-	result, err := s.login(ctx, list, newUserFn, advertisedHost)
+	result, err := s.login(ctx, list, advertisedHost)
 	if err != nil {
 		return wire.SNACMessage{}, fmt.Errorf("login: %w", err)
 	}
@@ -480,7 +483,7 @@ func (l *loginProperties) fromTLV(list wire.TLVList) error {
 
 // login validates a user's credentials and creates their session. it returns
 // metadata used in both BUCP and FLAP authentication responses.
-func (s AuthService) login(ctx context.Context, tlv wire.TLVList, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error) {
+func (s AuthService) login(ctx context.Context, tlv wire.TLVList, advertisedHost string) (wire.TLVRestBlock, error) {
 
 	props := loginProperties{}
 	if err := props.fromTLV(tlv); err != nil {
@@ -512,7 +515,7 @@ func (s AuthService) login(ctx context.Context, tlv wire.TLVList, newUserFn func
 		if s.config.DisableAuth {
 			// auth disabled, create the user
 			s.logger.Debug("login: auth disabled, creating user", "screen_name", props.screenName)
-			return s.createUser(ctx, props, newUserFn, advertisedHost)
+			return s.createUser(ctx, props, advertisedHost)
 		}
 		// auth enabled, return separate login errors for ICQ and AIM
 		loginErr := wire.LoginErrInvalidUsernameOrPassword
@@ -591,15 +594,8 @@ func (s AuthService) login(ctx context.Context, tlv wire.TLVList, newUserFn func
 	return s.loginSuccessResponse(props, advertisedHost)
 }
 
-func (s AuthService) createUser(ctx context.Context, props loginProperties, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error) {
-
-	var err error
-	if props.screenName.IsUIN() {
-		err = props.screenName.ValidateUIN()
-	} else {
-		err = props.screenName.ValidateAIMHandle()
-	}
-
+func (s AuthService) createUser(ctx context.Context, props loginProperties, advertisedHost string) (wire.TLVRestBlock, error) {
+	err := s.createAccount(ctx, props.screenName, "welcome1")
 	if err != nil {
 		switch {
 		case errors.Is(err, state.ErrAIMHandleInvalidFormat) || errors.Is(err, state.ErrAIMHandleLength):
@@ -611,16 +607,6 @@ func (s AuthService) createUser(ctx context.Context, props loginProperties, newU
 		}
 	}
 
-	newUser, err := newUserFn(props.screenName)
-	if err != nil {
-		return wire.TLVRestBlock{}, err
-	}
-
-	err = s.userManager.InsertUser(ctx, newUser)
-	if err != nil {
-		return wire.TLVRestBlock{}, err
-	}
-
 	return s.loginSuccessResponse(props, advertisedHost)
 }
 

+ 40 - 49
foodgroup/auth_test.go

@@ -38,8 +38,8 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 		// mockParams is the list of params sent to mocks that satisfy this
 		// method's dependencies
 		mockParams mockParams
-		// newUserFn is the function that registers a new user account
-		newUserFn func(screenName state.DisplayScreenName) (state.User, error)
+		// createAccount is the function that creates a new user account
+		createAccount state.CreateAccountFunc
 		// expectOutput is the SNAC sent from the server to client
 		expectOutput wire.SNACMessage
 		// wantErr is the error we expect from the method
@@ -461,11 +461,6 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 							result:     nil,
 						},
 					},
-					insertUserParams: insertUserParams{
-						{
-							user: user,
-						},
-					},
 				},
 				cookieBakerParams: cookieBakerParams{
 					cookieIssueParams: cookieIssueParams{
@@ -483,8 +478,10 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 					},
 				},
 			},
-			newUserFn: func(screenName state.DisplayScreenName) (state.User, error) {
-				return user, nil
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, user.DisplayScreenName, screenName)
+				assert.Equal(t, "welcome1", password)
+				return nil
 			},
 			expectOutput: wire.SNACMessage{
 				Frame: wire.SNACFrame{
@@ -527,6 +524,11 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 					},
 				},
 			},
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("2coolforschool"), screenName)
+				assert.Equal(t, "welcome1", password)
+				return state.ErrAIMHandleInvalidFormat
+			},
 			expectOutput: wire.SNACMessage{
 				Frame: wire.SNACFrame{
 					FoodGroup: wire.BUCP,
@@ -566,6 +568,11 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 					},
 				},
 			},
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("99"), screenName)
+				assert.Equal(t, "welcome1", password)
+				return state.ErrICQUINInvalidFormat
+			},
 			expectOutput: wire.SNACMessage{
 				Frame: wire.SNACFrame{
 					FoodGroup: wire.BUCP,
@@ -620,9 +627,6 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 					},
 				},
 			},
-			newUserFn: func(screenName state.DisplayScreenName) (state.User, error) {
-				return user, nil
-			},
 			expectOutput: wire.SNACMessage{
 				Frame: wire.SNACFrame{
 					FoodGroup: wire.BUCP,
@@ -761,11 +765,6 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 					User(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
-			for _, params := range tc.mockParams.insertUserParams {
-				userManager.EXPECT().
-					InsertUser(matchContext(), params.user).
-					Return(params.err)
-			}
 			cookieBaker := newMockCookieBaker(t)
 			for _, params := range tc.mockParams.cookieIssueParams {
 				cookieBaker.EXPECT().
@@ -786,9 +785,10 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 				userManager:                userManager,
 				sessionRetriever:           sessionRetriever,
 				maxConcurrentLoginsPerUser: 2,
+				createAccount:              tc.createAccount,
 				logger:                     slog.Default(),
 			}
-			outputSNAC, err := svc.BUCPLogin(context.Background(), tc.inputSNAC, tc.newUserFn, tc.advertisedHost)
+			outputSNAC, err := svc.BUCPLogin(context.Background(), tc.inputSNAC, tc.advertisedHost)
 			assert.ErrorIs(t, err, tc.wantErr)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 		})
@@ -815,8 +815,8 @@ func TestAuthService_FLAPLogin(t *testing.T) {
 		// mockParams is the list of params sent to mocks that satisfy this
 		// method's dependencies
 		mockParams mockParams
-		// newUserFn is the function that registers a new user account
-		newUserFn func(screenName state.DisplayScreenName) (state.User, error)
+		// createAccount is the function that creates a new user account
+		createAccount state.CreateAccountFunc
 		// expectOutput is the response sent from the server to client
 		expectOutput wire.TLVRestBlock
 		// wantErr is the error we expect from the method
@@ -1021,11 +1021,6 @@ func TestAuthService_FLAPLogin(t *testing.T) {
 							result:     nil,
 						},
 					},
-					insertUserParams: insertUserParams{
-						{
-							user: user,
-						},
-					},
 				},
 				cookieBakerParams: cookieBakerParams{
 					cookieIssueParams: cookieIssueParams{
@@ -1043,8 +1038,10 @@ func TestAuthService_FLAPLogin(t *testing.T) {
 					},
 				},
 			},
-			newUserFn: func(screenName state.DisplayScreenName) (state.User, error) {
-				return user, nil
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, user.DisplayScreenName, screenName)
+				assert.Equal(t, "welcome1", password)
+				return nil
 			},
 			expectOutput: wire.TLVRestBlock{
 				TLVList: wire.TLVList{
@@ -1094,9 +1091,6 @@ func TestAuthService_FLAPLogin(t *testing.T) {
 					},
 				},
 			},
-			newUserFn: func(screenName state.DisplayScreenName) (state.User, error) {
-				return user, nil
-			},
 			expectOutput: wire.TLVRestBlock{
 				TLVList: wire.TLVList{
 					wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
@@ -1214,11 +1208,6 @@ func TestAuthService_FLAPLogin(t *testing.T) {
 					User(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
-			for _, params := range tc.mockParams.insertUserParams {
-				userManager.EXPECT().
-					InsertUser(matchContext(), params.user).
-					Return(params.err)
-			}
 			cookieBaker := newMockCookieBaker(t)
 			for _, params := range tc.mockParams.cookieIssueParams {
 				cookieBaker.EXPECT().
@@ -1226,12 +1215,13 @@ func TestAuthService_FLAPLogin(t *testing.T) {
 					Return(params.cookieOut, params.err)
 			}
 			svc := AuthService{
-				config:      tc.cfg,
-				cookieBaker: cookieBaker,
-				userManager: userManager,
-				logger:      slog.Default(),
+				config:        tc.cfg,
+				cookieBaker:   cookieBaker,
+				userManager:   userManager,
+				createAccount: tc.createAccount,
+				logger:        slog.Default(),
 			}
-			outputSNAC, err := svc.FLAPLogin(context.Background(), tc.inputSNAC, tc.newUserFn, tc.advertisedHost)
+			outputSNAC, err := svc.FLAPLogin(context.Background(), tc.inputSNAC, tc.advertisedHost)
 			assert.ErrorIs(t, err, tc.wantErr)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 		})
@@ -1258,8 +1248,8 @@ func TestAuthService_KerberosLogin(t *testing.T) {
 		// mockParams is the list of params sent to mocks that satisfy this
 		// method's dependencies
 		mockParams mockParams
-		// newUserFn is the function that registers a new user account
-		newUserFn func(screenName state.DisplayScreenName) (state.User, error)
+		// createAccount is the function that creates a new user account
+		createAccount state.CreateAccountFunc
 		// expectOutput is the response sent from the server to client
 		expectOutput wire.SNACMessage
 		// wantErr is the error we expect from the method
@@ -1570,9 +1560,10 @@ func TestAuthService_KerberosLogin(t *testing.T) {
 				sessionRetriever:           sessionRetriever,
 				timeNow:                    tc.timeNow,
 				maxConcurrentLoginsPerUser: 2,
+				createAccount:              tc.createAccount,
 				logger:                     slog.Default(),
 			}
-			outputSNAC, err := svc.KerberosLogin(context.Background(), tc.inputSNAC, tc.newUserFn, tc.advertisedHost)
+			outputSNAC, err := svc.KerberosLogin(context.Background(), tc.inputSNAC, tc.advertisedHost)
 			assert.ErrorIs(t, err, tc.wantErr)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 		})
@@ -1760,7 +1751,7 @@ func TestAuthService_RegisterChatSession_HappyPath(t *testing.T) {
 	chatCookieBuf := &bytes.Buffer{}
 	assert.NoError(t, wire.MarshalBE(serverCookie, chatCookieBuf))
 
-	svc := NewAuthService(config.Config{}, nil, nil, chatSessionRegistry, nil, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), slog.Default())
+	svc := NewAuthService(config.Config{}, nil, nil, chatSessionRegistry, nil, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
 
 	have, err := svc.RegisterChatSession(context.Background(), serverCookie)
 	assert.NoError(t, err)
@@ -1970,7 +1961,7 @@ func TestAuthService_RegisterBOSSession(t *testing.T) {
 					Return(params.result, params.err)
 			}
 
-			svc := NewAuthService(config.Config{}, sessionRegistry, nil, nil, userManager, nil, nil, accountManager, bartItemManager, wire.DefaultRateLimitClasses(), slog.Default())
+			svc := NewAuthService(config.Config{}, sessionRegistry, nil, nil, userManager, nil, nil, accountManager, bartItemManager, wire.DefaultRateLimitClasses(), nil, slog.Default())
 
 			have, err := svc.RegisterBOSSession(context.Background(), tc.cookie)
 			assert.NoError(t, err)
@@ -2001,7 +1992,7 @@ func TestAuthService_RetrieveBOSSession_HappyPath(t *testing.T) {
 		User(matchContext(), instance.IdentScreenName()).
 		Return(&state.User{IdentScreenName: instance.IdentScreenName()}, nil)
 
-	svc := NewAuthService(config.Config{}, nil, sessionRetriever, nil, userManager, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), slog.Default())
+	svc := NewAuthService(config.Config{}, nil, sessionRetriever, nil, userManager, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
 
 	have, err := svc.RetrieveBOSSession(context.Background(), aimAuthCookie)
 	assert.NoError(t, err)
@@ -2026,7 +2017,7 @@ func TestAuthService_RetrieveBOSSession_SessionNotFound(t *testing.T) {
 		User(matchContext(), instance.IdentScreenName()).
 		Return(&state.User{IdentScreenName: instance.IdentScreenName()}, nil)
 
-	svc := NewAuthService(config.Config{}, nil, sessionRetriever, nil, userManager, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), slog.Default())
+	svc := NewAuthService(config.Config{}, nil, sessionRetriever, nil, userManager, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
 
 	have, err := svc.RetrieveBOSSession(context.Background(), aimAuthCookie)
 	assert.NoError(t, err)
@@ -2119,7 +2110,7 @@ func TestAuthService_SignoutChat(t *testing.T) {
 					RemoveSession(matchSession(params.screenName))
 			}
 
-			svc := NewAuthService(config.Config{}, nil, nil, sessionManager, nil, nil, chatMessageRelayer, nil, nil, wire.DefaultRateLimitClasses(), slog.Default())
+			svc := NewAuthService(config.Config{}, nil, nil, sessionManager, nil, nil, chatMessageRelayer, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
 			svc.SignoutChat(context.Background(), tt.instance)
 		})
 	}
@@ -2164,7 +2155,7 @@ func TestAuthService_Signout(t *testing.T) {
 			for _, params := range tt.mockParams.removeSessionParams {
 				sessionManager.EXPECT().RemoveSession(matchSession(params.screenName))
 			}
-			svc := NewAuthService(config.Config{}, sessionManager, nil, nil, nil, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), slog.Default())
+			svc := NewAuthService(config.Config{}, sessionManager, nil, nil, nil, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
 
 			svc.Signout(context.Background(), tt.instance)
 		})

+ 0 - 8
foodgroup/helpers_test.go

@@ -270,7 +270,6 @@ type buddyIconMetadataParams []struct {
 // UserManager methods
 type userManagerParams struct {
 	getUserParams
-	insertUserParams
 }
 
 // getUserParams is the list of parameters passed at the mock
@@ -281,13 +280,6 @@ type getUserParams []struct {
 	err        error
 }
 
-// insertUserParams is the list of parameters passed at the mock
-// UserManager.InsertUser call site
-type insertUserParams []struct {
-	user state.User
-	err  error
-}
-
 // sessionRegistryParams is a helper struct that contains mock parameters for
 // SessionRegistry methods
 type sessionRegistryParams struct {

+ 0 - 8
server/http/helpers_test.go

@@ -327,7 +327,6 @@ type userManagerParams struct {
 	allUsersParams
 	deleteUserParams
 	getUserParams
-	insertUserParams
 	setUserPasswordParams
 }
 
@@ -353,13 +352,6 @@ type getUserParams []struct {
 	err        error
 }
 
-// insertUserParams is the list of parameters passed at the mock
-// UserManager.InsertUser call site
-type insertUserParams []struct {
-	u   state.User
-	err error
-}
-
 // setUserPasswordParams is the list of parameters passed at the mock
 // UserManager.SetUserPassword call site
 type setUserPasswordParams []struct {

+ 13 - 28
server/http/mgmt_api.go

@@ -24,7 +24,7 @@ import (
 	"github.com/mk6i/open-oscar-server/wire"
 )
 
-func NewManagementAPI(bld config.Build, listener string, userManager UserManager, sessionRetriever SessionRetriever, buddyBroadcaster BuddyBroadcaster, chatRoomRetriever ChatRoomRetriever, chatRoomCreator ChatRoomCreator, chatRoomDeleter ChatRoomDeleter, chatSessionRetriever ChatSessionRetriever, directoryManager DirectoryManager, messageRelayer MessageRelayer, bartAssetManager BARTAssetManager, feedbagRetriever FeedBagRetriever, feedbagManager FeedbagManager, accountManager AccountManager, profileRetriever ProfileRetriever, webAPIKeyManager WebAPIKeyManager, logger *slog.Logger) *Server {
+func NewManagementAPI(bld config.Build, listener string, userManager UserManager, sessionRetriever SessionRetriever, buddyBroadcaster BuddyBroadcaster, chatRoomRetriever ChatRoomRetriever, chatRoomCreator ChatRoomCreator, chatRoomDeleter ChatRoomDeleter, chatSessionRetriever ChatSessionRetriever, directoryManager DirectoryManager, messageRelayer MessageRelayer, bartAssetManager BARTAssetManager, feedbagRetriever FeedBagRetriever, feedbagManager FeedbagManager, accountManager AccountManager, profileRetriever ProfileRetriever, webAPIKeyManager WebAPIKeyManager, createAccount state.CreateAccountFunc, logger *slog.Logger) *Server {
 	mux := http.NewServeMux()
 
 	// Handlers for '/user' route
@@ -35,7 +35,7 @@ func NewManagementAPI(bld config.Build, listener string, userManager UserManager
 		getUserHandler(w, r, userManager, logger)
 	})
 	mux.HandleFunc("POST /user", func(w http.ResponseWriter, r *http.Request) {
-		postUserHandler(w, r, userManager, uuid.New, logger)
+		postUserHandler(w, r, createAccount, logger)
 	})
 
 	// Handlers for '/user/password' route
@@ -383,7 +383,7 @@ func getUserHandler(w http.ResponseWriter, r *http.Request, userManager UserMana
 }
 
 // postUserHandler handles the POST /user endpoint.
-func postUserHandler(w http.ResponseWriter, r *http.Request, userManager UserManager, newUUID func() uuid.UUID, logger *slog.Logger) {
+func postUserHandler(w http.ResponseWriter, r *http.Request, createAccount state.CreateAccountFunc, logger *slog.Logger) {
 	input, err := userFromBody(r)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusBadRequest)
@@ -392,35 +392,20 @@ func postUserHandler(w http.ResponseWriter, r *http.Request, userManager UserMan
 
 	sn := state.DisplayScreenName(input.ScreenName)
 
-	if sn.IsUIN() {
-		if err := sn.ValidateUIN(); err != nil {
-			http.Error(w, fmt.Sprintf("invalid uin: %s", err), http.StatusBadRequest)
-			return
-		}
-	} else {
-		if err := sn.ValidateAIMHandle(); err != nil {
-			http.Error(w, fmt.Sprintf("invalid screen name: %s", err), http.StatusBadRequest)
-			return
-		}
-	}
-
-	user := state.User{
-		AuthKey:           newUUID().String(),
-		DisplayScreenName: sn,
-		IdentScreenName:   sn.IdentScreenName(),
-		IsICQ:             sn.IsUIN(),
-	}
-
-	if err := user.HashPassword(input.Password); err != nil {
-		http.Error(w, fmt.Sprintf("invalid password: %s", err), http.StatusBadRequest)
-		return
-	}
-
-	err = userManager.InsertUser(r.Context(), user)
+	err = createAccount(r.Context(), sn, input.Password)
 	switch {
 	case errors.Is(err, state.ErrDupUser):
 		http.Error(w, "user already exists", http.StatusConflict)
 		return
+	case errors.Is(err, state.ErrAIMHandleInvalidFormat), errors.Is(err, state.ErrAIMHandleLength):
+		http.Error(w, fmt.Sprintf("invalid screen name: %s", err), http.StatusBadRequest)
+		return
+	case errors.Is(err, state.ErrICQUINInvalidFormat):
+		http.Error(w, fmt.Sprintf("invalid uin: %s", err), http.StatusBadRequest)
+		return
+	case errors.Is(err, state.ErrPasswordInvalid):
+		http.Error(w, fmt.Sprintf("invalid password: %s", err), http.StatusBadRequest)
+		return
 	case err != nil:
 		logger.Error("error inserting user POST /user", "err", err.Error())
 		http.Error(w, "internal server error", http.StatusInternalServerError)

+ 45 - 86
server/http/mgmt_api_test.go

@@ -1,6 +1,7 @@
 package http
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -15,7 +16,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 
@@ -1139,58 +1139,32 @@ func TestUserHandler_GET(t *testing.T) {
 
 func TestUserHandler_POST(t *testing.T) {
 	tt := []struct {
-		name       string
-		body       string
-		UUID       uuid.UUID
-		want       string
-		password   string
-		statusCode int
-		mockParams mockParams
+		name          string
+		body          string
+		want          string
+		statusCode    int
+		createAccount state.CreateAccountFunc
 	}{
 		{
-			name: "with valid AIM user",
-			body: `{"screen_name":"userA", "password":"thepassword"}`,
-			UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
-
+			name:       "with valid AIM user",
+			body:       `{"screen_name":"userA", "password":"thepassword"}`,
 			want:       `User account created successfully.`,
-			password:   "thepassword",
 			statusCode: http.StatusCreated,
-			mockParams: mockParams{
-				userManagerParams: userManagerParams{
-					insertUserParams: insertUserParams{
-						{
-							u: state.User{
-								AuthKey:           uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
-								DisplayScreenName: "userA",
-								IdentScreenName:   state.NewIdentScreenName("userA"),
-							},
-							err: nil,
-						},
-					},
-				},
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("userA"), screenName)
+				assert.Equal(t, "thepassword", password)
+				return nil
 			},
 		},
 		{
 			name:       "with valid ICQ user",
 			body:       `{"screen_name":"100003", "password":"thepass"}`,
-			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `User account created successfully.`,
-			password:   "thepass",
 			statusCode: http.StatusCreated,
-			mockParams: mockParams{
-				userManagerParams: userManagerParams{
-					insertUserParams: insertUserParams{
-						{
-							u: state.User{
-								AuthKey:           uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
-								DisplayScreenName: "100003",
-								IdentScreenName:   state.NewIdentScreenName("100003"),
-								IsICQ:             true,
-							},
-							err: nil,
-						},
-					},
-				},
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("100003"), screenName)
+				assert.Equal(t, "thepass", password)
+				return nil
 			},
 		},
 		{
@@ -1202,74 +1176,68 @@ func TestUserHandler_POST(t *testing.T) {
 		{
 			name:       "user handler error",
 			body:       `{"screen_name":"userA", "password":"thepassword"}`,
-			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `internal server error`,
-			password:   "thepassword",
 			statusCode: http.StatusInternalServerError,
-			mockParams: mockParams{
-				userManagerParams: userManagerParams{
-					insertUserParams: insertUserParams{
-						{
-							u: state.User{
-								AuthKey:           uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
-								DisplayScreenName: "userA",
-								IdentScreenName:   state.NewIdentScreenName("userA"),
-							},
-							err: io.EOF,
-						},
-					},
-				},
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("userA"), screenName)
+				assert.Equal(t, "thepassword", password)
+				return io.EOF
 			},
 		},
 		{
 			name:       "duplicate user",
 			body:       `{"screen_name":"userA", "password":"thepassword"}`,
-			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `user already exists`,
-			password:   "thepassword",
 			statusCode: http.StatusConflict,
-			mockParams: mockParams{
-				userManagerParams: userManagerParams{
-					insertUserParams: insertUserParams{
-						{
-							u: state.User{
-								AuthKey:           uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
-								DisplayScreenName: "userA",
-								IdentScreenName:   state.NewIdentScreenName("userA"),
-							},
-							err: state.ErrDupUser,
-						},
-					},
-				},
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("userA"), screenName)
+				assert.Equal(t, "thepassword", password)
+				return state.ErrDupUser
 			},
 		},
 		{
 			name:       "invalid AIM screen name",
 			body:       `{"screen_name":"a", "password":"thepassword"}`,
-			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `invalid screen name: screen name must be between 3 and 16 characters`,
 			statusCode: http.StatusBadRequest,
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("a"), screenName)
+				assert.Equal(t, "thepassword", password)
+				return state.ErrAIMHandleLength
+			},
 		},
 		{
 			name:       "invalid AIM password",
 			body:       `{"screen_name":"userA", "password":"1"}`,
-			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `invalid password: invalid password length: password length must be between 4-16 characters`,
 			statusCode: http.StatusBadRequest,
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("userA"), screenName)
+				assert.Equal(t, "1", password)
+				return fmt.Errorf("%w: password length must be between 4-16 characters", state.ErrPasswordInvalid)
+			},
 		},
 		{
 			name:       "invalid ICQ UIN",
 			body:       `{"screen_name":"1000", "password":"thepass"}`,
-			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `invalid uin: uin must be a number in the range 10000-2147483646`,
 			statusCode: http.StatusBadRequest,
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("1000"), screenName)
+				assert.Equal(t, "thepass", password)
+				return state.ErrICQUINInvalidFormat
+			},
 		},
 		{
 			name:       "invalid ICQ password",
 			body:       `{"screen_name":"100003", "password":"thelongpassword"}`,
-			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `invalid password: invalid password length: password must be between 6-8 characters`,
 			statusCode: http.StatusBadRequest,
+			createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
+				assert.Equal(t, state.DisplayScreenName("100003"), screenName)
+				assert.Equal(t, "thelongpassword", password)
+				return fmt.Errorf("%w: password must be between 6-8 characters", state.ErrPasswordInvalid)
+			},
 		},
 	}
 
@@ -1278,16 +1246,7 @@ func TestUserHandler_POST(t *testing.T) {
 			request := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(tc.body))
 			responseRecorder := httptest.NewRecorder()
 
-			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.userManagerParams.insertUserParams {
-				assert.NoError(t, params.u.HashPassword(tc.password))
-				userManager.EXPECT().
-					InsertUser(matchContext(), params.u).
-					Return(params.err)
-			}
-
-			newUUID := func() uuid.UUID { return tc.UUID }
-			postUserHandler(responseRecorder, request, userManager, newUUID, slog.Default())
+			postUserHandler(responseRecorder, request, tc.createAccount, slog.Default())
 
 			if responseRecorder.Code != tc.statusCode {
 				t.Errorf("want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)

+ 2 - 3
server/kerberos/kerberos.go

@@ -12,12 +12,11 @@ import (
 	"golang.org/x/sync/errgroup"
 
 	"github.com/mk6i/open-oscar-server/config"
-	"github.com/mk6i/open-oscar-server/state"
 	"github.com/mk6i/open-oscar-server/wire"
 )
 
 type AuthService interface {
-	KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)
+	KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string) (wire.SNACMessage, error)
 }
 
 func NewKerberosServer(listeners []config.Listener, logger *slog.Logger, authService AuthService) *Server {
@@ -112,7 +111,7 @@ func postHandler(w http.ResponseWriter, r *http.Request, authService AuthService
 		return
 	}
 
-	response, err := authService.KerberosLogin(r.Context(), body, state.NewStubUser, listenAddress)
+	response, err := authService.KerberosLogin(r.Context(), body, listenAddress)
 	if err != nil {
 		logger.Error("authService.KerberosLogin", "err", err.Error())
 		http.Error(w, "internal server error", http.StatusInternalServerError)

+ 1 - 1
server/kerberos/kerberos_test.go

@@ -201,7 +201,7 @@ func TestKerberosLoginHandler(t *testing.T) {
 				mockAuth := newMockAuthService(t)
 				if tt.expectLogin {
 					mockAuth.EXPECT().
-						KerberosLogin(mock.Anything, tt.request.Body, mock.Anything, mock.Anything).
+						KerberosLogin(mock.Anything, tt.request.Body, mock.Anything).
 						Return(tt.response, tt.responseErr)
 				}
 				srv = NewKerberosServer(tt.listeners, log, mockAuth)

+ 14 - 21
server/kerberos/mock_auth_test.go

@@ -7,7 +7,6 @@ package kerberos
 import (
 	"context"
 
-	"github.com/mk6i/open-oscar-server/state"
 	"github.com/mk6i/open-oscar-server/wire"
 	mock "github.com/stretchr/testify/mock"
 )
@@ -40,8 +39,8 @@ func (_m *mockAuthService) EXPECT() *mockAuthService_Expecter {
 }
 
 // KerberosLogin provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, inBody, newUserFn, advertisedHost)
+func (_mock *mockAuthService) KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string) (wire.SNACMessage, error) {
+	ret := _mock.Called(ctx, inBody, advertisedHost)
 
 	if len(ret) == 0 {
 		panic("no return value specified for KerberosLogin")
@@ -49,16 +48,16 @@ func (_mock *mockAuthService) KerberosLogin(ctx context.Context, inBody wire.SNA
 
 	var r0 wire.SNACMessage
 	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, string) (wire.SNACMessage, error)); ok {
+		return returnFunc(ctx, inBody, advertisedHost)
 	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, string) wire.SNACMessage); ok {
+		r0 = returnFunc(ctx, inBody, advertisedHost)
 	} else {
 		r0 = ret.Get(0).(wire.SNACMessage)
 	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) error); ok {
-		r1 = returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, string) error); ok {
+		r1 = returnFunc(ctx, inBody, advertisedHost)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -73,13 +72,12 @@ type mockAuthService_KerberosLogin_Call struct {
 // KerberosLogin is a helper method to define mock.On call
 //   - ctx context.Context
 //   - inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest
-//   - newUserFn func(screenName state.DisplayScreenName) (state.User, error)
 //   - advertisedHost string
-func (_e *mockAuthService_Expecter) KerberosLogin(ctx interface{}, inBody interface{}, newUserFn interface{}, advertisedHost interface{}) *mockAuthService_KerberosLogin_Call {
-	return &mockAuthService_KerberosLogin_Call{Call: _e.mock.On("KerberosLogin", ctx, inBody, newUserFn, advertisedHost)}
+func (_e *mockAuthService_Expecter) KerberosLogin(ctx interface{}, inBody interface{}, advertisedHost interface{}) *mockAuthService_KerberosLogin_Call {
+	return &mockAuthService_KerberosLogin_Call{Call: _e.mock.On("KerberosLogin", ctx, inBody, advertisedHost)}
 }
 
-func (_c *mockAuthService_KerberosLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string)) *mockAuthService_KerberosLogin_Call {
+func (_c *mockAuthService_KerberosLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string)) *mockAuthService_KerberosLogin_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		var arg0 context.Context
 		if args[0] != nil {
@@ -89,19 +87,14 @@ func (_c *mockAuthService_KerberosLogin_Call) Run(run func(ctx context.Context,
 		if args[1] != nil {
 			arg1 = args[1].(wire.SNAC_0x050C_0x0002_KerberosLoginRequest)
 		}
-		var arg2 func(screenName state.DisplayScreenName) (state.User, error)
+		var arg2 string
 		if args[2] != nil {
-			arg2 = args[2].(func(screenName state.DisplayScreenName) (state.User, error))
-		}
-		var arg3 string
-		if args[3] != nil {
-			arg3 = args[3].(string)
+			arg2 = args[2].(string)
 		}
 		run(
 			arg0,
 			arg1,
 			arg2,
-			arg3,
 		)
 	})
 	return _c
@@ -112,7 +105,7 @@ func (_c *mockAuthService_KerberosLogin_Call) Return(sNACMessage wire.SNACMessag
 	return _c
 }
 
-func (_c *mockAuthService_KerberosLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)) *mockAuthService_KerberosLogin_Call {
+func (_c *mockAuthService_KerberosLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string) (wire.SNACMessage, error)) *mockAuthService_KerberosLogin_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 42 - 60
server/oscar/mock_auth_test.go

@@ -113,8 +113,8 @@ func (_c *mockAuthService_BUCPChallenge_Call) RunAndReturn(run func(ctx context.
 }
 
 // BUCPLogin provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, inBody, newUserFn, advertisedHost)
+func (_mock *mockAuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error) {
+	ret := _mock.Called(ctx, inBody, advertisedHost)
 
 	if len(ret) == 0 {
 		panic("no return value specified for BUCPLogin")
@@ -122,16 +122,16 @@ func (_mock *mockAuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x
 
 	var r0 wire.SNACMessage
 	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, string) (wire.SNACMessage, error)); ok {
+		return returnFunc(ctx, inBody, advertisedHost)
 	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, string) wire.SNACMessage); ok {
+		r0 = returnFunc(ctx, inBody, advertisedHost)
 	} else {
 		r0 = ret.Get(0).(wire.SNACMessage)
 	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) error); ok {
-		r1 = returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, string) error); ok {
+		r1 = returnFunc(ctx, inBody, advertisedHost)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -146,13 +146,12 @@ type mockAuthService_BUCPLogin_Call struct {
 // BUCPLogin is a helper method to define mock.On call
 //   - ctx context.Context
 //   - inBody wire.SNAC_0x17_0x02_BUCPLoginRequest
-//   - newUserFn func(screenName state.DisplayScreenName) (state.User, error)
 //   - advertisedHost string
-func (_e *mockAuthService_Expecter) BUCPLogin(ctx interface{}, inBody interface{}, newUserFn interface{}, advertisedHost interface{}) *mockAuthService_BUCPLogin_Call {
-	return &mockAuthService_BUCPLogin_Call{Call: _e.mock.On("BUCPLogin", ctx, inBody, newUserFn, advertisedHost)}
+func (_e *mockAuthService_Expecter) BUCPLogin(ctx interface{}, inBody interface{}, advertisedHost interface{}) *mockAuthService_BUCPLogin_Call {
+	return &mockAuthService_BUCPLogin_Call{Call: _e.mock.On("BUCPLogin", ctx, inBody, advertisedHost)}
 }
 
-func (_c *mockAuthService_BUCPLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string)) *mockAuthService_BUCPLogin_Call {
+func (_c *mockAuthService_BUCPLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string)) *mockAuthService_BUCPLogin_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		var arg0 context.Context
 		if args[0] != nil {
@@ -162,19 +161,14 @@ func (_c *mockAuthService_BUCPLogin_Call) Run(run func(ctx context.Context, inBo
 		if args[1] != nil {
 			arg1 = args[1].(wire.SNAC_0x17_0x02_BUCPLoginRequest)
 		}
-		var arg2 func(screenName state.DisplayScreenName) (state.User, error)
+		var arg2 string
 		if args[2] != nil {
-			arg2 = args[2].(func(screenName state.DisplayScreenName) (state.User, error))
-		}
-		var arg3 string
-		if args[3] != nil {
-			arg3 = args[3].(string)
+			arg2 = args[2].(string)
 		}
 		run(
 			arg0,
 			arg1,
 			arg2,
-			arg3,
 		)
 	})
 	return _c
@@ -185,7 +179,7 @@ func (_c *mockAuthService_BUCPLogin_Call) Return(sNACMessage wire.SNACMessage, e
 	return _c
 }
 
-func (_c *mockAuthService_BUCPLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)) *mockAuthService_BUCPLogin_Call {
+func (_c *mockAuthService_BUCPLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error)) *mockAuthService_BUCPLogin_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -251,8 +245,8 @@ func (_c *mockAuthService_CrackCookie_Call) RunAndReturn(run func(authCookie []b
 }
 
 // FLAPLogin provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error) {
-	ret := _mock.Called(ctx, inFrame, newUserFn, advertisedHost)
+func (_mock *mockAuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error) {
+	ret := _mock.Called(ctx, inFrame, advertisedHost)
 
 	if len(ret) == 0 {
 		panic("no return value specified for FLAPLogin")
@@ -260,16 +254,16 @@ func (_mock *mockAuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSi
 
 	var r0 wire.TLVRestBlock
 	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, func(screenName state.DisplayScreenName) (state.User, error), string) (wire.TLVRestBlock, error)); ok {
-		return returnFunc(ctx, inFrame, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, string) (wire.TLVRestBlock, error)); ok {
+		return returnFunc(ctx, inFrame, advertisedHost)
 	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, func(screenName state.DisplayScreenName) (state.User, error), string) wire.TLVRestBlock); ok {
-		r0 = returnFunc(ctx, inFrame, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, string) wire.TLVRestBlock); ok {
+		r0 = returnFunc(ctx, inFrame, advertisedHost)
 	} else {
 		r0 = ret.Get(0).(wire.TLVRestBlock)
 	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.FLAPSignonFrame, func(screenName state.DisplayScreenName) (state.User, error), string) error); ok {
-		r1 = returnFunc(ctx, inFrame, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.FLAPSignonFrame, string) error); ok {
+		r1 = returnFunc(ctx, inFrame, advertisedHost)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -284,13 +278,12 @@ type mockAuthService_FLAPLogin_Call struct {
 // FLAPLogin is a helper method to define mock.On call
 //   - ctx context.Context
 //   - inFrame wire.FLAPSignonFrame
-//   - newUserFn func(screenName state.DisplayScreenName) (state.User, error)
 //   - advertisedHost string
-func (_e *mockAuthService_Expecter) FLAPLogin(ctx interface{}, inFrame interface{}, newUserFn interface{}, advertisedHost interface{}) *mockAuthService_FLAPLogin_Call {
-	return &mockAuthService_FLAPLogin_Call{Call: _e.mock.On("FLAPLogin", ctx, inFrame, newUserFn, advertisedHost)}
+func (_e *mockAuthService_Expecter) FLAPLogin(ctx interface{}, inFrame interface{}, advertisedHost interface{}) *mockAuthService_FLAPLogin_Call {
+	return &mockAuthService_FLAPLogin_Call{Call: _e.mock.On("FLAPLogin", ctx, inFrame, advertisedHost)}
 }
 
-func (_c *mockAuthService_FLAPLogin_Call) Run(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string)) *mockAuthService_FLAPLogin_Call {
+func (_c *mockAuthService_FLAPLogin_Call) Run(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string)) *mockAuthService_FLAPLogin_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		var arg0 context.Context
 		if args[0] != nil {
@@ -300,19 +293,14 @@ func (_c *mockAuthService_FLAPLogin_Call) Run(run func(ctx context.Context, inFr
 		if args[1] != nil {
 			arg1 = args[1].(wire.FLAPSignonFrame)
 		}
-		var arg2 func(screenName state.DisplayScreenName) (state.User, error)
+		var arg2 string
 		if args[2] != nil {
-			arg2 = args[2].(func(screenName state.DisplayScreenName) (state.User, error))
-		}
-		var arg3 string
-		if args[3] != nil {
-			arg3 = args[3].(string)
+			arg2 = args[2].(string)
 		}
 		run(
 			arg0,
 			arg1,
 			arg2,
-			arg3,
 		)
 	})
 	return _c
@@ -323,14 +311,14 @@ func (_c *mockAuthService_FLAPLogin_Call) Return(tLVRestBlock wire.TLVRestBlock,
 	return _c
 }
 
-func (_c *mockAuthService_FLAPLogin_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error)) *mockAuthService_FLAPLogin_Call {
+func (_c *mockAuthService_FLAPLogin_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error)) *mockAuthService_FLAPLogin_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // KerberosLogin provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, inBody, newUserFn, advertisedHost)
+func (_mock *mockAuthService) KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string) (wire.SNACMessage, error) {
+	ret := _mock.Called(ctx, inBody, advertisedHost)
 
 	if len(ret) == 0 {
 		panic("no return value specified for KerberosLogin")
@@ -338,16 +326,16 @@ func (_mock *mockAuthService) KerberosLogin(ctx context.Context, inBody wire.SNA
 
 	var r0 wire.SNACMessage
 	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, string) (wire.SNACMessage, error)); ok {
+		return returnFunc(ctx, inBody, advertisedHost)
 	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, string) wire.SNACMessage); ok {
+		r0 = returnFunc(ctx, inBody, advertisedHost)
 	} else {
 		r0 = ret.Get(0).(wire.SNACMessage)
 	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) error); ok {
-		r1 = returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x050C_0x0002_KerberosLoginRequest, string) error); ok {
+		r1 = returnFunc(ctx, inBody, advertisedHost)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -362,13 +350,12 @@ type mockAuthService_KerberosLogin_Call struct {
 // KerberosLogin is a helper method to define mock.On call
 //   - ctx context.Context
 //   - inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest
-//   - newUserFn func(screenName state.DisplayScreenName) (state.User, error)
 //   - advertisedHost string
-func (_e *mockAuthService_Expecter) KerberosLogin(ctx interface{}, inBody interface{}, newUserFn interface{}, advertisedHost interface{}) *mockAuthService_KerberosLogin_Call {
-	return &mockAuthService_KerberosLogin_Call{Call: _e.mock.On("KerberosLogin", ctx, inBody, newUserFn, advertisedHost)}
+func (_e *mockAuthService_Expecter) KerberosLogin(ctx interface{}, inBody interface{}, advertisedHost interface{}) *mockAuthService_KerberosLogin_Call {
+	return &mockAuthService_KerberosLogin_Call{Call: _e.mock.On("KerberosLogin", ctx, inBody, advertisedHost)}
 }
 
-func (_c *mockAuthService_KerberosLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string)) *mockAuthService_KerberosLogin_Call {
+func (_c *mockAuthService_KerberosLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string)) *mockAuthService_KerberosLogin_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		var arg0 context.Context
 		if args[0] != nil {
@@ -378,19 +365,14 @@ func (_c *mockAuthService_KerberosLogin_Call) Run(run func(ctx context.Context,
 		if args[1] != nil {
 			arg1 = args[1].(wire.SNAC_0x050C_0x0002_KerberosLoginRequest)
 		}
-		var arg2 func(screenName state.DisplayScreenName) (state.User, error)
+		var arg2 string
 		if args[2] != nil {
-			arg2 = args[2].(func(screenName state.DisplayScreenName) (state.User, error))
-		}
-		var arg3 string
-		if args[3] != nil {
-			arg3 = args[3].(string)
+			arg2 = args[2].(string)
 		}
 		run(
 			arg0,
 			arg1,
 			arg2,
-			arg3,
 		)
 	})
 	return _c
@@ -401,7 +383,7 @@ func (_c *mockAuthService_KerberosLogin_Call) Return(sNACMessage wire.SNACMessag
 	return _c
 }
 
-func (_c *mockAuthService_KerberosLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)) *mockAuthService_KerberosLogin_Call {
+func (_c *mockAuthService_KerberosLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string) (wire.SNACMessage, error)) *mockAuthService_KerberosLogin_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 2 - 2
server/oscar/server.go

@@ -457,7 +457,7 @@ func (s oscarServer) processFLAPAuth(
 	flapc *wire.FlapClient,
 	advertisedHost string,
 ) error {
-	tlv, err := s.AuthService.FLAPLogin(ctx, signonFrame, state.NewStubUser, advertisedHost)
+	tlv, err := s.AuthService.FLAPLogin(ctx, signonFrame, advertisedHost)
 	if err != nil {
 		return err
 	}
@@ -515,7 +515,7 @@ func (s oscarServer) processBUCPAuth(ctx context.Context, flapc *wire.FlapClient
 				if err := wire.UnmarshalBE(&loginRequest, buf); err != nil {
 					return err
 				}
-				outSNAC, err := s.BUCPLogin(ctx, loginRequest, state.NewStubUser, advertisedHost)
+				outSNAC, err := s.BUCPLogin(ctx, loginRequest, advertisedHost)
 				if err != nil {
 					return err
 				}

+ 2 - 2
server/oscar/server_test.go

@@ -222,7 +222,7 @@ func TestOscarServer_RouteConnection_Auth_BUCP(t *testing.T) {
 			Body: wire.SNAC_0x17_0x07_BUCPChallengeResponse{},
 		}, nil)
 	authService.EXPECT().
-		BUCPLogin(matchContext(), mock.Anything, mock.Anything, "localhost:5190").
+		BUCPLogin(matchContext(), mock.Anything, "localhost:5190").
 		Return(wire.SNACMessage{
 			Frame: wire.SNACFrame{
 				FoodGroup: wire.BUCP,
@@ -297,7 +297,7 @@ func TestOscarServer_RouteConnection_Auth_FLAP(t *testing.T) {
 
 	authService := newMockAuthService(t)
 	authService.EXPECT().
-		FLAPLogin(matchContext(), mock.Anything, mock.Anything, "localhost:5190").
+		FLAPLogin(matchContext(), mock.Anything, "localhost:5190").
 		Return(wire.TLVRestBlock{
 			TLVList: []wire.TLV{
 				wire.NewTLVBE(wire.LoginTLVTagsScreenName, "testuser"),

+ 3 - 3
server/oscar/types.go

@@ -47,10 +47,10 @@ type RateLimitUpdater interface {
 
 type AuthService interface {
 	BUCPChallenge(ctx context.Context, inBody wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error)
-	BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)
+	BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error)
 	CrackCookie(authCookie []byte) (state.ServerCookie, error)
-	FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error)
-	KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)
+	FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error)
+	KerberosLogin(ctx context.Context, inBody wire.SNAC_0x050C_0x0002_KerberosLoginRequest, advertisedHost string) (wire.SNACMessage, error)
 	RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)
 	RegisterChatSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)
 	RetrieveBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)

+ 1 - 1
server/toc/cmd_client.go

@@ -1675,7 +1675,7 @@ func (s OSCARProxy) Signon(ctx context.Context, args []byte) (*state.SessionInst
 	signonFrame.Append(wire.NewTLVBE(wire.LoginTLVTagsScreenName, userName))
 	signonFrame.Append(wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, passwordHash))
 
-	block, err := s.AuthService.FLAPLogin(ctx, signonFrame, state.NewStubUser, "")
+	block, err := s.AuthService.FLAPLogin(ctx, signonFrame, "")
 	if err != nil {
 		return nil, []string{s.runtimeErr(ctx, fmt.Errorf("AuthService.FLAPLogin: %w", err))}
 	}

+ 2 - 9
server/toc/cmd_client_test.go

@@ -3864,7 +3864,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 									},
 								},
 							},
-							newUserFn: state.NewStubUser,
 							tlv: wire.TLVRestBlock{
 								TLVList: wire.TLVList{
 									wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
@@ -3920,8 +3919,7 @@ func TestOSCARProxy_Signon(t *testing.T) {
 									},
 								},
 							},
-							newUserFn: state.NewStubUser,
-							err:       io.EOF,
+							err: io.EOF,
 						},
 					},
 				},
@@ -3943,7 +3941,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 									},
 								},
 							},
-							newUserFn: state.NewStubUser,
 							tlv: wire.TLVRestBlock{
 								TLVList: wire.TLVList{
 									wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
@@ -3982,7 +3979,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 									},
 								},
 							},
-							newUserFn: state.NewStubUser,
 							tlv: wire.TLVRestBlock{
 								TLVList: wire.TLVList{
 									wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
@@ -4029,7 +4025,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 									},
 								},
 							},
-							newUserFn: state.NewStubUser,
 							tlv: wire.TLVRestBlock{
 								TLVList: wire.TLVList{
 									wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
@@ -4083,7 +4078,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 									},
 								},
 							},
-							newUserFn: state.NewStubUser,
 							tlv: wire.TLVRestBlock{
 								TLVList: wire.TLVList{
 									wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
@@ -4137,7 +4131,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 									},
 								},
 							},
-							newUserFn: state.NewStubUser,
 							tlv: wire.TLVRestBlock{
 								TLVList: wire.TLVList{
 									wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword),
@@ -4163,7 +4156,7 @@ func TestOSCARProxy_Signon(t *testing.T) {
 			authSvc := newMockAuthService(t)
 			for _, params := range tc.mockParams.flapLoginParams {
 				authSvc.EXPECT().
-					FLAPLogin(matchContext(), params.frame, mock.Anything, "").
+					FLAPLogin(matchContext(), params.frame, "").
 					Return(params.tlv, params.err)
 			}
 			for _, params := range tc.mockParams.crackCookieParams {

+ 3 - 4
server/toc/helpers_test.go

@@ -119,10 +119,9 @@ type oServiceParams struct {
 }
 
 type flapLoginParams []struct {
-	frame     wire.FLAPSignonFrame
-	newUserFn func(screenName state.DisplayScreenName) (state.User, error)
-	tlv       wire.TLVRestBlock
-	err       error
+	frame wire.FLAPSignonFrame
+	tlv   wire.TLVRestBlock
+	err   error
 }
 
 type registerBOSSessionParams []struct {

+ 28 - 40
server/toc/mock_auth_service_test.go

@@ -113,8 +113,8 @@ func (_c *mockAuthService_BUCPChallenge_Call) RunAndReturn(run func(ctx context.
 }
 
 // BUCPLogin provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, inBody, newUserFn, advertisedHost)
+func (_mock *mockAuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error) {
+	ret := _mock.Called(ctx, inBody, advertisedHost)
 
 	if len(ret) == 0 {
 		panic("no return value specified for BUCPLogin")
@@ -122,16 +122,16 @@ func (_mock *mockAuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x
 
 	var r0 wire.SNACMessage
 	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, string) (wire.SNACMessage, error)); ok {
+		return returnFunc(ctx, inBody, advertisedHost)
 	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, string) wire.SNACMessage); ok {
+		r0 = returnFunc(ctx, inBody, advertisedHost)
 	} else {
 		r0 = ret.Get(0).(wire.SNACMessage)
 	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, func(screenName state.DisplayScreenName) (state.User, error), string) error); ok {
-		r1 = returnFunc(ctx, inBody, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, string) error); ok {
+		r1 = returnFunc(ctx, inBody, advertisedHost)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -146,13 +146,12 @@ type mockAuthService_BUCPLogin_Call struct {
 // BUCPLogin is a helper method to define mock.On call
 //   - ctx context.Context
 //   - inBody wire.SNAC_0x17_0x02_BUCPLoginRequest
-//   - newUserFn func(screenName state.DisplayScreenName) (state.User, error)
 //   - advertisedHost string
-func (_e *mockAuthService_Expecter) BUCPLogin(ctx interface{}, inBody interface{}, newUserFn interface{}, advertisedHost interface{}) *mockAuthService_BUCPLogin_Call {
-	return &mockAuthService_BUCPLogin_Call{Call: _e.mock.On("BUCPLogin", ctx, inBody, newUserFn, advertisedHost)}
+func (_e *mockAuthService_Expecter) BUCPLogin(ctx interface{}, inBody interface{}, advertisedHost interface{}) *mockAuthService_BUCPLogin_Call {
+	return &mockAuthService_BUCPLogin_Call{Call: _e.mock.On("BUCPLogin", ctx, inBody, advertisedHost)}
 }
 
-func (_c *mockAuthService_BUCPLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string)) *mockAuthService_BUCPLogin_Call {
+func (_c *mockAuthService_BUCPLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string)) *mockAuthService_BUCPLogin_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		var arg0 context.Context
 		if args[0] != nil {
@@ -162,19 +161,14 @@ func (_c *mockAuthService_BUCPLogin_Call) Run(run func(ctx context.Context, inBo
 		if args[1] != nil {
 			arg1 = args[1].(wire.SNAC_0x17_0x02_BUCPLoginRequest)
 		}
-		var arg2 func(screenName state.DisplayScreenName) (state.User, error)
+		var arg2 string
 		if args[2] != nil {
-			arg2 = args[2].(func(screenName state.DisplayScreenName) (state.User, error))
-		}
-		var arg3 string
-		if args[3] != nil {
-			arg3 = args[3].(string)
+			arg2 = args[2].(string)
 		}
 		run(
 			arg0,
 			arg1,
 			arg2,
-			arg3,
 		)
 	})
 	return _c
@@ -185,7 +179,7 @@ func (_c *mockAuthService_BUCPLogin_Call) Return(sNACMessage wire.SNACMessage, e
 	return _c
 }
 
-func (_c *mockAuthService_BUCPLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)) *mockAuthService_BUCPLogin_Call {
+func (_c *mockAuthService_BUCPLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error)) *mockAuthService_BUCPLogin_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -251,8 +245,8 @@ func (_c *mockAuthService_CrackCookie_Call) RunAndReturn(run func(authCookie []b
 }
 
 // FLAPLogin provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error) {
-	ret := _mock.Called(ctx, inFrame, newUserFn, advertisedHost)
+func (_mock *mockAuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error) {
+	ret := _mock.Called(ctx, inFrame, advertisedHost)
 
 	if len(ret) == 0 {
 		panic("no return value specified for FLAPLogin")
@@ -260,16 +254,16 @@ func (_mock *mockAuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSi
 
 	var r0 wire.TLVRestBlock
 	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, func(screenName state.DisplayScreenName) (state.User, error), string) (wire.TLVRestBlock, error)); ok {
-		return returnFunc(ctx, inFrame, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, string) (wire.TLVRestBlock, error)); ok {
+		return returnFunc(ctx, inFrame, advertisedHost)
 	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, func(screenName state.DisplayScreenName) (state.User, error), string) wire.TLVRestBlock); ok {
-		r0 = returnFunc(ctx, inFrame, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, string) wire.TLVRestBlock); ok {
+		r0 = returnFunc(ctx, inFrame, advertisedHost)
 	} else {
 		r0 = ret.Get(0).(wire.TLVRestBlock)
 	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.FLAPSignonFrame, func(screenName state.DisplayScreenName) (state.User, error), string) error); ok {
-		r1 = returnFunc(ctx, inFrame, newUserFn, advertisedHost)
+	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.FLAPSignonFrame, string) error); ok {
+		r1 = returnFunc(ctx, inFrame, advertisedHost)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -284,13 +278,12 @@ type mockAuthService_FLAPLogin_Call struct {
 // FLAPLogin is a helper method to define mock.On call
 //   - ctx context.Context
 //   - inFrame wire.FLAPSignonFrame
-//   - newUserFn func(screenName state.DisplayScreenName) (state.User, error)
 //   - advertisedHost string
-func (_e *mockAuthService_Expecter) FLAPLogin(ctx interface{}, inFrame interface{}, newUserFn interface{}, advertisedHost interface{}) *mockAuthService_FLAPLogin_Call {
-	return &mockAuthService_FLAPLogin_Call{Call: _e.mock.On("FLAPLogin", ctx, inFrame, newUserFn, advertisedHost)}
+func (_e *mockAuthService_Expecter) FLAPLogin(ctx interface{}, inFrame interface{}, advertisedHost interface{}) *mockAuthService_FLAPLogin_Call {
+	return &mockAuthService_FLAPLogin_Call{Call: _e.mock.On("FLAPLogin", ctx, inFrame, advertisedHost)}
 }
 
-func (_c *mockAuthService_FLAPLogin_Call) Run(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string)) *mockAuthService_FLAPLogin_Call {
+func (_c *mockAuthService_FLAPLogin_Call) Run(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string)) *mockAuthService_FLAPLogin_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		var arg0 context.Context
 		if args[0] != nil {
@@ -300,19 +293,14 @@ func (_c *mockAuthService_FLAPLogin_Call) Run(run func(ctx context.Context, inFr
 		if args[1] != nil {
 			arg1 = args[1].(wire.FLAPSignonFrame)
 		}
-		var arg2 func(screenName state.DisplayScreenName) (state.User, error)
+		var arg2 string
 		if args[2] != nil {
-			arg2 = args[2].(func(screenName state.DisplayScreenName) (state.User, error))
-		}
-		var arg3 string
-		if args[3] != nil {
-			arg3 = args[3].(string)
+			arg2 = args[2].(string)
 		}
 		run(
 			arg0,
 			arg1,
 			arg2,
-			arg3,
 		)
 	})
 	return _c
@@ -323,7 +311,7 @@ func (_c *mockAuthService_FLAPLogin_Call) Return(tLVRestBlock wire.TLVRestBlock,
 	return _c
 }
 
-func (_c *mockAuthService_FLAPLogin_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error)) *mockAuthService_FLAPLogin_Call {
+func (_c *mockAuthService_FLAPLogin_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error)) *mockAuthService_FLAPLogin_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 2 - 2
server/toc/types.go

@@ -44,9 +44,9 @@ type OServiceService interface {
 
 type AuthService interface {
 	BUCPChallenge(ctx context.Context, inBody wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error)
-	BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)
+	BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error)
 	CrackCookie(authCookie []byte) (state.ServerCookie, error)
-	FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error)
+	FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error)
 	RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)
 	RegisterChatSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)
 	RetrieveBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)

+ 1 - 1
server/webapi/handlers/session.go

@@ -33,7 +33,7 @@ type SessionHandler struct {
 // AuthService defines methods needed for authentication.
 type AuthService interface {
 	BUCPChallenge(ctx context.Context, bodyIn wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error)
-	BUCPLogin(ctx context.Context, bodyIn wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)
+	BUCPLogin(ctx context.Context, bodyIn wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error)
 	RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)
 }
 

+ 2 - 2
server/webapi/types.go

@@ -45,9 +45,9 @@ type OServiceService interface {
 
 type AuthService interface {
 	BUCPChallenge(ctx context.Context, inBody wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error)
-	BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.SNACMessage, error)
+	BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error)
 	CrackCookie(authCookie []byte) (state.ServerCookie, error)
-	FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error), advertisedHost string) (wire.TLVRestBlock, error)
+	FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error)
 	RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)
 	RegisterChatSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)
 	RetrieveBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)

+ 50 - 0
state/account.go

@@ -0,0 +1,50 @@
+package state
+
+import (
+	"context"
+
+	"github.com/google/uuid"
+)
+
+// CreateAccountFunc creates a new user account in the database.
+//
+// Possible errors:
+//   - ErrAIMHandleInvalidFormat: screen name doesn't start with a letter, ends with a space, or contains invalid
+//     characters
+//   - ErrAIMHandleLength: screen name has less than 3 non-space characters or more than 16 characters
+//   - ErrICQUINInvalidFormat: UIN is not a number or is not in the valid range (10000-2147483646)
+//   - ErrPasswordInvalid: password length is invalid (AIM: 4-16 chars, ICQ: 6-8 chars)
+//   - ErrDupUser: a user with the same screen name already exists
+//   - Other errors from the underlying user store (e.g., database errors)
+type CreateAccountFunc func(ctx context.Context, screenName DisplayScreenName, password string) error
+
+// NewAccountCreator returns an account creation function.
+func NewAccountCreator(insertUser func(ctx context.Context, u User) error) CreateAccountFunc {
+	return func(ctx context.Context, screenName DisplayScreenName, password string) error {
+		if screenName.IsUIN() {
+			if err := screenName.ValidateUIN(); err != nil {
+				return err
+			}
+		} else {
+			if err := screenName.ValidateAIMHandle(); err != nil {
+				return err
+			}
+		}
+
+		user := User{
+			AuthKey:           uuid.NewString(),
+			DisplayScreenName: screenName,
+			IdentScreenName:   screenName.IdentScreenName(),
+			IsICQ:             screenName.IsUIN(),
+		}
+		if err := user.HashPassword(password); err != nil {
+			return err
+		}
+
+		if err := insertUser(ctx, user); err != nil {
+			return err
+		}
+
+		return nil
+	}
+}

+ 122 - 0
state/account_test.go

@@ -0,0 +1,122 @@
+package state
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewAccountCreator(t *testing.T) {
+	tests := []struct {
+		name          string
+		screenName    DisplayScreenName
+		password      string
+		insertUserErr error
+		wantErr       error
+		validateUser  func(*testing.T, User)
+	}{
+		{
+			name:       "Success with valid AIM handle",
+			screenName: "TestUser123",
+			password:   "validpass123",
+			wantErr:    nil,
+			validateUser: func(t *testing.T, u User) {
+				assert.Equal(t, DisplayScreenName("TestUser123"), u.DisplayScreenName)
+				assert.Equal(t, NewIdentScreenName("testuser123"), u.IdentScreenName)
+				assert.False(t, u.IsICQ)
+				assert.NotEmpty(t, u.AuthKey)
+				assert.NotNil(t, u.WeakMD5Pass)
+				assert.NotNil(t, u.StrongMD5Pass)
+			},
+		},
+		{
+			name:       "Success with valid UIN",
+			screenName: "12345678",
+			password:   "valid12",
+			wantErr:    nil,
+			validateUser: func(t *testing.T, u User) {
+				assert.Equal(t, DisplayScreenName("12345678"), u.DisplayScreenName)
+				assert.Equal(t, NewIdentScreenName("12345678"), u.IdentScreenName)
+				assert.True(t, u.IsICQ)
+				assert.NotEmpty(t, u.AuthKey)
+				assert.NotNil(t, u.WeakMD5Pass)
+				assert.NotNil(t, u.StrongMD5Pass)
+			},
+		},
+		{
+			name:       "Success with AIM handle containing spaces",
+			screenName: "Test User 123",
+			password:   "validpass123",
+			wantErr:    nil,
+			validateUser: func(t *testing.T, u User) {
+				assert.Equal(t, DisplayScreenName("Test User 123"), u.DisplayScreenName)
+				assert.Equal(t, NewIdentScreenName("test user 123"), u.IdentScreenName)
+				assert.False(t, u.IsICQ)
+			},
+		},
+		{
+			name:       "Invalid AIM handle - too short",
+			screenName: "Us",
+			password:   "validpass123",
+			wantErr:    ErrAIMHandleLength,
+		},
+		{
+			name:       "Invalid AIM handle - starts with number",
+			screenName: "1User",
+			password:   "validpass123",
+			wantErr:    ErrAIMHandleInvalidFormat,
+		},
+		{
+			name:       "Invalid AIM handle - ends with space",
+			screenName: "User123 ",
+			password:   "validpass123",
+			wantErr:    ErrAIMHandleInvalidFormat,
+		},
+		{
+			name:       "Invalid UIN - too small",
+			screenName: "9999",
+			password:   "valid12",
+			wantErr:    ErrICQUINInvalidFormat,
+		},
+		{
+			name:       "Invalid UIN - too large",
+			screenName: "2147483647",
+			password:   "valid12",
+			wantErr:    ErrICQUINInvalidFormat,
+		},
+		{
+			name:       "Invalid UIN - contains non-digits",
+			screenName: "12345abc",
+			password:   "valid12",
+			wantErr:    ErrAIMHandleInvalidFormat, // Will be validated as AIM handle since IsUIN() returns false
+		},
+		{
+			name:          "insertUser error",
+			screenName:    "TestUser123",
+			password:      "validpass123",
+			insertUserErr: assert.AnError,
+			wantErr:       assert.AnError,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var capturedUser User
+
+			insertUser := func(ctx context.Context, u User) error {
+				capturedUser = u
+				return tt.insertUserErr
+			}
+
+			createAccount := NewAccountCreator(insertUser)
+			err := createAccount(context.Background(), tt.screenName, tt.password)
+
+			assert.ErrorIs(t, err, tt.wantErr)
+
+			if tt.validateUser != nil {
+				tt.validateUser(t, capturedUser)
+			}
+		})
+	}
+}

+ 0 - 19
state/user.go

@@ -9,8 +9,6 @@ import (
 	"time"
 	"unicode"
 
-	"github.com/google/uuid"
-
 	"github.com/mk6i/open-oscar-server/wire"
 )
 
@@ -133,23 +131,6 @@ func (s DisplayScreenName) String() string {
 	return string(s)
 }
 
-// NewStubUser creates a new user with canned credentials. The default password
-// is "welcome1". This is typically used for development purposes.
-func NewStubUser(screenName DisplayScreenName) (User, error) {
-	uid, err := uuid.NewRandom()
-	if err != nil {
-		return User{}, err
-	}
-	u := User{
-		IdentScreenName:   NewIdentScreenName(string(screenName)),
-		DisplayScreenName: screenName,
-		AuthKey:           uid.String(),
-		IsICQ:             screenName.IsUIN(),
-	}
-	err = u.HashPassword("welcome1")
-	return u, err
-}
-
 // User represents a user account.
 type User struct {
 	// IdentScreenName is the AIM screen name.

+ 23 - 20
state/user_store_test.go

@@ -10,6 +10,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/google/uuid"
+
 	"github.com/mk6i/open-oscar-server/wire"
 
 	"github.com/stretchr/testify/assert"
@@ -567,20 +569,6 @@ func TestSQLiteUserStore_DeleteUser_DeleteNonExistentUser(t *testing.T) {
 	assert.ErrorIs(t, ErrNoUser, err)
 }
 
-func TestNewStubUser(t *testing.T) {
-	have, err := NewStubUser("userA")
-	assert.NoError(t, err)
-
-	want := User{
-		IdentScreenName:   NewIdentScreenName("userA"),
-		DisplayScreenName: "userA",
-		AuthKey:           have.AuthKey,
-	}
-	assert.NoError(t, want.HashPassword("welcome1"))
-
-	assert.Equal(t, want, have)
-}
-
 func newFeedbagItem(classID uint16, itemID uint16, name string) wire.FeedbagItem {
 	return wire.FeedbagItem{
 		ClassID: classID,
@@ -2022,8 +2010,13 @@ func TestSQLiteUserStore_RetrieveMessages(t *testing.T) {
 
 	createStubUser := func(t *testing.T, store SQLiteUserStore, screenName DisplayScreenName) {
 		t.Helper()
-		user, err := NewStubUser(screenName)
-		require.NoError(t, err)
+		user := User{
+			IdentScreenName:   NewIdentScreenName(string(screenName)),
+			DisplayScreenName: screenName,
+			AuthKey:           uuid.New().String(),
+			IsICQ:             screenName.IsUIN(),
+		}
+		require.NoError(t, user.HashPassword("welcome1"))
 		require.NoError(t, store.InsertUser(context.Background(), user))
 	}
 
@@ -2099,8 +2092,13 @@ func TestSQLiteUserStore_DeleteMessages(t *testing.T) {
 
 	createStubUser := func(t *testing.T, store SQLiteUserStore, screenName DisplayScreenName) {
 		t.Helper()
-		user, err := NewStubUser(screenName)
-		require.NoError(t, err)
+		user := User{
+			IdentScreenName:   NewIdentScreenName(string(screenName)),
+			DisplayScreenName: screenName,
+			AuthKey:           uuid.New().String(),
+			IsICQ:             screenName.IsUIN(),
+		}
+		require.NoError(t, user.HashPassword("welcome1"))
 		require.NoError(t, store.InsertUser(context.Background(), user))
 	}
 
@@ -2183,8 +2181,13 @@ func TestSQLiteUserStore_SaveMessage(t *testing.T) {
 
 	createStubUser := func(t *testing.T, store SQLiteUserStore, screenName DisplayScreenName) {
 		t.Helper()
-		user, err := NewStubUser(screenName)
-		require.NoError(t, err)
+		user := User{
+			IdentScreenName:   NewIdentScreenName(string(screenName)),
+			DisplayScreenName: screenName,
+			AuthKey:           uuid.New().String(),
+			IsICQ:             screenName.IsUIN(),
+		}
+		require.NoError(t, user.HashPassword("welcome1"))
 		require.NoError(t, store.InsertUser(context.Background(), user))
 	}
 	createStubUser(t, *store, DisplayScreenName("Sender"))