Blame view

auth.go 4.99 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
  )
f84e7607d   Marko Tikvić   added dictionary;...
14
15
  var appName = "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
  }
f84e7607d   Marko Tikvić   added dictionary;...
30
31
32
33
  func InitJWT(appName, secret string) {
  	appName = appName
  	secret = secret
  }
0feac5059   Marko Tikvić   renamed ValidateC...
34
35
  // ValidateHash hashes pass and salt and returns comparison result with resultHash
  func ValidateHash(pass, salt, resultHash string) (bool, error) {
bc3671b26   Marko Tikvić   refactoring token...
36
  	hash, _, err := CreateHash(pass, salt)
90fd36e9b   Marko Tikvić   resolved some dep...
37
  	if err != nil {
f84e7607d   Marko Tikvić   added dictionary;...
38
  		return false, err
90fd36e9b   Marko Tikvić   resolved some dep...
39
  	}
f84e7607d   Marko Tikvić   added dictionary;...
40
41
  	res := hash == resultHash
  	return res, nil
90fd36e9b   Marko Tikvić   resolved some dep...
42
  }
bc3671b26   Marko Tikvić   refactoring token...
43
44
45
46
  // 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...
47
48
  	// chech if message is presalted
  	if presalt == "" {
bc3671b26   Marko Tikvić   refactoring token...
49
  		salt, err = randomSalt()
90fd36e9b   Marko Tikvić   resolved some dep...
50
51
52
53
54
55
56
57
  		if err != nil {
  			return "", "", err
  		}
  	} else {
  		salt = presalt
  	}
  
  	// convert strings to raw byte slices
33fd58161   markotikvic   minor changes, sh...
58
  	rawstr := []byte(str)
90fd36e9b   Marko Tikvić   resolved some dep...
59
60
61
62
  	rawsalt, err := hex.DecodeString(salt)
  	if err != nil {
  		return "", "", err
  	}
33fd58161   markotikvic   minor changes, sh...
63

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

90fd36e9b   Marko Tikvić   resolved some dep...
73
74
75
  	hash = hex.EncodeToString(rawhash)
  	return hash, salt, nil
  }
bc3671b26   Marko Tikvić   refactoring token...
76
  // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims.
6ec91280b   Marko Tikvić   working on docume...
77
  // It returns an error if it fails.
de25e1deb   Marko Tikvić   removed redundant...
78
  func CreateAuthToken(username string, roleName string, roleID int64) (TokenClaims, error) {
bc3671b26   Marko Tikvić   refactoring token...
79
  	t0 := (time.Now()).Unix()
3fffcb954   Marko Tikvić   removed old http API
80
  	t1 := (time.Now().Add(time.Hour * 24 * 7)).Unix()
90fd36e9b   Marko Tikvić   resolved some dep...
81
  	claims := TokenClaims{
bc3671b26   Marko Tikvić   refactoring token...
82
83
  		TokenType: "Bearer",
  		Username:  username,
de25e1deb   Marko Tikvić   removed redundant...
84
85
  		RoleName:  roleName,
  		RoleID:    roleID,
bc3671b26   Marko Tikvić   refactoring token...
86
  		ExpiresIn: t1 - t0,
90fd36e9b   Marko Tikvić   resolved some dep...
87
  	}
bc3671b26   Marko Tikvić   refactoring token...
88
89
90
91
  	// initialize jwt.StandardClaims fields (anonymous struct)
  	claims.IssuedAt = t0
  	claims.ExpiresAt = t1
  	claims.Issuer = appName
90fd36e9b   Marko Tikvić   resolved some dep...
92
93
  
  	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
bc3671b26   Marko Tikvić   refactoring token...
94
  	token, err := jwtToken.SignedString([]byte(secret))
90fd36e9b   Marko Tikvić   resolved some dep...
95
  	if err != nil {
bc3671b26   Marko Tikvić   refactoring token...
96
  		return TokenClaims{}, err
90fd36e9b   Marko Tikvić   resolved some dep...
97
  	}
bc3671b26   Marko Tikvić   refactoring token...
98
99
  	claims.Token = token
  	return claims, nil
90fd36e9b   Marko Tikvić   resolved some dep...
100
  }
3fffcb954   Marko Tikvić   removed old http API
101
102
  // 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
103
104
  func RefreshAuthToken(tok string) (TokenClaims, error) {
  	token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc)
90fd36e9b   Marko Tikvić   resolved some dep...
105
  	if err != nil {
052f8a3a6   Marko Tikvić   token validation ...
106
107
108
109
110
111
112
113
114
  		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...
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
  }
3f8e3c437   Marko Tikvić   minor changes
125
  func AuthCheck(req *http.Request, roles string) (*TokenClaims, error) {
d29773cc4   Marko Tikvić   ProcessRBAC
126
127
128
  	// validate token and check expiration date
  	claims, err := GetTokenClaims(req)
  	if err != nil {
3f8e3c437   Marko Tikvić   minor changes
129
130
131
132
133
  		return claims, err
  	}
  
  	if roles == "" {
  		return claims, nil
d29773cc4   Marko Tikvić   ProcessRBAC
134
  	}
3f8e3c437   Marko Tikvić   minor changes
135

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

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

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

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

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

bc3671b26   Marko Tikvić   refactoring token...
192
193
194
  // 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
195
  }