Blame view

auth_utility.go 4.97 KB
ea858b8a7   Marko Tikvić   refactoring
1
  package webutility
90fd36e9b   Marko Tikvić   resolved some dep...
2
3
  
  import (
90fd36e9b   Marko Tikvić   resolved some dep...
4
  	"errors"
90fd36e9b   Marko Tikvić   resolved some dep...
5
6
7
8
9
  	"time"
  	"crypto/sha256"
  	"crypto/rand"
  	"encoding/hex"
  	"strings"
33d137a67   Marko Tikvić   Functional role c...
10
  	"net/http"
90fd36e9b   Marko Tikvić   resolved some dep...
11
  	"github.com/dgrijalva/jwt-go"
90fd36e9b   Marko Tikvić   resolved some dep...
12
  )
7d3deb50d   Marko Tikvić   modified list_con...
13
14
  const OneDay   = time.Hour*24
  const OneWeek  = OneDay*7
90fd36e9b   Marko Tikvić   resolved some dep...
15
16
17
  const saltSize = 32
  const appName  = "korisnicki-centar"
  const secret   = "korisnicki-centar-api"
33d137a67   Marko Tikvić   Functional role c...
18
19
20
21
  const RoleAdmin    string = "ADMINISTRATOR"
  const RoleManager  string = "RUKOVODILAC"
  const RoleReporter string = "REPORTER"
  const RoleOperator string = "OPERATER"
6ec91280b   Marko Tikvić   working on docume...
22
  // TokenClaims are JWT token claims.
90fd36e9b   Marko Tikvić   resolved some dep...
23
24
  type TokenClaims struct {
  	Username string `json:"username"`
7d3deb50d   Marko Tikvić   modified list_con...
25
  	Role     string `json:"role"`
90fd36e9b   Marko Tikvić   resolved some dep...
26
27
  	jwt.StandardClaims
  }
6ec91280b   Marko Tikvić   working on docume...
28
  // CredentialsStruct is an instace of username/password values. 
90fd36e9b   Marko Tikvić   resolved some dep...
29
30
31
32
  type CredentialsStruct struct {
  	Username string `json:"username"`
  	Password string `json:"password"`
  }
6ec91280b   Marko Tikvić   working on docume...
33
  // generateSalt returns a string of random characters of 'saltSize' length.
4b4ea384f   Marko Tikvić   hmm
34
  func generateSalt() (salt string, err error) {
90fd36e9b   Marko Tikvić   resolved some dep...
35
  	rawsalt := make([]byte, saltSize)
33fd58161   markotikvic   minor changes, sh...
36

4b4ea384f   Marko Tikvić   hmm
37
  	_, err = rand.Read(rawsalt)
90fd36e9b   Marko Tikvić   resolved some dep...
38
39
40
  	if err != nil {
  		return "", err
  	}
33fd58161   markotikvic   minor changes, sh...
41

90fd36e9b   Marko Tikvić   resolved some dep...
42
43
44
  	salt = hex.EncodeToString(rawsalt)
  	return salt, nil
  }
e1fbb41f9   Marko Tikvić   added comments
45
46
  // HashString hashes input string with SHA256 algorithm.
  // If the presalt parameter is not provided HashString will generate new salt string.
6ec91280b   Marko Tikvić   working on docume...
47
  // Returns hash and salt string or an error if it fails.
33fd58161   markotikvic   minor changes, sh...
48
  func HashString(str string, presalt string) (hash, salt string, err error) {
90fd36e9b   Marko Tikvić   resolved some dep...
49
50
  	// chech if message is presalted
  	if presalt == "" {
33fd58161   markotikvic   minor changes, sh...
51
  		salt, err = generateSalt()
90fd36e9b   Marko Tikvić   resolved some dep...
52
53
54
55
56
57
58
59
  		if err != nil {
  			return "", "", err
  		}
  	} else {
  		salt = presalt
  	}
  
  	// convert strings to raw byte slices
33fd58161   markotikvic   minor changes, sh...
60
  	rawstr := []byte(str)
90fd36e9b   Marko Tikvić   resolved some dep...
61
62
63
64
  	rawsalt, err := hex.DecodeString(salt)
  	if err != nil {
  		return "", "", err
  	}
33fd58161   markotikvic   minor changes, sh...
65
66
67
  
  	rawdata := make([]byte, len(rawstr) + len(rawsalt))
  	rawdata = append(rawdata, rawstr...)
90fd36e9b   Marko Tikvić   resolved some dep...
68
69
70
71
72
73
  	rawdata = append(rawdata, rawsalt...)
  
  	// hash message + salt
  	hasher := sha256.New()
  	hasher.Write(rawdata)
  	rawhash := hasher.Sum(nil)
33fd58161   markotikvic   minor changes, sh...
74

90fd36e9b   Marko Tikvić   resolved some dep...
75
76
77
  	hash = hex.EncodeToString(rawhash)
  	return hash, salt, nil
  }
