Commit 077dae33c8d628fad5c60e081dfa2a6651ef1df4
1 parent
d666282954
Exists in
master
and in
1 other branch
removed role constants
Showing
1 changed file
with
7 additions
and
21 deletions
Show diff stats
auth_utility.go
1 | // TODO: Improve roles | 1 | // TODO: Improve roles |
2 | package webutility | 2 | package webutility |
3 | 3 | ||
4 | import ( | 4 | import ( |
5 | "crypto/rand" | 5 | "crypto/rand" |
6 | "crypto/sha256" | 6 | "crypto/sha256" |
7 | "encoding/hex" | 7 | "encoding/hex" |
8 | "errors" | 8 | "errors" |
9 | "net/http" | 9 | "net/http" |
10 | "strings" | 10 | "strings" |
11 | "time" | 11 | "time" |
12 | 12 | ||
13 | "github.com/dgrijalva/jwt-go" | 13 | "github.com/dgrijalva/jwt-go" |
14 | ) | 14 | ) |
15 | 15 | ||
16 | const OneDay = time.Hour * 24 | 16 | const OneDay = time.Hour * 24 |
17 | const OneWeek = OneDay * 7 | 17 | const OneWeek = OneDay * 7 |
18 | const saltSize = 32 | 18 | const saltSize = 32 |
19 | const appName = "korisnicki-centar" | 19 | const appName = "korisnicki-centar" |
20 | const secret = "korisnicki-centar-api" | 20 | const secret = "korisnicki-centar-api" |
21 | 21 | ||
22 | const RoleAdmin string = "ADMINISTRATOR" | ||
23 | const RoleManager string = "RUKOVODILAC" | ||
24 | const RoleReporter string = "REPORTER" | ||
25 | const RoleOperator string = "OPERATER" | ||
26 | const RoleAdminID uint32 = 1 | ||
27 | const RoleManagerID uint32 = 2 | ||
28 | const RoleReporterID uint32 = 3 | ||
29 | const RoleOperatorID uint32 = 4 | ||
30 | |||
31 | type Role struct { | 22 | type Role struct { |
32 | name string | 23 | Name string `json:"name"` |
33 | id uint32 | 24 | ID uint32 `json:"id"` |
34 | } | 25 | } |
35 | 26 | ||
36 | // TokenClaims are JWT token claims. | 27 | // TokenClaims are JWT token claims. |
37 | type TokenClaims struct { | 28 | type TokenClaims struct { |
38 | Username string `json:"username"` | 29 | Username string `json:"username"` |
39 | Role string `json:"role"` | 30 | Role string `json:"role"` |
40 | RoleID uint32 `json:"roleID"` | 31 | RoleID uint32 `json:"roleID"` |
41 | jwt.StandardClaims | 32 | jwt.StandardClaims |
42 | } | 33 | } |
43 | 34 | ||
44 | // CredentialsStruct is an instace of username/password values. | 35 | // CredentialsStruct is an instace of username/password values. |
45 | type CredentialsStruct struct { | 36 | type CredentialsStruct struct { |
46 | Username string `json:"username"` | 37 | Username string `json:"username"` |
47 | Password string `json:"password"` | 38 | Password string `json:"password"` |
48 | } | 39 | } |
49 | 40 | ||
50 | var admin Role = Role{RoleAdmin, RoleAdminID} | ||
51 | var manager Role = Role{RoleManager, RoleManagerID} | ||
52 | var reporter Role = Role{RoleReporter, RoleReporterID} | ||
53 | var operator Role = Role{RoleOperator, RoleOperatorID} | ||
54 | |||
55 | // generateSalt returns a string of random characters of 'saltSize' length. | 41 | // generateSalt returns a string of random characters of 'saltSize' length. |
56 | func generateSalt() (salt string, err error) { | 42 | func generateSalt() (salt string, err error) { |
57 | rawsalt := make([]byte, saltSize) | 43 | rawsalt := make([]byte, saltSize) |
58 | 44 | ||
59 | _, err = rand.Read(rawsalt) | 45 | _, err = rand.Read(rawsalt) |
60 | if err != nil { | 46 | if err != nil { |
61 | return "", err | 47 | return "", err |
62 | } | 48 | } |
63 | 49 | ||
64 | salt = hex.EncodeToString(rawsalt) | 50 | salt = hex.EncodeToString(rawsalt) |
65 | return salt, nil | 51 | return salt, nil |
66 | } | 52 | } |
67 | 53 | ||
68 | // HashString hashes input string with SHA256 algorithm. | 54 | // HashString hashes input string using SHA256. |
69 | // If the presalt parameter is not provided HashString will generate new salt string. | 55 | // If the presalt parameter is not provided HashString will generate new salt string. |
70 | // Returns hash and salt string or an error if it fails. | 56 | // Returns hash and salt string or an error if it fails. |
71 | func HashString(str string, presalt string) (hash, salt string, err error) { | 57 | func HashString(str, presalt string) (hash, salt string, err error) { |
72 | // chech if message is presalted | 58 | // chech if message is presalted |
73 | if presalt == "" { | 59 | if presalt == "" { |
74 | salt, err = generateSalt() | 60 | salt, err = generateSalt() |
75 | if err != nil { | 61 | if err != nil { |
76 | return "", "", err | 62 | return "", "", err |
77 | } | 63 | } |
78 | } else { | 64 | } else { |
79 | salt = presalt | 65 | salt = presalt |
80 | } | 66 | } |
81 | 67 | ||
82 | // convert strings to raw byte slices | 68 | // convert strings to raw byte slices |
83 | rawstr := []byte(str) | 69 | rawstr := []byte(str) |
84 | rawsalt, err := hex.DecodeString(salt) | 70 | rawsalt, err := hex.DecodeString(salt) |
85 | if err != nil { | 71 | if err != nil { |
86 | return "", "", err | 72 | return "", "", err |
87 | } | 73 | } |
88 | 74 | ||
89 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) | 75 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) |
90 | rawdata = append(rawdata, rawstr...) | 76 | rawdata = append(rawdata, rawstr...) |
91 | rawdata = append(rawdata, rawsalt...) | 77 | rawdata = append(rawdata, rawsalt...) |
92 | 78 | ||
93 | // hash message + salt | 79 | // hash message + salt |
94 | hasher := sha256.New() | 80 | hasher := sha256.New() |
95 | hasher.Write(rawdata) | 81 | hasher.Write(rawdata) |
96 | rawhash := hasher.Sum(nil) | 82 | rawhash := hasher.Sum(nil) |
97 | 83 | ||
98 | hash = hex.EncodeToString(rawhash) | 84 | hash = hex.EncodeToString(rawhash) |
99 | return hash, salt, nil | 85 | return hash, salt, nil |
100 | } | 86 | } |
101 | 87 | ||
102 | // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. | 88 | // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. |
103 | // It returns an error if it fails. | 89 | // It returns an error if it fails. |
104 | func CreateAPIToken(username, role string, roleID uint32) (string, error) { | 90 | func CreateAPIToken(username string, role Role) (string, error) { |
105 | var apiToken string | 91 | var apiToken string |
106 | var err error | 92 | var err error |
107 | 93 | ||
108 | if err != nil { | 94 | if err != nil { |
109 | return "", err | 95 | return "", err |
110 | } | 96 | } |
111 | 97 | ||
112 | claims := TokenClaims{ | 98 | claims := TokenClaims{ |
113 | username, | 99 | username, |
114 | role, | 100 | role.Name, |
115 | roleID, | 101 | role.ID, |
116 | jwt.StandardClaims{ | 102 | jwt.StandardClaims{ |
117 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), | 103 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), |
118 | Issuer: appName, | 104 | Issuer: appName, |
119 | }, | 105 | }, |
120 | } | 106 | } |
121 | 107 | ||
122 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 108 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
123 | apiToken, err = jwtToken.SignedString([]byte(secret)) | 109 | apiToken, err = jwtToken.SignedString([]byte(secret)) |
124 | if err != nil { | 110 | if err != nil { |
125 | return "", err | 111 | return "", err |
126 | } | 112 | } |
127 | return apiToken, nil | 113 | return apiToken, nil |
128 | } | 114 | } |
129 | 115 | ||
130 | // RefreshAPIToken prolongs JWT token's expiration date for one week. | 116 | // RefreshAPIToken prolongs JWT token's expiration date for one week. |
131 | // It returns new JWT token or an error if it fails. | 117 | // It returns new JWT token or an error if it fails. |
132 | func RefreshAPIToken(tokenString string) (string, error) { | 118 | func RefreshAPIToken(tokenString string) (string, error) { |
133 | var newToken string | 119 | var newToken string |
134 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 120 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
135 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) | 121 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) |
136 | if err != nil { | 122 | if err != nil { |
137 | return "", err | 123 | return "", err |
138 | } | 124 | } |
139 | 125 | ||
140 | // type assertion | 126 | // type assertion |
141 | claims, ok := token.Claims.(*TokenClaims) | 127 | claims, ok := token.Claims.(*TokenClaims) |
142 | if !ok || !token.Valid { | 128 | if !ok || !token.Valid { |
143 | return "", errors.New("token is not valid") | 129 | return "", errors.New("token is not valid") |
144 | } | 130 | } |
145 | 131 | ||
146 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() | 132 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() |
147 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 133 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
148 | 134 | ||
149 | newToken, err = jwtToken.SignedString([]byte(secret)) | 135 | newToken, err = jwtToken.SignedString([]byte(secret)) |
150 | if err != nil { | 136 | if err != nil { |
151 | return "", err | 137 | return "", err |
152 | } | 138 | } |
153 | 139 | ||
154 | return newToken, nil | 140 | return newToken, nil |
155 | } | 141 | } |
156 | 142 | ||
157 | // ParseAPIToken parses JWT token claims. | 143 | // ParseAPIToken parses JWT token claims. |
158 | // It returns a pointer to TokenClaims struct or an error if it fails. | 144 | // It returns a pointer to TokenClaims struct or an error if it fails. |
159 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { | 145 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { |
160 | if ok := strings.HasPrefix(tokenString, "Bearer "); ok { | 146 | if ok := strings.HasPrefix(tokenString, "Bearer "); ok { |
161 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 147 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
162 | } else { | 148 | } else { |
163 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 149 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
164 | } | 150 | } |
165 | 151 | ||
166 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) | 152 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) |
167 | if err != nil { | 153 | if err != nil { |
168 | return &TokenClaims{}, err | 154 | return &TokenClaims{}, err |
169 | } | 155 | } |
170 | 156 | ||
171 | // type assertion | 157 | // type assertion |
172 | claims, ok := token.Claims.(*TokenClaims) | 158 | claims, ok := token.Claims.(*TokenClaims) |
173 | if !ok || !token.Valid { | 159 | if !ok || !token.Valid { |
174 | return &TokenClaims{}, errors.New("token is not valid") | 160 | return &TokenClaims{}, errors.New("token is not valid") |
175 | } | 161 | } |
176 | return claims, nil | 162 | return claims, nil |
177 | } | 163 | } |
178 | 164 | ||
179 | func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) { | 165 | func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) { |
180 | token := r.Header.Get("Authorization") | 166 | token := r.Header.Get("Authorization") |
181 | if ok := strings.HasPrefix(token, "Bearer "); ok { | 167 | if ok := strings.HasPrefix(token, "Bearer "); ok { |
182 | token = strings.TrimPrefix(token, "Bearer ") | 168 | token = strings.TrimPrefix(token, "Bearer ") |
183 | } else { | 169 | } else { |
184 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 170 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
185 | } | 171 | } |
186 | 172 | ||
187 | parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc) | 173 | parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc) |
188 | if err != nil { | 174 | if err != nil { |
189 | return &TokenClaims{}, err | 175 | return &TokenClaims{}, err |
190 | } | 176 | } |
191 | 177 | ||
192 | // type assertion | 178 | // type assertion |
193 | claims, ok := parsedToken.Claims.(*TokenClaims) | 179 | claims, ok := parsedToken.Claims.(*TokenClaims) |
194 | if !ok || !parsedToken.Valid { | 180 | if !ok || !parsedToken.Valid { |
195 | return &TokenClaims{}, errors.New("token is not valid") | 181 | return &TokenClaims{}, errors.New("token is not valid") |
196 | } | 182 | } |
197 | return claims, err | 183 | return claims, err |
198 | } | 184 | } |
199 | 185 | ||
200 | // secretFunc returns byte slice of API secret keyword. | 186 | // secretFunc returns byte slice of API secret keyword. |
201 | func secretFunc(token *jwt.Token) (interface{}, error) { | 187 | func secretFunc(token *jwt.Token) (interface{}, error) { |
202 | return []byte(secret), nil | 188 | return []byte(secret), nil |
203 | } | 189 | } |
204 | 190 | ||
205 | // RbacCheck returns true if role that made HTTP request is authorized to | 191 | // RbacCheck returns true if role that made HTTP request is authorized to |
206 | // access the resource it is targeting. | 192 | // access the resource it is targeting. |
207 | // It exctracts user's role from the JWT token located in Authorization header of | 193 | // It exctracts user's role from the JWT token located in Authorization header of |
208 | // http.Request and then compares it with the list of supplied roles and returns | 194 | // http.Request and then compares it with the list of supplied roles and returns |
209 | // true if there's a match, if "*" is provided or if the authRoles is nil. | 195 | // true if there's a match, if "*" is provided or if the authRoles is nil. |
210 | // Otherwise it returns false. | 196 | // Otherwise it returns false. |
211 | func RbacCheck(req *http.Request, authRoles []string) bool { | 197 | func RbacCheck(req *http.Request, authRoles []string) bool { |
212 | if authRoles == nil { | 198 | if authRoles == nil { |
213 | return true | 199 | return true |
214 | } | 200 | } |
215 | 201 | ||
216 | token := req.Header.Get("Authorization") | 202 | token := req.Header.Get("Authorization") |
217 | claims, err := ParseAPIToken(token) | 203 | claims, err := ParseAPIToken(token) |
218 | if err != nil { | 204 | if err != nil { |
219 | return false | 205 | return false |
220 | } | 206 | } |
221 | 207 | ||
222 | for _, r := range authRoles { | 208 | for _, r := range authRoles { |
223 | if claims.Role == r || r == "*" { | 209 | if claims.Role == r || r == "*" { |
224 | return true | 210 | return true |
225 | } | 211 | } |
226 | } | 212 | } |
227 | 213 | ||
228 | return false | 214 | return false |
229 | } | 215 | } |
230 | 216 | ||
231 | // Rbac sets common headers and performs RBAC. | 217 | // Rbac sets common headers and performs RBAC. |
232 | // If RBAC passes it calls the handlerFunc. | 218 | // If RBAC passes it calls the handlerFunc. |
233 | func RbacHandler(handlerFunc http.HandlerFunc, authRoles []string) http.HandlerFunc { | 219 | func RbacHandler(handlerFunc http.HandlerFunc, authRoles []string) http.HandlerFunc { |
234 | return func(w http.ResponseWriter, req *http.Request) { | 220 | return func(w http.ResponseWriter, req *http.Request) { |
235 | w.Header().Set("Access-Control-Allow-Origin", "*") | 221 | w.Header().Set("Access-Control-Allow-Origin", "*") |
236 | 222 | ||
237 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | 223 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") |
238 | 224 | ||
239 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, | 225 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, |
240 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) | 226 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) |
241 | 227 | ||
242 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | 228 | w.Header().Set("Content-Type", "application/json; charset=utf-8") |
243 | 229 | ||
244 | // TODO: Check for content type | 230 | // TODO: Check for content type |
245 | 231 | ||
246 | if req.Method == "OPTIONS" { | 232 | if req.Method == "OPTIONS" { |
247 | return | 233 | return |
248 | } | 234 | } |
249 | 235 | ||
250 | err := req.ParseForm() | 236 | err := req.ParseForm() |
251 | if err != nil { | 237 | if err != nil { |
252 | BadRequestResponse(w, req) | 238 | BadRequestResponse(w, req) |
253 | return | 239 | return |
254 | } | 240 | } |
255 | 241 | ||
256 | if !RbacCheck(req, authRoles) { | 242 | if !RbacCheck(req, authRoles) { |
257 | UnauthorizedResponse(w, req) | 243 | UnauthorizedResponse(w, req) |
258 | return | 244 | return |
259 | } | 245 | } |
260 | 246 | ||
261 | // execute HandlerFunc | 247 | // execute HandlerFunc |
262 | handlerFunc(w, req) | 248 | handlerFunc(w, req) |
263 | } | 249 | } |
264 | } | 250 | } |
265 | 251 |