all repos

onasty @ 9d9c06c8e8bbcb6f4232e37cb2ca8b48ab2d5e1e

a one-time notes service
3 files changed, 56 insertions(+), 15 deletions(-)
fix: compare old and new password hashes (#127)

* test(e2e): if provided password is not correct

* refactor: use hasher.Compare to compare old and new password

* fix typos

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(e2e): assert the error

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Author: Smirnov Oleksandr ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2025-06-05 19:49:32 +0300
Parent: 9f5c654
M e2e/apiv1_auth_test.go
···
        4
        4
         	"net/http"

      
        5
        5
         

      
        6
        6
         	"github.com/gofrs/uuid/v5"

      
        
        7
        +	"github.com/olexsmir/onasty/internal/hasher"

      
        7
        8
         	"github.com/olexsmir/onasty/internal/models"

      
        8
        9
         )

      
        9
        10
         

      ···
        298
        299
         	e.Empty(sessionDB.RefreshToken)

      
        299
        300
         }

      
        300
        301
         

      
        301
        
        -type apiv1AtuhChangePasswordRequest struct {

      
        
        302
        +type apiv1AuthChangePasswordRequest struct {

      
        302
        303
         	CurrentPassword string `json:"current_password"`

      
        303
        304
         	NewPassword     string `json:"new_password"`

      
        304
        305
         }

      ···
        312
        313
         	httpResp := e.httpRequest(

      
        313
        314
         		http.MethodPost,

      
        314
        315
         		"/api/v1/auth/change-password",

      
        315
        
        -		e.jsonify(apiv1AtuhChangePasswordRequest{

      
        
        316
        +		e.jsonify(apiv1AuthChangePasswordRequest{

      
        316
        317
         			CurrentPassword: password,

      
        317
        318
         			NewPassword:     newPassword,

      
        318
        319
         		}),

      ···
        323
        324
         

      
        324
        325
         	userDB := e.getUserByEmail(email)

      
        325
        326
         	e.NoError(e.hasher.Compare(userDB.Password, newPassword))

      
        
        327
        +}

      
        
        328
        +

      
        
        329
        +func (e *AppTestSuite) TestAuthV1_ChangePassword_wrongPassword() {

      
        
        330
        +	password := e.uuid()

      
        
        331
        +	newPassword := e.uuid()

      
        
        332
        +	email := e.uuid() + "@test.com"

      
        
        333
        +	_, toks := e.createAndSingIn(email, password)

      
        
        334
        +

      
        
        335
        +	httpResp := e.httpRequest(

      
        
        336
        +		http.MethodPost,

      
        
        337
        +		"/api/v1/auth/change-password",

      
        
        338
        +		e.jsonify(apiv1AuthChangePasswordRequest{

      
        
        339
        +			CurrentPassword: e.uuid(),

      
        
        340
        +			NewPassword:     newPassword,

      
        
        341
        +		}),

      
        
        342
        +		toks.AccessToken,

      
        
        343
        +	)

      
        
        344
        +

      
        
        345
        +	e.Equal(http.StatusBadRequest, httpResp.Code)

      
        
        346
        +

      
        
        347
        +	userDB := e.getUserByEmail(email)

      
        
        348
        +

      
        
        349
        +	err := e.hasher.Compare(userDB.Password, newPassword)

      
        
        350
        +	e.ErrorIs(err, hasher.ErrMismatchedHashes)

      
        326
        351
         }

      
        327
        352
         

      
        328
        353
         type (

      
M internal/service/usersrv/usersrv.go
···
        188
        188
         	userID uuid.UUID,

      
        189
        189
         	inp dtos.ChangeUserPassword,

      
        190
        190
         ) error {

      
        191
        
        -	// TODO: compare current password with providede, and assert on mismatch

      
        192
        
        -

      
        193
        191
         	//nolint:exhaustruct

      
        194
        192
         	if err := (models.User{Password: inp.NewPassword}).ValidatePassword(); err != nil {

      
        195
        193
         		return err

      
        196
        194
         	}

      
        197
        195
         

      
        198
        
        -	oldPass, err := u.hasher.Hash(inp.CurrentPassword)

      
        
        196
        +	user, err := u.userstore.GetByID(ctx, userID)

      
        199
        197
         	if err != nil {

      
        200
        198
         		return err

      
        201
        199
         	}

      
        202
        200
         

      
        
        201
        +	if err = u.hasher.Compare(user.Password, inp.CurrentPassword); err != nil {

      
        
        202
        +		return errors.Join(err, models.ErrUserInvalidPassword)

      
        
        203
        +	}

      
        
        204
        +

      
        203
        205
         	newPass, err := u.hasher.Hash(inp.NewPassword)

      
        204
        206
         	if err != nil {

      
        205
        207
         		return err

      
        206
        208
         	}

      
        207
        209
         

      
        208
        
        -	if err := u.userstore.ChangePassword(ctx, userID, oldPass, newPass); err != nil {

      
        
        210
        +	if err := u.userstore.ChangePassword(ctx, userID, newPass); err != nil {

      
        209
        211
         		return err

      
        210
        212
         	}

      
        211
        213
         

      
M internal/store/psql/userepo/userepo.go
···
        14
        14
         type UserStorer interface {

      
        15
        15
         	Create(ctx context.Context, inp models.User) (uuid.UUID, error)

      
        16
        16
         

      
        17
        
        -	// GetUserByCredentials returns user by email and password

      
        
        17
        +	// GetByEmail returns user by email and password

      
        18
        18
         	// the password should be hashed

      
        19
        19
         	GetByEmail(ctx context.Context, email string) (models.User, error)

      
        
        20
        +	GetUserIDByEmail(ctx context.Context, email string) (uuid.UUID, error)

      
        
        21
        +	GetByID(ctx context.Context, userID uuid.UUID) (models.User, error)

      
        20
        22
         

      
        21
        
        -	GetUserIDByEmail(ctx context.Context, email string) (uuid.UUID, error)

      
        22
        23
         	MarkUserAsActivated(ctx context.Context, id uuid.UUID) error

      
        23
        24
         

      
        24
        25
         	// ChangePassword changes user password from oldPassword to newPassword

      
        25
        26
         	// and oldPassword and newPassword should be hashed

      
        26
        
        -	ChangePassword(ctx context.Context, userID uuid.UUID, oldPassword, newPassword string) error

      
        
        27
        +	ChangePassword(ctx context.Context, userID uuid.UUID, newPassword string) error

      
        27
        28
         

      
        28
        29
         	// SetPassword sets new password for user by their id

      
        29
        30
         	// password should be hashed

      ···
        112
        113
         	return id, err

      
        113
        114
         }

      
        114
        115
         

      
        
        116
        +func (r *UserRepo) GetByID(ctx context.Context, userID uuid.UUID) (models.User, error) {

      
        
        117
        +	query := `--sql

      
        
        118
        +select id, email, password, activated, created_at, last_login_at

      
        
        119
        +from users

      
        
        120
        +where id = $1`

      
        
        121
        +

      
        
        122
        +	var user models.User

      
        
        123
        +	err := r.db.QueryRow(ctx, query, userID).

      
        
        124
        +		Scan(&user.ID, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt)

      
        
        125
        +	if errors.Is(err, pgx.ErrNoRows) {

      
        
        126
        +		return models.User{}, models.ErrUserNotFound

      
        
        127
        +	}

      
        
        128
        +

      
        
        129
        +	return user, err

      
        
        130
        +}

      
        
        131
        +

      
        115
        132
         func (r *UserRepo) GetByOAuthID(

      
        116
        133
         	ctx context.Context,

      
        117
        134
         	provider, providerID string,

      ···
        164
        181
         func (r *UserRepo) ChangePassword(

      
        165
        182
         	ctx context.Context,

      
        166
        183
         	userID uuid.UUID,

      
        167
        
        -	oldPass, newPass string,

      
        
        184
        +	newPasswd string,

      
        168
        185
         ) error {

      
        169
        186
         	query, args, err := pgq.

      
        170
        187
         		Update("users").

      
        171
        
        -		Set("password", newPass).

      
        172
        
        -		Where(pgq.Eq{

      
        173
        
        -			"id":       userID.String(),

      
        174
        
        -			"password": oldPass,

      
        175
        
        -		}).

      
        
        188
        +		Set("password", newPasswd).

      
        
        189
        +		Where(pgq.Eq{"id": userID.String()}).

      
        176
        190
         		SQL()

      
        177
        191
         	if err != nil {

      
        178
        192
         		return err