package auth import ( "context" "fmt" "golang.org/x/crypto/bcrypt" "github.com/kindredsystems/silo/internal/db" ) // BcryptCost is the cost parameter for bcrypt hashing. const BcryptCost = 12 // LocalBackend authenticates against bcrypt password hashes in the users table. type LocalBackend struct { users *db.UserRepository } // NewLocalBackend creates a local authentication backend. func NewLocalBackend(users *db.UserRepository) *LocalBackend { return &LocalBackend{users: users} } // Name returns "local". func (b *LocalBackend) Name() string { return "local" } // Authenticate verifies a username and password against the local database. func (b *LocalBackend) Authenticate(ctx context.Context, username, password string) (*User, error) { dbUser, hash, err := b.users.GetWithPasswordHash(ctx, username) if err != nil { return nil, fmt.Errorf("looking up user: %w", err) } if dbUser == nil { return nil, fmt.Errorf("user not found") } if hash == "" { return nil, fmt.Errorf("no local password set") } if !dbUser.IsActive { return nil, fmt.Errorf("account is disabled") } if dbUser.AuthSource != "local" { return nil, fmt.Errorf("user authenticates via %s", dbUser.AuthSource) } if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil { return nil, fmt.Errorf("invalid password") } return &User{ ID: dbUser.ID, Username: dbUser.Username, DisplayName: dbUser.DisplayName, Email: dbUser.Email, Role: dbUser.Role, AuthSource: "local", }, nil } // HashPassword creates a bcrypt hash of the given password. func HashPassword(password string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), BcryptCost) if err != nil { return "", fmt.Errorf("hashing password: %w", err) } return string(hash), nil }