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