Files
OwnCord/Server/auth/password.go
T
jevb 7404347a1d fix: address code review — 8 issues across server and client
Server fixes:
- voice_leave: broadcast voice_leave even on DB error so peers don't see ghost users (H1)
- migrate: record migration inside transaction for atomicity (H2)
- password: use init() with panic for dummyHash to catch bcrypt init failures (M1)
- password: replace //nolint:errcheck with explicit _ = discard (M2)
- handlers: clarify edit permission comment, fix error message wording (M3)
- auth_handler: distinguish duplicate username (400) from DB error (500) (M4)

Client fixes:
- media: revert YouTube oEmbed to browser fetch — no need to disable cert verification (C1)
- messages.store: fix prependMessages cap to keep newest messages, not oldest (H5)
- ChannelSidebar: ref-count globalDragAc to prevent multi-instance teardown race (H6)
- attachments: replace console.error with project logger (M6)
- ws: clarify lastSeq reset comment to match actual behavior (M5)
2026-03-24 21:35:40 +01:00

73 lines
2.3 KiB
Go

package auth
import (
"errors"
"golang.org/x/crypto/bcrypt"
)
const (
bcryptCost = 12
minPassLen = 8
maxPassLen = 72 // bcrypt silently truncates beyond 72 bytes
)
// ErrPasswordTooShort is returned when the password is below the minimum length.
var ErrPasswordTooShort = errors.New("password must be at least 8 characters")
// ErrPasswordTooLong is returned when the password exceeds bcrypt's 72-byte limit.
var ErrPasswordTooLong = errors.New("password must not exceed 72 characters")
// HashPassword returns a bcrypt hash of password using cost 12.
func HashPassword(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
if err != nil {
return "", err
}
return string(hash), nil
}
// dummyHash is a pre-computed bcrypt hash used to prevent timing side-channels
// when the user does not exist. Comparing against this dummy ensures that
// CheckPassword takes roughly constant time regardless of whether a valid hash
// was supplied.
var dummyHash []byte
func init() {
h, err := bcrypt.GenerateFromPassword([]byte("dummy-timing-pad"), bcryptCost)
if err != nil {
panic("auth: failed to generate dummy bcrypt hash: " + err.Error())
}
dummyHash = h
}
// CheckPassword reports whether password matches hash. Returns false on any
// error, including an empty or malformed hash. When hash is empty (user does
// not exist), a dummy bcrypt comparison is performed to prevent timing-based
// username enumeration.
func CheckPassword(hash, password string) bool {
if hash == "" {
// Perform a dummy comparison so the response time is indistinguishable
// from a real check, preventing timing-based username enumeration.
// The error is intentionally discarded: we always return false here.
// The comparison is performed only to consume time and prevent
// timing-based username enumeration.
_ = bcrypt.CompareHashAndPassword(dummyHash, []byte(password))
return false
}
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// ValidatePasswordStrength returns an error if password fails strength
// requirements: minimum 8 characters, maximum 72 characters.
func ValidatePasswordStrength(password string) error {
if len(password) < minPassLen {
return ErrPasswordTooShort
}
if len(password) > maxPassLen {
return ErrPasswordTooLong
}
return nil
}