Commit b291ac8c49a98a8c273e85fcc4000db6348a4d1f
1 parent
25e0015504
Exists in
master
and in
1 other branch
clened up
Showing
3 changed files
with
7 additions
and
17 deletions
Show diff stats
auth_utility.go
1 | package restutility | 1 | package restutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | // "fmt" | ||
5 | "errors" | 4 | "errors" |
6 | // "os" | ||
7 | "time" | 5 | "time" |
8 | "crypto/sha256" | 6 | "crypto/sha256" |
9 | "crypto/rand" | 7 | "crypto/rand" |
10 | "encoding/hex" | 8 | "encoding/hex" |
11 | "strings" | 9 | "strings" |
12 | "github.com/dgrijalva/jwt-go" | 10 | "github.com/dgrijalva/jwt-go" |
13 | // "github.com/SermoDigital/jose/jwt" | ||
14 | ) | 11 | ) |
15 | 12 | ||
16 | const OneDay = time.Hour*24 | 13 | const OneDay = time.Hour*24 |
17 | const OneWeek = OneDay*7 | 14 | const OneWeek = OneDay*7 |
18 | const saltSize = 32 | 15 | const saltSize = 32 |
19 | const appName = "korisnicki-centar" | 16 | const appName = "korisnicki-centar" |
20 | const secret = "korisnicki-centar-api" | 17 | const secret = "korisnicki-centar-api" |
21 | 18 | ||
22 | type Token struct { | 19 | type Token struct { |
23 | TokenString string `json:"token"` | 20 | TokenString string `json:"token"` |
24 | } | 21 | } |
25 | 22 | ||
26 | type TokenClaims struct { | 23 | type TokenClaims struct { |
27 | Username string `json:"username"` | 24 | Username string `json:"username"` |
28 | Role string `json:"role"` | 25 | Role string `json:"role"` |
29 | jwt.StandardClaims | 26 | jwt.StandardClaims |
30 | } | 27 | } |
31 | 28 | ||
32 | type CredentialsStruct struct { | 29 | type CredentialsStruct struct { |
33 | Username string `json:"username"` | 30 | Username string `json:"username"` |
34 | Password string `json:"password"` | 31 | Password string `json:"password"` |
35 | } | 32 | } |
36 | 33 | ||
37 | func generateSalt() (string, error) { | 34 | func GenerateSalt() (string, error) { |
38 | salt := "" | 35 | salt := "" |
39 | 36 | ||
40 | rawsalt := make([]byte, saltSize) | 37 | rawsalt := make([]byte, saltSize) |
41 | _, err := rand.Read(rawsalt) | 38 | _, err := rand.Read(rawsalt) |
42 | if err != nil { | 39 | if err != nil { |
43 | return "", err | 40 | return "", err |
44 | } | 41 | } |
45 | salt = hex.EncodeToString(rawsalt) | 42 | salt = hex.EncodeToString(rawsalt) |
46 | return salt, nil | 43 | return salt, nil |
47 | } | 44 | } |
48 | 45 | ||
49 | func hashMessage(message string, presalt string) (string, string, error) { | 46 | func HashMessage(message string, presalt string) (string, string, error) { |
50 | hash, salt := "", "" | 47 | hash, salt := "", "" |
51 | var err error | 48 | var err error |
52 | 49 | ||
53 | // chech if message is presalted | 50 | // chech if message is presalted |
54 | if presalt == "" { | 51 | if presalt == "" { |
55 | salt, err = generateSalt() | 52 | salt, err = GenerateSalt() |
56 | if err != nil { | 53 | if err != nil { |
57 | return "", "", err | 54 | return "", "", err |
58 | } | 55 | } |
59 | } else { | 56 | } else { |
60 | salt = presalt | 57 | salt = presalt |
61 | } | 58 | } |
62 | 59 | ||
63 | // convert strings to raw byte slices | 60 | // convert strings to raw byte slices |
64 | rawmessage := []byte(message) | 61 | rawmessage := []byte(message) |
65 | rawsalt, err := hex.DecodeString(salt) | 62 | rawsalt, err := hex.DecodeString(salt) |
66 | if err != nil { | 63 | if err != nil { |
67 | return "", "", err | 64 | return "", "", err |
68 | } | 65 | } |
69 | rawdata := make([]byte, len(rawmessage) + len(rawsalt)) | 66 | rawdata := make([]byte, len(rawmessage) + len(rawsalt)) |
70 | rawdata = append(rawdata, rawmessage...) | 67 | rawdata = append(rawdata, rawmessage...) |
71 | rawdata = append(rawdata, rawsalt...) | 68 | rawdata = append(rawdata, rawsalt...) |
72 | 69 | ||
73 | // hash message + salt | 70 | // hash message + salt |
74 | hasher := sha256.New() | 71 | hasher := sha256.New() |
75 | hasher.Write(rawdata) | 72 | hasher.Write(rawdata) |
76 | rawhash := hasher.Sum(nil) | 73 | rawhash := hasher.Sum(nil) |
77 | hash = hex.EncodeToString(rawhash) | 74 | hash = hex.EncodeToString(rawhash) |
78 | return hash, salt, nil | 75 | return hash, salt, nil |
79 | } | 76 | } |
80 | 77 | ||
81 | func issueAPIToken(username, role string) (Token, error) { | 78 | func IssueAPIToken(username, role string) (Token, error) { |
82 | var apiToken Token | 79 | var apiToken Token |
83 | var err error | 80 | var err error |
84 | 81 | ||
85 | if err != nil { | 82 | if err != nil { |
86 | return Token{}, err | 83 | return Token{}, err |
87 | } | 84 | } |
88 | 85 | ||
89 | claims := TokenClaims{ | 86 | claims := TokenClaims{ |
90 | username, | 87 | username, |
91 | role, | 88 | role, |
92 | jwt.StandardClaims{ | 89 | jwt.StandardClaims{ |
93 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), | 90 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), |
94 | Issuer: appName, | 91 | Issuer: appName, |
95 | }, | 92 | }, |
96 | } | 93 | } |
97 | 94 | ||
98 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 95 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
99 | apiToken.TokenString, err = jwtToken.SignedString([]byte(secret)) | 96 | apiToken.TokenString, err = jwtToken.SignedString([]byte(secret)) |
100 | if err != nil { | 97 | if err != nil { |
101 | return Token{}, err | 98 | return Token{}, err |
102 | } | 99 | } |
103 | return apiToken, nil | 100 | return apiToken, nil |
104 | } | 101 | } |
105 | 102 | ||
106 | func refreshAPIToken(tokenString string) (Token, error) { | 103 | func RefreshAPIToken(tokenString string) (Token, error) { |
107 | var newToken Token | 104 | var newToken Token |
108 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 105 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
109 | token, err := parseTokenFunc(tokenString) | 106 | token, err := parseTokenFunc(tokenString) |
110 | if err != nil { | 107 | if err != nil { |
111 | return Token{}, err | 108 | return Token{}, err |
112 | } | 109 | } |
113 | 110 | ||
114 | // type assertion | 111 | // type assertion |
115 | claims, ok := token.Claims.(*TokenClaims) | 112 | claims, ok := token.Claims.(*TokenClaims) |
116 | if !ok || !token.Valid { | 113 | if !ok || !token.Valid { |
117 | return Token{}, errors.New("token is not valid") | 114 | return Token{}, errors.New("token is not valid") |
118 | } | 115 | } |
119 | 116 | ||
120 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() | 117 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() |
121 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 118 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
122 | 119 | ||
123 | newToken.TokenString, err = jwtToken.SignedString([]byte(secret)) | 120 | newToken.TokenString, err = jwtToken.SignedString([]byte(secret)) |
124 | if err != nil { | 121 | if err != nil { |
125 | return Token{}, err | 122 | return Token{}, err |
126 | } | 123 | } |
127 | 124 | ||
128 | return newToken, nil | 125 | return newToken, nil |
129 | } | 126 | } |
130 | 127 | ||
131 | func parseAPIToken(tokenString string) (*TokenClaims, error) { | 128 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { |
132 | if ok := strings.HasPrefix(tokenString, "Bearer"); ok { | 129 | if ok := strings.HasPrefix(tokenString, "Bearer"); ok { |
133 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 130 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
134 | } else { | 131 | } else { |
135 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 132 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
136 | } | 133 | } |
137 | 134 | ||
138 | token, err := parseTokenFunc(tokenString) | 135 | token, err := parseTokenFunc(tokenString) |
139 | if err != nil { | 136 | if err != nil { |
140 | return &TokenClaims{}, err | 137 | return &TokenClaims{}, err |
141 | } | 138 | } |
142 | 139 | ||
143 | // type assertion | 140 | // type assertion |
144 | claims, ok := token.Claims.(*TokenClaims) | 141 | claims, ok := token.Claims.(*TokenClaims) |
145 | if !ok || !token.Valid { | 142 | if !ok || !token.Valid { |
146 | return &TokenClaims{}, errors.New("token is not valid") | 143 | return &TokenClaims{}, errors.New("token is not valid") |
147 | } | 144 | } |
148 | return claims, nil | 145 | return claims, nil |
149 | } | 146 | } |
150 | 147 | ||
151 | func parseTokenFunc(tokenString string) (*jwt.Token, error) { | 148 | func parseTokenFunc(tokenString string) (*jwt.Token, error) { |
152 | token, err := jwt.ParseWithClaims(tokenString, | 149 | token, err := jwt.ParseWithClaims(tokenString, |
153 | &TokenClaims{}, | 150 | &TokenClaims{}, |
154 | func(token *jwt.Token) (interface{}, error) { | 151 | func(token *jwt.Token) (interface{}, error) { |
155 | return []byte(secret), nil | 152 | return []byte(secret), nil |
156 | }, | 153 | }, |
157 | ) | 154 | ) |
158 | return token, err | 155 | return token, err |
159 | } | 156 | } |
160 | |||
161 | func authMinRegReq(uname, pword string) (bool, error) { | ||
162 | return true, nil | ||
163 | } | ||
164 | |||
165 | 157 |
format_utility.go
1 | package restutility | 1 | package restutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "strings" | ||
5 | "time" | 4 | "time" |
6 | "strconv" | ||
7 | ) | 5 | ) |
8 | 6 | ||
9 | func UnixToDate(input int64) time.Time { | 7 | func UnixToDate(input int64) time.Time { |
10 | return time.Unix(input, 0) | 8 | return time.Unix(input, 0) |
11 | } | 9 | } |
12 | 10 | ||
13 | func DateToUnix(input interface{}) int64 { | 11 | func DateToUnix(input interface{}) int64 { |
14 | if input != nil { | 12 | if input != nil { |
15 | t := input.(time.Time) | 13 | t := input.(time.Time) |
16 | return t.Unix() | 14 | return t.Unix() |
17 | 15 | ||
18 | } | 16 | } |
19 | return 0 | 17 | return 0 |
20 | } | 18 | } |
21 | 19 | ||
22 | func EqualQuotes(input string) string { | 20 | func EqualQuotes(input string) string { |
23 | if input != "" { | 21 | if input != "" { |
24 | return " = '" + input + "'" | 22 | return " = '" + input + "'" |
25 | } | 23 | } |
26 | return "" | 24 | return "" |
27 | } | 25 | } |
28 | 26 | ||
29 | func LikeQuotes(input string) string { | 27 | func LikeQuotes(input string) string { |
30 | if input != "" { | 28 | if input != "" { |
31 | return " LIKE UPPER('%" + input + "%')" | 29 | return " LIKE UPPER('%" + input + "%')" |
32 | } | 30 | } |
33 | return "" | 31 | return "" |
34 | } | 32 | } |
35 | 33 | ||
36 | 34 |
http_utility.go
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 = "/api/v1" | 8 | var _apiVersion = "/api/v1" |
9 | 9 | ||
10 | func SetApiVersion(ver string) string { | 10 | func SetApiVersion(ver string) string { |
11 | _apiVersion = ver | 11 | _apiVersion = ver |
12 | return _apiVeresion | 12 | return _apiVersion |
13 | } | 13 | } |
14 | 14 | ||
15 | //// | 15 | //// |
16 | //// ERROR UTILITY | 16 | //// ERROR UTILITY |
17 | //// | 17 | //// |
18 | 18 | ||
19 | const templateHttpErr500_EN = "An internal server error has occurred." | 19 | const templateHttpErr500_EN = "An internal server error has occurred." |
20 | const templateHttpErr500_RS = "Došlo je do greške na serveru." | 20 | const templateHttpErr500_RS = "Došlo je do greške na serveru." |
21 | const templateHttpErr400_EN = "Bad request: invalid request body." | 21 | const templateHttpErr400_EN = "Bad request: invalid request body." |
22 | const templateHttpErr400_RS = "Neispravan zahtev." | 22 | const templateHttpErr400_RS = "Neispravan zahtev." |
23 | 23 | ||
24 | type HttpError struct { | 24 | type HttpError struct { |
25 | Error []HttpErrorDesc `json:"error"` | 25 | Error []HttpErrorDesc `json:"error"` |
26 | Request string `json:"request"` | 26 | Request string `json:"request"` |
27 | } | 27 | } |
28 | 28 | ||
29 | type HttpErrorDesc struct { | 29 | type HttpErrorDesc struct { |
30 | Lang string `json:"lang"` | 30 | Lang string `json:"lang"` |
31 | Desc string `json:"description"` | 31 | Desc string `json:"description"` |
32 | } | 32 | } |
33 | 33 | ||
34 | func RespondWithHttpError(w http.ResponseWriter, | 34 | func RespondWithHttpError(w http.ResponseWriter, |
35 | req *http.Request, | 35 | req *http.Request, |
36 | code int, | 36 | code int, |
37 | httpErr []HttpErrorDesc) { | 37 | httpErr []HttpErrorDesc) { |
38 | 38 | ||
39 | err := HttpError{ | 39 | err := HttpError{ |
40 | Error: httpErr, | 40 | Error: httpErr, |
41 | Request: req.Method + " " + req.URL.Path, | 41 | Request: req.Method + " " + req.URL.Path, |
42 | } | 42 | } |
43 | w.WriteHeader(code) | 43 | w.WriteHeader(code) |
44 | json.NewEncoder(w).Encode(err) | 44 | json.NewEncoder(w).Encode(err) |
45 | } | 45 | } |
46 | 46 | ||
47 | func RespondWithHttpError400(w http.ResponseWriter, req *http.Request) { | 47 | func RespondWithHttpError400(w http.ResponseWriter, req *http.Request) { |
48 | RespondWithHttpError(w, req, http.StatusBadRequest, []HttpErrorDesc{ | 48 | RespondWithHttpError(w, req, http.StatusBadRequest, []HttpErrorDesc{ |
49 | {Lang: "en", Desc: templateHttpErr400_EN}, | 49 | {Lang: "en", Desc: templateHttpErr400_EN}, |
50 | {Lang: "rs", Desc: templateHttpErr400_RS}, | 50 | {Lang: "rs", Desc: templateHttpErr400_RS}, |
51 | }) | 51 | }) |
52 | } | 52 | } |
53 | 53 | ||
54 | func RespondWithHttpError500(w http.ResponseWriter, req *http.Request) { | 54 | func RespondWithHttpError500(w http.ResponseWriter, req *http.Request) { |
55 | RespondWithHttpError(w, req, http.StatusInternalServerError, []HttpErrorDesc{ | 55 | RespondWithHttpError(w, req, http.StatusInternalServerError, []HttpErrorDesc{ |
56 | {Lang: "en", Desc: templateHttpErr500_EN}, | 56 | {Lang: "en", Desc: templateHttpErr500_EN}, |
57 | {Lang: "rs", Desc: templateHttpErr500_RS}, | 57 | {Lang: "rs", Desc: templateHttpErr500_RS}, |
58 | }) | 58 | }) |
59 | } | 59 | } |
60 | 60 | ||
61 | func DeliverPayload(w http.ResponseWriter, payload JSONPayload) { | 61 | func DeliverPayload(w http.ResponseWriter, payload JSONPayload) { |
62 | json.NewEncoder(w).Encode(payload) | 62 | json.NewEncoder(w).Encode(payload) |
63 | payload.Data = nil | 63 | payload.Data = nil |
64 | } | 64 | } |
65 | 65 | ||
66 | //// | 66 | //// |
67 | //// HANDLER FUNC WRAPPER | 67 | //// HANDLER FUNC WRAPPER |
68 | //// | 68 | //// |
69 | 69 | ||
70 | // wrapHandlerFunc is as wrapper function for route handlers. | 70 | // wrapHandlerFunc is as wrapper function for route handlers. |
71 | // Sets common headers and checks for token validity. | 71 | // Sets common headers and checks for token validity. |
72 | func HandleFuncWrap(fn http.HandlerFunc) http.HandlerFunc { | 72 | func HandleFuncWrap(fn http.HandlerFunc) 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 req.URL.Path != _apiVersion + "/token/new" { | 101 | if req.URL.Path != _apiVersion + "/token/new" { |
102 | token := req.Header.Get("Authorization") | 102 | token := req.Header.Get("Authorization") |
103 | if _, err := ParseAPIToken(token); err != nil { | 103 | if _, err := ParseAPIToken(token); err != nil { |
104 | RespondWithHttpError(w, req, http.StatusUnauthorized, | 104 | RespondWithHttpError(w, req, http.StatusUnauthorized, |
105 | []HttpErrorDesc{ | 105 | []HttpErrorDesc{ |
106 | {Lang: "en", Desc: "Unauthorized request."}, | 106 | {Lang: "en", Desc: "Unauthorized request."}, |
107 | {Lang: "rs", Desc: "Neautorizovani zahtev."}, | 107 | {Lang: "rs", Desc: "Neautorizovani zahtev."}, |
108 | }) | 108 | }) |
109 | return | 109 | return |
110 | } | 110 | } |
111 | } | 111 | } |
112 | 112 | ||
113 | err := req.ParseForm() | 113 | err := req.ParseForm() |
114 | if err != nil { | 114 | if err != nil { |
115 | RespondWithHttpError(w, req, http.StatusBadRequest, | 115 | RespondWithHttpError(w, req, http.StatusBadRequest, |
116 | []HttpErrorDesc{ | 116 | []HttpErrorDesc{ |
117 | {Lang: "en", Desc: templateHttpErr400_EN}, | 117 | {Lang: "en", Desc: templateHttpErr400_EN}, |
118 | {Lang: "rs", Desc: templateHttpErr400_RS}, | 118 | {Lang: "rs", Desc: templateHttpErr400_RS}, |
119 | }) | 119 | }) |
120 | return | 120 | return |
121 | } | 121 | } |
122 | fn(w, req) | 122 | fn(w, req) |
123 | } | 123 | } |
124 | } | 124 | } |
125 | 125 | ||
126 | //// | 126 | //// |
127 | //// NOT FOUND HANDLER | 127 | //// NOT FOUND HANDLER |
128 | //// | 128 | //// |
129 | 129 | ||
130 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { | 130 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { |
131 | RespondWithHttpError(w, req, http.StatusNotFound, []HttpErrorDesc{ | 131 | RespondWithHttpError(w, req, http.StatusNotFound, []HttpErrorDesc{ |
132 | {Lang: "en", Desc: "Not found."}, | 132 | {Lang: "en", Desc: "Not found."}, |
133 | {Lang: "rs", Desc: "Traženi resurs ne postoji."}, | 133 | {Lang: "rs", Desc: "Traženi resurs ne postoji."}, |
134 | }) | 134 | }) |
135 | } | 135 | } |
136 | 136 |