Blame view

auth.go 6.32 KB
ea858b8a7   Marko Tikvić   refactoring
1
  package webutility
90fd36e9b   Marko Tikvić   resolved some dep...
2
3
  
  import (
90fd36e9b   Marko Tikvić   resolved some dep...
4
  	"crypto/rand"
d2ddf82ef   Marko Tikvić   started on new rbac
5
  	"crypto/sha256"
90fd36e9b   Marko Tikvić   resolved some dep...
6
  	"encoding/hex"
d2ddf82ef   Marko Tikvić   started on new rbac
7
  	"errors"
33d137a67   Marko Tikvić   Functional role c...
8
  	"net/http"
d2ddf82ef   Marko Tikvić   started on new rbac
9
10
  	"strings"
  	"time"
33d137a67   Marko Tikvić   Functional role c...
11

90fd36e9b   Marko Tikvić   resolved some dep...
12
  	"github.com/dgrijalva/jwt-go"
90fd36e9b   Marko Tikvić   resolved some dep...
13
  )
d2ddf82ef   Marko Tikvić   started on new rbac
14
15
  const OneDay = time.Hour * 24
  const OneWeek = OneDay * 7
90fd36e9b   Marko Tikvić   resolved some dep...
16
  const saltSize = 32
f84e7607d   Marko Tikvić   added dictionary;...
17
18
19
  
  var appName = "webutility"
  var secret = "webutility"
90fd36e9b   Marko Tikvić   resolved some dep...
20

d2ddf82ef   Marko Tikvić   started on new rbac
21
  type Role struct {
077dae33c   Marko Tikvić   removed role cons...
22
  	Name string `json:"name"`
a205e8f40   Marko Tikvić   changes
23
  	ID   int64  `json:"id"`
d2ddf82ef   Marko Tikvić   started on new rbac
24
  }
6ec91280b   Marko Tikvić   working on docume...
25
  // TokenClaims are JWT token claims.
90fd36e9b   Marko Tikvić   resolved some dep...
26
  type TokenClaims struct {
a205e8f40   Marko Tikvić   changes
27
28
29
30
  	// extending a struct
  	jwt.StandardClaims
  
  	// custom claims
6620591d8   Marko Tikvić   moved DeliverPayl...
31
32
33
34
  	Token     string `json:"access_token"`
  	TokenType string `json:"token_type"`
  	Username  string `json:"username"`
  	Role      string `json:"role"`
a205e8f40   Marko Tikvić   changes
35
  	RoleID    int64  `json:"role_id"`
6620591d8   Marko Tikvić   moved DeliverPayl...
36
  	ExpiresIn int64  `json:"expires_in"`
90fd36e9b   Marko Tikvić   resolved some dep...
37
  }
f84e7607d   Marko Tikvić   added dictionary;...
38
39
40
41
  func InitJWT(appName, secret string) {
  	appName = appName
  	secret = secret
  }
bc3671b26   Marko Tikvić   refactoring token...
42
  // ValidateCredentials hashes pass and salt and returns comparison result with resultHash
f84e7607d   Marko Tikvić   added dictionary;...
43
  func ValidateCredentials(pass, salt, resultHash string) (bool, error) {
bc3671b26   Marko Tikvić   refactoring token...
44
  	hash, _, err := CreateHash(pass, salt)
90fd36e9b   Marko Tikvić   resolved some dep...
45
  	if err != nil {
f84e7607d   Marko Tikvić   added dictionary;...
46
  		return false, err
90fd36e9b   Marko Tikvić   resolved some dep...
47
  	}
f84e7607d   Marko Tikvić   added dictionary;...
48
49
  	res := hash == resultHash
  	return res, nil
90fd36e9b   Marko Tikvić   resolved some dep...
50
  }
bc3671b26   Marko Tikvić   refactoring token...
51
52
53
54
  // CreateHash hashes str using SHA256.
  // If the presalt parameter is not provided CreateHash will generate new salt string.
  // Returns hash and salt strings or an error if it fails.
  func CreateHash(str, presalt string) (hash, salt string, err error) {
90fd36e9b   Marko Tikvić   resolved some dep...
55
56
  	// chech if message is presalted
  	if presalt == "" {
bc3671b26   Marko Tikvić   refactoring token...
57
  		salt, err = randomSalt()
90fd36e9b   Marko Tikvić   resolved some dep...
58
59
60
61
62
63
64
65
  		if err != nil {
  			return "", "", err
  		}
  	} else {
  		salt = presalt
  	}
  
  	// convert strings to raw byte slices
33fd58161   markotikvic   minor changes, sh...
66
  	rawstr := []byte(str)
90fd36e9b   Marko Tikvić   resolved some dep...
67
68
69
70
  	rawsalt, err := hex.DecodeString(salt)
  	if err != nil {
  		return "", "", err
  	}
33fd58161   markotikvic   minor changes, sh...
71

d2ddf82ef   Marko Tikvić   started on new rbac
72
  	rawdata := make([]byte, len(rawstr)+len(rawsalt))
33fd58161   markotikvic   minor changes, sh...
73
  	rawdata = append(rawdata, rawstr...)
90fd36e9b   Marko Tikvić   resolved some dep...
74
75
76
77
78
79
  	rawdata = append(rawdata, rawsalt...)
  
  	// hash message + salt
  	hasher := sha256.New()
  	hasher.Write(rawdata)
  	rawhash := hasher.Sum(nil)
33fd58161   markotikvic   minor changes, sh...
80

90fd36e9b   Marko Tikvić   resolved some dep...
81
82
83
  	hash = hex.EncodeToString(rawhash)
  	return hash, salt, nil
  }
