Commit 2529f43f82dc7685272d6ef5db33ecb858cccf8a
1 parent
33d137a671
Exists in
master
and in
1 other branch
removed pritnfs
Showing
1 changed file
with
0 additions
and
2 deletions
Show diff stats
auth_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "errors" | 4 | "errors" |
5 | "time" | 5 | "time" |
6 | "crypto/sha256" | 6 | "crypto/sha256" |
7 | "crypto/rand" | 7 | "crypto/rand" |
8 | "encoding/hex" | 8 | "encoding/hex" |
9 | "strings" | 9 | "strings" |
10 | "net/http" | 10 | "net/http" |
11 | 11 | ||
12 | "github.com/dgrijalva/jwt-go" | 12 | "github.com/dgrijalva/jwt-go" |
13 | "fmt" | ||
14 | ) | 13 | ) |
15 | 14 | ||
16 | const OneDay = time.Hour*24 | 15 | const OneDay = time.Hour*24 |
17 | const OneWeek = OneDay*7 | 16 | const OneWeek = OneDay*7 |
18 | const saltSize = 32 | 17 | const saltSize = 32 |
19 | const appName = "korisnicki-centar" | 18 | const appName = "korisnicki-centar" |
20 | const secret = "korisnicki-centar-api" | 19 | const secret = "korisnicki-centar-api" |
21 | 20 | ||
22 | const RoleAdmin string = "ADMINISTRATOR" | 21 | const RoleAdmin string = "ADMINISTRATOR" |
23 | const RoleManager string = "RUKOVODILAC" | 22 | const RoleManager string = "RUKOVODILAC" |
24 | const RoleReporter string = "REPORTER" | 23 | const RoleReporter string = "REPORTER" |
25 | const RoleOperator string = "OPERATER" | 24 | const RoleOperator string = "OPERATER" |
26 | 25 | ||
27 | // TokenClaims are JWT token claims. | 26 | // TokenClaims are JWT token claims. |
28 | type TokenClaims struct { | 27 | type TokenClaims struct { |
29 | Username string `json:"username"` | 28 | Username string `json:"username"` |
30 | Role string `json:"role"` | 29 | Role string `json:"role"` |
31 | jwt.StandardClaims | 30 | jwt.StandardClaims |
32 | } | 31 | } |
33 | 32 | ||
34 | // CredentialsStruct is an instace of username/password values. | 33 | // CredentialsStruct is an instace of username/password values. |
35 | type CredentialsStruct struct { | 34 | type CredentialsStruct struct { |
36 | Username string `json:"username"` | 35 | Username string `json:"username"` |
37 | Password string `json:"password"` | 36 | Password string `json:"password"` |
38 | } | 37 | } |
39 | 38 | ||
40 | // generateSalt returns a string of random characters of 'saltSize' length. | 39 | // generateSalt returns a string of random characters of 'saltSize' length. |
41 | func generateSalt() (salt string, err error) { | 40 | func generateSalt() (salt string, err error) { |
42 | rawsalt := make([]byte, saltSize) | 41 | rawsalt := make([]byte, saltSize) |
43 | 42 | ||
44 | _, err = rand.Read(rawsalt) | 43 | _, err = rand.Read(rawsalt) |
45 | if err != nil { | 44 | if err != nil { |
46 | return "", err | 45 | return "", err |
47 | } | 46 | } |
48 | 47 | ||
49 | salt = hex.EncodeToString(rawsalt) | 48 | salt = hex.EncodeToString(rawsalt) |
50 | return salt, nil | 49 | return salt, nil |
51 | } | 50 | } |
52 | 51 | ||
53 | // HashString hashes input string with SHA256 algorithm. | 52 | // HashString hashes input string with SHA256 algorithm. |
54 | // If the presalt parameter is not provided HashString will generate new salt string. | 53 | // If the presalt parameter is not provided HashString will generate new salt string. |
55 | // Returns hash and salt string or an error if it fails. | 54 | // Returns hash and salt string or an error if it fails. |
56 | func HashString(str string, presalt string) (hash, salt string, err error) { | 55 | func HashString(str string, presalt string) (hash, salt string, err error) { |
57 | // chech if message is presalted | 56 | // chech if message is presalted |
58 | if presalt == "" { | 57 | if presalt == "" { |
59 | salt, err = generateSalt() | 58 | salt, err = generateSalt() |
60 | if err != nil { | 59 | if err != nil { |
61 | return "", "", err | 60 | return "", "", err |
62 | } | 61 | } |
63 | } else { | 62 | } else { |
64 | salt = presalt | 63 | salt = presalt |
65 | } | 64 | } |
66 | 65 | ||
67 | // convert strings to raw byte slices | 66 | // convert strings to raw byte slices |
68 | rawstr := []byte(str) | 67 | rawstr := []byte(str) |
69 | rawsalt, err := hex.DecodeString(salt) | 68 | rawsalt, err := hex.DecodeString(salt) |
70 | if err != nil { | 69 | if err != nil { |
71 | return "", "", err | 70 | return "", "", err |
72 | } | 71 | } |
73 | 72 | ||
74 | rawdata := make([]byte, len(rawstr) + len(rawsalt)) | 73 | rawdata := make([]byte, len(rawstr) + len(rawsalt)) |
75 | rawdata = append(rawdata, rawstr...) | 74 | rawdata = append(rawdata, rawstr...) |
76 | rawdata = append(rawdata, rawsalt...) | 75 | rawdata = append(rawdata, rawsalt...) |
77 | 76 | ||
78 | // hash message + salt | 77 | // hash message + salt |
79 | hasher := sha256.New() | 78 | hasher := sha256.New() |
80 | hasher.Write(rawdata) | 79 | hasher.Write(rawdata) |
81 | rawhash := hasher.Sum(nil) | 80 | rawhash := hasher.Sum(nil) |
82 | 81 | ||
83 | hash = hex.EncodeToString(rawhash) | 82 | hash = hex.EncodeToString(rawhash) |
84 | return hash, salt, nil | 83 | return hash, salt, nil |
85 | } | 84 | } |
86 | 85 | ||
87 | // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. | 86 | // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. |
88 | // It returns an error if it fails. | 87 | // It returns an error if it fails. |
89 | func CreateAPIToken(username, role string) (string, error) { | 88 | func CreateAPIToken(username, role string) (string, error) { |
90 | var apiToken string | 89 | var apiToken string |
91 | var err error | 90 | var err error |
92 | 91 | ||
93 | if err != nil { | 92 | if err != nil { |
94 | return "", err | 93 | return "", err |
95 | } | 94 | } |
96 | 95 | ||
97 | claims := TokenClaims{ | 96 | claims := TokenClaims{ |
98 | username, | 97 | username, |
99 | role, | 98 | role, |
100 | jwt.StandardClaims{ | 99 | jwt.StandardClaims{ |
101 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), | 100 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), |
102 | Issuer: appName, | 101 | Issuer: appName, |
103 | }, | 102 | }, |
104 | } | 103 | } |
105 | 104 | ||
106 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 105 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
107 | apiToken, err = jwtToken.SignedString([]byte(secret)) | 106 | apiToken, err = jwtToken.SignedString([]byte(secret)) |
108 | if err != nil { | 107 | if err != nil { |
109 | return "", err | 108 | return "", err |
110 | } | 109 | } |
111 | return apiToken, nil | 110 | return apiToken, nil |
112 | } | 111 | } |
113 | 112 | ||
114 | // RefreshAPIToken prolongs JWT token's expiration date for one week. | 113 | // RefreshAPIToken prolongs JWT token's expiration date for one week. |
115 | // It returns new JWT token or an error if it fails. | 114 | // It returns new JWT token or an error if it fails. |
116 | func RefreshAPIToken(tokenString string) (string, error) { | 115 | func RefreshAPIToken(tokenString string) (string, error) { |
117 | var newToken string | 116 | var newToken string |
118 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 117 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
119 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) | 118 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) |
120 | if err != nil { | 119 | if err != nil { |
121 | return "", err | 120 | return "", err |
122 | } | 121 | } |
123 | 122 | ||
124 | // type assertion | 123 | // type assertion |
125 | claims, ok := token.Claims.(*TokenClaims) | 124 | claims, ok := token.Claims.(*TokenClaims) |
126 | if !ok || !token.Valid { | 125 | if !ok || !token.Valid { |
127 | return "", errors.New("token is not valid") | 126 | return "", errors.New("token is not valid") |
128 | } | 127 | } |
129 | 128 | ||
130 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() | 129 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() |
131 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 130 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
132 | 131 | ||
133 | newToken, err = jwtToken.SignedString([]byte(secret)) | 132 | newToken, err = jwtToken.SignedString([]byte(secret)) |
134 | if err != nil { | 133 | if err != nil { |
135 | return "", err | 134 | return "", err |
136 | } | 135 | } |
137 | 136 | ||
138 | return newToken, nil | 137 | return newToken, nil |
139 | } | 138 | } |
140 | 139 | ||
141 | // ParseAPIToken parses JWT token claims. | 140 | // ParseAPIToken parses JWT token claims. |
142 | // It returns a pointer to TokenClaims struct or an error if it fails. | 141 | // It returns a pointer to TokenClaims struct or an error if it fails. |
143 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { | 142 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { |
144 | if ok := strings.HasPrefix(tokenString, "Bearer "); ok { | 143 | if ok := strings.HasPrefix(tokenString, "Bearer "); ok { |
145 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 144 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
146 | } else { | 145 | } else { |
147 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 146 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
148 | } | 147 | } |
149 | 148 | ||
150 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) | 149 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) |
151 | if err != nil { | 150 | if err != nil { |
152 | return &TokenClaims{}, err | 151 | return &TokenClaims{}, err |
153 | } | 152 | } |
154 | 153 | ||
155 | // type assertion | 154 | // type assertion |
156 | claims, ok := token.Claims.(*TokenClaims) | 155 | claims, ok := token.Claims.(*TokenClaims) |
157 | if !ok || !token.Valid { | 156 | if !ok || !token.Valid { |
158 | return &TokenClaims{}, errors.New("token is not valid") | 157 | return &TokenClaims{}, errors.New("token is not valid") |
159 | } | 158 | } |
160 | return claims, nil | 159 | return claims, nil |
161 | } | 160 | } |
162 | 161 | ||
163 | func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) { | 162 | func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) { |
164 | token := r.Header.Get("Authorization") | 163 | token := r.Header.Get("Authorization") |
165 | if ok := strings.HasPrefix(token, "Bearer "); ok { | 164 | if ok := strings.HasPrefix(token, "Bearer "); ok { |
166 | token = strings.TrimPrefix(token, "Bearer ") | 165 | token = strings.TrimPrefix(token, "Bearer ") |
167 | } else { | 166 | } else { |
168 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 167 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
169 | } | 168 | } |
170 | 169 | ||
171 | parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc) | 170 | parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc) |
172 | if err != nil { | 171 | if err != nil { |
173 | return &TokenClaims{}, err | 172 | return &TokenClaims{}, err |
174 | } | 173 | } |
175 | 174 | ||
176 | // type assertion | 175 | // type assertion |
177 | claims, ok := parsedToken.Claims.(*TokenClaims) | 176 | claims, ok := parsedToken.Claims.(*TokenClaims) |
178 | if !ok || !parsedToken.Valid { | 177 | if !ok || !parsedToken.Valid { |
179 | return &TokenClaims{}, errors.New("token is not valid") | 178 | return &TokenClaims{}, errors.New("token is not valid") |
180 | } | 179 | } |
181 | return claims, err | 180 | return claims, err |
182 | } | 181 | } |
183 | 182 | ||
184 | // secretFunc returns byte slice of API secret keyword. | 183 | // secretFunc returns byte slice of API secret keyword. |
185 | func secretFunc(token *jwt.Token) (interface{}, error) { | 184 | func secretFunc(token *jwt.Token) (interface{}, error) { |
186 | return []byte(secret), nil | 185 | return []byte(secret), nil |
187 | } | 186 | } |
188 | 187 | ||
189 | // roleAuthorized returns true if role from userClaims matches any of the authorizedRoles | 188 | // roleAuthorized returns true if role from userClaims matches any of the authorizedRoles |
190 | // or if authorizedRoles contains "*". | 189 | // or if authorizedRoles contains "*". |
191 | func roleAuthorized(authorizedRoles []string, userClaims *TokenClaims) bool { | 190 | func roleAuthorized(authorizedRoles []string, userClaims *TokenClaims) bool { |
192 | if userClaims == nil { | 191 | if userClaims == nil { |
193 | return false | 192 | return false |
194 | } | 193 | } |
195 | for _, r := range authorizedRoles { | 194 | for _, r := range authorizedRoles { |
196 | fmt.Printf("comparing %s with %s\n", userClaims.Role, r) | ||
197 | if userClaims.Role == r || r == "*" { | 195 | if userClaims.Role == r || r == "*" { |
198 | return true | 196 | return true |
199 | } | 197 | } |
200 | } | 198 | } |
201 | return false | 199 | return false |
202 | } | 200 | } |
203 | 201 |