github.com/pusher/oauth2_proxy@v3.2.0+incompatible/htpasswd.go (about) 1 package main 2 3 import ( 4 "crypto/sha1" 5 "encoding/base64" 6 "encoding/csv" 7 "io" 8 "log" 9 "os" 10 11 "golang.org/x/crypto/bcrypt" 12 ) 13 14 // Lookup passwords in a htpasswd file 15 // Passwords must be generated with -B for bcrypt or -s for SHA1. 16 17 // HtpasswdFile represents the structure of an htpasswd file 18 type HtpasswdFile struct { 19 Users map[string]string 20 } 21 22 // NewHtpasswdFromFile constructs an HtpasswdFile from the file at the path given 23 func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) { 24 r, err := os.Open(path) 25 if err != nil { 26 return nil, err 27 } 28 defer r.Close() 29 return NewHtpasswd(r) 30 } 31 32 // NewHtpasswd consctructs an HtpasswdFile from an io.Reader (opened file) 33 func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) { 34 csvReader := csv.NewReader(file) 35 csvReader.Comma = ':' 36 csvReader.Comment = '#' 37 csvReader.TrimLeadingSpace = true 38 39 records, err := csvReader.ReadAll() 40 if err != nil { 41 return nil, err 42 } 43 h := &HtpasswdFile{Users: make(map[string]string)} 44 for _, record := range records { 45 h.Users[record[0]] = record[1] 46 } 47 return h, nil 48 } 49 50 // Validate checks a users password against the HtpasswdFile entries 51 func (h *HtpasswdFile) Validate(user string, password string) bool { 52 realPassword, exists := h.Users[user] 53 if !exists { 54 return false 55 } 56 57 shaPrefix := realPassword[:5] 58 if shaPrefix == "{SHA}" { 59 shaValue := realPassword[5:] 60 d := sha1.New() 61 d.Write([]byte(password)) 62 return shaValue == base64.StdEncoding.EncodeToString(d.Sum(nil)) 63 } 64 65 bcryptPrefix := realPassword[:4] 66 if bcryptPrefix == "$2a$" || bcryptPrefix == "$2b$" || bcryptPrefix == "$2x$" || bcryptPrefix == "$2y$" { 67 return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password)) == nil 68 } 69 70 log.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user) 71 return false 72 }