Blame view

auth.go 5.65 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
  )
fbf92700f   Marko Tikvić   renamed package v...
14
15
  var _issuer = "webutility"
  var _secret = "webutility"
90fd36e9b   Marko Tikvić   resolved some dep...
16

6ec91280b   Marko Tikvić   working on docume...
17
  // TokenClaims are JWT token claims.
90fd36e9b   Marko Tikvić   resolved some dep...
18
  type TokenClaims struct {
a205e8f40   Marko Tikvić   changes
19
20
21
22
  	// extending a struct
  	jwt.StandardClaims
  
  	// custom claims
6620591d8   Marko Tikvić   moved DeliverPayl...
23
24
25
  	Token     string `json:"access_token"`
  	TokenType string `json:"token_type"`
  	Username  string `json:"username"`
de25e1deb   Marko Tikvić   removed redundant...
26
  	RoleName  string `json:"role"`
a205e8f40   Marko Tikvić   changes
27
  	RoleID    int64  `json:"role_id"`
6620591d8   Marko Tikvić   moved DeliverPayl...
28
  	ExpiresIn int64  `json:"expires_in"`
90fd36e9b   Marko Tikvić   resolved some dep...
29
  }
707782344   Marko Tikvić   lint; vet
30
  // InitJWT ...
fbf92700f   Marko Tikvić   renamed package v...
31
32
33
  func InitJWT(issuer, secret string) {
  	_issuer = issuer
  	_secret = secret
f84e7607d   Marko Tikvić   added dictionary;...
34
  }
0feac5059   Marko Tikvić   renamed ValidateC...
35
36
  // ValidateHash hashes pass and salt and returns comparison result with resultHash
  func ValidateHash(pass, salt, resultHash string) (bool, error) {
bc3671b26   Marko Tikvić   refactoring token...
37
  	hash, _, err := CreateHash(pass, salt)
90fd36e9b   Marko Tikvić   resolved some dep...
38
  	if err != nil {
f84e7607d   Marko Tikvić   added dictionary;...
39
  		return false, err
90fd36e9b   Marko Tikvić   resolved some dep...
40
  	}
f84e7607d   Marko Tikvić   added dictionary;...
41
42
  	res := hash == resultHash
  	return res, nil
90fd36e9b   Marko Tikvić   resolved some dep...
43
  }
bc3671b26   Marko Tikvić   refactoring token...
44
45
46
47
  // 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...
48
49
  	// chech if message is presalted
  	if presalt == "" {
bc3671b26   Marko Tikvić   refactoring token...
50
  		salt, err = randomSalt()
90fd36e9b   Marko Tikvić   resolved some dep...
51
52
53
54
55
56
57
58
  		if err != nil {
  			return "", "", err
  		}
  	} else {
  		salt = presalt
  	}
  
  	// convert strings to raw byte slices
33fd58161   markotikvic   minor changes, sh...
59
  	rawstr := []byte(str)
90fd36e9b   Marko Tikvić   resolved some dep...
60
61
62
63
  	rawsalt, err := hex.DecodeString(salt)
  	if err != nil {
  		return "", "", err
  	}
33fd58161   markotikvic   minor changes, sh...
64

d2ddf82ef   Marko Tikvić   started on new rbac
65
  	rawdata := make([]byte, len(rawstr)+len(rawsalt))
33fd58161   markotikvic   minor changes, sh...
66
  	rawdata = append(rawdata, rawstr...)
90fd36e9b   Marko Tikvić   resolved some dep...
67
68
69
70
71
72
  	rawdata = append(rawdata, rawsalt...)
  
  	// hash message + salt
  	hasher := sha256.New()
  	hasher.Write(rawdata)
  	rawhash := hasher.Sum(nil)
33fd58161   markotikvic   minor changes, sh...
73

90fd36e9b   Marko Tikvić   resolved some dep...
74
75
76
  	hash = hex.EncodeToString(rawhash)
  	return hash, salt, nil
  }
bc3671b26   Marko Tikvić   refactoring token...
77
  // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims.
6ec91280b   Marko Tikvić   working on docume...
78
  // It returns an error if it fails.
