Blame view

auth.go 5.05 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

3fffcb954   Marko Tikvić   removed old http API
179
  // randomSalt returns a string of 32 random characters.
bc3671b26   Marko Tikvić   refactoring token...
180
  func randomSalt() (s string, err error) {
368c7f87b   Marko Tikvić   pagination work
181
  	const saltSize = 32
bc3671b26   Marko Tikvić   refactoring token...
182
  	rawsalt := make([]byte, saltSize)
d2ddf82ef   Marko Tikvić   started on new rbac
183

bc3671b26   Marko Tikvić   refactoring token...
184
  	_, err = rand.Read(rawsalt)
d2ddf82ef   Marko Tikvić   started on new rbac
185
  	if err != nil {
bc3671b26   Marko Tikvić   refactoring token...
186
  		return "", err
33d137a67   Marko Tikvić   Functional role c...
187
  	}
d2ddf82ef   Marko Tikvić   started on new rbac
188

bc3671b26   Marko Tikvić   refactoring token...
189
190
  	s = hex.EncodeToString(rawsalt)
  	return s, nil
33d137a67   Marko Tikvić   Functional role c...
191
  }
d2ddf82ef   Marko Tikvić   started on new rbac
192

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