Merge pull request #103 from george-e-shaw-iv/master

Bundle Creation Sends Email to Bundle Client
This commit is contained in:
George Shaw 2017-12-05 17:27:47 -06:00 committed by GitHub
commit cb0a9ae577
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 427 additions and 68 deletions

View file

@ -1,27 +0,0 @@
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

@ -51,30 +51,6 @@
</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>
@ -95,8 +71,8 @@
<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>

View file

@ -9,6 +9,8 @@ import (
"strconv"
"github.com/Ennovar/gPanel/pkg/database"
"github.com/Ennovar/gPanel/pkg/emailer"
"github.com/Ennovar/gPanel/pkg/encryption"
"github.com/Ennovar/gPanel/pkg/file"
"github.com/Ennovar/gPanel/pkg/gpaccount"
)
@ -24,6 +26,7 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
Name string `json:"name"`
AccPort int `json:"account_port"`
PubPort int `json:"public_port"`
Email string `json:"email"`
}
err := json.NewDecoder(req.Body).Decode(&createBundleRequestData)
@ -82,10 +85,9 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
ds, err := database.Open(newBundle + "/" + database.DB_MAIN)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
defer ds.Close()
var databaseBundlePorts struct {
Account int `json:"account"`
@ -97,14 +99,75 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
err = ds.Put(database.BUCKET_PORTS, []byte("bundle_ports"), databaseBundlePorts)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
var defaultBundleUser database.Struct_Users
defaultBundleUser.Pass, err = encryption.HashPassword("root")
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
defaultBundleUser.Secret = ""
err = ds.Put(database.BUCKET_USERS, []byte("root"), defaultBundleUser)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
ds.Close()
bundles[createBundleRequestData.Name] = gpaccount.New(newBundle+"/", databaseBundlePorts.Account, databaseBundlePorts.Public)
_ = bundles[createBundleRequestData.Name].Start()
_ = bundles[createBundleRequestData.Name].Public.Start()
ds, err = database.Open("server/" + database.DB_SETTINGS)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
defer ds.Close()
var smtpSettings database.Struct_SMTP
err = ds.Get(database.BUCKET_GENERAL, []byte("smtp"), &smtpSettings)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
mail, err := emailer.New(smtpSettings.Type, emailer.Credentials{
Username: smtpSettings.Username,
Password: smtpSettings.Password,
Server: smtpSettings.Server,
Port: smtpSettings.Port,
})
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
msg := string("Your new gPanel Bundle has been successfully registered.\r\n\n" +
"Account Port: " + strconv.Itoa(createBundleRequestData.AccPort) + "\r\n" +
"Public Port: " + strconv.Itoa(createBundleRequestData.PubPort) + "\r\n\n" +
"Default account username: root\r\n" +
"Default account password: root")
err = mail.SendSimple(createBundleRequestData.Email, "New gPanel Bundle - "+createBundleRequestData.Name, msg)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
res.WriteHeader(http.StatusOK)
res.Write([]byte(createBundleRequestData.Name))

49
pkg/api/email/get_smtp.go Normal file
View file

@ -0,0 +1,49 @@
package email
import (
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/Ennovar/gPanel/pkg/database"
)
func GetSMTP(res http.ResponseWriter, req *http.Request, logger *log.Logger, dir string) bool {
if req.Method != "GET" {
logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed))
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
ds, err := database.Open(dir + database.DB_SETTINGS)
if err != nil || ds == nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
defer ds.Close()
var smtpDbData database.Struct_SMTP
err = ds.Get(database.BUCKET_GENERAL, []byte("smtp"), &smtpDbData)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
// Remove password
smtpDbData.Password = ""
b, err := json.Marshal(smtpDbData)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
res.WriteHeader(http.StatusOK)
res.Write(b)
return true
}

60
pkg/api/email/set_smtp.go Normal file
View file

@ -0,0 +1,60 @@
package email
import (
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/Ennovar/gPanel/pkg/database"
"github.com/Ennovar/gPanel/pkg/emailer"
)
func SetSMTP(res http.ResponseWriter, req *http.Request, logger *log.Logger, dir string) bool {
if req.Method != "POST" && req.Method != "UPDATE" {
logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed))
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
var smtpRequestData database.Struct_SMTP
err := json.NewDecoder(req.Body).Decode(&smtpRequestData)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
// Test Authentication
_, err = emailer.New(smtpRequestData.Type, emailer.Credentials{
Username: smtpRequestData.Username,
Password: smtpRequestData.Password,
Server: smtpRequestData.Server,
Port: smtpRequestData.Port,
})
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
ds, err := database.Open(dir + database.DB_SETTINGS)
if err != nil || ds == nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
defer ds.Close()
err = ds.Put(database.BUCKET_GENERAL, []byte("smtp"), smtpRequestData)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
res.WriteHeader(http.StatusNoContent)
return true
}

