forgejo/services/auth/source/db/authenticate.go
Gusted 7d2a7b8559 feat: add validating user password as trace region (#7981)
- Password hashing can take a measurable amount of time, make this more visible in the trace by capturing the computations done in the password hash in their own region.
- Ref: forgejo/forgejo#6470

## Screenshot

![image](/attachments/9834b094-a78f-4ac2-847e-91f221a84833)

The upper part are where the tasks are shown (and nothing else). The bottom part is where the interesting execution tracing happens and the part where the user password hashing happens is now properly indicated/highlighted and does not need to be inferred by looking at the stack traces.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7981
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
2025-05-28 14:46:23 +02:00

87 lines
2.5 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db
import (
"context"
"fmt"
user_model "forgejo.org/models/user"
"forgejo.org/modules/setting"
"forgejo.org/modules/util"
)
// ErrUserPasswordNotSet represents a "ErrUserPasswordNotSet" kind of error.
type ErrUserPasswordNotSet struct {
UID int64
Name string
}
func (err ErrUserPasswordNotSet) Error() string {
return fmt.Sprintf("user's password isn't set [uid: %d, name: %s]", err.UID, err.Name)
}
// Unwrap unwraps this error as a ErrInvalidArgument error
func (err ErrUserPasswordNotSet) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrUserPasswordInvalid represents a "ErrUserPasswordInvalid" kind of error.
type ErrUserPasswordInvalid struct {
UID int64
Name string
}
func (err ErrUserPasswordInvalid) Error() string {
return fmt.Sprintf("user's password is invalid [uid: %d, name: %s]", err.UID, err.Name)
}
// Unwrap unwraps this error as a ErrInvalidArgument error
func (err ErrUserPasswordInvalid) Unwrap() error {
return util.ErrInvalidArgument
}
// Authenticate authenticates the provided user against the DB
func Authenticate(ctx context.Context, user *user_model.User, login, password string) (*user_model.User, error) {
if user == nil {
return nil, user_model.ErrUserNotExist{Name: login}
}
if !user.IsPasswordSet() {
return nil, ErrUserPasswordNotSet{UID: user.ID, Name: user.Name}
} else if !user.ValidatePassword(ctx, password) {
return nil, ErrUserPasswordInvalid{UID: user.ID, Name: user.Name}
}
// Update password hash if server password hash algorithm have changed
// Or update the password when the salt length doesn't match the current
// recommended salt length, this in order to migrate user's salts to a more secure salt.
if user.PasswdHashAlgo != setting.PasswordHashAlgo || len(user.Salt) != user_model.SaltByteLength*2 {
if err := user.SetPassword(password); err != nil {
return nil, err
}
if err := user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
return nil, err
}
}
// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
// user could be hinted to resend confirm email.
if user.ProhibitLogin {
return nil, user_model.ErrUserProhibitLogin{
UID: user.ID,
Name: user.Name,
}
}
// attempting to login as a non-user account
if user.Type != user_model.UserTypeIndividual {
return nil, user_model.ErrUserProhibitLogin{
UID: user.ID,
Name: user.Name,
}
}
return user, nil
}