From 3a87c1507210da1c4e1657f5a0c4cd6960254a65 Mon Sep 17 00:00:00 2001 From: Uggah Date: Sun, 16 Apr 2023 21:09:42 +0200 Subject: [PATCH 1/9] Implement PAM authentication controller --- go.mod | 1 + go.sum | 2 ++ internal/pam/pam.go | 52 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 internal/pam/pam.go diff --git a/go.mod b/go.mod index e5b032f..d99d88e 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/msteinert/pam v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.9.3 // indirect diff --git a/go.sum b/go.sum index 3949a6f..1f6b3dd 100644 --- a/go.sum +++ b/go.sum @@ -142,6 +142,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/msteinert/pam v1.1.0 h1:VhLun/0n0kQYxiRBJJvVpC2jR6d21SWJFjpvUVj20Kc= +github.com/msteinert/pam v1.1.0/go.mod h1:M4FPeAW8g2ITO68W8gACDz13NDJyOQM9IQsQhrR6TOI= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/internal/pam/pam.go b/internal/pam/pam.go new file mode 100644 index 0000000..adfa689 --- /dev/null +++ b/internal/pam/pam.go @@ -0,0 +1,52 @@ +package pam + +import ( + "errors" + "fmt" + "github.com/Excubitor-Monitoring/Excubitor-Backend/internal/logging" + "github.com/msteinert/pam" +) + +type PAMPasswordCredentials struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func (cred PAMPasswordCredentials) Authenticate() bool { + return passwordAuthentication(cred.Username, cred.Password) +} + +var ErrUnrecognizedMessageStyle = errors.New("unrecognized message style") + +func passwordAuthentication(username string, password string) bool { + logger := logging.GetLogger() + + t, err := pam.StartFunc("excubitor", username, func(style pam.Style, msg string) (string, error) { + switch style { + case pam.PromptEchoOff: + fallthrough + case pam.PromptEchoOn: + return password, nil + case pam.ErrorMsg: + return "", fmt.Errorf("authentication error: %s", msg) + case pam.TextInfo: + return "", nil + default: + return "", ErrUnrecognizedMessageStyle + } + }) + + if err != nil { + logger.Warn(fmt.Sprintf("Authentication of user %s failed on start: %s", username, err)) + return false + } + + err = t.Authenticate(0) + if err != nil { + logger.Warn(fmt.Sprintf("Authentication of user %s failed on authentication: %s", username, err)) + return false + } + + logger.Trace(fmt.Sprintf("Password-based authentication of user %s was successful!", username)) + return true +} From dfde21a16dd3df273af5a96382ca6d3a94f88ab2 Mon Sep 17 00:00:00 2001 From: Uggah Date: Sun, 16 Apr 2023 21:10:08 +0200 Subject: [PATCH 2/9] Add ReturnError method --- internal/http_server/http_error.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/internal/http_server/http_error.go b/internal/http_server/http_error.go index c0fecc7..08a3412 100644 --- a/internal/http_server/http_error.go +++ b/internal/http_server/http_error.go @@ -1,6 +1,10 @@ package http_server -import "time" +import ( + "encoding/json" + "net/http" + "time" +) type Error struct { Timestamp time.Time `json:"timestamp"` @@ -15,3 +19,21 @@ func NewHTTPError(message string, path string) Error { path, } } + +func ReturnError(w http.ResponseWriter, r *http.Request, status int, reason string) { + w.WriteHeader(status) + + httpError := NewHTTPError(reason, r.RequestURI) + + bytes, err := json.Marshal(httpError) + if err != nil { + logger.Error("Couldn't encode http error!") + return + } + + _, err = w.Write(bytes) + if err != nil { + logger.Error("Couldn't write http error!") + return + } +} From 50ea4c2a20b37b7df907a7a312a842949410c953 Mon Sep 17 00:00:00 2001 From: Uggah Date: Sun, 16 Apr 2023 21:11:03 +0200 Subject: [PATCH 3/9] Add prototype handleAuthRequest Method and auth middleware --- cmd/excubitor/main.go | 2 + internal/http_server/authentication.go | 59 ++++++++++++++++++++++++++ internal/http_server/http_server.go | 3 +- 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 internal/http_server/authentication.go diff --git a/cmd/excubitor/main.go b/cmd/excubitor/main.go index 857da36..80112b1 100644 --- a/cmd/excubitor/main.go +++ b/cmd/excubitor/main.go @@ -32,6 +32,8 @@ func initConfig() error { viper.SetDefault("logging.method", "CONSOLE") viper.SetDefault("http.port", 8080) viper.SetDefault("http.host", "localhost") + viper.SetDefault("http.cors.allowed_origin", "*") + viper.SetDefault("http.auth.jwt.secret", "") if _, err := os.Stat("config.yml"); errors.Is(err, fs.ErrNotExist) { err := viper.WriteConfig() diff --git a/internal/http_server/authentication.go b/internal/http_server/authentication.go new file mode 100644 index 0000000..d68954a --- /dev/null +++ b/internal/http_server/authentication.go @@ -0,0 +1,59 @@ +package http_server + +import ( + "encoding/json" + "github.com/Excubitor-Monitoring/Excubitor-Backend/internal/pam" + "io" + "net/http" +) + +type Credentials interface { + Authenticate() bool +} + +type authRequest struct { + Method string `json:"method"` + Credentials Credentials `json:"credentials"` +} + +type authResponse struct { + Token string `json:"token"` +} + +func handleAuthRequest(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Body != nil { + bytes, err := io.ReadAll(r.Body) + if err != nil { + ReturnError(w, r, http.StatusBadRequest, "Can't read message body!") + return + } + + request := &authRequest{} + err = json.Unmarshal(bytes, request) + if err != nil { + ReturnError(w, r, http.StatusBadRequest, "Can't decode message body!") + } + + switch request.Method { + case "PAM": + if request.Credentials.(pam.PAMPasswordCredentials).Authenticate() { + logger.Info("Logged in successfully") + _, err := w.Write(nil) + if err != nil { + return + } + } else { + logger.Info("Login attempt was unsuccessful") + } + } + } +} + +func auth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // TODO: Authenticate based on JWT token provided as bearer + next.ServeHTTP(w, r) + }) +} diff --git a/internal/http_server/http_server.go b/internal/http_server/http_server.go index 38ba42b..79fefba 100644 --- a/internal/http_server/http_server.go +++ b/internal/http_server/http_server.go @@ -19,10 +19,11 @@ func Start() error { logger = logging.GetLogger() - logger.Debug(fmt.Sprintf("Starting HTTP Server on port %d", port)) + logger.Info(fmt.Sprintf("Starting HTTP Server on port %d", port)) mux := http.NewServeMux() mux.HandleFunc("/info", info) + mux.HandleFunc("/auth", handleAuthRequest) mux.HandleFunc("/ws", wsInit) err := http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), mux) if err != nil { From 22f728a5deb0639b0d01454099f35ef3de73a41d Mon Sep 17 00:00:00 2001 From: Uggah Date: Sat, 22 Apr 2023 14:51:41 +0200 Subject: [PATCH 4/9] Implement authentication using jwt access and refresh tokens --- go.mod | 1 + go.sum | 2 + internal/http_server/authentication.go | 186 ++++++++++++++++++++++++- internal/http_server/http_error.go | 1 + internal/http_server/http_server.go | 5 +- internal/http_server/jwt.go | 24 ++++ 6 files changed, 210 insertions(+), 9 deletions(-) create mode 100644 internal/http_server/jwt.go diff --git a/go.mod b/go.mod index d99d88e..c85958d 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect diff --git a/go.sum b/go.sum index 1f6b3dd..9b702e0 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I= github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/internal/http_server/authentication.go b/internal/http_server/authentication.go index d68954a..31c0fbd 100644 --- a/internal/http_server/authentication.go +++ b/internal/http_server/authentication.go @@ -2,9 +2,14 @@ package http_server import ( "encoding/json" + "fmt" "github.com/Excubitor-Monitoring/Excubitor-Backend/internal/pam" + "github.com/golang-jwt/jwt/v5" + "github.com/spf13/viper" "io" "net/http" + "strings" + "time" ) type Credentials interface { @@ -12,20 +17,31 @@ type Credentials interface { } type authRequest struct { - Method string `json:"method"` - Credentials Credentials `json:"credentials"` + Method string `json:"method"` + Credentials map[string]interface{} `json:"credentials"` } type authResponse struct { - Token string `json:"token"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` +} + +type refreshResponse struct { + AccessToken string `json:"access_token"` } func handleAuthRequest(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") + if r.Method != http.MethodPost { + ReturnError(w, r, http.StatusMethodNotAllowed, "Method is not allowed!") + return + } + if r.Body != nil { bytes, err := io.ReadAll(r.Body) if err != nil { + logger.Debug(fmt.Sprintf("Couldn't read message body of auth request from %s", r.RemoteAddr)) ReturnError(w, r, http.StatusBadRequest, "Can't read message body!") return } @@ -33,27 +49,185 @@ func handleAuthRequest(w http.ResponseWriter, r *http.Request) { request := &authRequest{} err = json.Unmarshal(bytes, request) if err != nil { + logger.Debug(fmt.Sprintf("Couldn't decode message body of auth request from %s", r.RemoteAddr)) ReturnError(w, r, http.StatusBadRequest, "Can't decode message body!") + return } switch request.Method { case "PAM": - if request.Credentials.(pam.PAMPasswordCredentials).Authenticate() { + username := request.Credentials["username"].(string) + password := request.Credentials["password"].(string) + + pamCredentials := pam.PAMPasswordCredentials{Username: username, Password: password} + + if pamCredentials.Authenticate() { logger.Info("Logged in successfully") - _, err := w.Write(nil) + + accessTokenClaims := jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": username, + "exp": time.Now().Add(30 * time.Minute).Unix(), + } + + accessToken, err := signAccessToken(accessTokenClaims) + if err != nil { + logger.Error(fmt.Sprintf("Couldn't sign access token for %s! Reason: %s", r.RemoteAddr, err)) + ReturnError(w, r, http.StatusInternalServerError, "Internal Server Error!") + return + } + + refreshTokenClaims := jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": username, + "exp": time.Now().Add(4 * time.Hour).Unix(), + } + + refreshToken, err := signRefreshToken(refreshTokenClaims) + if err != nil { + logger.Error(fmt.Sprintf("Couldn't sign refresh token for %s! Reason: %s", r.RemoteAddr, err)) + ReturnError(w, r, http.StatusInternalServerError, "Internal Server Error!") + return + } + + tokens := &authResponse{ + accessToken, + refreshToken, + } + + jsonResponse, err := json.Marshal(tokens) + if err != nil { + logger.Error(fmt.Sprintf("Couldn't assemble json response for auth request from %s.", r.RemoteAddr)) + ReturnError(w, r, http.StatusInternalServerError, "Internal Server Error!") + return + } + + w.WriteHeader(http.StatusOK) + _, err = w.Write(jsonResponse) if err != nil { return } } else { logger.Info("Login attempt was unsuccessful") + ReturnError(w, r, http.StatusUnauthorized, "Invalid username or password!") + return } + default: + ReturnError(w, r, http.StatusBadRequest, "Unsupported authentication method: "+request.Method) + return + } + } +} + +func handleRefreshRequest(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + authorization := r.Header.Get("Authorization") + + if !strings.HasPrefix(authorization, "Bearer ") { + w.Header().Set("WWW-Authenticate", "Bearer") + ReturnError(w, r, http.StatusUnauthorized, "Bearer authentication is needed!") + return + } + + token := strings.Split(authorization, "Bearer ")[1] + + jwtToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + return []byte(viper.GetString("http.auth.jwt.refreshTokenSecret")), nil + }, jwt.WithValidMethods([]string{"HS256"}), jwt.WithIssuer("excubitor-backend")) + + if err != nil { + switch err { + case jwt.ErrTokenExpired: + logger.Debug(fmt.Sprintf("Attempt to refresh access token with expired token from %s!", r.RemoteAddr)) + ReturnError(w, r, http.StatusUnauthorized, "Token expired!") + return + case jwt.ErrSignatureInvalid: + logger.Warn(fmt.Sprintf("Attempt to authenticate with invalid signature from %s! Token: %s", r.RemoteAddr, token)) + case jwt.ErrTokenInvalidClaims: + logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token type from %s!", r.RemoteAddr)) + default: + logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token from %s! Reason: %s - Token: %s", r.RemoteAddr, err, token)) } + + ReturnError(w, r, http.StatusUnauthorized, "Invalid token!") + return + } + + username, err := jwtToken.Claims.GetSubject() + if err != nil { + logger.Warn(fmt.Sprintf("Couldn't read subject claim of refresh token from %s! Reason: %s", r.RemoteAddr, err)) + } + + accessTokenClaims := jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": username, + "exp": time.Now().Add(30 * time.Minute).Unix(), + } + + accessToken, err := signAccessToken(accessTokenClaims) + if err != nil { + logger.Error(fmt.Sprintf("Couldn't sign access token for %s! Reason: %s", r.RemoteAddr, err)) + ReturnError(w, r, http.StatusInternalServerError, "Internal Server Error!") + return + } + + jsonResponse, err := json.Marshal(refreshResponse{accessToken}) + if err != nil { + logger.Error(fmt.Sprintf("Couldn't encode access token for %s! Reason: %s", r.RequestURI, err)) + ReturnError(w, r, http.StatusInternalServerError, "Internal Server Error!") + return + } + + w.WriteHeader(http.StatusOK) + _, err = w.Write(jsonResponse) + if err != nil { + logger.Error(fmt.Sprintf("Couldn't send access token to %s! Reason: %s", r.RemoteAddr, err)) + return } } func auth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO: Authenticate based on JWT token provided as bearer + authorization := r.Header.Get("Authorization") + + if !strings.HasPrefix(authorization, "Bearer ") { + w.Header().Set("WWW-Authenticate", "Bearer") + ReturnError(w, r, http.StatusUnauthorized, "Bearer authentication is needed!") + return + } + + token := strings.Split(authorization, "Bearer ")[1] + + jwtToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + return []byte(viper.GetString("http.auth.jwt.accessTokenSecret")), nil + }, jwt.WithValidMethods([]string{"HS256"}), jwt.WithIssuer("excubitor-backend")) + + if err != nil { + switch err { + case jwt.ErrTokenExpired: + logger.Debug(fmt.Sprintf("Attempt to authenticate with expired token from %s!", r.RemoteAddr)) + ReturnError(w, r, http.StatusUnauthorized, "Token expired!") + return + case jwt.ErrSignatureInvalid: + logger.Warn(fmt.Sprintf("Attempt to authenticate with invalid signature from %s! Token: %s", r.RemoteAddr, token)) + case jwt.ErrTokenInvalidClaims: + logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token type from %s!", r.RemoteAddr)) + default: + logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token from %s! Reason: %s - Token: %s", r.RemoteAddr, err, token)) + } + + ReturnError(w, r, http.StatusUnauthorized, "Invalid token!") + return + } + + user, err := jwtToken.Claims.GetSubject() + if err != nil { + logger.Warn(fmt.Sprintf("Couldn't read token subject from %s!", user)) + } + + logger.Trace(fmt.Sprintf("User %s authenticated successfully using JWT token!", user)) + next.ServeHTTP(w, r) }) } diff --git a/internal/http_server/http_error.go b/internal/http_server/http_error.go index 08a3412..102e4b4 100644 --- a/internal/http_server/http_error.go +++ b/internal/http_server/http_error.go @@ -22,6 +22,7 @@ func NewHTTPError(message string, path string) Error { func ReturnError(w http.ResponseWriter, r *http.Request, status int, reason string) { w.WriteHeader(status) + w.Header().Set("Content-Type", "application/json") httpError := NewHTTPError(reason, r.RequestURI) diff --git a/internal/http_server/http_server.go b/internal/http_server/http_server.go index 79fefba..45e051a 100644 --- a/internal/http_server/http_server.go +++ b/internal/http_server/http_server.go @@ -24,7 +24,8 @@ func Start() error { mux := http.NewServeMux() mux.HandleFunc("/info", info) mux.HandleFunc("/auth", handleAuthRequest) - mux.HandleFunc("/ws", wsInit) + mux.HandleFunc("/auth/refresh", handleRefreshRequest) + mux.Handle("/ws", auth(http.HandlerFunc(wsInit))) err := http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), mux) if err != nil { return err @@ -64,8 +65,6 @@ func info(w http.ResponseWriter, r *http.Request) { } func wsInit(w http.ResponseWriter, r *http.Request) { - // TODO: Handle authentication here... - conn, _, _, err := ws.UpgradeHTTP(r, w) if err != nil { logger.Error(fmt.Sprintf("Connection from %s couldn't be upgraded: %s", r.RemoteAddr, err)) diff --git a/internal/http_server/jwt.go b/internal/http_server/jwt.go new file mode 100644 index 0000000..fbe23b4 --- /dev/null +++ b/internal/http_server/jwt.go @@ -0,0 +1,24 @@ +package http_server + +import ( + "github.com/golang-jwt/jwt/v5" + "github.com/spf13/viper" +) + +func signAccessToken(claims jwt.MapClaims) (string, error) { + return signToken(claims, []byte(viper.GetString("http.auth.jwt.accessTokenSecret"))) +} + +func signRefreshToken(claims jwt.MapClaims) (string, error) { + return signToken(claims, []byte(viper.GetString("http.auth.jwt.refreshTokenSecret"))) +} + +func signToken(claims jwt.MapClaims, key interface{}) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + signedString, err := token.SignedString(key) + if err != nil { + return "", err + } + + return signedString, nil +} From 402e4d4ff6310925f9f5643ff260f2f7e7719426 Mon Sep 17 00:00:00 2001 From: Uggah Date: Sat, 22 Apr 2023 21:08:12 +0200 Subject: [PATCH 5/9] Add test helper method to parse HTTP errors --- internal/http_server/http_error_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/http_server/http_error_test.go b/internal/http_server/http_error_test.go index de18f3c..8101776 100644 --- a/internal/http_server/http_error_test.go +++ b/internal/http_server/http_error_test.go @@ -1,6 +1,7 @@ package http_server import ( + "encoding/json" "github.com/stretchr/testify/assert" "testing" "time" @@ -13,3 +14,14 @@ func TestNewError(t *testing.T) { assert.Equal(t, "Something.Something", httpError.Path) assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) } + +func parseHTTPError(jsonInput []byte) Error { + output := &Error{} + + err := json.Unmarshal(jsonInput, output) + if err != nil { + panic(err) + } + + return *output +} From 69f6d01ea365c78b9831ee69ba411b9afb2bf505 Mon Sep 17 00:00:00 2001 From: Uggah Date: Sat, 22 Apr 2023 21:09:30 +0200 Subject: [PATCH 6/9] Fix error handling in authentication.go --- internal/http_server/authentication.go | 29 ++++++++++++-------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/internal/http_server/authentication.go b/internal/http_server/authentication.go index 31c0fbd..34f457f 100644 --- a/internal/http_server/authentication.go +++ b/internal/http_server/authentication.go @@ -2,6 +2,7 @@ package http_server import ( "encoding/json" + "errors" "fmt" "github.com/Excubitor-Monitoring/Excubitor-Backend/internal/pam" "github.com/golang-jwt/jwt/v5" @@ -137,17 +138,14 @@ func handleRefreshRequest(w http.ResponseWriter, r *http.Request) { }, jwt.WithValidMethods([]string{"HS256"}), jwt.WithIssuer("excubitor-backend")) if err != nil { - switch err { - case jwt.ErrTokenExpired: + if errors.Is(err, jwt.ErrTokenExpired) { logger.Debug(fmt.Sprintf("Attempt to refresh access token with expired token from %s!", r.RemoteAddr)) ReturnError(w, r, http.StatusUnauthorized, "Token expired!") return - case jwt.ErrSignatureInvalid: - logger.Warn(fmt.Sprintf("Attempt to authenticate with invalid signature from %s! Token: %s", r.RemoteAddr, token)) - case jwt.ErrTokenInvalidClaims: - logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token type from %s!", r.RemoteAddr)) - default: - logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token from %s! Reason: %s - Token: %s", r.RemoteAddr, err, token)) + } else if errors.Is(err, jwt.ErrSignatureInvalid) { + logger.Warn(fmt.Sprintf("Attempt to authenticate with invalid signature from %s!", r.RemoteAddr)) + } else { + logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token from %s! Reason: %s", r.RemoteAddr, err)) } ReturnError(w, r, http.StatusUnauthorized, "Invalid token!") @@ -157,6 +155,8 @@ func handleRefreshRequest(w http.ResponseWriter, r *http.Request) { username, err := jwtToken.Claims.GetSubject() if err != nil { logger.Warn(fmt.Sprintf("Couldn't read subject claim of refresh token from %s! Reason: %s", r.RemoteAddr, err)) + ReturnError(w, r, http.StatusBadRequest, "Token has no subject!") + return } accessTokenClaims := jwt.MapClaims{ @@ -204,17 +204,14 @@ func auth(next http.Handler) http.Handler { }, jwt.WithValidMethods([]string{"HS256"}), jwt.WithIssuer("excubitor-backend")) if err != nil { - switch err { - case jwt.ErrTokenExpired: + if errors.Is(err, jwt.ErrTokenExpired) { logger.Debug(fmt.Sprintf("Attempt to authenticate with expired token from %s!", r.RemoteAddr)) ReturnError(w, r, http.StatusUnauthorized, "Token expired!") return - case jwt.ErrSignatureInvalid: - logger.Warn(fmt.Sprintf("Attempt to authenticate with invalid signature from %s! Token: %s", r.RemoteAddr, token)) - case jwt.ErrTokenInvalidClaims: - logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token type from %s!", r.RemoteAddr)) - default: - logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token from %s! Reason: %s - Token: %s", r.RemoteAddr, err, token)) + } else if errors.Is(err, jwt.ErrSignatureInvalid) { + logger.Warn(fmt.Sprintf("Attempt to authenticate with invalid signature from %s!", r.RemoteAddr)) + } else { + logger.Debug(fmt.Sprintf("Attempt to authenticate with invalid token from %s! Reason: %s", r.RemoteAddr, err)) } ReturnError(w, r, http.StatusUnauthorized, "Invalid token!") From 284b7ab09f9c7945f372d588c474a270a9a69e3b Mon Sep 17 00:00:00 2001 From: Uggah Date: Sat, 22 Apr 2023 21:09:45 +0200 Subject: [PATCH 7/9] Remove unnecessary logging --- internal/http_server/authentication.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/http_server/authentication.go b/internal/http_server/authentication.go index 34f457f..e1d6c27 100644 --- a/internal/http_server/authentication.go +++ b/internal/http_server/authentication.go @@ -63,8 +63,6 @@ func handleAuthRequest(w http.ResponseWriter, r *http.Request) { pamCredentials := pam.PAMPasswordCredentials{Username: username, Password: password} if pamCredentials.Authenticate() { - logger.Info("Logged in successfully") - accessTokenClaims := jwt.MapClaims{ "iss": "excubitor-backend", "sub": username, @@ -109,7 +107,6 @@ func handleAuthRequest(w http.ResponseWriter, r *http.Request) { return } } else { - logger.Info("Login attempt was unsuccessful") ReturnError(w, r, http.StatusUnauthorized, "Invalid username or password!") return } From 3f257be4be92a885eefeedd249ab7aa310d8a8a5 Mon Sep 17 00:00:00 2001 From: Uggah Date: Sat, 22 Apr 2023 21:10:02 +0200 Subject: [PATCH 8/9] Check for method on refresh request --- internal/http_server/authentication.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/http_server/authentication.go b/internal/http_server/authentication.go index e1d6c27..0a68085 100644 --- a/internal/http_server/authentication.go +++ b/internal/http_server/authentication.go @@ -120,6 +120,11 @@ func handleAuthRequest(w http.ResponseWriter, r *http.Request) { func handleRefreshRequest(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") + if r.Method != http.MethodPost { + ReturnError(w, r, http.StatusMethodNotAllowed, "Method is not allowed!") + return + } + authorization := r.Header.Get("Authorization") if !strings.HasPrefix(authorization, "Bearer ") { From 9e76eb68f194c754fa1811b47e2806cf25700ced Mon Sep 17 00:00:00 2001 From: Uggah Date: Sat, 22 Apr 2023 21:10:10 +0200 Subject: [PATCH 9/9] Add tests for authentication --- internal/http_server/authentication_test.go | 685 ++++++++++++++++++++ 1 file changed, 685 insertions(+) create mode 100644 internal/http_server/authentication_test.go diff --git a/internal/http_server/authentication_test.go b/internal/http_server/authentication_test.go new file mode 100644 index 0000000..3aa9141 --- /dev/null +++ b/internal/http_server/authentication_test.go @@ -0,0 +1,685 @@ +package http_server + +import ( + "encoding/json" + "github.com/Excubitor-Monitoring/Excubitor-Backend/internal/logging" + "github.com/golang-jwt/jwt/v5" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "io" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" +) + +func TestMain(m *testing.M) { + err := logging.SetDefaultLogger("CONSOLE") + if err != nil { + panic(err) + } + code := m.Run() + os.Exit(code) +} + +func TestHandleAuthRequestInvalidMethod(t *testing.T) { + + methods := []string{http.MethodGet, http.MethodOptions, http.MethodDelete, + http.MethodPatch, http.MethodHead, http.MethodConnect, http.MethodTrace} + + for _, m := range methods { + req := httptest.NewRequest(m, "/auth", nil) + w := httptest.NewRecorder() + + handleAuthRequest(w, req) + + res := w.Result() + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + assert.Equal(t, http.StatusMethodNotAllowed, res.StatusCode) + + httpError := parseHTTPError(body) + + assert.Equal(t, "Method is not allowed!", httpError.Message) + assert.Equal(t, "/auth", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) + + err = res.Body.Close() + if err != nil { + return + } + } +} + +func TestHandleAuthRequestUndecipherableBody(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + } + + req := httptest.NewRequest(http.MethodPost, "/auth", strings.NewReader("")) + req.RemoteAddr = "SampleAddress" + w := httptest.NewRecorder() + + handleAuthRequest(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + assert.Equal(t, "Can't decode message body!", httpError.Message) + assert.Equal(t, "/auth", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestHandleAuthRequestUnknownMethod(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + } + + req := httptest.NewRequest(http.MethodPost, "/auth", + strings.NewReader(`{ "method": "UnknownMethod", "credentials": { "something": "No credentials needed..." } }`)) + req.RemoteAddr = "SampleAddress" + w := httptest.NewRecorder() + + handleAuthRequest(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + assert.Equal(t, "Unsupported authentication method: UnknownMethod", httpError.Message) + assert.Equal(t, "/auth", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestHandleAuthRequestPAMInvalidCredentials(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + } + + req := httptest.NewRequest(http.MethodPost, "/auth", + strings.NewReader(`{ "method": "PAM", "credentials": { "username": "testuser", "password": "123456" } }`)) + req.RemoteAddr = "SampleAddress" + w := httptest.NewRecorder() + + handleAuthRequest(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Invalid username or password!", httpError.Message) + assert.Equal(t, "/auth", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestHandleRefreshRequestInvalidMethod(t *testing.T) { + methods := []string{http.MethodGet, http.MethodOptions, http.MethodDelete, + http.MethodPatch, http.MethodHead, http.MethodConnect, http.MethodTrace} + + for _, m := range methods { + req := httptest.NewRequest(m, "/auth/refresh", nil) + w := httptest.NewRecorder() + + handleRefreshRequest(w, req) + + res := w.Result() + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + assert.Equal(t, http.StatusMethodNotAllowed, res.StatusCode) + + httpError := parseHTTPError(body) + + assert.Equal(t, "Method is not allowed!", httpError.Message) + assert.Equal(t, "/auth/refresh", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) + + err = res.Body.Close() + if err != nil { + return + } + } +} + +func TestHandleRefreshRequestNoHeader(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + } + + req := httptest.NewRequest(http.MethodPost, "/auth/refresh", nil) + req.RemoteAddr = "SampleAddress" + w := httptest.NewRecorder() + + handleRefreshRequest(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Bearer authentication is needed!", httpError.Message) + assert.Equal(t, "Bearer", res.Header.Get("WWW-Authenticate")) + assert.Equal(t, "/auth/refresh", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestHandleRefreshRequestInvalidHeader(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + } + + req := httptest.NewRequest(http.MethodPost, "/auth/refresh", nil) + req.RemoteAddr = "SampleAddress" + req.Header.Set("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=") + w := httptest.NewRecorder() + + handleRefreshRequest(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Bearer authentication is needed!", httpError.Message) + assert.Equal(t, "Bearer", res.Header.Get("WWW-Authenticate")) + assert.Equal(t, "/auth/refresh", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestHandleRefreshRequestInvalidTokenExpired(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + return + } + + viper.SetDefault("http.auth.jwt.accessTokenSecret", "123456") + viper.SetDefault("http.auth.jwt.refreshTokenSecret", "abcdef") + + token, err := signRefreshToken(jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": "testuser", + "exp": time.Now().Add(-4 * time.Hour).Unix(), + }) + if err != nil { + t.Error(err) + return + } + + req := httptest.NewRequest(http.MethodPost, "/auth/refresh", nil) + req.RemoteAddr = "SampleAddress" + req.Header.Set("Authorization", "Bearer "+token) + w := httptest.NewRecorder() + + handleRefreshRequest(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Token expired!", httpError.Message) + assert.Equal(t, "/auth/refresh", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestHandleRefreshRequestInvalidTokenInvalidSignature(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + return + } + + viper.SetDefault("http.auth.jwt.accessTokenSecret", "123456") + viper.SetDefault("http.auth.jwt.refreshTokenSecret", "abcdef") + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": "testuser", + "exp": time.Now().Add(-4 * time.Hour).Unix(), + }) + signedToken, err := token.SignedString([]byte("someOtherKey")) + if err != nil { + t.Error(err) + return + } + + req := httptest.NewRequest(http.MethodPost, "/auth/refresh", nil) + req.RemoteAddr = "SampleAddress" + req.Header.Set("Authorization", "Bearer "+signedToken) + w := httptest.NewRecorder() + + handleRefreshRequest(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Invalid token!", httpError.Message) + assert.Equal(t, "/auth/refresh", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestHandleRefreshRequest(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + return + } + + viper.SetDefault("http.auth.jwt.accessTokenSecret", "123456") + viper.SetDefault("http.auth.jwt.refreshTokenSecret", "abcdef") + + token, err := signRefreshToken(jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": "testuser", + "exp": time.Now().Add(4 * time.Hour).Unix(), + }) + if err != nil { + t.Error(err) + return + } + + req := httptest.NewRequest(http.MethodPost, "/auth/refresh", nil) + req.RemoteAddr = "SampleAddress" + req.Header.Set("Authorization", "Bearer "+token) + w := httptest.NewRecorder() + + handleRefreshRequest(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + responseObject := &refreshResponse{} + + err = json.Unmarshal(body, responseObject) + if err != nil { + t.Error(err) + return + } + + parsedToken, err := jwt.Parse(responseObject.AccessToken, func(token *jwt.Token) (interface{}, error) { + return []byte(viper.GetString("http.auth.jwt.accessTokenSecret")), nil + }) + if err != nil { + t.Error(err) + return + } + + subject, err := parsedToken.Claims.GetSubject() + if err != nil { + t.Error(err) + return + } + + issuer, err := parsedToken.Claims.GetIssuer() + if err != nil { + t.Error(err) + return + } + + assert.Equal(t, http.StatusOK, res.StatusCode) + assert.Equal(t, subject, "testuser") + assert.Equal(t, issuer, "excubitor-backend") +} + +func TestAuthNoHeader(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + } + + req := httptest.NewRequest(http.MethodPost, "/someEndpoint", nil) + req.RemoteAddr = "SampleAddress" + w := httptest.NewRecorder() + + handler := auth(nil) + handler.ServeHTTP(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Bearer authentication is needed!", httpError.Message) + assert.Equal(t, "Bearer", res.Header.Get("WWW-Authenticate")) + assert.Equal(t, "/someEndpoint", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestAuthInvalidHeader(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + } + + req := httptest.NewRequest(http.MethodPost, "/someEndpoint", nil) + req.RemoteAddr = "SampleAddress" + req.Header.Set("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=") + w := httptest.NewRecorder() + + handler := auth(nil) + handler.ServeHTTP(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Bearer authentication is needed!", httpError.Message) + assert.Equal(t, "Bearer", res.Header.Get("WWW-Authenticate")) + assert.Equal(t, "/someEndpoint", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestAuthTokenExpired(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + return + } + + viper.SetDefault("http.auth.jwt.accessTokenSecret", "123456") + viper.SetDefault("http.auth.jwt.refreshTokenSecret", "abcdef") + + token, err := signAccessToken(jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": "testuser", + "exp": time.Now().Add(-30 * time.Minute).Unix(), + }) + if err != nil { + t.Error(err) + return + } + + req := httptest.NewRequest(http.MethodPost, "/someEndpoint", nil) + req.RemoteAddr = "SampleAddress" + req.Header.Set("Authorization", "Bearer "+token) + w := httptest.NewRecorder() + + handler := auth(nil) + handler.ServeHTTP(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Token expired!", httpError.Message) + assert.Equal(t, "/someEndpoint", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestAuthInvalidSignature(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + return + } + + viper.SetDefault("http.auth.jwt.accessTokenSecret", "123456") + viper.SetDefault("http.auth.jwt.refreshTokenSecret", "abcdef") + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": "testuser", + "exp": time.Now().Add(-4 * time.Hour).Unix(), + }) + signedToken, err := token.SignedString([]byte("someOtherKey")) + if err != nil { + t.Error(err) + return + } + + req := httptest.NewRequest(http.MethodPost, "/someEndpoint", nil) + req.RemoteAddr = "SampleAddress" + req.Header.Set("Authorization", "Bearer "+signedToken) + w := httptest.NewRecorder() + + handler := auth(nil) + handler.ServeHTTP(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + return + } + + httpError := parseHTTPError(body) + + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + assert.Equal(t, "Invalid token!", httpError.Message) + assert.Equal(t, "/someEndpoint", httpError.Path) + assert.True(t, time.Since(httpError.Timestamp) < time.Since(time.Now().Add(-time.Second)) && time.Until(httpError.Timestamp) < 0) +} + +func TestAuth(t *testing.T) { + var err error + + logger, err = logging.GetConsoleLoggerInstance() + if err != nil { + t.Error(err) + return + } + + viper.SetDefault("http.auth.jwt.accessTokenSecret", "123456") + viper.SetDefault("http.auth.jwt.refreshTokenSecret", "abcdef") + + token, err := signAccessToken(jwt.MapClaims{ + "iss": "excubitor-backend", + "sub": "testuser", + "exp": time.Now().Add(30 * time.Minute).Unix(), + }) + if err != nil { + t.Error(err) + return + } + + req := httptest.NewRequest(http.MethodPost, "/auth/refresh", nil) + req.RemoteAddr = "SampleAddress" + req.Header.Set("Authorization", "Bearer "+token) + w := httptest.NewRecorder() + + handler := auth(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + _, err := writer.Write([]byte{}) + if err != nil { + t.Error(err) + return + } + })) + handler.ServeHTTP(w, req) + + res := w.Result() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + t.Error(err) + } + }(res.Body) + + assert.Equal(t, http.StatusOK, res.StatusCode) +}