starting to create server->account->public structure

This commit is contained in:
George Shaw 2017-11-14 17:38:35 -06:00
parent 10f80f8639
commit 75b65b5e86
35 changed files with 527 additions and 38 deletions

13
main.go
View file

@ -4,20 +4,15 @@ import (
"log"
"net/http"
"github.com/Ennovar/gPanel/pkg/webhost"
"github.com/Ennovar/gPanel/pkg/server"
"github.com/gorilla/context"
)
func main() {
webhost := webhost.New()
gpServer := server.New()
log.Printf("To Exit: CTRL+C")
go func() {
log.Print("Listening (public) on localhost:3000, serving out of the document_roots/public/ directory...")
_ = webhost.Public.Start()
}()
log.Print("Listening (private) on localhost:2082, serving out of the document_roots/webhost/ directory...")
http.ListenAndServe("localhost:2082", context.ClearHandler(&webhost))
log.Print("Listening (server) on localhost:2083, serving out of the server/document_root/ directory...")
http.ListenAndServe("localhost:2083", context.ClearHandler(gpServer))
}

View file

@ -1,11 +1,15 @@
// Package webhost handles the logic of the webhosting panel
package webhost
// Package account handles the logic of the gPanel account server
package account
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
"github.com/Ennovar/gPanel/pkg/api"
"github.com/Ennovar/gPanel/pkg/file"
@ -14,20 +18,72 @@ import (
)
type Controller struct {
Directory string
Public *public.Controller
ServerLogger *file.Handler
Directory string
DocumentRoot string
Port int
Public *public.Controller
GracefulShutdownTimeout time.Duration
Status int
ServerLogger *file.Handler
}
// New returns a new PrivateHost type.
func New() Controller {
var controller Controller
var server http.Server
// New returns a new Controller reference.
func New(root string) *Controller {
serverErrorLogger, _ := file.Open(file.LOG_SERVER_ERRORS, true, true)
return Controller{
Directory: "document_roots/webhost/",
Public: public.New(),
ServerLogger: serverErrorLogger,
controller = Controller{
Directory: root,
DocumentRoot: "account/",
Port: 2082,
Public: public.New(root + "public/"),
GracefulShutdownTimeout: 5 * time.Second,
Status: 0,
ServerLogger: serverErrorLogger,
}
server = http.Server{
Addr: "localhost:" + strconv.Itoa(controller.Port),
Handler: &controller,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
MaxHeaderBytes: 0,
}
return &controller
}
func (con *Controller) Start() error {
if con.Status == 1 {
return errors.New("Account server is already on.")
}
con.Status = 1
go server.ListenAndServe()
return nil
}
func (con *Controller) Stop(graceful bool) error {
if graceful {
context, cancel := context.WithTimeout(context.Background(), con.GracefulShutdownTimeout)
defer cancel()
err := server.Shutdown(context)
if err == nil {
return nil
}
fmt.Printf("Graceful shutdown failed attempting forced: %v\n", err)
}
if err := server.Close(); err != nil {
return err
}
con.Status = 0
return nil
}
// ServeHTTP function routes all requests for the private webhost server. It is used in the main
@ -41,7 +97,7 @@ func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
}
if reqAuth(path) {
if !checkAuth(res, req) {
if !con.checkAuth(res, req) {
con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusUnauthorized) + "::" + http.StatusText(http.StatusUnauthorized))
http.Error(res, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return

View file

@ -0,0 +1,88 @@
// Package account handles the logic of the gPanel account server
package account
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 (con *Controller) checkAuth(res http.ResponseWriter, req *http.Request) bool {
store := networking.GetStore(networking.ACCOUNT_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, con.Directory)
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
}

View file

@ -80,7 +80,7 @@ func Auth(res http.ResponseWriter, req *http.Request) bool {
return false
}
store := networking.GetStore(networking.COOKIES_USER_AUTH)
store := networking.GetStore(networking.ACCOUNT_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 {

View file

@ -6,8 +6,8 @@ import "github.com/Ennovar/gPanel/pkg/database"
// GetSecret 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 GetSecret(user string) (string, error) {
ds, err := database.Open(database.DBLOC_MAIN)
func GetSecret(user string, directory string) (string, error) {
ds, err := database.Open(directory + database.DBLOC_MAIN)
if err != nil {
return "", err
}

View file

@ -16,7 +16,7 @@ func Logout(res http.ResponseWriter, req *http.Request) bool {
return false
}
store := networking.GetStore(networking.COOKIES_USER_AUTH)
store := networking.GetStore(networking.ACCOUNT_USER_AUTH)
err := store.Delete(res, req)
if err != nil {

View file

@ -30,8 +30,8 @@ type Datastore struct {
// Open function will open the database and return a Datastore struct
// that has a handle within it for various datastore functions.
func Open(filename string) (*Datastore, error) {
db, err := bolt.Open(filename, 0666, &bolt.Options{Timeout: 15 * time.Second})
func Open(filepath string) (*Datastore, error) {
db, err := bolt.Open(filepath, 0666, &bolt.Options{Timeout: 15 * time.Second})
if err != nil {
return nil, err

View file

@ -11,7 +11,8 @@ import (
var key = []byte("GbP=K4#f$khYuZpStK68GyHxGg$4@5K-")
const (
COOKIES_USER_AUTH = "gpanel-webhost-user-auth"
ACCOUNT_USER_AUTH = "gpanel-account-user-auth"
SERVER_USER_AUTH = "gpanel-server-user-auth"
)
type store struct {

View file

@ -16,7 +16,8 @@ import (
)
type Controller struct {
Directory string
DocumentRoot string
Port int
GracefulShutdownTimeout time.Duration
Status int
ClientLogger *file.Handler
@ -28,13 +29,14 @@ var controller Controller
var server http.Server
// New function returns a new PublicWeb type.
func New() *Controller {
func New(root string) *Controller {
clientLogHandler, _ := file.Open(file.LOG_CLIENT_ERRORS, true, true)
serverLogHandler, _ := file.Open(file.LOG_CLIENT_ERRORS, true, true)
loadLogHandler, _ := file.Open(file.LOG_LOADTIME, true, true)
controller = Controller{
Directory: "document_roots/public/",
DocumentRoot: root,
Port: 3000,
GracefulShutdownTimeout: 5 * time.Second,
Status: 0,
ClientLogger: clientLogHandler,
@ -141,9 +143,9 @@ func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
path := req.URL.Path[1:]
if len(path) == 0 {
path = (con.Directory + "index.html")
path = (con.DocumentRoot + "index.html")
} else {
path = (con.Directory + path)
path = (con.DocumentRoot + path)
}
f, err := os.Open(path)

View file

@ -1,5 +1,4 @@
// Package webhost handles the logic of the webhosting panel
package webhost
package server
import (
"net/http"
@ -38,8 +37,8 @@ func reqAuth(path string) bool {
// 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)
func (con *Controller) checkAuth(res http.ResponseWriter, req *http.Request) bool {
store := networking.GetStore(networking.SERVER_USER_AUTH)
session_value, err := store.Read(res, req, "user")
if err != nil || session_value == nil {
@ -51,7 +50,7 @@ func checkAuth(res http.ResponseWriter, req *http.Request) bool {
return false
}
stored_secret, err := user.GetSecret(username)
stored_secret, err := user.GetSecret(username, con.Directory)
if stored_secret == "" {
return false
}

62
pkg/server/server.go Normal file
View file

@ -0,0 +1,62 @@
package server
import (
"io"
"net/http"
"os"
"github.com/Ennovar/gPanel/pkg/account"
"github.com/Ennovar/gPanel/pkg/routing"
)
type Controller struct {
Directory string
DocumentRoot string
Bundles []account.Controller
}
func New() *Controller {
return &Controller{
Directory: "server/",
DocumentRoot: "document_root/",
Bundles: []account.Controller{},
}
}
func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
path := req.URL.Path[1:]
if len(path) == 0 {
path = (con.Directory + con.DocumentRoot + "index.html")
} else {
path = (con.Directory + con.DocumentRoot + path)
}
if reqAuth(path) {
if !con.checkAuth(res, req) {
http.Error(res, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
}
f, err := os.Open(path)
if err != nil {
routing.HttpThrowStatus(http.StatusNotFound, res)
return
}
contentType, err := routing.GetContentType(path)
if err != nil {
routing.HttpThrowStatus(http.StatusUnsupportedMediaType, res)
return
}
res.Header().Add("Content-Type", contentType)
_, err = io.Copy(res, f)
if err != nil {
routing.HttpThrowStatus(http.StatusInternalServerError, res)
return
}
}

View file

@ -0,0 +1,31 @@
/* Sticky Footer Start */
html {
position:relative;
min-height:100%;
}
body {
margin-bottom:60px;
}
.sticky-footer {
position:absolute;
bottom:0;
width:100%;
height:60px;
line-height:60px;
background-color:#E0EBF5;
}
/* Sticky Footer End */
/* Navbar Start */
.navbar {
background-color:#E0EBF5 !important;
}
/* Navbar End */
/* General Start */
.btn {
cursor:pointer;
}
/* General End */

View file

@ -0,0 +1,31 @@
jQuery('#loginForm').on('submit', function(e){
e.preventDefault();
var formData = {};
for(var y = 0, yy = this.length; y < yy; y++) {
var input = this[y];
if(input.name) {
formData[input.name] = input.value;
}
}
var xhr = new XMLHttpRequest();
xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.send(JSON.stringify(formData));
xhr.onloadend = function() {
if(xhr.status == 200 || xhr.status == 204) {
jQuery('.index-alert').html('<strong>Login Success: </strong>Redirecting in 2 seconds...');
jQuery('.index-alert').removeClass('alert-danger').addClass('alert-success').removeClass('d-none');
setTimeout(function(){
window.location.href = "/gPanel.html";
}, 2000);
}
else {
jQuery('.index-alert').html("<strong>Login Error: </strong>" + xhr.response);
jQuery('.index-alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none');
}
}
});

View file

@ -0,0 +1,20 @@
jQuery('#logoutForm').on('submit', function(e){
e.preventDefault();
var check = confirm('Are you sure you want to logut?');
if(check) {
var xhr = new XMLHttpRequest();
xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true);
xhr.send();
xhr.onloadend = function() {
if(xhr.status == 200 || xhr.status == 204) {
window.location.href = '/';
}
else {
alert('An error has occurred. Please contact your server\'s administrator.');
}
}
}
});

View file

@ -0,0 +1,27 @@
jQuery('#registerForm').on('submit', function(e){
e.preventDefault();
var formData = {};
for(var y = 0, yy = this.length; y < yy; y++) {
var input = this[y];
if(input.name) {
formData[input.name] = input.value;
}
}
var xhr = new XMLHttpRequest();
xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.send(JSON.stringify(formData));
xhr.onloadend = function() {
if(xhr.status == 200 || xhr.status == 204) {
jQuery('.index-alert').html('<strong>Register Success: </strong>You may now login.');
jQuery('.index-alert').removeClass('alert-danger').addClass('alert-success').removeClass('d-none');
}
else {
jQuery('.index-alert').html("<strong>Register Error: </strong>" + xhr.response);
jQuery('.index-alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none');
}
}
});

View file

@ -0,0 +1,4 @@
jQuery('#searchForm').on('submit', function(e){
e.preventDefault();
alert('Search functionality coming soon.');
});

View file

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>gPanel Server</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link type="text/css" rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<nav class="navbar navbar-expand-md navbar-light mb-4">
<a class="navbar-brand text-primary" href="#">gPanel Server</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<form id="searchForm" method="POST" action="#" class="form-inline mt-2 mt-md-0 ml-auto">
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-primary my-2 my-sm-0" type="submit">Search</button>
</form>
<form id="logoutForm" method="POST" action="api/user/logout" class="form-inline mt-2 mt-md-0 ml-3">
<button class="btn btn-primary my-2 my-sm-0" type="submit">Logout</button>
</form>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">Test</h4>
<h6 class="card-subtitle mb-4 text-muted">Test</h6>
<button class="btn btn-outline-primary">Test</button>
</div>
<div class="card-footer text-muted text-center">
TEST
</div>
</div>
</div>
</div>
</div>
<footer class="sticky-footer">
<div class="container">
<div class="row no-gutters">
<div class="col-md-6 col-12">
<p class="text-muted m-0">Managed by <a target="_blank" href="http://ennovar.io/">Ennovar</a></p>
</div>
<div class="col-md-6 col-12 d-flex justify-content-center justify-content-md-end">
<p class="text-muted m-0">Contribute on <a target="_blank" href="https://github.com/Ennovar/gPanel">Github <i class="fa fa-github" aria-hidden="true"></i></a></p>
</div>
</div>
</div>
</footer>
<!-- KEEP AT BOTTOM OF BODY TAGS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/formHandlers/search.js"></script>
<script type="text/javascript" src="assets/js/formHandlers/logout.js"></script>
<!-- KEEP AT BOTTOM OF BODY TAGS -->
</body>
</html>

View file

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>gPanel Server</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link type="text/css" rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<nav class="navbar navbar-expand-md navbar-light mb-4">
<a class="navbar-brand text-primary" href="#">gPanel Server</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<form id="searchForm" method="POST" action="#" class="form-inline mt-2 mt-md-0 ml-auto">
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-primary my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-12 d-flex justify-content-center align-items-center">
<div class="alert index-alert d-none" role="alert"></div>
</div>
<div class="col-12 d-flex justify-content-center align-items-center">
<form id="loginForm" method="POST" action="api/user/auth">
<div class="form-group">
<label class="sr-only" for="loginUsername">Username</label>
<div class="input-group mb-2">
<div class="input-group-addon"><i class="fa fa-user" aria-hidden="true"></i></div>
<input name="user" type="text" class="form-control" id="loginUsername" placeholder="Username">
</div>
</div>
<div class="form-group">
<label class="sr-only" for="loginPassword">Password</label>
<div class="input-group mb-2">
<div class="input-group-addon"><i class="fa fa-key" aria-hidden="true"></i></div>
<input name="pass" type="password" class="form-control" id="loginPassword" placeholder="Password">
</div>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
<div class="col-12 d-flex justify-content-center align-items-center mt-5">
<form id="registerForm" method="POST" action="api/user/register">
<div class="form-group">
<label class="sr-only" for="loginUsername">Username</label>
<div class="input-group mb-2">
<div class="input-group-addon"><i class="fa fa-user" aria-hidden="true"></i></div>
<input name="user" type="text" class="form-control" id="loginUsername" placeholder="Username">
</div>
</div>
<div class="form-group">
<label class="sr-only" for="loginPassword">Password</label>
<div class="input-group mb-2">
<div class="input-group-addon"><i class="fa fa-key" aria-hidden="true"></i></div>
<input name="pass" type="password" class="form-control" id="loginPassword" placeholder="Password">
</div>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
<div class="col-12 d-flex justify-content-center align-items-center mt-2">
<small class="text-muted">Register is Temporary, for development purposes only.</small>
</div>
</div>
</div>
<footer class="sticky-footer">
<div class="container">
<div class="row no-gutters">
<div class="col-md-6 col-12">
<p class="text-muted m-0">Managed by <a target="_blank" href="http://ennovar.io/">Ennovar</a></p>
</div>
<div class="col-md-6 col-12 d-flex justify-content-center justify-content-md-end">
<p class="text-muted m-0">Contribute on <a target="_blank" href="https://github.com/Ennovar/gPanel">Github <i class="fa fa-github" aria-hidden="true"></i></a></p>
</div>
</div>
</div>
</footer>
<!-- KEEP AT BOTTOM OF BODY TAGS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/formHandlers/login.js"></script>
<script type="text/javascript" src="assets/js/formHandlers/register.js"></script>
<script type="text/javascript" src="assets/js/formHandlers/search.js"></script>
<!-- KEEP AT BOTTOM OF BODY TAGS -->
</body>
</html>