diff --git a/backend/internal/features/users/controllers/management_controller_test.go b/backend/internal/features/users/controllers/management_controller_test.go index 47595d6..a68ef31 100644 --- a/backend/internal/features/users/controllers/management_controller_test.go +++ b/backend/internal/features/users/controllers/management_controller_test.go @@ -726,41 +726,28 @@ func Test_InviteUserToWorkspace_MembershipReceivedAfterSignUp(t *testing.T) { assert.Equal(t, workspaces_dto.AddStatusInvited, inviteResponse.Status) - // 3. Sign up the invited user + // 3. Sign up the invited user (now returns token directly) signUpRequest := users_dto.SignUpRequestDTO{ Email: inviteEmail, Password: "testpassword123", Name: "Invited User", } - resp := test_utils.MakePostRequest( + var signInResponse users_dto.SignInResponseDTO + test_utils.MakePostRequestAndUnmarshal( t, router, "/api/v1/users/signup", "", signUpRequest, http.StatusOK, - ) - assert.Contains(t, string(resp.Body), "User created successfully") - - // 4. Sign in the newly registered user - signInRequest := users_dto.SignInRequestDTO{ - Email: inviteEmail, - Password: "testpassword123", - } - - var signInResponse users_dto.SignInResponseDTO - test_utils.MakePostRequestAndUnmarshal( - t, - router, - "/api/v1/users/signin", - "", - signInRequest, - http.StatusOK, &signInResponse, ) - // 5. Verify user is automatically added as member to workspace + assert.NotEmpty(t, signInResponse.Token) + assert.Equal(t, inviteEmail, signInResponse.Email) + + // 4. Verify user is automatically added as member to workspace var membersResponse workspaces_dto.GetMembersResponseDTO test_utils.MakeGetRequestAndUnmarshal( t, diff --git a/backend/internal/features/users/controllers/user_controller.go b/backend/internal/features/users/controllers/user_controller.go index df7d401..0845583 100644 --- a/backend/internal/features/users/controllers/user_controller.go +++ b/backend/internal/features/users/controllers/user_controller.go @@ -52,7 +52,7 @@ func (c *UserController) RegisterProtectedRoutes(router *gin.RouterGroup) { // @Accept json // @Produce json // @Param request body users_dto.SignUpRequestDTO true "User signup data" -// @Success 200 +// @Success 200 {object} users_dto.SignInResponseDTO // @Failure 400 // @Router /users/signup [post] func (c *UserController) SignUp(ctx *gin.Context) { @@ -84,13 +84,19 @@ func (c *UserController) SignUp(ctx *gin.Context) { } } - err := c.userService.SignUp(&request) + user, err := c.userService.SignUp(&request) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - ctx.JSON(http.StatusOK, gin.H{"message": "User created successfully"}) + response, err := c.userService.GenerateAccessToken(user) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) + return + } + + ctx.JSON(http.StatusOK, response) } // SignIn diff --git a/backend/internal/features/users/controllers/user_controller_test.go b/backend/internal/features/users/controllers/user_controller_test.go index 906c9e2..6c8be5c 100644 --- a/backend/internal/features/users/controllers/user_controller_test.go +++ b/backend/internal/features/users/controllers/user_controller_test.go @@ -27,7 +27,20 @@ func Test_SignUpUser_WithValidData_UserCreated(t *testing.T) { Name: "Test User", } - test_utils.MakePostRequest(t, router, "/api/v1/users/signup", "", request, http.StatusOK) + var response users_dto.SignInResponseDTO + test_utils.MakePostRequestAndUnmarshal( + t, + router, + "/api/v1/users/signup", + "", + request, + http.StatusOK, + &response, + ) + + assert.NotEmpty(t, response.Token) + assert.NotEqual(t, uuid.Nil, response.UserID) + assert.Equal(t, request.Email, response.Email) } func Test_SignUpUser_WithInvalidJSON_ReturnsBadRequest(t *testing.T) { diff --git a/backend/internal/features/users/services/user_services.go b/backend/internal/features/users/services/user_services.go index 3bfb689..a847598 100644 --- a/backend/internal/features/users/services/user_services.go +++ b/backend/internal/features/users/services/user_services.go @@ -44,19 +44,19 @@ func (s *UserService) SetEmailSender(sender users_interfaces.EmailSender) { s.emailSender = sender } -func (s *UserService) SignUp(request *users_dto.SignUpRequestDTO) error { +func (s *UserService) SignUp(request *users_dto.SignUpRequestDTO) (*users_models.User, error) { existingUser, err := s.userRepository.GetUserByEmail(request.Email) if err != nil { - return fmt.Errorf("failed to check existing user: %w", err) + return nil, fmt.Errorf("failed to check existing user: %w", err) } if existingUser != nil && existingUser.Status != users_enums.UserStatusInvited { - return errors.New("user with this email already exists") + return nil, errors.New("user with this email already exists") } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(request.Password), bcrypt.DefaultCost) if err != nil { - return fmt.Errorf("failed to hash password: %w", err) + return nil, fmt.Errorf("failed to hash password: %w", err) } hashedPasswordStr := string(hashedPassword) @@ -67,39 +67,45 @@ func (s *UserService) SignUp(request *users_dto.SignUpRequestDTO) error { existingUser.ID, hashedPasswordStr, ); err != nil { - return fmt.Errorf("failed to set password: %w", err) + return nil, fmt.Errorf("failed to set password: %w", err) } if err := s.userRepository.UpdateUserStatus( existingUser.ID, users_enums.UserStatusActive, ); err != nil { - return fmt.Errorf("failed to activate user: %w", err) + return nil, fmt.Errorf("failed to activate user: %w", err) } name := request.Name if err := s.userRepository.UpdateUserInfo(existingUser.ID, &name, nil); err != nil { - return fmt.Errorf("failed to update name: %w", err) + return nil, fmt.Errorf("failed to update name: %w", err) + } + + // Fetch updated user to ensure we have the latest data + updatedUser, err := s.userRepository.GetUserByID(existingUser.ID) + if err != nil { + return nil, fmt.Errorf("failed to get updated user: %w", err) } s.auditLogWriter.WriteAuditLog( - fmt.Sprintf("Invited user completed registration: %s", existingUser.Email), - &existingUser.ID, + fmt.Sprintf("Invited user completed registration: %s", updatedUser.Email), + &updatedUser.ID, nil, ) - return nil + return updatedUser, nil } // Get settings to check registration policy for new users settings, err := s.settingsService.GetSettings() if err != nil { - return fmt.Errorf("failed to get settings: %w", err) + return nil, fmt.Errorf("failed to get settings: %w", err) } // Check if external registrations are allowed if !settings.IsAllowExternalRegistrations { - return errors.New("external registration is disabled") + return nil, errors.New("external registration is disabled") } user := &users_models.User{ @@ -114,7 +120,7 @@ func (s *UserService) SignUp(request *users_dto.SignUpRequestDTO) error { } if err := s.userRepository.CreateUser(user); err != nil { - return fmt.Errorf("failed to create user: %w", err) + return nil, fmt.Errorf("failed to create user: %w", err) } s.auditLogWriter.WriteAuditLog( @@ -123,7 +129,7 @@ func (s *UserService) SignUp(request *users_dto.SignUpRequestDTO) error { nil, ) - return nil + return user, nil } func (s *UserService) SignIn( @@ -258,6 +264,7 @@ func (s *UserService) GenerateAccessToken( return &users_dto.SignInResponseDTO{ UserID: user.ID, + Email: user.Email, Token: tokenString, }, nil } diff --git a/frontend/src/entity/users/api/userApi.ts b/frontend/src/entity/users/api/userApi.ts index 4f20419..2a520d5 100644 --- a/frontend/src/entity/users/api/userApi.ts +++ b/frontend/src/entity/users/api/userApi.ts @@ -31,10 +31,18 @@ const notifyAuthListeners = () => { }; export const userApi = { - async signUp(signUpRequest: SignUpRequest) { + async signUp(signUpRequest: SignUpRequest): Promise { const requestOptions: RequestOptions = new RequestOptions(); requestOptions.setBody(JSON.stringify(signUpRequest)); - return apiHelper.fetchPostRaw(`${getApplicationServer()}/api/v1/users/signup`, requestOptions); + + return apiHelper + .fetchPostJson(`${getApplicationServer()}/api/v1/users/signup`, requestOptions) + .then((response: unknown): SignInResponse => { + const typedResponse = response as SignInResponse; + saveAuthorizedData(typedResponse.token, typedResponse.userId); + notifyAuthListeners(); + return typedResponse; + }); }, async signIn(signInRequest: SignInRequest): Promise { diff --git a/frontend/src/features/users/ui/SignUpComponent.tsx b/frontend/src/features/users/ui/SignUpComponent.tsx index 87fcdde..6528aee 100644 --- a/frontend/src/features/users/ui/SignUpComponent.tsx +++ b/frontend/src/features/users/ui/SignUpComponent.tsx @@ -92,11 +92,6 @@ export function SignUpComponent({ onSwitchToSignIn }: SignUpComponentProps): JSX name, cloudflareTurnstileToken: token, }); - await userApi.signIn({ - email, - password, - cloudflareTurnstileToken: token, - }); } catch (e) { setSignUpError(StringUtils.capitalizeFirstLetter((e as Error).message)); resetCloudflareTurnstile();