Commit c7aadbb39d060fc166404ee45d5ba7e7e352fc20

Authored by Marko Tikvić
1 parent 79071a5d4f
Exists in master and in 1 other branch v2

minor changes

Showing 1 changed file with 7 additions and 17 deletions   Show diff stats
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "crypto/rand" 4 "crypto/rand"
5 "crypto/sha256" 5 "crypto/sha256"
6 "encoding/hex" 6 "encoding/hex"
7 "errors" 7 "errors"
8 "net/http" 8 "net/http"
9 "strings" 9 "strings"
10 "time" 10 "time"
11 11
12 "github.com/dgrijalva/jwt-go" 12 "github.com/dgrijalva/jwt-go"
13 ) 13 )
14 14
15 const OneDay = time.Hour * 24 15 const OneDay = time.Hour * 24
16 const OneWeek = OneDay * 7 16 const OneWeek = OneDay * 7
17 const saltSize = 32 17 const saltSize = 32
18 const appName = "korisnicki-centar" 18 const appName = "korisnicki-centar"
19 const secret = "korisnicki-centar-api" 19 const secret = "korisnicki-centar-api"
20 20
21 type Role struct { 21 type Role struct {
22 Name string `json:"name"` 22 Name string `json:"name"`
23 ID uint32 `json:"id"` 23 ID int `json:"id"`
24 } 24 }
25 25
26 // TokenClaims are JWT token claims. 26 // TokenClaims are JWT token claims.
27 type TokenClaims struct { 27 type TokenClaims struct {
28 Token string `json:"access_token"` 28 Token string `json:"access_token"`
29 TokenType string `json:"token_type"` 29 TokenType string `json:"token_type"`
30 Username string `json:"username"` 30 Username string `json:"username"`
31 Role string `json:"role"` 31 Role string `json:"role"`
32 RoleID uint32 `json:"role_id"` 32 RoleID int `json:"role_id"`
33 ExpiresIn int64 `json:"expires_in"` 33 ExpiresIn int64 `json:"expires_in"`
34 34
35 // extending a struct 35 // extending a struct
36 jwt.StandardClaims 36 jwt.StandardClaims
37 } 37 }
38 38
39 // CredentialsStruct is an instace of username/password values.
40 type CredentialsStruct struct {
41 Username string `json:"username"`
42 Password string `json:"password"`
43 RoleID uint32 `json:"roleID"`
44 }
45
46 // ValidateCredentials hashes pass and salt and returns comparison result with resultHash 39 // ValidateCredentials hashes pass and salt and returns comparison result with resultHash
47 func ValidateCredentials(pass, salt, resultHash string) bool { 40 func ValidateCredentials(pass, salt, resultHash string) bool {
48 hash, _, err := CreateHash(pass, salt) 41 hash, _, err := CreateHash(pass, salt)
49 if err != nil { 42 if err != nil {
50 return false 43 return false
51 } 44 }
52 return hash == resultHash 45 return hash == resultHash
53 } 46 }
54 47
55 // CreateHash hashes str using SHA256. 48 // CreateHash hashes str using SHA256.
56 // If the presalt parameter is not provided CreateHash will generate new salt string. 49 // If the presalt parameter is not provided CreateHash will generate new salt string.
57 // Returns hash and salt strings or an error if it fails. 50 // Returns hash and salt strings or an error if it fails.
58 func CreateHash(str, presalt string) (hash, salt string, err error) { 51 func CreateHash(str, presalt string) (hash, salt string, err error) {
59 // chech if message is presalted 52 // chech if message is presalted
60 if presalt == "" { 53 if presalt == "" {
61 salt, err = randomSalt() 54 salt, err = randomSalt()
62 if err != nil { 55 if err != nil {
63 return "", "", err 56 return "", "", err
64 } 57 }
65 } else { 58 } else {
66 salt = presalt 59 salt = presalt
67 } 60 }
68 61
69 // convert strings to raw byte slices 62 // convert strings to raw byte slices
70 rawstr := []byte(str) 63 rawstr := []byte(str)
71 rawsalt, err := hex.DecodeString(salt) 64 rawsalt, err := hex.DecodeString(salt)
72 if err != nil { 65 if err != nil {
73 return "", "", err 66 return "", "", err
74 } 67 }
75 68
76 rawdata := make([]byte, len(rawstr)+len(rawsalt)) 69 rawdata := make([]byte, len(rawstr)+len(rawsalt))
77 rawdata = append(rawdata, rawstr...) 70 rawdata = append(rawdata, rawstr...)
78 rawdata = append(rawdata, rawsalt...) 71 rawdata = append(rawdata, rawsalt...)
79 72
80 // hash message + salt 73 // hash message + salt
81 hasher := sha256.New() 74 hasher := sha256.New()
82 hasher.Write(rawdata) 75 hasher.Write(rawdata)
83 rawhash := hasher.Sum(nil) 76 rawhash := hasher.Sum(nil)
84 77
85 hash = hex.EncodeToString(rawhash) 78 hash = hex.EncodeToString(rawhash)
86 return hash, salt, nil 79 return hash, salt, nil
87 } 80 }
88 81
89 // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. 82 // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims.
90 // It returns an error if it fails. 83 // It returns an error if it fails.
91 func CreateAuthToken(username string, role Role) (TokenClaims, error) { 84 func CreateAuthToken(username string, role Role) (TokenClaims, error) {
92 t0 := (time.Now()).Unix() 85 t0 := (time.Now()).Unix()
93 t1 := (time.Now().Add(OneWeek)).Unix() 86 t1 := (time.Now().Add(OneWeek)).Unix()
94 claims := TokenClaims{ 87 claims := TokenClaims{
95 TokenType: "Bearer", 88 TokenType: "Bearer",
96 Username: username, 89 Username: username,
97 Role: role.Name, 90 Role: role.Name,
98 RoleID: role.ID, 91 RoleID: role.ID,
99 ExpiresIn: t1 - t0, 92 ExpiresIn: t1 - t0,
100 } 93 }
101 // initialize jwt.StandardClaims fields (anonymous struct) 94 // initialize jwt.StandardClaims fields (anonymous struct)
102 claims.IssuedAt = t0 95 claims.IssuedAt = t0
103 claims.ExpiresAt = t1 96 claims.ExpiresAt = t1
104 claims.Issuer = appName 97 claims.Issuer = appName
105 98
106 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 99 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
107 token, err := jwtToken.SignedString([]byte(secret)) 100 token, err := jwtToken.SignedString([]byte(secret))
108 if err != nil { 101 if err != nil {
109 return TokenClaims{}, err 102 return TokenClaims{}, err
110 } 103 }
111 claims.Token = token 104 claims.Token = token
112 return claims, nil 105 return claims, nil
113 } 106 }
114 107
115 // RefreshAuthToken prolongs JWT token's expiration date for one week. 108 // RefreshAuthToken returns new JWT token with sprolongs JWT token's expiration date for one week.
116 // It returns new JWT token or an error if it fails. 109 // It returns new JWT token or an error if it fails.
117 func RefreshAuthToken(req *http.Request) (TokenClaims, error) { 110 func RefreshAuthToken(tok string) (TokenClaims, error) {
118 authHead := req.Header.Get("Authorization") 111 token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc)
119 tokenstr := strings.TrimPrefix(authHead, "Bearer ")
120 token, err := jwt.ParseWithClaims(tokenstr, &TokenClaims{}, secretFunc)
121 if err != nil { 112 if err != nil {
122 if validation, ok := err.(*jwt.ValidationError); ok { 113 if validation, ok := err.(*jwt.ValidationError); ok {
123 // don't return error if token is expired 114 // don't return error if token is expired
124 // just extend it 115 // just extend it
125 if !(validation.Errors&jwt.ValidationErrorExpired != 0) { 116 if !(validation.Errors&jwt.ValidationErrorExpired != 0) {
126 return TokenClaims{}, err 117 return TokenClaims{}, err
127 } 118 }
128 } else { 119 } else {
129 return TokenClaims{}, err 120 return TokenClaims{}, err
130 } 121 }
131 } 122 }
132 123
133 // type assertion 124 // type assertion
134 claims, ok := token.Claims.(*TokenClaims) 125 claims, ok := token.Claims.(*TokenClaims)
135 if !ok { 126 if !ok {
136 return TokenClaims{}, errors.New("token is not valid") 127 return TokenClaims{}, errors.New("token is not valid")
137 } 128 }
138 129
139 // extend token expiration date 130 // extend token expiration date
140 return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID}) 131 return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID})
141 } 132 }
142 133
143 // RbacCheck returns true if user that made HTTP request is authorized to 134 // RbacCheck returns true if user that made HTTP request is authorized to
144 // access the resource it is targeting. 135 // access the resource it is targeting.
145 // It exctracts user's role from the JWT token located in Authorization header of 136 // It exctracts user's role from the JWT token located in Authorization header of
146 // http.Request and then compares it with the list of supplied roles and returns 137 // http.Request and then compares it with the list of supplied roles and returns
147 // true if there's a match, if "*" is provided or if the authRoles is nil. 138 // true if there's a match, if "*" is provided or if the authRoles is nil.
148 // Otherwise it returns false. 139 // Otherwise it returns false.
149 func RbacCheck(req *http.Request, authRoles []string) bool { 140 func RbacCheck(req *http.Request, authRoles []string) bool {
150 if authRoles == nil { 141 if authRoles == nil {
151 return true 142 return true
152 } 143 }
153 144
154 // validate token and check expiration date 145 // validate token and check expiration date
155 claims, err := GetTokenClaims(req) 146 claims, err := GetTokenClaims(req)
156 if err != nil { 147 if err != nil {
157 return false 148 return false
158 } 149 }
159 // check if token has expired 150 // check if token has expired
160 if claims.ExpiresAt < (time.Now()).Unix() { 151 if claims.ExpiresAt < (time.Now()).Unix() {
161 return false 152 return false
162 } 153 }
163 154
164 // check if role extracted from token matches 155 // check if role extracted from token matches
165 // any of the provided (allowed) ones 156 // any of the provided (allowed) ones
166 for _, r := range authRoles { 157 for _, r := range authRoles {
167 if claims.Role == r || r == "*" { 158 if claims.Role == r || r == "*" {
168 return true 159 return true
169 } 160 }
170 } 161 }
171 162
172 return false 163 return false
173 } 164 }
174 165
175 // ProcessRBAC returns token claims and boolean value based on user's rights to access resource specified in req. 166 // ProcessRBAC returns token claims and boolean value based on user's rights to access resource specified in req.
176 // It exctracts user's role from the JWT token located in Authorization header of 167 // It exctracts user's role from the JWT token located in Authorization header of
177 // http.Request and then compares it with the list of supplied roles and returns 168 // HTTP request and then compares it with the list of supplied (authorized);
178 // true if there's a match, if "*" is provided or if the authRoles is nil. 169 // it returns true if there's a match, if "*" is provided or if the authRoles is nil.
179 // Otherwise it returns false.
180 func ProcessRBAC(req *http.Request, authRoles []string) (*TokenClaims, bool) { 170 func ProcessRBAC(req *http.Request, authRoles []string) (*TokenClaims, bool) {
181 if authRoles == nil { 171 if authRoles == nil {
182 return nil, true 172 return nil, true
183 } 173 }
184 174
185 // validate token and check expiration date 175 // validate token and check expiration date
186 claims, err := GetTokenClaims(req) 176 claims, err := GetTokenClaims(req)
187 if err != nil { 177 if err != nil {
188 return claims, false 178 return claims, false
189 } 179 }
190 // check if token has expired 180 // check if token has expired
191 if claims.ExpiresAt < (time.Now()).Unix() { 181 if claims.ExpiresAt < (time.Now()).Unix() {
192 return claims, false 182 return claims, false
193 } 183 }
194 184
195 // check if role extracted from token matches 185 // check if role extracted from token matches
196 // any of the provided (allowed) ones 186 // any of the provided (allowed) ones
197 for _, r := range authRoles { 187 for _, r := range authRoles {
198 if claims.Role == r || r == "*" { 188 if claims.Role == r || r == "*" {
199 return claims, true 189 return claims, true
200 } 190 }
201 } 191 }
202 192
203 return claims, false 193 return claims, false
204 } 194 }
205 195
206 // GetTokenClaims extracts JWT claims from Authorization header of the request. 196 // GetTokenClaims extracts JWT claims from Authorization header of the request.
207 // Returns token claims or an error. 197 // Returns token claims or an error.
208 func GetTokenClaims(req *http.Request) (*TokenClaims, error) { 198 func GetTokenClaims(req *http.Request) (*TokenClaims, error) {
209 // check for and strip 'Bearer' prefix 199 // check for and strip 'Bearer' prefix
210 var tokstr string 200 var tokstr string
211 authHead := req.Header.Get("Authorization") 201 authHead := req.Header.Get("Authorization")
212 if ok := strings.HasPrefix(authHead, "Bearer "); ok { 202 if ok := strings.HasPrefix(authHead, "Bearer "); ok {
213 tokstr = strings.TrimPrefix(authHead, "Bearer ") 203 tokstr = strings.TrimPrefix(authHead, "Bearer ")
214 } else { 204 } else {
215 return &TokenClaims{}, errors.New("authorization header in incomplete") 205 return &TokenClaims{}, errors.New("authorization header in incomplete")
216 } 206 }
217 207
218 token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) 208 token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc)
219 if err != nil { 209 if err != nil {
220 return &TokenClaims{}, err 210 return &TokenClaims{}, err
221 } 211 }
222 212
223 // type assertion 213 // type assertion
224 claims, ok := token.Claims.(*TokenClaims) 214 claims, ok := token.Claims.(*TokenClaims)
225 if !ok || !token.Valid { 215 if !ok || !token.Valid {
226 return &TokenClaims{}, errors.New("token is not valid") 216 return &TokenClaims{}, errors.New("token is not valid")
227 } 217 }
228 218
229 return claims, nil 219 return claims, nil
230 } 220 }
231 221
232 // randomSalt returns a string of random characters of 'saltSize' length. 222 // randomSalt returns a string of random characters of 'saltSize' length.
233 func randomSalt() (s string, err error) { 223 func randomSalt() (s string, err error) {
234 rawsalt := make([]byte, saltSize) 224 rawsalt := make([]byte, saltSize)
235 225
236 _, err = rand.Read(rawsalt) 226 _, err = rand.Read(rawsalt)
237 if err != nil { 227 if err != nil {
238 return "", err 228 return "", err
239 } 229 }
240 230
241 s = hex.EncodeToString(rawsalt) 231 s = hex.EncodeToString(rawsalt)
242 return s, nil 232 return s, nil
243 } 233 }
244 234
245 // secretFunc returns byte slice of API secret keyword. 235 // secretFunc returns byte slice of API secret keyword.
246 func secretFunc(token *jwt.Token) (interface{}, error) { 236 func secretFunc(token *jwt.Token) (interface{}, error) {
247 return []byte(secret), nil 237 return []byte(secret), nil
248 } 238 }
249 239