bc3671b26   Marko Tikvić   refactoring token...
84
  // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims.
6ec91280b   Marko Tikvić   working on docume...
85
  // It returns an error if it fails.
bc3671b26   Marko Tikvić   refactoring token...
86
87
88
  func CreateAuthToken(username string, role Role) (TokenClaims, error) {
  	t0 := (time.Now()).Unix()
  	t1 := (time.Now().Add(OneWeek)).Unix()
90fd36e9b   Marko Tikvić   resolved some dep...
89
  	claims := TokenClaims{
bc3671b26   Marko Tikvić   refactoring token...
90
91
92
93
94
  		TokenType: "Bearer",
  		Username:  username,
  		Role:      role.Name,
  		RoleID:    role.ID,
  		ExpiresIn: t1 - t0,
90fd36e9b   Marko Tikvić   resolved some dep...
95
  	}
bc3671b26   Marko Tikvić   refactoring token...
96
97
98
99
  	// initialize jwt.StandardClaims fields (anonymous struct)
  	claims.IssuedAt = t0
  	claims.ExpiresAt = t1
  	claims.Issuer = appName
90fd36e9b   Marko Tikvić   resolved some dep...
100
101
  
  	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
bc3671b26   Marko Tikvić   refactoring token...
102
  	token, err := jwtToken.SignedString([]byte(secret))
90fd36e9b   Marko Tikvić   resolved some dep...
103
  	if err != nil {
bc3671b26   Marko Tikvić   refactoring token...
104
  		return TokenClaims{}, err
90fd36e9b   Marko Tikvić   resolved some dep...
105
  	}
bc3671b26   Marko Tikvić   refactoring token...
106
107
  	claims.Token = token
  	return claims, nil
90fd36e9b   Marko Tikvić   resolved some dep...
108
  }
c7aadbb39   Marko Tikvić   minor changes
109
  // RefreshAuthToken returns new JWT token with sprolongs JWT token's expiration date for one week.
6ec91280b   Marko Tikvić   working on docume...
110
  // It returns new JWT token or an error if it fails.
c7aadbb39   Marko Tikvić   minor changes
111
112
  func RefreshAuthToken(tok string) (TokenClaims, error) {
  	token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc)
90fd36e9b   Marko Tikvić   resolved some dep...
113
  	if err != nil {
052f8a3a6   Marko Tikvić   token validation ...
114
115
116
117
118
119
120
121
122
  		if validation, ok := err.(*jwt.ValidationError); ok {
  			// don't return error if token is expired
  			// just extend it
  			if !(validation.Errors&jwt.ValidationErrorExpired != 0) {
  				return TokenClaims{}, err
  			}
  		} else {
  			return TokenClaims{}, err
  		}
90fd36e9b   Marko Tikvić   resolved some dep...
123
124
125
126
  	}
  
  	// type assertion
  	claims, ok := token.Claims.(*TokenClaims)
052f8a3a6   Marko Tikvić   token validation ...
127
  	if !ok {
bc3671b26   Marko Tikvić   refactoring token...
128
  		return TokenClaims{}, errors.New("token is not valid")
90fd36e9b   Marko Tikvić   resolved some dep...
129
  	}
bc3671b26   Marko Tikvić   refactoring token...
130
131
  	// extend token expiration date
  	return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID})
90fd36e9b   Marko Tikvić   resolved some dep...
132
  }
d29773cc4   Marko Tikvić   ProcessRBAC
133
  // RbacCheck returns true if user that made HTTP request is authorized to
bc3671b26   Marko Tikvić   refactoring token...
134
135
136
137
138
  // access the resource it is targeting.
  // It exctracts user's role from the JWT token located in Authorization header of
  // http.Request and then compares it with the list of supplied roles and returns
  // true if there's a match, if "*" is provided or if the authRoles is nil.
  // Otherwise it returns false.
