mirror of
https://github.com/wahyd4/loginsrv.git
synced 2026-08-10 05:18:33 +10:00
126 lines
2.7 KiB
Go
126 lines
2.7 KiB
Go
package htpasswd
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"github.com/abbot/go-http-auth"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Auth is the htpassword authenticater
|
|
type Auth struct {
|
|
filename string
|
|
userHash map[string]string
|
|
// Used in func reloadIfChanged to reload htpasswd file if it changed
|
|
modTime time.Time
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewAuth creates an htpassword authenticater
|
|
func NewAuth(filename string) (*Auth, error) {
|
|
a := &Auth{
|
|
filename: filename,
|
|
}
|
|
return a, a.parse(filename)
|
|
}
|
|
|
|
func (a *Auth) parse(filename string) error {
|
|
r, err := os.Open(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fileInfo, err := os.Stat(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.modTime = fileInfo.ModTime()
|
|
|
|
cr := csv.NewReader(r)
|
|
cr.Comma = ':'
|
|
cr.Comment = '#'
|
|
cr.TrimLeadingSpace = true
|
|
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
a.userHash = map[string]string{}
|
|
for {
|
|
record, err := cr.Read()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(record) != 2 {
|
|
return fmt.Errorf("password file in wrong format (%v)", filename)
|
|
}
|
|
a.userHash[record[0]] = record[1]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Authenticate the user
|
|
func (a *Auth) Authenticate(username, password string) (bool, error) {
|
|
reloadIfChanged(a)
|
|
a.mu.RLock()
|
|
defer a.mu.RUnlock()
|
|
if hash, exist := a.userHash[username]; exist {
|
|
h := []byte(hash)
|
|
p := []byte(password)
|
|
if strings.HasPrefix(hash, "$2y$") || strings.HasPrefix(hash, "$2b$") {
|
|
matchErr := bcrypt.CompareHashAndPassword(h, p)
|
|
return (matchErr == nil), nil
|
|
}
|
|
if strings.HasPrefix(hash, "{SHA}") {
|
|
return compareSha(h, p), nil
|
|
}
|
|
if strings.HasPrefix(hash, "$apr1$") {
|
|
return compareMD5(h, p), nil
|
|
}
|
|
return false, fmt.Errorf("unknown algorithm for user %q", username)
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// Reload htpasswd file if it changed during current run
|
|
func reloadIfChanged(a *Auth) {
|
|
fileInfo, err := os.Stat(a.filename)
|
|
if err != nil {
|
|
//On error, retain current file
|
|
return
|
|
}
|
|
|
|
currentmodTime := fileInfo.ModTime()
|
|
|
|
if currentmodTime != a.modTime {
|
|
a.modTime = currentmodTime
|
|
a.parse(a.filename)
|
|
}
|
|
}
|
|
|
|
func compareSha(hashedPassword, password []byte) bool {
|
|
d := sha1.New()
|
|
d.Write(password)
|
|
return 1 == subtle.ConstantTimeCompare(hashedPassword[5:], []byte(base64.StdEncoding.EncodeToString(d.Sum(nil))))
|
|
}
|
|
|
|
func compareMD5(hashedPassword, password []byte) bool {
|
|
parts := bytes.SplitN(hashedPassword, []byte("$"), 4)
|
|
if len(parts) != 4 {
|
|
return false
|
|
}
|
|
magic := []byte("$" + string(parts[1]) + "$")
|
|
salt := parts[2]
|
|
return 1 == subtle.ConstantTimeCompare(hashedPassword, auth.MD5Crypt(password, salt, magic))
|
|
}
|