Blame view

auth_utility.go 5.04 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"
33d137a67   Marko Tikvić   Functional role c...
12
  	"fmt"
90fd36e9b   Marko Tikvić   resolved some dep...
13
  )
7d3deb50d   Marko Tikvić   modified list_con...
14
15
  const OneDay   = time.Hour*24
  const OneWeek  = OneDay*7
90fd36e9b   Marko Tikvić   resolved some dep...
16
17
18
  const saltSize = 32
  const appName  = "korisnicki-centar"
  const secret   = "korisnicki-centar-api"
33d137a67   Marko Tikvić   Functional role c...
19
20
21
22
  const RoleAdmin    string = "ADMINISTRATOR"
  const RoleManager  string = "RUKOVODILAC"
  const RoleReporter string = "REPORTER"
  const RoleOperator string = "OPERATER"
6ec91280b   Marko Tikvić   working on docume...
23
  // TokenClaims are JWT token claims.
90fd36e9b   Marko Tikvić   resolved some dep...
24
25
  type TokenClaims struct {
  	Username string `json:"username"`
7d3deb50d   Marko Tikvić   modified list_con...
26
  	Role     string `json:"role"`
90fd36e9b   Marko Tikvić   resolved some dep...
27
28
  	jwt.StandardClaims
  }
6ec91280b   Marko Tikvić   working on docume...
29
  // CredentialsStruct is an instace of username/password values. 
90fd36e9b   Marko Tikvić   resolved some dep...
30
31
32
33
  type CredentialsStruct struct {
  	Username string `json:"username"`
  	Password string `json:"password"`
  }
6ec91280b   Marko Tikvić   working on docume...
34
  // generateSalt returns a string of random characters of 'saltSize' length.
4b4ea384f   Marko Tikvić   hmm
35
  func generateSalt() (salt string, err error) {
90fd36e9b   Marko Tikvić   resolved some dep...
36
  	rawsalt := make([]byte, saltSize)
33fd58161   markotikvic   minor changes, sh...
37

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

90fd36e9b   Marko Tikvić   resolved some dep...
43
44
45
  	salt = hex.EncodeToString(rawsalt)
  	return salt, nil
  }
e1fbb41f9   Marko Tikvić   added comments
46
47
  // 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...
48
  // Returns hash and salt string or an error if it fails.
33fd58161   markotikvic   minor changes, sh...
49
  func HashString(str string, presalt string) (hash, salt string, err error) {
90fd36e9b   Marko Tikvić   resolved some dep...
50
51
  	// chech if message is presalted
  	if presalt == "" {
33fd58161   markotikvic   minor changes, sh...
52
  		salt, err = generateSalt()
90fd36e9b   Marko Tikvić   resolved some dep...
53
54
55
56
57
58
59
60
  		if err != nil {
  			return "", "", err
  		}
  	} else {
  		salt = presalt
  	}
  
  	// convert strings to raw byte slices
33fd58161   markotikvic   minor changes, sh...
61
  	rawstr := []byte(str)
90fd36e9b   Marko Tikvić   resolved some dep...
62
63
64
65
  	rawsalt, err := hex.DecodeString(salt)
  	if err != nil {
  		return "", "", err
  	}
33fd58161   markotikvic   minor changes, sh...
66
67
68
  
  	rawdata := make([]byte, len(rawstr) + len(rawsalt))
  	rawdata = append(rawdata, rawstr...)
90fd36e9b   Marko Tikvić   resolved some dep...
69
70
71
72
73
74
  	rawdata = append(rawdata, rawsalt...)
  
  	// hash message + salt
  	hasher := sha256.New()
  	hasher.Write(rawdata)
  	rawhash := hasher.Sum(nil)
33fd58161   markotikvic   minor changes, sh...
75

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