de25e1deb   Marko Tikvić   removed redundant...
79
  func CreateAuthToken(username string, roleName string, roleID int64) (TokenClaims, error) {
bc3671b26   Marko Tikvić   refactoring token...
80
  	t0 := (time.Now()).Unix()
3fffcb954   Marko Tikvić   removed old http API
81
  	t1 := (time.Now().Add(time.Hour * 24 * 7)).Unix()
90fd36e9b   Marko Tikvić   resolved some dep...
82
  	claims := TokenClaims{
bc3671b26   Marko Tikvić   refactoring token...
83
84
  		TokenType: "Bearer",
  		Username:  username,
de25e1deb   Marko Tikvić   removed redundant...
85
86
  		RoleName:  roleName,
  		RoleID:    roleID,
bc3671b26   Marko Tikvić   refactoring token...
87
  		ExpiresIn: t1 - t0,
90fd36e9b   Marko Tikvić   resolved some dep...
88
  	}
bc3671b26   Marko Tikvić   refactoring token...
89
90
91
  	// initialize jwt.StandardClaims fields (anonymous struct)
  	claims.IssuedAt = t0
  	claims.ExpiresAt = t1
fbf92700f   Marko Tikvić   renamed package v...
92
  	claims.Issuer = _issuer
90fd36e9b   Marko Tikvić   resolved some dep...
93
94
  
  	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
fbf92700f   Marko Tikvić   renamed package v...
95
  	token, err := jwtToken.SignedString([]byte(_secret))
90fd36e9b   Marko Tikvić   resolved some dep...
96
  	if err != nil {
bc3671b26   Marko Tikvić   refactoring token...
97
  		return TokenClaims{}, err
90fd36e9b   Marko Tikvić   resolved some dep...
98
  	}
bc3671b26   Marko Tikvić   refactoring token...
99
100
  	claims.Token = token
  	return claims, nil
90fd36e9b   Marko Tikvić   resolved some dep...
101
  }
3fffcb954   Marko Tikvić   removed old http API
102
103
  // RefreshAuthToken returns new JWT token with same claims contained in tok but with prolonged expiration date.
  // It returns an error if it fails.
c7aadbb39   Marko Tikvić   minor changes
104
105
  func RefreshAuthToken(tok string) (TokenClaims, error) {
  	token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc)
90fd36e9b   Marko Tikvić   resolved some dep...
106
  	if err != nil {
052f8a3a6   Marko Tikvić   token validation ...
107
  		if validation, ok := err.(*jwt.ValidationError); ok {
6faf94f85   Marko Tikvić   Fixed auth check ...
108
  			// don't return error if token is expired, just extend it
052f8a3a6   Marko Tikvić   token validation ...
109
110
111
112
113
114
  			if !(validation.Errors&jwt.ValidationErrorExpired != 0) {
  				return TokenClaims{}, err
  			}
  		} else {
  			return TokenClaims{}, err
  		}
90fd36e9b   Marko Tikvić   resolved some dep...
115
116
117
118
  	}
  
  	// type assertion
  	claims, ok := token.Claims.(*TokenClaims)
052f8a3a6   Marko Tikvić   token validation ...
119
  	if !ok {
bc3671b26   Marko Tikvić   refactoring token...
120
  		return TokenClaims{}, errors.New("token is not valid")
90fd36e9b   Marko Tikvić   resolved some dep...
121
  	}
bc3671b26   Marko Tikvić   refactoring token...
122
  	// extend token expiration date
de25e1deb   Marko Tikvić   removed redundant...
123
  	return CreateAuthToken(claims.Username, claims.RoleName, claims.RoleID)
90fd36e9b   Marko Tikvić   resolved some dep...
124
  }
707782344   Marko Tikvić   lint; vet
125
  // AuthCheck ...
3f8e3c437   Marko Tikvić   minor changes
126
  func AuthCheck(req *http.Request, roles string) (*TokenClaims, error) {
d29773cc4   Marko Tikvić   ProcessRBAC
127
128
129
  	// validate token and check expiration date
  	claims, err := GetTokenClaims(req)
  	if err != nil {
3f8e3c437   Marko Tikvić   minor changes
130
131
132
133
134
  		return claims, err
  	}
  
  	if roles == "" {
  		return claims, nil
d29773cc4   Marko Tikvić   ProcessRBAC
135
  	}
3f8e3c437   Marko Tikvić   minor changes
136

d29773cc4   Marko Tikvić   ProcessRBAC
137
138
  	// check if token has expired
  	if claims.ExpiresAt < (time.Now()).Unix() {
3f8e3c437   Marko Tikvić   minor changes
139
  		return claims, errors.New("token has expired")
d29773cc4   Marko Tikvić   ProcessRBAC
140
  	}
3fffcb954   Marko Tikvić   removed old http API
141
  	if roles == "*" {
3f8e3c437   Marko Tikvić   minor changes
142
  		return claims, nil
3fffcb954   Marko Tikvić   removed old http API
143
144
145
  	}
  
  	parts := strings.Split(roles, ",")
707782344   Marko Tikvić   lint; vet
146
  	for i := range parts {
3fffcb954   Marko Tikvić   removed old http API
147
  		r := strings.Trim(parts[i], " ")
de25e1deb   Marko Tikvić   removed redundant...
148
  		if claims.RoleName == r {
3f8e3c437   Marko Tikvić   minor changes
149
  			return claims, nil
d29773cc4   Marko Tikvić   ProcessRBAC
150
151
  		}
  	}
6faf94f85   Marko Tikvić   Fixed auth check ...
152
  	return claims, errors.New("unauthorized role access")
d29773cc4   Marko Tikvić   ProcessRBAC
153
  }