2d79a4120   Marko Tikvić   Responses contain...
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
  func RbacCheck(req *http.Request, authRoles []string) bool {
  	if authRoles == nil {
  		return true
  	}
  
  	// validate token and check expiration date
  	claims, err := GetTokenClaims(req)
  	if err != nil {
  		return false
  	}
  	// check if token has expired
  	if claims.ExpiresAt < (time.Now()).Unix() {
  		return false
  	}
  
  	// check if role extracted from token matches
  	// any of the provided (allowed) ones
  	for _, r := range authRoles {
  		if claims.Role == r || r == "*" {
  			return true
  		}
  	}
  
  	return false
  }
63b2ae620   Marko Tikvić   renamed files
164
  // AuthCheck returns token claims and boolean value based on user's rights to access resource specified in req.
d29773cc4   Marko Tikvić   ProcessRBAC
165
  // It exctracts user's role from the JWT token located in Authorization header of
c7aadbb39   Marko Tikvić   minor changes
166
167
  // HTTP request and then compares it with the list of supplied (authorized);
  // it returns true if there's a match, if "*" is provided or if the authRoles is nil.
63b2ae620   Marko Tikvić   renamed files
168
  func AuthCheck(req *http.Request, authRoles []string) (*TokenClaims, bool) {
d29773cc4   Marko Tikvić   ProcessRBAC
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
  	if authRoles == nil {
  		return nil, true
  	}
  
  	// validate token and check expiration date
  	claims, err := GetTokenClaims(req)
  	if err != nil {
  		return claims, false
  	}
  	// check if token has expired
  	if claims.ExpiresAt < (time.Now()).Unix() {
  		return claims, false
  	}
  
  	// check if role extracted from token matches
  	// any of the provided (allowed) ones
  	for _, r := range authRoles {
  		if claims.Role == r || r == "*" {
  			return claims, true
  		}
  	}
  
  	return claims, false
  }
bc3671b26   Marko Tikvić   refactoring token...
193
194
195
196
197
198
199
  // GetTokenClaims extracts JWT claims from Authorization header of the request.
  // Returns token claims or an error.
  func GetTokenClaims(req *http.Request) (*TokenClaims, error) {
  	// check for and strip 'Bearer' prefix
  	var tokstr string
  	authHead := req.Header.Get("Authorization")
  	if ok := strings.HasPrefix(authHead, "Bearer "); ok {
2d79a4120   Marko Tikvić   Responses contain...
200
  		tokstr = strings.TrimPrefix(authHead, "Bearer ")
33d137a67   Marko Tikvić   Functional role c...
201
  	} else {
bc3671b26   Marko Tikvić   refactoring token...
202
  		return &TokenClaims{}, errors.New("authorization header in incomplete")
33d137a67   Marko Tikvić   Functional role c...
203
  	}
bc3671b26   Marko Tikvić   refactoring token...
204
  	token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc)
33d137a67   Marko Tikvić   Functional role c...
205
206
207
208
209
  	if err != nil {
  		return &TokenClaims{}, err
  	}
  
  	// type assertion
bc3671b26   Marko Tikvić   refactoring token...
210
211
  	claims, ok := token.Claims.(*TokenClaims)
  	if !ok || !token.Valid {
33d137a67   Marko Tikvić   Functional role c...
212
213
  		return &TokenClaims{}, errors.New("token is not valid")
  	}
33d137a67   Marko Tikvić   Functional role c...
214

bc3671b26   Marko Tikvić   refactoring token...
215
  	return claims, nil
90fd36e9b   Marko Tikvić   resolved some dep...
216
  }
33d137a67   Marko Tikvić   Functional role c...
217

bc3671b26   Marko Tikvić   refactoring token...
218
219
220
  // randomSalt returns a string of random characters of 'saltSize' length.
  func randomSalt() (s string, err error) {
  	rawsalt := make([]byte, saltSize)
d2ddf82ef   Marko Tikvić   started on new rbac
221

bc3671b26   Marko Tikvić   refactoring token...
222
  	_, err = rand.Read(rawsalt)
d2ddf82ef   Marko Tikvić   started on new rbac
223
  	if err != nil {
bc3671b26   Marko Tikvić   refactoring token...
224
  		return "", err
33d137a67   Marko Tikvić   Functional role c...
225
  	}
d2ddf82ef   Marko Tikvić   started on new rbac
226

bc3671b26   Marko Tikvić   refactoring token...
227
228
  	s = hex.EncodeToString(rawsalt)
  	return s, nil
33d137a67   Marko Tikvić   Functional role c...
229
  }
d2ddf82ef   Marko Tikvić   started on new rbac
230

bc3671b26   Marko Tikvić   refactoring token...
231
232
233
  // secretFunc returns byte slice of API secret keyword.
  func secretFunc(token *jwt.Token) (interface{}, error) {
  	return []byte(secret), nil
d2ddf82ef   Marko Tikvić   started on new rbac
234
  }