View file

@ -4,6 +4,7 @@ package database
import (
"encoding/json"
"errors"
"strings"
"time"
"github.com/boltdb/bolt"
@ -11,14 +12,19 @@ import (
// Database constants
const (
DB_MAIN = "datastore.db"
DB_MAIN = "datastore.db"
DB_SETTINGS = "settings.db"
)
// Bucket constants
const (
// DB_MAIN BUCKETS
BUCKET_USERS = "users"
BUCKET_PORTS = "ports"
BUCKET_FILTERED_IPS = "filtered_ips"
// DB_SETTINGS BUCKETS
BUCKET_GENERAL = "general"
)
// Error codes
@ -45,19 +51,28 @@ func Open(filepath string) (*Datastore, error) {
// Ensure that all top-level buckets exist
err = ds.handle.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(BUCKET_USERS))
if err != nil {
return err
if strings.HasSuffix(filepath, DB_MAIN) {
_, err := tx.CreateBucketIfNotExists([]byte(BUCKET_USERS))
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_PORTS))
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_FILTERED_IPS))
if err != nil {
return err
}
}
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_PORTS))
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_FILTERED_IPS))
if err != nil {
return err
if strings.HasSuffix(filepath, DB_SETTINGS) {
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_GENERAL))
if err != nil {
return err
}
}
return nil

9
pkg/database/settings.go Normal file
View file

@ -0,0 +1,9 @@
package database
type Struct_SMTP struct {
Type string `json:"type"`
Username string `json:"username"`
Password string `json:"password"`
Server string `json:"server"`
Port int `json:"port"`
}

74
pkg/emailer/emailer.go Normal file
View file

@ -0,0 +1,74 @@
package emailer
import (
"errors"
"net/smtp"
"strconv"
)
type Credentials struct {
Username string
Password string
Server string
Port int
}
type Emailer struct {
auth smtp.Auth
cred Credentials
}
func New(eType string, creds Credentials) (*Emailer, error) {
var a smtp.Auth
switch eType {
case "crammd5":
a = smtp.CRAMMD5Auth(creds.Username, creds.Password)
default:
a = smtp.PlainAuth("", creds.Username, creds.Password, creds.Server)
}
if a == nil {
return nil, errors.New("unable to authenticate")
}
return &Emailer{
auth: a,
cred: creds,
}, nil
}
// SendSimple function will fill out the to/subject email headers for you and allow
// you to just input a string as the email body.
func (e *Emailer) SendSimple(to string, subject string, body string) error {
if e.auth == nil {
return errors.New("smtp server authentication has expired")
}
m := []byte("To:" + to + "\r\n" +
"Subject:" + subject + "\r\n" +
"\r\n" +
body + "\r\n")
err := smtp.SendMail(e.cred.Server+":"+strconv.Itoa(e.cred.Port), e.auth, e.cred.Username, []string{to}, m)
if err != nil {
return err
}
return nil
}
// SendCustom function will not fill out the to/subject email headers for you and allow
// you to send a completely custom message in the form of bytes.
func (e *Emailer) SendCustom(to string, msg []byte) error {
if e.auth == nil {
return errors.New("smtp server authentication has expired")
}
err := smtp.SendMail(e.cred.Server+":"+strconv.Itoa(e.cred.Port), e.auth, e.cred.Username, []string{to}, msg)
if err != nil {
return err
}
return nil
}

View file