3fffcb954   Marko Tikvić   removed old http API
154
  // GetTokenClaims extracts JWT claims from Authorization header of req.
bc3671b26   Marko Tikvić   refactoring token...
155
156
157
158
159
160
  // 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...
161
  		tokstr = strings.TrimPrefix(authHead, "Bearer ")
33d137a67   Marko Tikvić   Functional role c...
162
  	} else {
65d214f47   Marko Tikvić   improved middlewa...
163
  		return &TokenClaims{}, errors.New("authorization header is incomplete")
33d137a67   Marko Tikvić   Functional role c...
164
  	}
bc3671b26   Marko Tikvić   refactoring token...
165
  	token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc)
33d137a67   Marko Tikvić   Functional role c...
166
167
168
169
170
  	if err != nil {
  		return &TokenClaims{}, err
  	}
  
  	// type assertion
bc3671b26   Marko Tikvić   refactoring token...
171
172
  	claims, ok := token.Claims.(*TokenClaims)
  	if !ok || !token.Valid {
33d137a67   Marko Tikvić   Functional role c...
173
174
  		return &TokenClaims{}, errors.New("token is not valid")
  	}
33d137a67   Marko Tikvić   Functional role c...
175

bc3671b26   Marko Tikvić   refactoring token...
176
  	return claims, nil
90fd36e9b   Marko Tikvić   resolved some dep...
177
  }
33d137a67   Marko Tikvić   Functional role c...
178

984b5e55a   Marko Tikvić   DecodeJWT
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
  func DecodeJWT(secret, token string) (*TokenClaims, error) {
  	secretfunc := func(*jwt.Token) (interface{}, error) {
  		return []byte(secret), nil
  	}
  
  	tok, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretfunc)
  	if err != nil {
  		if validation, ok := err.(*jwt.ValidationError); ok {
  			// don't return error if token is expired
  			if !(validation.Errors&jwt.ValidationErrorExpired != 0) {
  				return nil, err
  			}
  		} else {
  			return nil, err
  		}
  	}
  
  	// type assertion
  	claims, ok := tok.Claims.(*TokenClaims)
  	if !ok {
  		return &TokenClaims{}, errors.New("token is not valid")
  	}
  
  	return claims, nil
  }
3fffcb954   Marko Tikvić   removed old http API
204
  // randomSalt returns a string of 32 random characters.
bc3671b26   Marko Tikvić   refactoring token...
205
  func randomSalt() (s string, err error) {
368c7f87b   Marko Tikvić   pagination work
206
  	const saltSize = 32
bc3671b26   Marko Tikvić   refactoring token...
207
  	rawsalt := make([]byte, saltSize)
d2ddf82ef   Marko Tikvić   started on new rbac
208

bc3671b26   Marko Tikvić   refactoring token...
209
  	_, err = rand.Read(rawsalt)
d2ddf82ef   Marko Tikvić   started on new rbac
210
  	if err != nil {
bc3671b26   Marko Tikvić   refactoring token...
211
  		return "", err
33d137a67   Marko Tikvić   Functional role c...
212
  	}
d2ddf82ef   Marko Tikvić   started on new rbac
213

bc3671b26   Marko Tikvić   refactoring token...
214
215
  	s = hex.EncodeToString(rawsalt)
  	return s, nil
33d137a67   Marko Tikvić   Functional role c...
216
  }
d2ddf82ef   Marko Tikvić   started on new rbac
217

bc3671b26   Marko Tikvić   refactoring token...
218
219
  // secretFunc returns byte slice of API secret keyword.
  func secretFunc(token *jwt.Token) (interface{}, error) {
fbf92700f   Marko Tikvić   renamed package v...
220
  	return []byte(_secret), nil
d2ddf82ef   Marko Tikvić   started on new rbac
221
  }