-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.go
159 lines (131 loc) · 4.12 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
package auth
import (
"errors"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/jinzhu/gorm"
"github.com/satori/go.uuid"
"golang.org/x/crypto/bcrypt"
)
// Authenticator exposes the minimal set of operations needed for authentication
type Authenticator interface {
Register(user *User, password string) error
GetToken(email string, password string, opts *GetTokenOpts) (token string, err error)
Validate(token string) (*Claims, error)
}
type auth struct {
databaseHandler DatabaseHandler
signingKey []byte
}
type TokenData map[string]string
// Claims represents data that are encoded into an authentication token
type Claims struct {
UserUUID string `json:"user_uuid"`
Permissions int64 `json:"permissions"`
Email string `json:"email"`
Data TokenData `json:"data"`
jwt.StandardClaims
}
type GetTokenOpts struct {
RequestedPermissions Permissions
TimeToLive time.Duration
Data TokenData
}
// ErrorValidatingToken indicates issues validating the provided token
var ErrorValidatingToken = errors.New("problem validating token")
// ErrorExceededMaxPermissionLevel indicates that a requested op exceeded the maximum permission level
var ErrorExceededMaxPermissionLevel = errors.New(
"you're requesting a token permission level that exceeds this user's maximum permission level",
)
// DefaultTTL represents the default time to live for a newly issued token in nanoseconds, in this case 8 hours
const DefaultTTL = time.Duration(2.88e13)
// NewAuthenticator returns a newly initialized Authenticator
func NewAuthenticator(dbConnection string, signingKey []byte) (Authenticator, error) {
d, err := NewDatabaseHandler(dbConnection)
if err != nil {
return nil, err
}
return &auth{
databaseHandler: d,
signingKey: signingKey,
}, nil
}
// NewAuthenticatorFromGORM returns a newly init'd Authenticator from a *gorm.DB
func NewAuthenticatorFromGORM(db *gorm.DB, signingKey []byte) (Authenticator, error) {
d, err := NewDatabaseHandlerFromGORM(db)
if err != nil {
return nil, err
}
return &auth{
databaseHandler: d,
signingKey: signingKey,
}, nil
}
// Register adds a new user.
func (a *auth) Register(newUser *User, password string) error {
// Always generate a new UUID for newUser
newUser.UUID = uuid.NewV4()
// Hash password, add to the newUser struct
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
newUser.HashedPassword = string(hash)
// Upsert user
err = a.databaseHandler.UpsertUser(*newUser)
if err != nil {
return err
}
return nil
}
// GetToken mints a new authentication token at the given requestedPermissions level, if possible.
func (a *auth) GetToken(email string, password string, opts *GetTokenOpts) (string, error) {
// Check database for User and verify credentials
user, err := a.databaseHandler.GetUser(User{Email: email})
if err != nil {
return "", err
}
// Check hashed password
err = bcrypt.CompareHashAndPassword([]byte(user.HashedPassword), []byte(password))
if err != nil {
// Passwords don't match!
return "", err
}
// Verify requestedPermissions
if opts.RequestedPermissions > user.MaxPermissionLevel {
return "", ErrorExceededMaxPermissionLevel
}
// Generate a login token for this user
var ttl = opts.TimeToLive
if ttl == 0 {
ttl = DefaultTTL
}
c := Claims{
UserUUID: user.UUID.String(),
Permissions: int64(opts.RequestedPermissions),
Email: user.Email,
Data: opts.Data,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(ttl).Unix(),
},
}
t := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
token, err := t.SignedString(a.signingKey)
if err != nil {
return "", err
}
return token, nil
}
// Validate decrypts and validates a token. Returns any claims embedded in the token.
func (a *auth) Validate(token string) (*Claims, error) {
t, err := jwt.ParseWithClaims(token, &Claims{}, func(jt *jwt.Token) (interface{}, error) {
return []byte(a.signingKey), nil
})
if err != nil {
return nil, err
}
if claims, ok := t.Claims.(*Claims); ok && t.Valid {
return claims, nil
}
return &Claims{}, ErrorValidatingToken
}