go-passwd/passwd.go
2022-12-07 18:44:58 -07:00

113 lines
1.9 KiB
Go

package passwd
import (
"errors"
"fmt"
"strings"
)
type Passwder interface {
Passwd(string, string) (string, error)
ApplyPasswd(*Passwd)
}
type Passwd struct {
m map[string]Passwder
d Passwder
f Passwder
}
func New(opts ...Passwder) *Passwd {
p := &Passwd{m: make(map[string]Passwder)}
p.Options(opts...)
return p
}
func (p *Passwd) Options(opts ...Passwder) {
for _, o := range opts {
o.ApplyPasswd(p)
}
}
func (p *Passwd) Register(name string, pass Passwder) {
p.m[name] = pass
if p.d == nil {
p.SetDefault(pass)
}
}
func (p *Passwd) SetDefault(pass Passwder) {
p.d = pass
}
func (p *Passwd) SetFallthrough(pass Passwder) {
p.f = pass
}
func (p *Passwd) Passwd(pass, hash string) (string, error) {
if hash == "" {
return p.d.Passwd(pass, hash)
}
name, algo := p.getAlgo(hash)
if algo == nil {
algo = p.f
}
if algo == nil {
return "", fmt.Errorf("%w: %s", ErrNoHandler, name)
}
return algo.Passwd(pass, hash)
}
func (p *Passwd) IsPreferred(hash string) bool {
_, algo := p.getAlgo(hash)
if algo != nil && algo == p.d {
// if the algorithm defines its own check for preference.
if ck, ok := algo.(interface{ IsPreferred(string) bool }); ok {
return ck.IsPreferred(hash)
}
return true
}
return false
}
func (p *Passwd) getAlgo(hash string) (string, Passwder) {
var algo string
if !strings.HasPrefix(hash, "$") {
return p.getName(p.f), p.f
}
if _, h, ok := strings.Cut(hash, "$"); ok {
algo, _, ok = strings.Cut(h, "$")
if !ok {
return "", nil
}
if passwd, ok := p.m[algo]; ok {
return algo, passwd
}
return algo, nil
}
return p.getName(p.f), p.f
}
func (p *Passwd) getName(n Passwder) string {
if n == nil {
return ""
}
for k, v := range p.m {
if v == n {
return k
}
}
return "none"
}
var ErrNoMatch = errors.New("password does not match")
var ErrBadHash = errors.New("password hash is malformed")
var ErrNoHandler = errors.New("password handler not registered")