Commit 6f4b8a7111172ca9c9aa5db9898cfcfaf0b350df

Authored by Marko Tikvić
1 parent 4a51e54d7e
Exists in master and in 1 other branch v2

token response changed

Showing 3 changed files with 14 additions and 17 deletions   Show diff stats
1 TODO: 1 TODO:
2 * http utility: 2 * http utility:
3 1. add parameters to the ProcessHeaders to enable/disable token/role-access-rights checks 3 1. add parameters to the ProcessHeaders to enable/disable token/role-access-rights checks
4 2. check for Content-Type header, if clients expects something other than JSON respond with appropriate HTTP code
4 5
1 package restutility 1 package restutility
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 "github.com/dgrijalva/jwt-go" 10 "github.com/dgrijalva/jwt-go"
11 ) 11 )
12 12
13 const OneDay = time.Hour*24 13 const OneDay = time.Hour*24
14 const OneWeek = OneDay*7 14 const OneWeek = OneDay*7
15 const saltSize = 32 15 const saltSize = 32
16 const appName = "korisnicki-centar" 16 const appName = "korisnicki-centar"
17 const secret = "korisnicki-centar-api" 17 const secret = "korisnicki-centar-api"
18 18
19 type Token struct {
20 TokenString string `json:"token"`
21 }
22
23 type TokenClaims struct { 19 type TokenClaims struct {
24 Username string `json:"username"` 20 Username string `json:"username"`
25 Role string `json:"role"` 21 Role string `json:"role"`
26 jwt.StandardClaims 22 jwt.StandardClaims
27 } 23 }
28 24
29 type CredentialsStruct struct { 25 type CredentialsStruct struct {
30 Username string `json:"username"` 26 Username string `json:"username"`
31 Password string `json:"password"` 27 Password string `json:"password"`
32 } 28 }
33 29
34 func GenerateSalt() (string, error) { 30 func GenerateSalt() (string, error) {
35 salt := "" 31 salt := ""
36 32
37 rawsalt := make([]byte, saltSize) 33 rawsalt := make([]byte, saltSize)
38 _, err := rand.Read(rawsalt) 34 _, err := rand.Read(rawsalt)
39 if err != nil { 35 if err != nil {
40 return "", err 36 return "", err
41 } 37 }
42 salt = hex.EncodeToString(rawsalt) 38 salt = hex.EncodeToString(rawsalt)
43 return salt, nil 39 return salt, nil
44 } 40 }
45 41
46 func HashMessage(message string, presalt string) (string, string, error) { 42 func HashMessage(message string, presalt string) (string, string, error) {
47 hash, salt := "", "" 43 hash, salt := "", ""
48 var err error 44 var err error
49 45
50 // chech if message is presalted 46 // chech if message is presalted
51 if presalt == "" { 47 if presalt == "" {
52 salt, err = GenerateSalt() 48 salt, err = GenerateSalt()
53 if err != nil { 49 if err != nil {
54 return "", "", err 50 return "", "", err
55 } 51 }
56 } else { 52 } else {
57 salt = presalt 53 salt = presalt
58 } 54 }
59 55
60 // convert strings to raw byte slices 56 // convert strings to raw byte slices
61 rawmessage := []byte(message) 57 rawmessage := []byte(message)
62 rawsalt, err := hex.DecodeString(salt) 58 rawsalt, err := hex.DecodeString(salt)
63 if err != nil { 59 if err != nil {
64 return "", "", err 60 return "", "", err
65 } 61 }
66 rawdata := make([]byte, len(rawmessage) + len(rawsalt)) 62 rawdata := make([]byte, len(rawmessage) + len(rawsalt))
67 rawdata = append(rawdata, rawmessage...) 63 rawdata = append(rawdata, rawmessage...)
68 rawdata = append(rawdata, rawsalt...) 64 rawdata = append(rawdata, rawsalt...)
69 65
70 // hash message + salt 66 // hash message + salt
71 hasher := sha256.New() 67 hasher := sha256.New()
72 hasher.Write(rawdata) 68 hasher.Write(rawdata)
73 rawhash := hasher.Sum(nil) 69 rawhash := hasher.Sum(nil)
74 hash = hex.EncodeToString(rawhash) 70 hash = hex.EncodeToString(rawhash)
75 return hash, salt, nil 71 return hash, salt, nil
76 } 72 }
77 73
78 func IssueAPIToken(username, role string) (Token, error) { 74 func IssueAPIToken(username, role string) (string, error) {
79 var apiToken Token 75 var apiToken string
80 var err error 76 var err error
81 77
82 if err != nil { 78 if err != nil {
83 return Token{}, err 79 return "", err
84 } 80 }
85 81
86 claims := TokenClaims{ 82 claims := TokenClaims{
87 username, 83 username,
88 role, 84 role,
89 jwt.StandardClaims{ 85 jwt.StandardClaims{
90 ExpiresAt: (time.Now().Add(OneWeek)).Unix(), 86 ExpiresAt: (time.Now().Add(OneWeek)).Unix(),
91 Issuer: appName, 87 Issuer: appName,
92 }, 88 },
93 } 89 }
94 90
95 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 91 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
96 apiToken.TokenString, err = jwtToken.SignedString([]byte(secret)) 92 apiToken, err = jwtToken.SignedString([]byte(secret))
97 if err != nil { 93 if err != nil {
98 return Token{}, err 94 return "", err
99 } 95 }
100 return apiToken, nil 96 return apiToken, nil
101 } 97 }
102 98
103 func RefreshAPIToken(tokenString string) (Token, error) { 99 func RefreshAPIToken(tokenString string) (string, error) {
104 var newToken Token 100 var newToken string
105 tokenString = strings.TrimPrefix(tokenString, "Bearer ") 101 tokenString = strings.TrimPrefix(tokenString, "Bearer ")
106 token, err := parseTokenFunc(tokenString) 102 token, err := parseTokenFunc(tokenString)
107 if err != nil { 103 if err != nil {
108 return Token{}, err 104 return "", err
109 } 105 }
110 106
111 // type assertion 107 // type assertion
112 claims, ok := token.Claims.(*TokenClaims) 108 claims, ok := token.Claims.(*TokenClaims)
113 if !ok || !token.Valid { 109 if !ok || !token.Valid {
114 return Token{}, errors.New("token is not valid") 110 return "", errors.New("token is not valid")
115 } 111 }
116 112
117 claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() 113 claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix()
118 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 114 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
119 115
120 newToken.TokenString, err = jwtToken.SignedString([]byte(secret)) 116 newToken, err = jwtToken.SignedString([]byte(secret))
121 if err != nil { 117 if err != nil {
122 return Token{}, err 118 return "", err
123 } 119 }
124 120
125 return newToken, nil 121 return newToken, nil
126 } 122 }
127 123
128 func ParseAPIToken(tokenString string) (*TokenClaims, error) { 124 func ParseAPIToken(tokenString string) (*TokenClaims, error) {
129 if ok := strings.HasPrefix(tokenString, "Bearer"); ok { 125 if ok := strings.HasPrefix(tokenString, "Bearer"); ok {
130 tokenString = strings.TrimPrefix(tokenString, "Bearer ") 126 tokenString = strings.TrimPrefix(tokenString, "Bearer ")
131 } else { 127 } else {
132 return &TokenClaims{}, errors.New("Authorization header is incomplete") 128 return &TokenClaims{}, errors.New("Authorization header is incomplete")
133 } 129 }
134 130
135 token, err := parseTokenFunc(tokenString) 131 token, err := parseTokenFunc(tokenString)
136 if err != nil { 132 if err != nil {
137 return &TokenClaims{}, err 133 return &TokenClaims{}, err
138 } 134 }
139 135
140 // type assertion 136 // type assertion
141 claims, ok := token.Claims.(*TokenClaims) 137 claims, ok := token.Claims.(*TokenClaims)
142 if !ok || !token.Valid { 138 if !ok || !token.Valid {
143 return &TokenClaims{}, errors.New("token is not valid") 139 return &TokenClaims{}, errors.New("token is not valid")
144 } 140 }
145 return claims, nil 141 return claims, nil
146 } 142 }
147 143
148 func parseTokenFunc(tokenString string) (*jwt.Token, error) { 144 func parseTokenFunc(tokenString string) (*jwt.Token, error) {
149 token, err := jwt.ParseWithClaims(tokenString, 145 token, err := jwt.ParseWithClaims(tokenString,
150 &TokenClaims{}, 146 &TokenClaims{},
151 func(token *jwt.Token) (interface{}, error) { 147 func(token *jwt.Token) (interface{}, error) {
152 return []byte(secret), nil 148 return []byte(secret), nil
153 }, 149 },
154 ) 150 )
155 return token, err 151 return token, err
156 } 152 }
157 153
1 package restutility 1 package restutility
2 2
3 import ( 3 import (
4 "net/http" 4 "net/http"
5 "encoding/json" 5 "encoding/json"
6 ) 6 )
7 7
8 var _apiVersion = "" 8 var _apiVersion = ""
9 var _authEndPoint = "" 9 var _authEndPoint = ""
10 10
11 func SetApiVersion(ver string) string { 11 func SetApiVersion(ver string) string {
12 _apiVersion = ver 12 _apiVersion = ver
13 return _apiVersion 13 return _apiVersion
14 } 14 }
15 15
16 func SetAuthEndpoint(ep string) { 16 func SetAuthEndpoint(ep string) {
17 _authEndPoint = ep 17 _authEndPoint = ep
18 } 18 }
19 19
20 //// 20 ////
21 //// ERROR UTILITY 21 //// ERROR UTILITY
22 //// 22 ////
23 23
24 const templateHttpErr500_EN = "An internal server error has occurred." 24 const templateHttpErr500_EN = "An internal server error has occurred."
25 const templateHttpErr500_RS = "Došlo je do greške na serveru." 25 const templateHttpErr500_RS = "Došlo je do greške na serveru."
26 const templateHttpErr400_EN = "Bad request: invalid request body." 26 const templateHttpErr400_EN = "Bad request: invalid request body."
27 const templateHttpErr400_RS = "Neispravan zahtev." 27 const templateHttpErr400_RS = "Neispravan zahtev."
28 28
29 type HttpError struct { 29 type HttpError struct {
30 Error []HttpErrorDesc `json:"error"` 30 Error []HttpErrorDesc `json:"error"`
31 Request string `json:"request"` 31 Request string `json:"request"`
32 } 32 }
33 33
34 type HttpErrorDesc struct { 34 type HttpErrorDesc struct {
35 Lang string `json:"lang"` 35 Lang string `json:"lang"`
36 Desc string `json:"description"` 36 Desc string `json:"description"`
37 } 37 }
38 38
39 func RespondWithHttpError(w http.ResponseWriter, 39 func RespondWithHttpError(w http.ResponseWriter,
40 req *http.Request, 40 req *http.Request,
41 code int, 41 code int,
42 httpErr []HttpErrorDesc) { 42 httpErr []HttpErrorDesc) {
43 43
44 err := HttpError{ 44 err := HttpError{
45 Error: httpErr, 45 Error: httpErr,
46 Request: req.Method + " " + req.URL.Path, 46 Request: req.Method + " " + req.URL.Path,
47 } 47 }
48 w.WriteHeader(code) 48 w.WriteHeader(code)
49 json.NewEncoder(w).Encode(err) 49 json.NewEncoder(w).Encode(err)
50 } 50 }
51 51
52 func RespondWithHttpError400(w http.ResponseWriter, req *http.Request) { 52 func RespondWithHttpError400(w http.ResponseWriter, req *http.Request) {
53 RespondWithHttpError(w, req, http.StatusBadRequest, []HttpErrorDesc{ 53 RespondWithHttpError(w, req, http.StatusBadRequest, []HttpErrorDesc{
54 {Lang: "en", Desc: templateHttpErr400_EN}, 54 {Lang: "en", Desc: templateHttpErr400_EN},
55 {Lang: "rs", Desc: templateHttpErr400_RS}, 55 {Lang: "rs", Desc: templateHttpErr400_RS},
56 }) 56 })
57 } 57 }
58 58
59 func RespondWithHttpError500(w http.ResponseWriter, req *http.Request) { 59 func RespondWithHttpError500(w http.ResponseWriter, req *http.Request) {
60 RespondWithHttpError(w, req, http.StatusInternalServerError, []HttpErrorDesc{ 60 RespondWithHttpError(w, req, http.StatusInternalServerError, []HttpErrorDesc{
61 {Lang: "en", Desc: templateHttpErr500_EN}, 61 {Lang: "en", Desc: templateHttpErr500_EN},
62 {Lang: "rs", Desc: templateHttpErr500_RS}, 62 {Lang: "rs", Desc: templateHttpErr500_RS},
63 }) 63 })
64 } 64 }
65 65
66 //// 66 ////
67 //// HANDLER FUNC WRAPPER 67 //// HANDLER FUNC WRAPPER
68 //// 68 ////
69 69
70 //TODO: Add parameters to enable/disable token and roles authorization checks 70 //TODO: Add parameters to enable/disable token and roles authorization checks
71 // Sets common headers and checks for token validity. 71 // Sets common headers and checks for token validity.
72 func ProcessHeaders(fn http.HandlerFunc, shouldAuth bool) http.HandlerFunc { 72 func ProcessHeaders(fn http.HandlerFunc, authEnabled bool) http.HandlerFunc {
73 return func(w http.ResponseWriter, req *http.Request) { 73 return func(w http.ResponseWriter, req *http.Request) {
74 // @TODO: check Content-type header (must be application/json) 74 // @TODO: check Content-type header (must be application/json)
75 // ctype := w.Header.Get("Content-Type") 75 // ctype := w.Header.Get("Content-Type")
76 // if req.Method != "GET" && ctype != "application/json" { 76 // if req.Method != "GET" && ctype != "application/json" {
77 // replyWithHttpError(w, req, http.StatusBadRequest, 77 // replyWithHttpError(w, req, http.StatusBadRequest,
78 // "Not a supported content type: " + ctype) 78 // "Not a supported content type: " + ctype)
79 // } 79 // }
80 80
81 w.Header().Set("Access-Control-Allow-Origin", "*") 81 w.Header().Set("Access-Control-Allow-Origin", "*")
82 w.Header().Set("Access-Control-Allow-Methods", 82 w.Header().Set("Access-Control-Allow-Methods",
83 `POST, 83 `POST,
84 GET, 84 GET,
85 PUT, 85 PUT,
86 DELETE, 86 DELETE,
87 OPTIONS`) 87 OPTIONS`)
88 w.Header().Set("Access-Control-Allow-Headers", 88 w.Header().Set("Access-Control-Allow-Headers",
89 `Accept, 89 `Accept,
90 Content-Type, 90 Content-Type,
91 Content-Length, 91 Content-Length,
92 Accept-Encoding, 92 Accept-Encoding,
93 X-CSRF-Token, 93 X-CSRF-Token,
94 Authorization`) 94 Authorization`)
95 w.Header().Set("Content-Type", "application/json; charset=utf-8") 95 w.Header().Set("Content-Type", "application/json; charset=utf-8")
96 96
97 if req.Method == "OPTIONS" { 97 if req.Method == "OPTIONS" {
98 return 98 return
99 } 99 }
100 100
101 if shouldAuth { 101 if authEnabled {
102 if req.URL.Path != _apiVersion + _authEndPoint { 102 if req.URL.Path != _apiVersion + _authEndPoint {
103 token := req.Header.Get("Authorization") 103 token := req.Header.Get("Authorization")
104 if _, err := ParseAPIToken(token); err != nil { 104 if _, err := ParseAPIToken(token); err != nil {
105 RespondWithHttpError(w, req, http.StatusUnauthorized, 105 RespondWithHttpError(w, req, http.StatusUnauthorized,
106 []HttpErrorDesc{ 106 []HttpErrorDesc{
107 {Lang: "en", Desc: "Unauthorized request."}, 107 {Lang: "en", Desc: "Unauthorized request."},
108 {Lang: "rs", Desc: "Neautorizovani zahtev."}, 108 {Lang: "rs", Desc: "Neautorizovani zahtev."},
109 }) 109 })
110 return 110 return
111 } 111 }
112 } 112 }
113 } 113 }
114 114
115 err := req.ParseForm() 115 err := req.ParseForm()
116 if err != nil { 116 if err != nil {
117 RespondWithHttpError(w, req, http.StatusBadRequest, 117 RespondWithHttpError(w, req, http.StatusBadRequest,
118 []HttpErrorDesc{ 118 []HttpErrorDesc{
119 {Lang: "en", Desc: templateHttpErr400_EN}, 119 {Lang: "en", Desc: templateHttpErr400_EN},
120 {Lang: "rs", Desc: templateHttpErr400_RS}, 120 {Lang: "rs", Desc: templateHttpErr400_RS},
121 }) 121 })
122 return 122 return
123 } 123 }
124 124
125 // execute HandlerFunc 125 // execute HandlerFunc
126 fn(w, req) 126 fn(w, req)
127 } 127 }
128 } 128 }
129 129
130 //// 130 ////
131 //// NOT FOUND HANDLER 131 //// NOT FOUND HANDLER
132 //// 132 ////
133 133
134 func NotFoundHandler(w http.ResponseWriter, req *http.Request) { 134 func NotFoundHandler(w http.ResponseWriter, req *http.Request) {
135 RespondWithHttpError(w, req, http.StatusNotFound, []HttpErrorDesc{ 135 RespondWithHttpError(w, req, http.StatusNotFound, []HttpErrorDesc{
136 {Lang: "en", Desc: "Not found."}, 136 {Lang: "en", Desc: "Not found."},
137 {Lang: "rs", Desc: "Traženi resurs ne postoji."}, 137 {Lang: "rs", Desc: "Traženi resurs ne postoji."},
138 }) 138 })
139 } 139 }
140 140