@ -9,6 +9,7 @@ import (
"strings"
"github.com/Ennovar/gPanel/pkg/api/bundle"
"github.com/Ennovar/gPanel/pkg/api/email"
logapi "github.com/Ennovar/gPanel/pkg/api/log"
"github.com/Ennovar/gPanel/pkg/api/server"
"github.com/Ennovar/gPanel/pkg/api/user"
@ -86,6 +87,10 @@ func (con *Controller) apiHandler(res http.ResponseWriter, req *http.Request) (b
return true, logapi.Read(res, req, con.APILogger, con.Directory)
case "/log/delete":
return true, logapi.Truncate(res, req, con.APILogger, con.Directory)
case "/email/set_smtp":
return true, email.SetSMTP(res, req, con.APILogger, con.Directory)
case "/email/get_smtp":
return true, email.GetSMTP(res, req, con.APILogger, con.Directory)
default:
return false, false
}

View file

@ -0,0 +1,68 @@
var smtpModal = jQuery('.smtp-settings-modal');
jQuery('._js_smtp-credentials').on('click', function(e){
e.preventDefault();
var xhr = new XMLHttpRequest();
xhr.open('GET', 'api/email/get_smtp', true);
xhr.send();
xhr.onloadend = function() {
if(xhr.status == 200) {
var resp = JSON.parse(xhr.response);
if(resp["type"] == "crammd5") {
jQuery('#smtpType').val(resp["type"]).change();
}
jQuery('#smtpUsername').val(resp["username"]);
jQuery('#smtpServer').val(resp["server"]);
jQuery('#smtpPort').val(resp["port"]);
}
smtpModal.modal('show');
}
});
jQuery('._js_smtp-settings-form').on('submit', function(e){
e.preventDefault();
var flag = false;
jQuery(this).find('input').each(function(i){
if(jQuery(this) && jQuery(this).val()) return true;
else {
flag = true;
return false;
}
});
if(flag) {
alert('All inputs need to be filled out.');
return;
}
var requestData = {};
requestData["type"] = jQuery(this).find('#smtpType').val();
requestData["username"] = jQuery(this).find('#smtpUsername').val();
requestData["password"] = jQuery(this).find('#smtpPassword').val();
requestData["server"] = jQuery(this).find('#smtpServer').val();
requestData["port"] = parseInt(jQuery(this).find('#smtpPort').val());
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(requestData));
xhr.onloadend = function() {
if(xhr.status == 204) {
alert("New SMTP Settings Connect Successfully and are Saved.");
}
else {
if(xhr.response != undefined && xhr.response.length != 0) {
alert('Error: ' + xhr.response);
}
else {
alert('An error has occurred, refresh and try again. If problem persists please contact your administrator.');
}
}
}
});

View file

@ -44,6 +44,11 @@
<input name="name" type="text" class="form-control" id="newBundleName" aria-describedby="newBundleNameHelp" placeholder="Bundle Name">
<small id="newBundleNameHelp" class="form-text text-muted">Bundle name must be unqiue in terms of your current bundle collection.</small>
</div>
<div class="form-group">
<label for="newBundleEmail">Bundle Client Email</label>
<input name="email" type="text" class="form-control" id="newBundleEmail" aria-describedby="newBundleEmailHelp" placeholder="Bundle Client Email">
<small id="newBundleEmailHelp" class="form-text text-muted">The client whose bundle this is will recieve an email with instructions and default username/password.</small>
</div>
<div class="form-group">
<label for="newBundleAccountPort">Bundle Account Port</label>
<input name="account_port" type="number" class="form-control" id="newBundleAccountPort" aria-describedby="newBundleAccountPortHelp" min="2000" max="4000" value="2083">
@ -278,6 +283,53 @@
</div>
</div>
<!-- SMTP Settings Modal -->
<div class="modal fade smtp-settings-modal" tabindex="-1" role="dialog" aria-labelledby="smtp-settings-modal" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">SMTP Settings</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="_js_smtp-settings-form" action="api/email/set_smtp" method="POST">
<div class="form-group">
<label for="smtpType">Authentication Type</label>
<select class="form-control" id="smtpType">
<option selected value="plain">Plain</option>
<option value="crammd5">CRAM-MD5</option>
</select>
</div>
<div class="form-group">
<label for="smtpUsername">Username</label>
<input type="text" class="form-control" id="smtpUsername" placeholder="Username" value="">
</div>
<div class="form-group">
<label for="smtpPassword">Password</label>
<input type="password" class="form-control" id="smtpPassword" placeholder="Password" value="">
</div>
<div class="form-group">
<label for="smtpServer">Server</label>
<input type="text" class="form-control" id="smtpServer" placeholder="Server" value="">
</div>
<div class="form-group">
<label for="smtpPort">Port</label>
<input type="number" class="form-control" id="smtpPort" aria-describedby="newBundleAccountPortHelp" min="0" max="65535" value="587">
</div>
<div class="btn-group" role="group">
<button type="submit" class="btn btn-primary">Test & Set SMTP Settings</button>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-12">
@ -321,6 +373,20 @@
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">Server Settings</h4>
<h6 class="card-subtitle mb-4 text-muted">Set or update various settings, such as smtp credentials, that the server uses</h6>
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-primary _js_smtp-credentials">SMTP Credentials</button>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="sticky-footer">
@ -366,6 +432,8 @@
<script type="text/javascript" src="assets/js/panelHandlers/users/new.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/users/delete.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/users/new_password.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/settings/smtp.js"></script>
<!-- KEEP AT BOTTOM OF BODY TAGS -->
</body>
</html>

View file

@ -73,7 +73,6 @@
<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>