Commit c7aadbb39d060fc166404ee45d5ba7e7e352fc20
1 parent
79071a5d4f
Exists in
master
and in
1 other branch
minor changes
Showing
1 changed file
with
7 additions
and
17 deletions
Show diff stats
auth_utility.go
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 |