diff --git a/document_roots/webhost/api_testing.html b/document_roots/webhost/api_testing.html
deleted file mode 100644
index 6058327..0000000
--- a/document_roots/webhost/api_testing.html
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
- Private Test
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/document_roots/webhost/gPanel.html b/document_roots/webhost/gPanel.html
index 599f395..7b58171 100644
--- a/document_roots/webhost/gPanel.html
+++ b/document_roots/webhost/gPanel.html
@@ -39,6 +39,9 @@
+
diff --git a/pkg/api/api_handler.go b/pkg/api/api_handler.go
index 11a48d6..920e836 100644
--- a/pkg/api/api_handler.go
+++ b/pkg/api/api_handler.go
@@ -4,6 +4,8 @@ package api
import (
"net/http"
"strings"
+
+ "github.com/Ennovar/gPanel/pkg/api/user"
)
// HandleAPI function takes a path and determines if it is an API call, if it is it will
@@ -15,11 +17,11 @@ func HandleAPI(path string, res http.ResponseWriter, req *http.Request) (bool, b
switch suspectApi {
case "user_auth":
- return true, UserAuthentication(res, req)
+ return true, user.UserAuthentication(res, req)
case "user_register":
- return true, UserRegistration(res, req)
+ return true, user.UserRegistration(res, req)
case "user_logout":
- return true, UserLogout(res, req)
+ return true, user.UserLogout(res, req)
default:
return false, false
}
diff --git a/pkg/api/user.go b/pkg/api/user.go
deleted file mode 100644
index 3435966..0000000
--- a/pkg/api/user.go
+++ /dev/null
@@ -1,139 +0,0 @@
-// Package api handles all API calls
-package api
-
-import (
- "encoding/json"
- "net/http"
-
- "github.com/Ennovar/gPanel/pkg/database"
- "github.com/Ennovar/gPanel/pkg/encryption"
- "github.com/Ennovar/gPanel/pkg/networking"
-)
-
-// userRequestData struct is the structure of the JSON data to be
-// retrieved from the authentication API request
-var userRequestData struct {
- User string `json:"user"`
- Pass string `json:"pass"`
-}
-
-// userDatabaseData struct is the structure of the JSON data to be retrieved from
-// the bolt database inside of the user bucket, using the username as the key.
-var userDatabaseData struct {
- Pass string `json:"pass"`
-}
-
-// UserAuthentication function is accessed by an API call from the webhost root
-// by accessing /user_auth and sending it a post request with userRequestData
-// struct in JSON format.
-func UserAuthentication(res http.ResponseWriter, req *http.Request) bool {
- if req.Method != "POST" {
- http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
- return false
- }
-
- err := json.NewDecoder(req.Body).Decode(&userRequestData)
- if err != nil {
- http.Error(res, err.Error(), http.StatusBadRequest)
- return false
- }
-
- ds, err := database.Open(database.DBLOC_MAIN)
- if err != nil || ds == nil {
- http.Error(res, err.Error(), http.StatusBadRequest)
- return false
- }
- defer ds.Close()
-
- err = ds.Get(database.BUCKET_USERS, []byte(userRequestData.User), &userDatabaseData)
-
- if err == database.ErrKeyNotExist {
- http.Error(res, "User does not exist.", http.StatusUnauthorized)
- return false
- }
-
- err = encryption.CheckPassword([]byte(userDatabaseData.Pass), []byte(userRequestData.Pass))
- if err != nil {
- http.Error(res, err.Error(), http.StatusUnauthorized)
- return false
- }
-
- store := networking.GetStore(networking.COOKIES_USER_AUTH)
- err = store.Set(res, req, "auth", true, (60 * 60 * 24))
-
- if err != nil {
- http.Error(res, http.StatusText(500), http.StatusInternalServerError)
- return false
- }
-
- res.WriteHeader(http.StatusNoContent)
- return true
-}
-
-// UserRegistration function is accessed by an API call from the webhost root
-// by accessing /user_register and sending it a post request with userRequestData
-// struct in JSON format.
-func UserRegistration(res http.ResponseWriter, req *http.Request) bool {
- if req.Method != "POST" {
- http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
- return false
- }
-
- err := json.NewDecoder(req.Body).Decode(&userRequestData)
- if err != nil {
- http.Error(res, err.Error(), http.StatusBadRequest)
- return false
- } else if len(userRequestData.User) == 0 || len(userRequestData.Pass) == 0 {
- http.Error(res, "Username or password field cannot be blank", http.StatusBadRequest)
- return false
- }
-
- ds, err := database.Open(database.DBLOC_MAIN)
- if err != nil || ds == nil {
- http.Error(res, err.Error(), http.StatusBadRequest)
- return false
- }
- defer ds.Close()
-
- err = ds.Get(database.BUCKET_USERS, []byte(userRequestData.User), &userDatabaseData)
- if err != database.ErrKeyNotExist {
- http.Error(res, "Username already exists in the database", http.StatusBadRequest)
- return false
- }
-
- userDatabaseData.Pass, err = encryption.HashPassword(userRequestData.Pass)
- if err != nil {
- http.Error(res, err.Error(), http.StatusBadRequest)
- return false
- }
-
- err = ds.Put(database.BUCKET_USERS, []byte(userRequestData.User), userDatabaseData)
- if err != nil {
- http.Error(res, err.Error(), http.StatusBadRequest)
- return false
- }
-
- res.WriteHeader(http.StatusNoContent)
- return true
-}
-
-// UserRegistration function is accessed by an API call from the webhost root
-// by accessing /user_logout and sending it an empty POST request. This function will
-// delete the user-auth cookie session store
-func UserLogout(res http.ResponseWriter, req *http.Request) bool {
- if req.Method != "POST" {
- http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
- return false
- }
-
- store := networking.GetStore(networking.COOKIES_USER_AUTH)
- err := store.Delete(res, req)
-
- if err != nil {
- http.Error(res, http.StatusText(500), http.StatusInternalServerError)
- return false
- }
-
- res.WriteHeader(http.StatusNoContent)
- return true
-}
diff --git a/pkg/api/user/user.go b/pkg/api/user/user.go
new file mode 100644
index 0000000..2e48fd2
--- /dev/null
+++ b/pkg/api/user/user.go
@@ -0,0 +1,16 @@
+// Package user is a child of package api to handle api calls concerning users
+package user
+
+// userRequestData struct is the structure of the JSON data to be
+// retrieved from the authentication API request
+var userRequestData struct {
+ User string `json:"user"`
+ Pass string `json:"pass"`
+}
+
+// userDatabaseData struct is the structure of the JSON data to be retrieved from
+// the bolt database inside of the user bucket, using the username as the key.
+var userDatabaseData struct {
+ Pass string `json:"pass"`
+ Secret string `json:"secret"`
+}
diff --git a/pkg/api/user/user_auth.go b/pkg/api/user/user_auth.go
new file mode 100644
index 0000000..328811f
--- /dev/null
+++ b/pkg/api/user/user_auth.go
@@ -0,0 +1,87 @@
+// Package user is a child of package api to handle api calls concerning users
+package user
+
+import (
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "github.com/Ennovar/gPanel/pkg/database"
+ "github.com/Ennovar/gPanel/pkg/encryption"
+ "github.com/Ennovar/gPanel/pkg/networking"
+ jwt "github.com/dgrijalva/jwt-go"
+)
+
+// UserAuthentication function is accessed by an API call from the webhost root
+// by accessing /user_auth and sending it a post request with userRequestData
+// struct in JSON format.
+func UserAuthentication(res http.ResponseWriter, req *http.Request) bool {
+ if req.Method != "POST" {
+ http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
+ return false
+ }
+
+ err := json.NewDecoder(req.Body).Decode(&userRequestData)
+ if err != nil {
+ http.Error(res, err.Error(), http.StatusBadRequest)
+ return false
+ }
+
+ ds, err := database.Open(database.DBLOC_MAIN)
+ if err != nil || ds == nil {
+ http.Error(res, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+ defer ds.Close()
+
+ err = ds.Get(database.BUCKET_USERS, []byte(userRequestData.User), &userDatabaseData)
+
+ if err == database.ErrKeyNotExist {
+ http.Error(res, "User does not exist.", http.StatusUnauthorized)
+ return false
+ }
+
+ err = encryption.CheckPassword([]byte(userDatabaseData.Pass), []byte(userRequestData.Pass))
+ if err != nil {
+ http.Error(res, err.Error(), http.StatusUnauthorized)
+ return false
+ }
+
+ secret, err := encryption.RandomString()
+ if err != nil {
+ http.Error(res, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+ userDatabaseData.Secret = secret
+
+ err = ds.Put(database.BUCKET_USERS, []byte(userRequestData.User), userDatabaseData)
+ if err != nil {
+ http.Error(res, err.Error(), http.StatusBadRequest)
+ return false
+ }
+
+ now := time.Now().UTC()
+ claims := jwt.StandardClaims{
+ Subject: userRequestData.User,
+ NotBefore: now.Unix(),
+ IssuedAt: now.Unix(),
+ ExpiresAt: now.Add(24 * time.Hour).Unix(),
+ }
+
+ token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
+ if err != nil {
+ http.Error(res, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+
+ store := networking.GetStore(networking.COOKIES_USER_AUTH)
+ err = store.Set(res, req, "token", token, (60 * 60 * 24))
+ err2 := store.Set(res, req, "user", userRequestData.User, (60 * 60 * 24))
+ if err != nil || err2 != nil {
+ http.Error(res, http.StatusText(500), http.StatusInternalServerError)
+ return false
+ }
+
+ res.WriteHeader(http.StatusNoContent)
+ return true
+}
diff --git a/pkg/api/user/user_logout.go b/pkg/api/user/user_logout.go
new file mode 100644
index 0000000..b147747
--- /dev/null
+++ b/pkg/api/user/user_logout.go
@@ -0,0 +1,29 @@
+// Package user is a child of package api to handle api calls concerning users
+package user
+
+import (
+ "net/http"
+
+ "github.com/Ennovar/gPanel/pkg/networking"
+)
+
+// UserLogout function is accessed by an API call from the webhost root
+// by accessing /user_logout and sending it an empty POST request. This function will
+// delete the user-auth cookie session store
+func UserLogout(res http.ResponseWriter, req *http.Request) bool {
+ if req.Method != "POST" {
+ http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
+ return false
+ }
+
+ store := networking.GetStore(networking.COOKIES_USER_AUTH)
+ err := store.Delete(res, req)
+
+ if err != nil {
+ http.Error(res, http.StatusText(500), http.StatusInternalServerError)
+ return false
+ }
+
+ res.WriteHeader(http.StatusNoContent)
+ return true
+}
diff --git a/pkg/api/user/user_register.go b/pkg/api/user/user_register.go
new file mode 100644
index 0000000..da00c7a
--- /dev/null
+++ b/pkg/api/user/user_register.go
@@ -0,0 +1,59 @@
+// Package user is a child of package api to handle api calls concerning users
+package user
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/Ennovar/gPanel/pkg/database"
+ "github.com/Ennovar/gPanel/pkg/encryption"
+)
+
+// UserRegistration function is accessed by an API call from the webhost root
+// by accessing /user_register and sending it a post request with userRequestData
+// struct in JSON format.
+func UserRegistration(res http.ResponseWriter, req *http.Request) bool {
+ if req.Method != "POST" {
+ http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
+ return false
+ }
+
+ err := json.NewDecoder(req.Body).Decode(&userRequestData)
+ if err != nil {
+ http.Error(res, err.Error(), http.StatusBadRequest)
+ return false
+ } else if len(userRequestData.User) == 0 || len(userRequestData.Pass) == 0 {
+ http.Error(res, "Username or password field cannot be blank", http.StatusBadRequest)
+ return false
+ }
+
+ ds, err := database.Open(database.DBLOC_MAIN)
+ if err != nil || ds == nil {
+ http.Error(res, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+ defer ds.Close()
+
+ err = ds.Get(database.BUCKET_USERS, []byte(userRequestData.User), &userDatabaseData)
+ if err != database.ErrKeyNotExist {
+ http.Error(res, "Username already exists in the database", http.StatusBadRequest)
+ return false
+ }
+
+ userDatabaseData.Pass, err = encryption.HashPassword(userRequestData.Pass)
+ if err != nil {
+ http.Error(res, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+
+ userDatabaseData.Secret = ""
+
+ err = ds.Put(database.BUCKET_USERS, []byte(userRequestData.User), userDatabaseData)
+ if err != nil {
+ http.Error(res, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+
+ res.WriteHeader(http.StatusNoContent)
+ return true
+}
diff --git a/pkg/api/user/user_token.go b/pkg/api/user/user_token.go
new file mode 100644
index 0000000..deb4526
--- /dev/null
+++ b/pkg/api/user/user_token.go
@@ -0,0 +1,21 @@
+// Package user is a child of package api to handle api calls concerning users
+package user
+
+import "github.com/Ennovar/gPanel/pkg/database"
+
+// UserSecret is not accessible from the any client side request. It is
+// only used on the server side to help verify users are who they say they
+// are.
+func UserSecret(user string) (string, error) {
+ ds, err := database.Open(database.DBLOC_MAIN)
+ if err != nil {
+ return "", err
+ }
+
+ err = ds.Get(database.BUCKET_USERS, []byte(user), &userDatabaseData)
+ if err != nil {
+ return "", err
+ }
+
+ return userDatabaseData.Secret, nil
+}
diff --git a/pkg/encryption/random_string.go b/pkg/encryption/random_string.go
new file mode 100644
index 0000000..199bcfc
--- /dev/null
+++ b/pkg/encryption/random_string.go
@@ -0,0 +1,19 @@
+// Encryption package has functions inside of it that utilize various encypting and hashing techniques
+package encryption
+
+import (
+ "crypto/rand"
+ "fmt"
+)
+
+func RandomString() (string, error) {
+ n := 5
+ b := make([]byte, n)
+
+ if _, err := rand.Read(b); err != nil {
+ return "", err
+ }
+
+ s := fmt.Sprintf("%X", b)
+ return s, nil
+}
diff --git a/pkg/public/public.go b/pkg/public/public.go
index e241057..35e6831 100644
--- a/pkg/public/public.go
+++ b/pkg/public/public.go
@@ -11,19 +11,26 @@ import (
)
type PublicWeb struct {
- Directory string
+ Directory string
+ MaintenanceMode int
}
// NewPublicWeb returns a new PublicWeb type.
func NewPublicWeb() PublicWeb {
return PublicWeb{
- Directory: "document_roots/public/",
+ Directory: "document_roots/public/",
+ MaintenanceMode: 0,
}
}
// 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) {
+ if pub.MaintenanceMode == 1 {
+ w.WriteHeader(http.StatusServiceUnavailable)
+ w.Write([]byte("We are currently in maintenance mode, please check back later."))
+ }
+
path := req.URL.Path[1:]
if len(path) == 0 {
path = (pub.Directory + "index.html")
diff --git a/pkg/webhost/webhost.go b/pkg/webhost/webhost.go
index 2232fd9..66b4224 100644
--- a/pkg/webhost/webhost.go
+++ b/pkg/webhost/webhost.go
@@ -8,9 +8,11 @@ import (
"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 {
@@ -36,11 +38,10 @@ func reqAuth(path string) bool {
}
dismissibleFiles := []string{
- "api_testing.html",
"index.html",
"user_auth",
"user_register",
- "user_logut",
+ "user_logout",
}
for _, f := range dismissibleFiles {
if strings.HasSuffix(path, f) {
@@ -64,18 +65,81 @@ 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, "auth")
+ session_value, err := store.Read(w, req, "user")
if err != nil {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, "1")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if session_value == nil {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, "2")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
- if auth, ok := session_value.(bool); !ok || !auth {
+ username, ok := session_value.(string)
+ if !ok {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, "3")
+ http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+ return
+ }
+
+ stored_secret, err := user.UserSecret(username)
+ if stored_secret == "" {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, "4")
+ http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+ return
+ }
+
+ session_value, err = store.Read(w, req, "token")
+ if err != nil {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, "5")
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ if session_value == nil {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, "6")
+ http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+ return
+ }
+
+ tokenString, ok := session_value.(string)
+ if !ok {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, "7")
+ http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+ return
+ }
+
+ // if len(tokenString) < 7 {
+ // logging.Console("DEBUG::", logging.NORMAL_LOG, "8")
+ // http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+ // return
+ // }
+ // tokenString = tokenString[7:]
+
+ 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 {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, username)
+ logging.Console("DEBUG::", logging.NORMAL_LOG, tokenString)
+ logging.Console("DEBUG::", logging.NORMAL_LOG, stored_secret)
+ logging.Console("DEBUG::", logging.NORMAL_LOG, err.Error())
+ http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+ return
+ }
+
+ claims := t.Claims.(*jwt.StandardClaims)
+ if claims.Subject != username {
+ logging.Console("DEBUG::", logging.NORMAL_LOG, "10")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}