Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion modules/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (
"evolve/util/auth"
dbutil "evolve/util/db/user"
"fmt"
"strings"

"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)

type LoginReq struct {
Expand Down Expand Up @@ -45,6 +47,12 @@ func (l *LoginReq) validate() error {
return nil
}

func isBcryptHash(s string) bool {
return strings.HasPrefix(s, "$2a$") ||
strings.HasPrefix(s, "$2b$") ||
strings.HasPrefix(s, "$2y$")
}

func (l *LoginReq) Login(ctx context.Context) (map[string]string, error) {

logger := util.SharedLogger
Expand All @@ -59,15 +67,39 @@ func (l *LoginReq) Login(ctx context.Context) (map[string]string, error) {
}

// id is UUID(16 bytes) Google's UUID.
// Query user and get password hash
var id uuid.UUID
var role string
var storedPasswordHash string
err = db.QueryRow(ctx, "SELECT id, role, password FROM users WHERE username = $1 OR email = $2", l.UserName, l.Email).Scan(&id, &role, &storedPasswordHash)

err = db.QueryRow(ctx, "SELECT id, role FROM users WHERE (username = $1 OR email = $2) AND password = $3", l.UserName, l.Email, l.Password).Scan(&id, &role)
if err != nil {
logger.Error(fmt.Sprintf("Login: failed to query user: %v", err), err)
return nil, fmt.Errorf("invalid username/email or password")
}

if isBcryptHash(storedPasswordHash) {
err = bcrypt.CompareHashAndPassword([]byte(storedPasswordHash), []byte(l.Password))
if err != nil {
logger.Info("Login: invalid password attempt")
return nil, fmt.Errorf("invalid username/email or password")
}
} else {
// Compare plain text for legacy users
if storedPasswordHash != l.Password {
logger.Info("Login: invalid password attempt (legacy)")
return nil, fmt.Errorf("invalid username/email or password")
}

// Rehash and update DB for this user
newHash, err := bcrypt.GenerateFromPassword([]byte(l.Password), bcrypt.DefaultCost)
if err == nil {
_, _ = db.Exec(ctx, "UPDATE users SET password = $1 WHERE id = $2", string(newHash), id)
logger.Info(fmt.Sprintf("Upgraded password hash for user %v", id))
}
}
Comment on lines +87 to +100
Copy link
Member

@Ashrockzzz2003 Ashrockzzz2003 Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's great that you thought about this. But a better way is where you write a migration script and add it as part of the initialization of the service.

In that migration script you migrate all passwords to the hashed form. Your approach fails in cases where users have set actual passwords starting with $2b ... etc

Maintain a new column that says legacy/v2, maybe version numbers for each record in the user table. default value is v1, new passwords gets v2?


// Password verified, get user details
user, err := dbutil.UserById(ctx, id.String(), db)
if err != nil {
logger.Error(fmt.Sprintf("Login: failed to get user by id: %v", err), err)
Expand Down
10 changes: 9 additions & 1 deletion modules/register/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"evolve/util"
dbutil "evolve/util/db/user"
"fmt"

"golang.org/x/crypto/bcrypt"
)

type VerifyReq struct {
Expand Down Expand Up @@ -63,8 +65,14 @@ func (r *VerifyReq) Verify(ctx context.Context, user map[string]string) error {
return fmt.Errorf("invalid otp")
}

hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user["password"]), bcrypt.DefaultCost)
if err != nil {
logger.Error(fmt.Sprintf("RegisterVerify: failed to hash password: %v", err), err)
return fmt.Errorf("something went wrong")
}

// Insert user into user table.
if _, err := db.Exec(ctx, "INSERT INTO users (email, userName, fullName, password) VALUES($1, $2, $3, $4)", user["email"], user["userName"], user["fullName"], user["password"]); err != nil {
if _, err := db.Exec(ctx, "INSERT INTO users (email, userName, fullName, password) VALUES($1, $2, $3, $4)", user["email"], user["userName"], user["fullName"], string(hashedPassword)); err != nil {
logger.Error(fmt.Sprintf("RegisterVerify: failed to insert into users: %v", err), err)
return fmt.Errorf("something went wrong")
}
Expand Down