13 files changed,
49 insertions(+),
100 deletions(-)
Author:
Smirnov Oleksandr
ss2316544@gmail.com
Committed by:
GitHub
noreply@github.com
Committed at:
2025-05-21 00:35:22 +0300
Parent:
35d2b54
jump to
M
e2e/apiv1_auth_test.go
··· 8 8 ) 9 9 10 10 type apiv1AuthSignUpRequest struct { 11 - Username string `json:"username"` 12 11 Email string `json:"email"` 13 12 Password string `json:"password"` 14 13 } 15 14 16 15 func (e *AppTestSuite) TestAuthV1_SignUP() { 17 - username := "test" + e.uuid() 18 16 email := e.uuid() + "test@test.com" 19 17 password := "password" 20 18 ··· 22 20 http.MethodPost, 23 21 "/api/v1/auth/signup", 24 22 e.jsonify(apiv1AuthSignUpRequest{ 25 - Username: username, 26 23 Email: email, 27 24 Password: password, 28 25 }), 29 26 ) 30 27 31 - dbUser := e.getUserByUsername(username) 28 + dbUser := e.getUserByEmail(email) 32 29 hashedPasswd, err := e.hasher.Hash(password) 33 30 e.require.NoError(err) 34 31 ··· 40 37 func (e *AppTestSuite) TestAuthV1_SignUP_badrequest() { 41 38 tests := []struct { 42 39 name string 43 - username string 44 40 email string 45 41 password string 46 42 }{ 47 - {name: "all fields empty", email: "", password: "", username: ""}, 48 - { 49 - name: "non valid email", 50 - username: "testing", 51 - email: "email", 52 - password: "password", 53 - }, 54 - { 55 - name: "non valid password", 56 - email: "test@test.com", 57 - password: "12345", 58 - username: "test", 59 - }, 43 + {name: "all fields empty", email: "", password: ""}, 44 + {name: "non valid email", email: "email", password: "password"}, 45 + {name: "non valid password", email: "test@test.com", password: "12345"}, 60 46 } 61 47 for _, t := range tests { 62 48 httpResp := e.httpRequest( 63 49 http.MethodPost, 64 50 "/api/v1/auth/signup", 65 51 e.jsonify(apiv1AuthSignUpRequest{ 66 - Username: t.username, 67 52 Email: t.email, 68 53 Password: t.password, 69 54 }), ··· 92 77 http.MethodPost, 93 78 "/api/v1/auth/signup", 94 79 e.jsonify(apiv1AuthSignUpRequest{ 95 - Username: e.uuid(), 96 80 Email: email, 97 81 Password: password, 98 82 }), ··· 119 103 http.MethodPost, 120 104 "/api/v1/auth/signup", 121 105 e.jsonify(apiv1AuthSignUpRequest{ 122 - Username: e.uuid(), 123 106 Email: email, 124 107 Password: password, 125 108 }), ··· 143 126 144 127 func (e *AppTestSuite) TestAuthV1_ResendVerificationEmail_wrong() { 145 128 email, password := e.uuid()+"@"+e.uuid()+".com", "password" 146 - e.insertUser(e.uuid(), email, password, true) 129 + e.insertUser(email, password, true) 147 130 148 131 tests := []struct { 149 132 name string ··· 182 165 func (e *AppTestSuite) TestAuthV1_SignIn() { 183 166 email := e.uuid() + "email@email.com" 184 167 password := "qwerty" 185 - 186 - uid := e.insertUser("test", email, password, true) 168 + uid := e.insertUser(email, password, true) 187 169 188 170 httpResp := e.httpRequest( 189 171 http.MethodPost, ··· 208 190 func (e *AppTestSuite) TestAuthV1_SignIn_wrong() { 209 191 password := "password" 210 192 email := e.uuid() + "@test.com" 211 - e.insertUser(e.uuid(), email, "password", true) 193 + e.insertUser(email, "password", true) 212 194 213 195 unactivatedEmail := e.uuid() + "@test.com" 214 - e.insertUser(e.uuid(), unactivatedEmail, password, false) 196 + e.insertUser(unactivatedEmail, password, false) 215 197 216 198 //exhaustruct:ignore 217 199 tests := []struct { ··· 271 253 } 272 254 273 255 func (e *AppTestSuite) TestAuthV1_RefreshTokens() { 274 - uid, toks := e.createAndSingIn(e.uuid()+"@test.com", e.uuid(), "password") 256 + uid, toks := e.createAndSingIn(e.uuid()+"@test.com", "password") 275 257 httpResp := e.httpRequest( 276 258 http.MethodPost, 277 259 "/api/v1/auth/refresh-tokens", ··· 306 288 } 307 289 308 290 func (e *AppTestSuite) TestAuthV1_Logout() { 309 - uid, toks := e.createAndSingIn(e.uuid()+"@test.com", e.uuid(), "password") 291 + uid, toks := e.createAndSingIn(e.uuid()+"@test.com", "password") 310 292 311 293 sessionDB := e.getLastSessionByUserID(uid) 312 294 e.NotEmpty(sessionDB.RefreshToken) ··· 326 308 func (e *AppTestSuite) TestAuthV1_ChangePassword() { 327 309 password := e.uuid() 328 310 newPassword := e.uuid() 329 - username := e.uuid() 330 - _, toks := e.createAndSingIn(e.uuid()+"@test.com", username, password) 311 + email := e.uuid() + "@test.com" 312 + _, toks := e.createAndSingIn(email, password) 331 313 332 314 httpResp := e.httpRequest( 333 315 http.MethodPost, ··· 341 323 342 324 e.Equal(httpResp.Code, http.StatusOK) 343 325 344 - userDB := e.getUserByUsername(username) 345 - e.Equal(userDB.Username, username) 326 + userDB := e.getUserByEmail(email) 346 327 e.NoError(e.hasher.Compare(userDB.Password, newPassword)) 347 328 } 348 329 ··· 357 338 358 339 func (e *AppTestSuite) TestAuthV1_ResetPassword() { 359 340 email := e.uuid() + "@test.com" 360 - uname := e.uuid() 361 - uid, _ := e.createAndSingIn(email, uname, "password") 341 + uid, _ := e.createAndSingIn(email, "password") 362 342 363 343 httpResp := e.httpRequest( 364 344 http.MethodPost, ··· 384 364 }), 385 365 ) 386 366 387 - dbUser := e.getUserByUsername(uname) 367 + dbUser := e.getUserByEmail(email) 388 368 e.Equal(httpResp.Code, http.StatusOK) 389 369 e.NoError(e.hasher.Compare(dbUser.Password, password)) 390 370 ··· 393 373 } 394 374 395 375 func (e *AppTestSuite) TestAuthV1_ResetPassword_nonExistentUser() { 396 - _, _ = e.createAndSingIn(e.uuid()+"@test.comd", e.uuid(), "password") 376 + _, _ = e.createAndSingIn(e.uuid()+"@test.com", "password") 397 377 httpResp := e.httpRequest( 398 378 http.MethodPost, 399 379 "/api/v1/auth/reset-password", ··· 405 385 e.Equal(httpResp.Code, http.StatusBadRequest) 406 386 } 407 387 408 -// createAndSingIn creates an activated username, logs them in, 388 +// createAndSingIn creates an activated user, logs them in, 409 389 // and returns their userID along with access and refresh tokens. 410 390 func (e *AppTestSuite) createAndSingIn( 411 - email, username, password string, 391 + email, password string, 412 392 ) (uuid.UUID, apiv1AuthSignInResponse) { 413 - uid := e.insertUser(username, email, password, true) 393 + uid := e.insertUser(email, password, true) 414 394 httpResp := e.httpRequest( 415 395 http.MethodPost, 416 396 "/api/v1/auth/signin",
M
e2e/e2e_utils_db_test.go
··· 10 10 "github.com/olexsmir/onasty/internal/models" 11 11 ) 12 12 13 -// getUserByUsername queries user from db by it's username 14 -func (e *AppTestSuite) getUserByUsername(username string) models.User { 13 +// getUserByEmail queries user from db by it's email 14 +func (e *AppTestSuite) getUserByEmail(email string) models.User { 15 15 query, args, err := pgq. 16 - Select("id", "username", "email", "password", "created_at", "last_login_at"). 16 + Select("id", "email", "password", "created_at", "last_login_at"). 17 17 From("users"). 18 - Where(pgq.Eq{"username": username}). 18 + Where(pgq.Eq{"email": email}). 19 19 SQL() 20 20 e.require.NoError(err) 21 21 22 22 var user models.User 23 23 err = e.postgresDB.QueryRow(e.ctx, query, args...). 24 - Scan(&user.ID, &user.Username, &user.Email, &user.Password, &user.CreatedAt, &user.LastLoginAt) 24 + Scan(&user.ID, &user.Email, &user.Password, &user.CreatedAt, &user.LastLoginAt) 25 25 e.require.NoError(err) 26 26 27 27 return user 28 28 } 29 29 30 30 // insertUser inserts user into db 31 -func (e *AppTestSuite) insertUser(uname, email, passwd string, activated ...bool) uuid.UUID { 31 +func (e *AppTestSuite) insertUser(email, passwd string, activated ...bool) uuid.UUID { 32 32 p, err := e.hasher.Hash(passwd) 33 33 e.require.NoError(err) 34 34 ··· 39 39 40 40 query, args, err := pgq. 41 41 Insert("users"). 42 - Columns("username", "email", "password", "activated", "created_at", "last_login_at"). 43 - Values(uname, email, p, a, time.Now(), time.Now()). 42 + Columns("email", "password", "activated", "created_at", "last_login_at"). 43 + Values(email, p, a, time.Now(), time.Now()). 44 44 Returning("id"). 45 45 SQL() 46 46 e.require.NoError(err) ··· 77 77 // getLastUserByEmail gets last inserted [models.User] by user's email 78 78 func (e *AppTestSuite) getLastUserByEmail(em string) models.User { 79 79 query, args, err := pgq. 80 - Select("id", "username", "activated", "email", "password", "created_at", "last_login_at"). 80 + Select("id", "activated", "email", "password", "created_at", "last_login_at"). 81 81 From("users"). 82 82 Where(pgq.Eq{"email": em}). 83 83 OrderBy("created_at DESC"). ··· 87 87 88 88 var u models.User 89 89 err = e.postgresDB.QueryRow(e.ctx, query, args...). 90 - Scan(&u.ID, &u.Username, &u.Activated, &u.Email, &u.Password, &u.CreatedAt, &u.LastLoginAt) 90 + Scan(&u.ID, &u.Activated, &u.Email, &u.Password, &u.CreatedAt, &u.LastLoginAt) 91 91 if errors.Is(err, pgx.ErrNoRows) { 92 92 return models.User{} //nolint:exhaustruct 93 93 }
M
internal/models/user.go
··· 10 10 11 11 var ( 12 12 ErrUserEmailIsAlreadyInUse = errors.New("user: email is already in use") 13 - ErrUsernameIsAlreadyInUse = errors.New("user: username is already in use") 14 13 ErrUserIsAlreadyVerified = errors.New("user: user is already verified") 15 14 16 15 ErrResetPasswordTokenAlreadyUsed = errors.New("reset password token is already used") ··· 22 21 23 22 ErrUserInvalidEmail = errors.New("user: invalid email") 24 23 ErrUserInvalidPassword = errors.New("user: password too short, minimum 6 chars") 25 - ErrUserInvalidUsername = errors.New("user: username is required") 26 24 ) 27 25 28 26 type User struct { 29 27 ID uuid.UUID 30 - Username string 31 28 Email string 32 29 Activated bool 33 30 Password string ··· 39 36 _, err := mail.ParseAddress(u.Email) 40 37 if err != nil { 41 38 return ErrUserInvalidEmail 42 - } 43 - 44 - if len(u.Username) == 0 { 45 - return ErrUserInvalidUsername 46 39 } 47 40 48 41 return u.ValidatePassword()
M
internal/models/user_test.go
··· 11 11 name string 12 12 fail bool 13 13 14 - username string 15 14 email string 16 15 password string 17 16 }{ ··· 19 18 name: "valid", 20 19 fail: false, 21 20 email: "test@example.org", 22 - username: "iuserarchbtw", 23 21 password: "superhardasspassword", 24 22 }, 25 23 { 26 24 name: "all fields empty", 27 25 fail: true, 28 26 email: "", 29 - username: "", 30 27 password: "", 31 28 }, 32 29 { 33 30 name: "invalid email", 34 31 fail: true, 35 32 email: "test", 36 - username: "iuserarchbtw", 37 33 password: "superhardasspassword", 38 34 }, 39 35 { 40 36 name: "invalid password", 41 37 fail: true, 42 38 email: "test@example.org", 43 - username: "iuserarchbtw", 44 39 password: "12345", 45 40 }, 46 - { 47 - name: "invalid username", 48 - fail: true, 49 - email: "test@example.org", 50 - username: "", 51 - password: "superhardasspassword", 52 - }, 53 41 } 54 42 55 43 for _, tt := range tests { 56 44 t.Run(tt.name, func(t *testing.T) { 57 45 err := User{ //nolint:exhaustruct 58 - Username: tt.username, 59 46 Email: tt.email, 60 47 Password: tt.password, 61 48 }.Validate()
M
internal/service/usersrv/oauth.go
··· 4 4 "context" 5 5 "errors" 6 6 "log/slog" 7 - "strings" 8 7 "time" 9 8 10 9 "github.com/gofrs/uuid/v5" ··· 74 73 return userInfo, err 75 74 } 76 75 77 -func getUsernameFromEmail(email string) string { 78 - p := strings.Split(email, "@") 79 - return p[0] 80 -} 81 - 82 76 func (u *UserSrv) getUserByOAuthIDOrCreateOne( 83 77 ctx context.Context, 84 78 info oauth.UserInfo, ··· 88 82 if errors.Is(err, models.ErrUserNotFound) { 89 83 uid, cerr := u.userstore.Create(ctx, models.User{ 90 84 ID: uuid.Nil, 91 - Username: getUsernameFromEmail(info.Email), 92 85 Email: info.Email, 93 86 Activated: true, 94 87 Password: "",
M
internal/service/usersrv/usersrv.go
··· 93 93 } 94 94 95 95 func (u *UserSrv) SignUp(ctx context.Context, inp dtos.SignUp) (uuid.UUID, error) { 96 - hashedPassword, err := u.hasher.Hash(inp.Password) 97 - if err != nil { 98 - return uuid.UUID{}, err 99 - } 100 - 101 96 user := models.User{ 102 97 ID: uuid.Nil, // nil, because it does not get used here 103 - Username: inp.Username, 104 98 Email: inp.Email, 105 99 Activated: false, 106 - Password: hashedPassword, 100 + Password: inp.Password, 107 101 CreatedAt: inp.CreatedAt, 108 102 LastLoginAt: inp.LastLoginAt, 109 103 } 110 - if err = user.Validate(); err != nil { 104 + if err := user.Validate(); err != nil { 111 105 return uuid.Nil, err 112 106 } 107 + 108 + hashedPassword, err := u.hasher.Hash(inp.Password) 109 + if err != nil { 110 + return uuid.UUID{}, err 111 + } 112 + 113 + user.Password = hashedPassword 113 114 114 115 userID, err := u.userstore.Create(ctx, user) 115 116 if err != nil {
M
internal/store/psql/userepo/userepo.go
··· 51 51 func (r *UserRepo) Create(ctx context.Context, inp models.User) (uuid.UUID, error) { 52 52 query, args, err := pgq. 53 53 Insert("users"). 54 - Columns("username", "email", "password", "activated", "created_at", "last_login_at"). 55 - Values(inp.Username, inp.Email, inp.Password, inp.Activated, inp.CreatedAt, inp.LastLoginAt). 54 + Columns("email", "password", "activated", "created_at", "last_login_at"). 55 + Values(inp.Email, inp.Password, inp.Activated, inp.CreatedAt, inp.LastLoginAt). 56 56 Returning("id"). 57 57 SQL() 58 58 if err != nil { ··· 63 63 err = r.db.QueryRow(ctx, query, args...).Scan(&id) 64 64 65 65 // FIXME: somehow this does return errors but i can't errors.Is them in api layer 66 - if psqlutil.IsDuplicateErr(err, "users_username_key") { 67 - return uuid.UUID{}, models.ErrUsernameIsAlreadyInUse 68 - } 69 - 70 66 if psqlutil.IsDuplicateErr(err, "users_email_key") { 71 67 return uuid.UUID{}, models.ErrUserEmailIsAlreadyInUse 72 68 } ··· 79 75 email string, 80 76 ) (models.User, error) { 81 77 query, args, err := pgq. 82 - Select("id", "username", "email", "password", "activated", "created_at", "last_login_at"). 78 + Select("id", "email", "password", "activated", "created_at", "last_login_at"). 83 79 From("users"). 84 80 Where(pgq.Eq{"email": email}). 85 81 SQL() ··· 89 85 90 86 var user models.User 91 87 err = r.db.QueryRow(ctx, query, args...). 92 - Scan(&user.ID, &user.Username, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt) 88 + Scan(&user.ID, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt) 93 89 if errors.Is(err, pgx.ErrNoRows) { 94 90 return models.User{}, models.ErrUserNotFound 95 91 } ··· 121 117 provider, providerID string, 122 118 ) (models.User, error) { 123 119 query := `--sql 124 - select u.id, u.username, u.email, u.password, u.activated, u.created_at, u.last_login_at 120 + select u.id, u.email, u.password, u.activated, u.created_at, u.last_login_at 125 121 from users u 126 122 join oauth_identities oi on u.id = oi.user_id 127 123 where oi.provider = $1 ··· 130 126 131 127 var user models.User 132 128 err := r.db.QueryRow(ctx, query, provider, providerID). 133 - Scan(&user.ID, &user.Username, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt) 129 + Scan(&user.ID, &user.Email, &user.Password, &user.Activated, &user.CreatedAt, &user.LastLoginAt) 134 130 if errors.Is(err, pgx.ErrNoRows) { 135 131 return models.User{}, models.ErrUserNotFound 136 132 }
M
internal/transport/http/apiv1/auth.go
··· 9 9 ) 10 10 11 11 type signUpRequest struct { 12 - Username string `json:"username"` 13 12 Email string `json:"email"` 14 13 Password string `json:"password"` 15 14 } ··· 22 21 } 23 22 24 23 if _, err := a.usersrv.SignUp(c.Request.Context(), dtos.SignUp{ 25 - Username: req.Username, 26 24 Email: req.Email, 27 25 Password: req.Password, 28 26 CreatedAt: time.Now(),
M
internal/transport/http/apiv1/response.go
··· 21 21 errors.Is(err, models.ErrResetPasswordTokenAlreadyUsed) || 22 22 errors.Is(err, models.ErrResetPasswordTokenExpired) || 23 23 errors.Is(err, models.ErrUserEmailIsAlreadyInUse) || 24 - errors.Is(err, models.ErrUsernameIsAlreadyInUse) || 25 24 errors.Is(err, models.ErrUserIsAlreadyVerified) || 26 25 errors.Is(err, models.ErrUserIsNotActivated) || 27 26 errors.Is(err, models.ErrUserInvalidEmail) || 28 27 errors.Is(err, models.ErrUserInvalidPassword) || 29 - errors.Is(err, models.ErrUserInvalidUsername) || 30 28 // notes 31 29 errors.Is(err, models.ErrNoteContentIsEmpty) || 32 30 errors.Is(err, models.ErrNoteSlugIsAlreadyInUse) {
A
migrations/20250520211029_remove_username.down.sql
··· 1 +ALTER TABLE users 2 + add column username varchar(255) NOT NULL UNIQUE;
A
migrations/20250520211029_remove_username.up.sql
··· 1 +ALTER TABLE users 2 + DROP COLUMN username;