6ec91280b   Marko Tikvić   working on docume...
78
79
  // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims.
  // It returns an error if it fails.
33fd58161   markotikvic   minor changes, sh...
80
  func CreateAPIToken(username, role string) (string, error) {
7d3deb50d   Marko Tikvić   modified list_con...
81
  	var apiToken string
90fd36e9b   Marko Tikvić   resolved some dep...
82
83
84
  	var err error
  
  	if err != nil {
6f4b8a711   Marko Tikvić   token response ch...
85
  		return "", err
90fd36e9b   Marko Tikvić   resolved some dep...
86
87
88
89
90
91
92
93
94
95
96
97
  	}
  
  	claims := TokenClaims{
  		username,
  		role,
  		jwt.StandardClaims{
  			ExpiresAt: (time.Now().Add(OneWeek)).Unix(),
  			Issuer:    appName,
  		},
  	}
  
  	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
6f4b8a711   Marko Tikvić   token response ch...
98
  	apiToken, err = jwtToken.SignedString([]byte(secret))
90fd36e9b   Marko Tikvić   resolved some dep...
99
  	if err != nil {
6f4b8a711   Marko Tikvić   token response ch...
100
  		return "", err
90fd36e9b   Marko Tikvić   resolved some dep...
101
102
103
  	}
  	return apiToken, nil
  }
6ec91280b   Marko Tikvić   working on docume...
104
105
  // RefreshAPIToken prolongs JWT token's expiration date for one week.
  // It returns new JWT token or an error if it fails.
6f4b8a711   Marko Tikvić   token response ch...
106
  func RefreshAPIToken(tokenString string) (string, error) {
7d3deb50d   Marko Tikvić   modified list_con...
107
  	var newToken string
90fd36e9b   Marko Tikvić   resolved some dep...
108
  	tokenString = strings.TrimPrefix(tokenString, "Bearer ")
e1fbb41f9   Marko Tikvić   added comments
109
  	token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc)
90fd36e9b   Marko Tikvić   resolved some dep...
110
  	if err != nil {
6f4b8a711   Marko Tikvić   token response ch...
111
  		return "", err
90fd36e9b   Marko Tikvić   resolved some dep...
112
113
114
115
116
  	}
  
  	// type assertion
  	claims, ok := token.Claims.(*TokenClaims)
  	if !ok || !token.Valid {
6f4b8a711   Marko Tikvić   token response ch...
117
  		return "", errors.New("token is not valid")
90fd36e9b   Marko Tikvić   resolved some dep...
118
119
120
121
  	}
  
  	claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix()
  	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
6f4b8a711   Marko Tikvić   token response ch...
122
  	newToken, err = jwtToken.SignedString([]byte(secret))
90fd36e9b   Marko Tikvić   resolved some dep...
123
  	if err != nil {
6f4b8a711   Marko Tikvić   token response ch...
124
  		return "", err
90fd36e9b   Marko Tikvić   resolved some dep...
125
126
127
128
  	}
  
  	return newToken, nil
  }
e1fbb41f9   Marko Tikvić   added comments
129
  // ParseAPIToken parses JWT token claims.
6ec91280b   Marko Tikvić   working on docume...
130
  // It returns a pointer to TokenClaims struct or an error if it fails.
b291ac8c4   Marko Tikvić   clened up
131
  func ParseAPIToken(tokenString string) (*TokenClaims, error) {
e1fbb41f9   Marko Tikvić   added comments
132
  	if ok := strings.HasPrefix(tokenString, "Bearer "); ok {
90fd36e9b   Marko Tikvić   resolved some dep...
133
134
135
136
  		tokenString = strings.TrimPrefix(tokenString, "Bearer ")
  	} else {
  		return &TokenClaims{}, errors.New("Authorization header is incomplete")
  	}
e1fbb41f9   Marko Tikvić   added comments
137
  	token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc)
90fd36e9b   Marko Tikvić   resolved some dep...
138
139
140
141
142
143
144
145
146
147
148
  	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   Marko Tikvić   Functional role c...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
  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   Marko Tikvić   working on docume...
169
  // secretFunc returns byte slice of API secret keyword.
e1fbb41f9   Marko Tikvić   added comments
170
171
  func secretFunc(token *jwt.Token) (interface{}, error) {
  	return []byte(secret), nil
90fd36e9b   Marko Tikvić   resolved some dep...
172
  }
33d137a67   Marko Tikvić   Functional role c...
173
174
175
176
177
178
179
180
  
  // roleAuthorized returns true if role from userClaims matches any of the authorizedRoles
  // or if authorizedRoles contains "*".
  func roleAuthorized(authorizedRoles []string, userClaims *TokenClaims) bool {
  	if userClaims == nil {
  		return false
  	}
  	for _, r := range authorizedRoles {
33d137a67   Marko Tikvić   Functional role c...
181
182
183
184
185
186
  		if userClaims.Role == r || r == "*" {
  			return true
  		}
  	}
  	return false
  }