diff --git a/pkg/api/README.md b/pkg/api/README.md index 729c649..e7633c0 100644 --- a/pkg/api/README.md +++ b/pkg/api/README.md @@ -1,23 +1,3 @@ -# Working API Calls +# API Documentation -```go -// User Authentication API - pkg/api/user_auth.go -/* -JSON Data Required: - { - "user": "test", - "pass": "test", - } -*/ -func UserAuthentication(res http.ResponseWriter, req *http.Request) bool - -// User Registration API - pkg/api/user_auth.go -/* -JSON Data Required: - { - "user": "test", - "pass": "test", - } -*/ -func UserRegistration(res http.ResponseWriter, req *http.Request) bool -``` +Navigate inside of the folders of this package to see specific documentation on the various available APIs. diff --git a/pkg/api/api_handler.go b/pkg/api/api_handler.go index aed1a24..99ea2fc 100644 --- a/pkg/api/api_handler.go +++ b/pkg/api/api_handler.go @@ -16,11 +16,11 @@ func HandleAPI(path string, res http.ResponseWriter, req *http.Request) (bool, b suspectApi := strings.ToLower(splitUrl[len(splitUrl)-1]) switch suspectApi { - case "user_auth": + case "api/user/auth": return true, user.Auth(res, req) - case "user_register": + case "api/user/register": return true, user.Register(res, req) - case "user_logout": + case "api/user/logout": return true, user.Logout(res, req) default: return false, false diff --git a/pkg/api/user/README.md b/pkg/api/user/README.md new file mode 100644 index 0000000..a1f74ef --- /dev/null +++ b/pkg/api/user/README.md @@ -0,0 +1,39 @@ +# User API Documentation + +```go +/* +Relative API Path: + api/user/auth +Request: + { + "user": "test", + "pass": "test", + } +Response(204, 400, 401, 405, 500): + N/A +*/ +func UserAuthentication(res http.ResponseWriter, req *http.Request) bool + +/* +Relative API Path: + api/user/register +Request: + { + "user": "test", + "pass": "test", + } +Response (204, 400, 405, 500): + N/A +*/ +func UserRegistration(res http.ResponseWriter, req *http.Request) bool + +/* +Relative API Path: + api/user/logout +Request: + N/A +Response (204, 405, 500): + N/A +*/ +func UserLogout(res http.ResponseWriter, req *http.Request) bool +``` diff --git a/pkg/api/user/auth.go b/pkg/api/user/auth.go index 4eedb23..36b51f1 100644 --- a/pkg/api/user/auth.go +++ b/pkg/api/user/auth.go @@ -47,11 +47,7 @@ func Auth(res http.ResponseWriter, req *http.Request) bool { return false } - secret, err := encryption.RandomString() - if err != nil { - http.Error(res, err.Error(), http.StatusInternalServerError) - return false - } + secret := encryption.RandomString(16) userDatabaseData.Secret = secret err = ds.Put(database.BUCKET_USERS, []byte(userRequestData.User), userDatabaseData) diff --git a/pkg/encryption/random_string.go b/pkg/encryption/random_string.go index 199bcfc..cbe1de4 100644 --- a/pkg/encryption/random_string.go +++ b/pkg/encryption/random_string.go @@ -2,18 +2,22 @@ package encryption import ( - "crypto/rand" - "fmt" + "math/rand" + "time" ) -func RandomString() (string, error) { - n := 5 - b := make([]byte, n) +const charset = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + "1234567890!@#$%^&*()" - if _, err := rand.Read(b); err != nil { - return "", err +var seed *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) + +// RandomString function takes an integer length value in and returns a +// random string of that size built from the charset constant. +func RandomString(length int) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[seed.Intn(len(charset))] } - - s := fmt.Sprintf("%X", b) - return s, nil + return string(b) } diff --git a/pkg/encryption/random_string_test.go b/pkg/encryption/random_string_test.go new file mode 100644 index 0000000..dcd0557 --- /dev/null +++ b/pkg/encryption/random_string_test.go @@ -0,0 +1,30 @@ +// Encryption package has functions inside of it that utilize various encypting and hashing techniques +package encryption + +import "testing" + +func TestRandomString(t *testing.T) { + testData := []struct { + length int + output string + }{ + {16, ""}, + {16, ""}, + {16, ""}, + {16, ""}, + } + + for i := 0; i < len(testData); i++ { + testData[i].output = RandomString(testData[i].length) + } + + for i := 0; i < len(testData)-1; i++ { + compare := testData[i].output + + for ii := i + 1; ii < len(testData); ii++ { + if compare == testData[ii].output { + t.Errorf("Random string generator generated two strings with the same value. (%s - %s)", compare, testData[ii].output) + } + } + } +} diff --git a/pkg/public/public.go b/pkg/public/public.go index 35e6831..685e4e1 100644 --- a/pkg/public/public.go +++ b/pkg/public/public.go @@ -25,10 +25,10 @@ func NewPublicWeb() PublicWeb { // ServeHTTP function routes all requests for the public web server. It is used in the main // function inside of the http.ListenAndServe() function for the public host. -func (pub *PublicWeb) ServeHTTP(w http.ResponseWriter, req *http.Request) { +func (pub *PublicWeb) ServeHTTP(res http.ResponseWriter, req *http.Request) { if pub.MaintenanceMode == 1 { - w.WriteHeader(http.StatusServiceUnavailable) - w.Write([]byte("We are currently in maintenance mode, please check back later.")) + res.WriteHeader(http.StatusServiceUnavailable) + res.Write([]byte("We are currently in maintenance mode, please check back later.")) } path := req.URL.Path[1:] @@ -41,7 +41,7 @@ func (pub *PublicWeb) ServeHTTP(w http.ResponseWriter, req *http.Request) { f, err := os.Open(path) if err != nil { - routing.HttpThrowStatus(http.StatusNotFound, w) + routing.HttpThrowStatus(http.StatusNotFound, res) logging.Console(logging.PUBLIC_PREFIX, logging.NORMAL_LOG, "Path \""+path+"\" rendered a 404 error.") return } @@ -49,16 +49,16 @@ func (pub *PublicWeb) ServeHTTP(w http.ResponseWriter, req *http.Request) { contentType, err := routing.GetContentType(path) if err != nil { - routing.HttpThrowStatus(http.StatusUnsupportedMediaType, w) + routing.HttpThrowStatus(http.StatusUnsupportedMediaType, res) logging.Console(logging.PUBLIC_PREFIX, logging.NORMAL_LOG, "Path \""+path+"\" content type could not be determined, 404 error.") return } - w.Header().Add("Content-Type", contentType) - _, err = io.Copy(w, f) + res.Header().Add("Content-Type", contentType) + _, err = io.Copy(res, f) if err != nil { - routing.HttpThrowStatus(http.StatusInternalServerError, w) + routing.HttpThrowStatus(http.StatusInternalServerError, res) logging.Console(logging.PUBLIC_PREFIX, logging.NORMAL_LOG, "Path \""+path+"\" rendered a 500 error.") return } diff --git a/pkg/webhost/authentication.go b/pkg/webhost/authentication.go new file mode 100644 index 0000000..f126339 --- /dev/null +++ b/pkg/webhost/authentication.go @@ -0,0 +1,88 @@ +// Package webhost handles the logic of the webhosting panel +package webhost + +import ( + "net/http" + "strings" + + "github.com/Ennovar/gPanel/pkg/api/user" + "github.com/Ennovar/gPanel/pkg/networking" + jwt "github.com/dgrijalva/jwt-go" +) + +// reqAuth function checks to see if the given path requires authentication. +func reqAuth(path string) bool { + path = strings.ToLower(path) + + dismissibleTypes := []string{".css", ".js"} + for _, t := range dismissibleTypes { + if strings.HasSuffix(path, t) { + return false + } + } + + dismissibleFiles := []string{ + "index.html", + "api/user/auth", + "api/user/register", + "api/user/logout", + } + for _, f := range dismissibleFiles { + if strings.HasSuffix(path, f) { + return false + } + } + + return true +} + +// checkAuth function returns a boolean based on whether or not the current +// caller is authenticated based off of encrypted sessions using JWT values. +func checkAuth(res http.ResponseWriter, req *http.Request) bool { + store := networking.GetStore(networking.COOKIES_USER_AUTH) + + session_value, err := store.Read(res, req, "user") + if err != nil || session_value == nil { + return false + } + + username, ok := session_value.(string) + if !ok { + return false + } + + stored_secret, err := user.GetSecret(username) + if stored_secret == "" { + return false + } + + session_value, err = store.Read(res, req, "token") + if err != nil || session_value == nil { + return false + } + + tokenString, ok := session_value.(string) + if !ok { + return false + } + + keyfunc := func(t *jwt.Token) (interface{}, error) { + return []byte(stored_secret), nil + } + + p := jwt.Parser{ + ValidMethods: []string{"HS256", "HS384", "HS512"}, + } + t, err := p.ParseWithClaims(tokenString, &jwt.StandardClaims{}, keyfunc) + + if err != nil { + return false + } + + claims := t.Claims.(*jwt.StandardClaims) + if claims.Subject != username { + return false + } + + return true +} diff --git a/pkg/webhost/webhost.go b/pkg/webhost/webhost.go index 9117bf6..a62c872 100644 --- a/pkg/webhost/webhost.go +++ b/pkg/webhost/webhost.go @@ -5,14 +5,10 @@ import ( "io" "net/http" "os" - "strings" "github.com/Ennovar/gPanel/pkg/api" - "github.com/Ennovar/gPanel/pkg/api/user" "github.com/Ennovar/gPanel/pkg/logging" - "github.com/Ennovar/gPanel/pkg/networking" "github.com/Ennovar/gPanel/pkg/routing" - jwt "github.com/dgrijalva/jwt-go" ) type PrivateHost struct { @@ -26,35 +22,9 @@ func NewPrivateHost() PrivateHost { } } -// reqAuth function checks to see if the given path requires authentication. -func reqAuth(path string) bool { - path = strings.ToLower(path) - - dismissibleTypes := []string{".css", ".js"} - for _, t := range dismissibleTypes { - if strings.HasSuffix(path, t) { - return false - } - } - - dismissibleFiles := []string{ - "index.html", - "user_auth", - "user_register", - "user_logout", - } - for _, f := range dismissibleFiles { - if strings.HasSuffix(path, f) { - return false - } - } - - return true -} - // ServeHTTP function routes all requests for the private webhost server. It is used in the main // function inside of the http.ListenAndServe() function for the private webhost host. -func (priv *PrivateHost) ServeHTTP(w http.ResponseWriter, req *http.Request) { +func (priv *PrivateHost) ServeHTTP(res http.ResponseWriter, req *http.Request) { path := req.URL.Path[1:] if len(path) == 0 { path = (priv.Directory + "index.html") @@ -63,70 +33,13 @@ func (priv *PrivateHost) ServeHTTP(w http.ResponseWriter, req *http.Request) { } if reqAuth(path) { - store := networking.GetStore(networking.COOKIES_USER_AUTH) - - session_value, err := store.Read(w, req, "user") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if session_value == nil { - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - username, ok := session_value.(string) - if !ok { - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - stored_secret, err := user.GetSecret(username) - if stored_secret == "" { - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - session_value, err = store.Read(w, req, "token") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if session_value == nil { - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - tokenString, ok := session_value.(string) - if !ok { - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - keyfunc := func(t *jwt.Token) (interface{}, error) { - return []byte(stored_secret), nil - } - - p := jwt.Parser{ - ValidMethods: []string{"HS256", "HS384", "HS512"}, - } - t, err := p.ParseWithClaims(tokenString, &jwt.StandardClaims{}, keyfunc) - - if err != nil { - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - claims := t.Claims.(*jwt.StandardClaims) - if claims.Subject != username { - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + if !checkAuth(res, req) { + http.Error(res, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } } - isApi, _ := api.HandleAPI(path, w, req) + isApi, _ := api.HandleAPI(path, res, req) if isApi { // API methods handle HTTP logic from here @@ -136,7 +49,7 @@ func (priv *PrivateHost) ServeHTTP(w http.ResponseWriter, req *http.Request) { f, err := os.Open(path) if err != nil { - routing.HttpThrowStatus(http.StatusNotFound, w) + routing.HttpThrowStatus(http.StatusNotFound, res) logging.Console(logging.PRIVATE_PREFIX, logging.NORMAL_LOG, "Path \""+path+"\" rendered a 404 error.") return } @@ -144,16 +57,16 @@ func (priv *PrivateHost) ServeHTTP(w http.ResponseWriter, req *http.Request) { contentType, err := routing.GetContentType(path) if err != nil { - routing.HttpThrowStatus(http.StatusUnsupportedMediaType, w) + routing.HttpThrowStatus(http.StatusUnsupportedMediaType, res) logging.Console(logging.PUBLIC_PREFIX, logging.NORMAL_LOG, "Path \""+path+"\" content type could not be determined, 404 error.") return } - w.Header().Add("Content-Type", contentType) - _, err = io.Copy(w, f) + res.Header().Add("Content-Type", contentType) + _, err = io.Copy(res, f) if err != nil { - routing.HttpThrowStatus(http.StatusInternalServerError, w) + routing.HttpThrowStatus(http.StatusInternalServerError, res) logging.Console(logging.PUBLIC_PREFIX, logging.NORMAL_LOG, "Path \""+path+"\" rendered a 500 error.") return }