Blame view

auth_utility.go 5.15 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"
6b16c88f2   Marko Tikvić   Working version w...
22
23
24
25
  const RoleAdminID    uint32 = 1
  const RoleManagerID  uint32 = 2
  const RoleReporterID uint32 = 3
  const RoleOperatorID uint32 = 4
33d137a67   Marko Tikvić   Functional role c...
26

6ec91280b   Marko Tikvić   working on docume...
27
  // TokenClaims are JWT token claims.
90fd36e9b   Marko Tikvić   resolved some dep...
28
29
  type TokenClaims struct {
  	Username string `json:"username"`
7d3deb50d   Marko Tikvić   modified list_con...
30
  	Role     string `json:"role"`
6b16c88f2   Marko Tikvić   Working version w...
31
  	RoleID   uint32 `json:"roleID"`
90fd36e9b   Marko Tikvić   resolved some dep...
32
33
  	jwt.StandardClaims
  }
1d0f61553   Marko Tikvić   can't fetch clob
34
  // CredentialsStruct is an instace of username/password values.
90fd36e9b   Marko Tikvić   resolved some dep...
35
36
37
38
  type CredentialsStruct struct {
  	Username string `json:"username"`
  	Password string `json:"password"`
  }
6ec91280b   Marko Tikvić   working on docume...
39
  // generateSalt returns a string of random characters of 'saltSize' length.
4b4ea384f   Marko Tikvić   hmm
40
  func generateSalt() (salt string, err error) {
90fd36e9b   Marko Tikvić   resolved some dep...
41
  	rawsalt := make([]byte, saltSize)
33fd58161   markotikvic   minor changes, sh...
42

4b4ea384f   Marko Tikvić   hmm
43
  	_, err = rand.Read(rawsalt)
90fd36e9b   Marko Tikvić   resolved some dep...
44
45
46
  	if err != nil {
  		return "", err
  	}
33fd58161   markotikvic   minor changes, sh...
47

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

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