@@ -0,0 +1,49 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrExists indicates the specified user already exists
|
||||
ErrExists = errors.New("user already exists")
|
||||
// ErrNotExists indicates the specified user does not exist
|
||||
ErrNotExists = errors.New("user does not exist")
|
||||
// ErrInvalidAlgorithm indicates the htpasswd entry doesn't contain a
|
||||
// valid algorithm signature
|
||||
ErrInvalidAlgorithm = errors.New("invalid algorithm")
|
||||
// ErrInvalidPassword indicates the offered password doesn't match
|
||||
ErrInvalidPassword = errors.New("invalid password")
|
||||
)
|
||||
|
||||
// UserError indicates an error associated to the specified user
|
||||
type UserError struct {
|
||||
Name string
|
||||
Hint string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *UserError) Error() string {
|
||||
var buf strings.Builder
|
||||
var reason string
|
||||
|
||||
if e.Hint != "" {
|
||||
reason = e.Hint
|
||||
} else {
|
||||
reason = e.Err.Error()
|
||||
}
|
||||
|
||||
if e.Name == "" {
|
||||
return reason
|
||||
}
|
||||
|
||||
_, _ = buf.WriteString(e.Name)
|
||||
_, _ = buf.WriteString(": ")
|
||||
_, _ = buf.WriteString(reason)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (e *UserError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
+39
-18
@@ -51,8 +51,10 @@ func Parse(htpasswdBytes []byte) (Passwds, error) {
|
||||
_, exists := passwords[parts[0]]
|
||||
|
||||
if exists {
|
||||
err = errors.New("invalid htpasswd file - user " +
|
||||
parts[0] + " defined more than once")
|
||||
err = &UserError{
|
||||
Name: parts[0],
|
||||
Err: ErrExists,
|
||||
}
|
||||
return passwords, err
|
||||
}
|
||||
passwords[parts[0]] = parts[1]
|
||||
@@ -78,8 +80,12 @@ func CreateUser(file, user, passwd string, algo Hasher) error {
|
||||
// using the given name, password and hashing algorithm
|
||||
func (pp Passwds) CreateUser(user, passwd string, algo Hasher) error {
|
||||
if _, exists := pp[user]; exists {
|
||||
return fmt.Errorf("user %s already exists", user)
|
||||
return &UserError{
|
||||
Name: user,
|
||||
Err: ErrExists,
|
||||
}
|
||||
}
|
||||
|
||||
h, err := algo.Hash(passwd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -106,8 +112,12 @@ func UpdateUser(file, user, passwd string, algo Hasher) error {
|
||||
// using the given name, password and hashing algorithm
|
||||
func (pp Passwds) UpdateUser(user, passwd string, algo Hasher) error {
|
||||
if _, exists := pp[user]; !exists {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
return &UserError{
|
||||
Name: user,
|
||||
Err: ErrNotExists,
|
||||
}
|
||||
}
|
||||
|
||||
h, err := algo.Hash(passwd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -133,7 +143,10 @@ func DeleteUser(file, user string) error {
|
||||
// DeleteUser deletes the named user from the named file
|
||||
func (pp Passwds) DeleteUser(user string) error {
|
||||
if _, exists := pp[user]; !exists {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
return &UserError{
|
||||
Name: user,
|
||||
Err: ErrNotExists,
|
||||
}
|
||||
}
|
||||
|
||||
delete(pp, user)
|
||||
@@ -154,12 +167,20 @@ func VerifyUser(file, user, passwd string) error {
|
||||
// with the given Passwd object
|
||||
func (pp Passwds) VerifyUser(user, passwd string) error {
|
||||
if _, ok := pp[user]; !ok {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
return &UserError{
|
||||
Name: user,
|
||||
Err: ErrNotExists,
|
||||
}
|
||||
}
|
||||
alg, err := identifyHash(pp[user])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot identify algo %v", alg)
|
||||
|
||||
alg := identifyHash(pp[user])
|
||||
if alg == nil {
|
||||
return &UserError{
|
||||
Name: user,
|
||||
Err: ErrInvalidAlgorithm,
|
||||
}
|
||||
}
|
||||
|
||||
return alg.Match(passwd, pp[user])
|
||||
}
|
||||
|
||||
@@ -177,24 +198,24 @@ func (pp Passwds) Bytes() []byte {
|
||||
return pass
|
||||
}
|
||||
|
||||
func identifyHash(h string) (Hasher, error) {
|
||||
func identifyHash(h string) Hasher {
|
||||
switch {
|
||||
case strings.HasPrefix(h, "$2a$"), strings.HasPrefix(h, "$2y$"),
|
||||
strings.HasPrefix(h, "$2x$"), strings.HasPrefix(h, "$2b$"):
|
||||
return new(Bcrypt), nil
|
||||
return new(Bcrypt)
|
||||
case strings.HasPrefix(h, "$apr1$"):
|
||||
return new(Apr1), nil
|
||||
return new(Apr1)
|
||||
case strings.HasPrefix(h, "{SHA}"):
|
||||
return new(Sha), nil
|
||||
return new(Sha)
|
||||
case strings.HasPrefix(h, "{SSHA}"):
|
||||
return new(Ssha), nil
|
||||
return new(Ssha)
|
||||
case strings.HasPrefix(h, "$5$"):
|
||||
return new(Sha256), nil
|
||||
return new(Sha256)
|
||||
case strings.HasPrefix(h, "$6$"):
|
||||
return new(Sha512), nil
|
||||
return new(Sha512)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
func validLine(parts []string, lineNumber int, line string) (bool, error) {
|
||||
|
||||
Reference in New Issue
Block a user