diff --git a/auth.go b/auth.go index a5abefe..05bbb33 100644 --- a/auth.go +++ b/auth.go @@ -12,10 +12,6 @@ import ( "github.com/dgrijalva/jwt-go" ) -const OneDay = time.Hour * 24 -const OneWeek = OneDay * 7 -const saltSize = 32 - var appName = "webutility" var secret = "webutility" @@ -91,7 +87,7 @@ func CreateHash(str, presalt string) (hash, salt string, err error) { // It returns an error if it fails. func CreateAuthToken(username string, role Role) (TokenClaims, error) { t0 := (time.Now()).Unix() - t1 := (time.Now().Add(OneWeek)).Unix() + t1 := (time.Now().Add(time.Hour * 24 * 7)).Unix() claims := TokenClaims{ TokenType: "Bearer", Username: username, @@ -113,8 +109,8 @@ func CreateAuthToken(username string, role Role) (TokenClaims, error) { return claims, nil } -// RefreshAuthToken returns new JWT token with sprolongs JWT token's expiration date for one week. -// It returns new JWT token or an error if it fails. +// RefreshAuthToken returns new JWT token with same claims contained in tok but with prolonged expiration date. +// It returns an error if it fails. func RefreshAuthToken(tok string) (TokenClaims, error) { token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc) if err != nil { @@ -139,44 +135,13 @@ func RefreshAuthToken(tok string) (TokenClaims, error) { return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID}) } -// RbacCheck returns true if user that made HTTP request is authorized to -// access the resource it is targeting. -// It exctracts user's role from the JWT token located in Authorization header of -// http.Request and then compares it with the list of supplied roles and returns -// true if there's a match, if "*" is provided or if the authRoles is nil. -// Otherwise it returns false. -func RbacCheck(req *http.Request, authRoles []string) bool { - if authRoles == nil { - return true - } - - // validate token and check expiration date - claims, err := GetTokenClaims(req) - if err != nil { - return false - } - // check if token has expired - if claims.ExpiresAt < (time.Now()).Unix() { - return false - } - - // check if role extracted from token matches - // any of the provided (allowed) ones - for _, r := range authRoles { - if claims.Role == r || r == "*" { - return true - } - } - - return false -} - -// AuthCheck returns token claims and boolean value based on user's rights to access resource specified in req. -// It exctracts user's role from the JWT token located in Authorization header of -// HTTP request and then compares it with the list of supplied (authorized); -// it returns true if there's a match, if "*" is provided or if the authRoles is nil. -func AuthCheck(req *http.Request, authRoles []string) (*TokenClaims, bool) { - if authRoles == nil { +// AuthCheck returns JWT claims and boolean result of a check if req contains any role from roles. +// It checks if role extracted from reqest's Authorization header (JWT claims) matches any of +// provided comma-separated roles in roles. If roles is empty string check is skipped, +// otherwise role is extracted from token claims and compared against roles. +// If roles is "*" the check is automatically validated. +func AuthCheck(req *http.Request, roles string) (*TokenClaims, bool) { + if roles == "" { return nil, true } @@ -190,10 +155,14 @@ func AuthCheck(req *http.Request, authRoles []string) (*TokenClaims, bool) { return claims, false } - // check if role extracted from token matches - // any of the provided (allowed) ones - for _, r := range authRoles { - if claims.Role == r || r == "*" { + if roles == "*" { + return claims, true + } + + parts := strings.Split(roles, ",") + for i, _ := range parts { + r := strings.Trim(parts[i], " ") + if claims.Role == r { return claims, true } } @@ -201,7 +170,7 @@ func AuthCheck(req *http.Request, authRoles []string) (*TokenClaims, bool) { return claims, false } -// GetTokenClaims extracts JWT claims from Authorization header of the request. +// GetTokenClaims extracts JWT claims from Authorization header of req. // Returns token claims or an error. func GetTokenClaims(req *http.Request) (*TokenClaims, error) { // check for and strip 'Bearer' prefix @@ -227,7 +196,9 @@ func GetTokenClaims(req *http.Request) (*TokenClaims, error) { return claims, nil } -// randomSalt returns a string of random characters of 'saltSize' length. +// randomSalt returns a string of 32 random characters. +const saltSize = 32 + func randomSalt() (s string, err error) { rawsalt := make([]byte, saltSize) diff --git a/http.go b/http.go index ea5c69e..54670db 100644 --- a/http.go +++ b/http.go @@ -6,11 +6,6 @@ import ( "net/http" ) -type webError struct { - Request string `json:"request"` - Error string `json:"error"` -} - // NotFoundHandlerFunc writes HTTP error 404 to w. func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) { SetDefaultHeaders(w) @@ -54,9 +49,14 @@ func Created(w http.ResponseWriter, payload interface{}) { Success(w, payload, http.StatusCreated) } +type weberror struct { + Request string `json:"request"` + Error string `json:"error"` +} + // 4xx; 5xx func Error(w http.ResponseWriter, r *http.Request, code int, err string) { - werr := webError{Error: err, Request: r.Method + " " + r.RequestURI} + werr := weberror{Error: err, Request: r.Method + " " + r.RequestURI} w.WriteHeader(code) json.NewEncoder(w).Encode(werr) } @@ -90,76 +90,3 @@ func Conflict(w http.ResponseWriter, r *http.Request, err string) { func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { Error(w, r, http.StatusInternalServerError, err) } - -/// -/// Old API -/// - -const ( - templateHttpErr500_EN = "An internal server error has occurred." - templateHttpErr500_RS = "Došlo je do greške na serveru." - templateHttpErr400_EN = "Bad request." - templateHttpErr400_RS = "Neispravan zahtev." - templateHttpErr404_EN = "Resource not found." - templateHttpErr404_RS = "Resurs nije pronadjen." - templateHttpErr401_EN = "Unauthorized request." - templateHttpErr401_RS = "Neautorizovan zahtev." -) - -type httpError struct { - Error []HttpErrorDesc `json:"error"` - Request string `json:"request"` -} - -type HttpErrorDesc struct { - Lang string `json:"lang"` - Desc string `json:"description"` -} - -// DeliverPayload encodes payload as JSON to w. -func DeliverPayload(w http.ResponseWriter, payload Payload) { - // Don't write status OK in the headers here. Leave it up for the caller. - // E.g. Status 201. - json.NewEncoder(w).Encode(payload) - payload.Data = nil -} - -// ErrorResponse writes HTTP error to w. -func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { - err := httpError{desc, r.Method + " " + r.RequestURI} - w.WriteHeader(code) - json.NewEncoder(w).Encode(err) -} - -// NotFoundResponse writes HTTP error 404 to w. -func NotFoundResponse(w http.ResponseWriter, req *http.Request) { - ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ - {"en", templateHttpErr404_EN}, - {"rs", templateHttpErr404_RS}, - }) -} - -// BadRequestResponse writes HTTP error 400 to w. -func BadRequestResponse(w http.ResponseWriter, req *http.Request) { - ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ - {"en", templateHttpErr400_EN}, - {"rs", templateHttpErr400_RS}, - }) -} - -// InternalSeverErrorResponse writes HTTP error 500 to w. -func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { - ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ - {"en", templateHttpErr500_EN}, - {"rs", templateHttpErr500_RS}, - }) -} - -// UnauthorizedError writes HTTP error 401 to w. -func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { - w.Header().Set("WWW-Authenticate", "Bearer") - ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ - {"en", templateHttpErr401_EN}, - {"rs", templateHttpErr401_RS}, - }) -} diff --git a/json.go b/json.go index e0a927b..4bc010d 100644 --- a/json.go +++ b/json.go @@ -14,8 +14,9 @@ import ( ) var ( - mu = &sync.Mutex{} - metadata = make(map[string]Payload) + mu = &sync.Mutex{} + metadata = make(map[string]Payload) + updateQue = make(map[string][]byte) metadataDB *sql.DB diff --git a/localization.go b/localization.go index 17ca979..b71ae99 100644 --- a/localization.go +++ b/localization.go @@ -4,9 +4,11 @@ import ( "encoding/json" "errors" "io/ioutil" + "sync" ) type Dictionary struct { + my sync.Mutex locales map[string]map[string]string supported []string defaultLocale string @@ -34,6 +36,9 @@ func (d *Dictionary) AddLocale(loc, filePath string) error { for k, v := range data.(map[string]interface{}) { l[k] = v.(string) } + + mu.Lock() + defer mu.Unlock() d.locales[loc] = l d.supported = append(d.supported, loc) diff --git a/middleware.go b/middleware.go index 4e3e4a2..410758d 100644 --- a/middleware.go +++ b/middleware.go @@ -7,9 +7,7 @@ import ( "git.to-net.rs/marko.tikvic/gologger" ) -var reqLogger *gologger.Logger - -func WithSetHeaders(h http.HandlerFunc) http.HandlerFunc { +func SetHeaders(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { SetDefaultHeaders(w) if req.Method == http.MethodOptions { @@ -19,7 +17,7 @@ func WithSetHeaders(h http.HandlerFunc) http.HandlerFunc { } } -func WithParseForm(h http.HandlerFunc) http.HandlerFunc { +func ParseForm(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { err := req.ParseForm() if err != nil { @@ -30,13 +28,15 @@ func WithParseForm(h http.HandlerFunc) http.HandlerFunc { } } +var reqLogger *gologger.Logger + func EnableLogging(log string) error { var err error reqLogger, err = gologger.New(log, gologger.MaxLogSize5MB) return err } -func WithLog(h http.HandlerFunc) http.HandlerFunc { +func Log(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { reqLogger.LogRequest(req, "") t1 := time.Now() @@ -46,9 +46,9 @@ func WithLog(h http.HandlerFunc) http.HandlerFunc { } } -func WithAuth(authorizedRoles []string, h http.HandlerFunc) http.HandlerFunc { +func Auth(roles string, h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { - if _, ok := AuthCheck(req, authorizedRoles); !ok { + if _, ok := AuthCheck(req, roles); !ok { Unauthorized(w, req, "") return }