-
Notifications
You must be signed in to change notification settings - Fork 2
/
auth.go
163 lines (130 loc) · 3.69 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package auth
import (
"errors"
"fmt"
"strings"
"golang.org/x/crypto/bcrypt"
htbasicauth "github.com/jimstudt/http-authentication/basic"
"github.com/Parquery/revproxyry/config"
)
type hashing int
const (
Apr1MD5 hashing = 0
Bcrypt = 1
)
type Auth struct {
Username string
PasswordHash string
hashing hashing
// md5 represents a parsed MD5 password generated by Apache's htpasswd. Only set if MD5.
md5 htbasicauth.EncodedPasswd
}
// newAuth creates an authentication registry based on the authentication specified in the config.
func newAuth(username string, passwordHash string) (a *Auth, err error) {
a = &Auth{Username: username, PasswordHash: passwordHash}
switch {
case passwordHash == "":
err = errors.New("empty password hash")
return
case strings.HasPrefix(passwordHash, "$apr1$"):
a.hashing = Apr1MD5
a.md5, err = htbasicauth.AcceptMd5(passwordHash)
if err != nil {
err = fmt.Errorf("failed to parse Apr1 MD5 hash: %s", passwordHash)
return
}
case strings.HasPrefix(passwordHash, "$2a$") ||
strings.HasPrefix(passwordHash, "$2y$"):
a.hashing = Bcrypt
default:
err = fmt.Errorf("unknown prefix of the password hash: %s", passwordHash)
return
}
return
}
// Auths represents an authentication registry.
type Auths struct {
// authentication registry maps user name -> list of authentications for this user.
registry map[string][]*Auth
// All indicates whether everybody is granted access.
All bool
}
// New creates a new authentication registry.
func New(cfgAuths map[string]*config.Auth) (auths *Auths, err error) {
auths = &Auths{}
if len(cfgAuths) == 0 {
// If there are no authentications specified, everybody is granted access.
auths.All = true
return
} else {
// If one of the user names is empty, everybody is granted access.
hasEmptyUsername := false
for _, cfgAuth := range cfgAuths {
if cfgAuth.Username == "" {
hasEmptyUsername = true
}
}
if hasEmptyUsername {
auths.All = true
return
}
}
auths.registry = make(map[string][]*Auth)
for id, cfgAuth := range cfgAuths {
var auth *Auth
auth, err = newAuth(cfgAuth.Username, cfgAuth.PasswordHash)
if err != nil {
err = fmt.Errorf("failed to create an authentication from the configuration of an auth %s: %s",
id, err.Error())
return
}
auths.registry[cfgAuth.Username] = append(auths.registry[cfgAuth.Username], auth)
}
return
}
// Authenticate checks whether the user is authentic by checking his/her password against the authentication registry.
//
// If the authentication passes, ok is set to true. In case that the authentication fails, ok is false and the message
// indicates the reason of the authentication failure.
//
// If there was an error during the authentication, err will be set.
func (aa *Auths) Authenticate(username string, password string) (ok bool, msg string, err error) {
if aa.All {
ok = true
return
}
authLst, hasUsername := aa.registry[username]
if !hasUsername {
msg = fmt.Sprintf("unknown user name")
return
}
for _, a := range authLst {
switch a.hashing {
case Apr1MD5:
if a.md5.MatchesPassword(password) {
ok = true
return
}
msg = "invalid password"
return
case Bcrypt:
err = bcrypt.CompareHashAndPassword([]byte(a.PasswordHash), []byte(password))
switch {
case err == nil:
ok = true
return
case err == bcrypt.ErrMismatchedHashAndPassword:
// We need to void the error, since we set the message separately, and this case indicates that there was actually no authentication error.
err = nil
ok = false
msg = "invalid password"
return
default:
return
}
default:
panic(fmt.Sprintf("unhandled case: %d", a.hashing))
}
}
return
}