forked from martini-contrib/auth
/
basic.go
151 lines (125 loc) · 4.13 KB
/
basic.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
// Package auth implements Basic authentication.
package auth
import (
"encoding/base64"
"net/http"
"strings"
"time"
"github.com/patrickmn/go-cache"
"github.com/urfave/negroni"
"golang.org/x/crypto/bcrypt"
"github.com/nabeken/negroni-auth/datastore"
)
const (
defaultCacheExpireTime = 10 * time.Minute
defaultCachePurseTime = 60 * time.Second
)
// See https://godoc.org/golang.org/x/crypto/bcrypt#pkg-constants for more details.
var BcryptCost = 10
// NewSimpleBasic returns *datastore.Simple built from userid, password.
func NewSimpleBasic(userId, password string) (*datastore.Simple, error) {
hashedPassword, err := Hash(password)
if err != nil {
return nil, err
}
return &datastore.Simple{
Key: userId,
Value: hashedPassword,
}, nil
}
// requireAuth writes error to client which initiates the authentication process
// or requires reauthentication.
func requireAuth(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
http.Error(w, "Not Authorized", http.StatusUnauthorized)
}
// getCred get userid, password from request.
func getCred(req *http.Request) (string, string) {
// Split authorization header.
s := strings.SplitN(req.Header.Get("Authorization"), " ", 2)
if len(s) != 2 || s[0] != "Basic" {
return "", ""
}
// Decode credential.
cred, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return "", ""
}
// Split credential into userid, password.
pair := strings.SplitN(string(cred), ":", 2)
if len(pair) != 2 {
return "", ""
}
return pair[0], pair[1]
}
// Hash returns a hashed password.
func Hash(password string) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(password), BcryptCost)
}
// NewBasic returns a negroni.HandlerFunc that authenticates via Basic auth using data store.
// Writes a http.StatusUnauthorized if authentication fails.
func NewBasic(datastore datastore.Datastore) negroni.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
// Extract userid, password from request.
userId, password := getCred(req)
if userId == "" {
requireAuth(w)
return
}
// Extract hashed passwor from credentials.
hashedPassword, found := datastore.Get(userId)
if !found {
requireAuth(w)
return
}
// Check if the password is correct.
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
// Password not correct. Fail.
if err != nil {
requireAuth(w)
return
}
r := w.(negroni.ResponseWriter)
// Password correct.
if r.Status() != http.StatusUnauthorized {
next(w, req)
}
}
}
// Basic returns a negroni.HandlerFunc that authenticates via Basic Auth.
// Writes a http.StatusUnauthorized if authentication fails.
func Basic(userid, password string) negroni.HandlerFunc {
datastore, err := NewSimpleBasic(userid, password)
if err != nil {
panic(err)
}
return NewBasic(datastore)
}
// CacheBasic returns a negroni.HandlerFunc that authenticates via Basic auth using cache.
// Writes a http.StatusUnauthorized if authentication fails.
func CacheBasic(datastore datastore.Datastore, cacheExpireTime, cachePurseTime time.Duration) negroni.HandlerFunc {
var basic = NewBasic(datastore)
var c = cache.New(cacheExpireTime, cachePurseTime)
return func(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
// Get credential from request header.
credential := req.Header.Get("Authorization")
// Get authentication status by credential.
authenticated, found := c.Get(credential)
// Cache hit
if found && (authenticated == "true") {
next(w, req)
} else { // Cache miss. Unauthenticated.
basic(w, req, next)
r := w.(negroni.ResponseWriter)
// Password correct.
if r.Status() != http.StatusUnauthorized {
c.Set(credential, "true", cache.DefaultExpiration)
}
}
}
}
// CacheBasicDefault returns a negroni.HandlerFunc that authenticates via Basic auth using cache.
// with default cache configuration. Writes a http.StatusUnauthorized if authentication fails.
func CacheBasicDefault(datastore datastore.Datastore) negroni.HandlerFunc {
return CacheBasic(datastore, defaultCacheExpireTime, defaultCachePurseTime)
}