mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 00:32:03 +02:00
FIX (sign up): Return authorization token on sign up to avoid 2-step sign up
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -31,10 +31,18 @@ const notifyAuthListeners = () => {
|
||||
};
|
||||
|
||||
export const userApi = {
|
||||
async signUp(signUpRequest: SignUpRequest) {
|
||||
async signUp(signUpRequest: SignUpRequest): Promise<SignInResponse> {
|
||||
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<SignInResponse> {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user