summaryrefslogtreecommitdiff
path: root/system/admin
diff options
context:
space:
mode:
Diffstat (limited to 'system/admin')
-rw-r--r--system/admin/admin.go160
-rw-r--r--system/admin/auth.go11
-rw-r--r--system/admin/config.go5
-rw-r--r--system/admin/config/config.go89
-rw-r--r--system/admin/handlers.go127
-rw-r--r--system/admin/server.go185
-rw-r--r--system/admin/static/dashboard/css/admin.css44
-rw-r--r--system/admin/user/auth.go106
8 files changed, 676 insertions, 51 deletions
diff --git a/system/admin/admin.go b/system/admin/admin.go
index 410c9f4..1c04bce 100644
--- a/system/admin/admin.go
+++ b/system/admin/admin.go
@@ -7,12 +7,13 @@ import (
"html/template"
"github.com/nilslice/cms/content"
+ "github.com/nilslice/cms/system/db"
)
-const adminHTML = `<!doctype html>
+var startAdminHTML = `<!doctype html>
<html lang="en">
<head>
- <title>CMS</title>
+ <title>{{ .Logo }}</title>
<script type="text/javascript" src="/admin/static/common/js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="/admin/static/common/js/util.js"></script>
<script type="text/javascript" src="/admin/static/dashboard/js/materialize.min.js"></script>
@@ -32,7 +33,7 @@ const adminHTML = `<!doctype html>
<div class="navbar-fixed">
<nav class="grey darken-2">
<div class="nav-wrapper">
- <a class="brand-logo" href="/admin">CMS</a>
+ <a class="brand-logo" href="/admin">{{ .Logo }}</a>
<ul class="right">
<li><a href="/admin/logout">Logout</a></li>
@@ -41,8 +42,9 @@ const adminHTML = `<!doctype html>
</nav>
</div>
- <div class="admin-ui row">
-
+ <div class="admin-ui row">`
+
+var mainAdminHTML = `
<div class="left-nav col s3">
<div class="card">
<ul class="card-content collection">
@@ -57,6 +59,7 @@ const adminHTML = `<!doctype html>
<div class="card-title">System</div>
<div class="row collection-item">
<li><a class="col s12" href="/admin/configure"><i class="tiny left material-icons">settings</i>Configuration</a></li>
+ <li><a class="col s12" href="/admin/configure/users"><i class="tiny left material-icons">supervisor_account</i>Users</a></li>
</div>
</ul>
</div>
@@ -65,26 +68,167 @@ const adminHTML = `<!doctype html>
<div class="subview col s9">
{{ .Subview }}
</div>
- {{ end }}
+ {{ end }}`
+
+var endAdminHTML = `
</div>
</body>
</html>`
type admin struct {
+ Logo string
Types map[string]func() interface{}
Subview template.HTML
}
// Admin ...
func Admin(view []byte) ([]byte, error) {
+ cfg, err := db.Config("name")
+ if err != nil {
+ return nil, err
+ }
+
+ if cfg == nil {
+ cfg = []byte("")
+ }
+
a := admin{
+ Logo: string(cfg),
Types: content.Types,
Subview: template.HTML(view),
}
buf := &bytes.Buffer{}
- tmpl := template.Must(template.New("admin").Parse(adminHTML))
- err := tmpl.Execute(buf, a)
+ html := startAdminHTML + mainAdminHTML + endAdminHTML
+ tmpl := template.Must(template.New("admin").Parse(html))
+ err = tmpl.Execute(buf, a)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+var initAdminHTML = `
+<div class="init col s5">
+<div class="card">
+<div class="card-content">
+ <div class="card-title">Welcome!</div>
+ <blockquote>You need to initialize your system by filling out the form below. All of
+ this information can be updated later on, but you will not be able to start
+ without first completing this step.</blockquote>
+ <form method="post" action="/admin/init" class="row">
+ <div>Configuration</div>
+ <div class="input-field col s12">
+ <input placeholder="Enter the name of your site (interal use only)" class="validate required" type="text" id="name" name="name"/>
+ <label for="name" class="active">Site Name</label>
+ </div>
+ <div class="input-field col s12">
+ <input placeholder="Used for acquiring SSL certificate (e.g. www.example.com or example.com)" class="validate" type="text" id="domain" name="domain"/>
+ <label for="domain" class="active">Domain</label>
+ </div>
+ <div>Admin Details</div>
+ <div class="input-field col s12">
+ <input placeholder="Your email address e.g. you@example.com" class="validate required" type="email" id="email" name="email"/>
+ <label for="email" class="active">Email</label>
+ </div>
+ <div class="input-field col s12">
+ <input placeholder="Enter a strong password" class="validate required" type="password" id="password" name="password"/>
+ <label for="password" class="active">Password</label>
+ </div>
+ <button class="btn waves-effect waves-light right">Start</button>
+ </form>
+</div>
+</div>
+</div>
+<script>
+ $(function() {
+ $('.nav-wrapper ul.right').hide();
+
+ var logo = $('a.brand-logo');
+ var name = $('input#name');
+
+ name.on('change', function(e) {
+ logo.text(e.target.value);
+ });
+ });
+</script>
+`
+
+// Init ...
+func Init() ([]byte, error) {
+ html := startAdminHTML + initAdminHTML + endAdminHTML
+
+ cfg, err := db.Config("name")
+ if err != nil {
+ return nil, err
+ }
+
+ if cfg == nil {
+ cfg = []byte("")
+ }
+
+ a := admin{
+ Logo: string(cfg),
+ }
+
+ buf := &bytes.Buffer{}
+ tmpl := template.Must(template.New("init").Parse(html))
+ err = tmpl.Execute(buf, a)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+var loginAdminHTML = `
+<div class="init col s5">
+<div class="card">
+<div class="card-content">
+ <div class="card-title">Welcome!</div>
+ <blockquote>Please log in to the system using your email address and password.</blockquote>
+ <form method="post" action="/admin/login" class="row">
+ <div class="input-field col s12">
+ <input placeholder="Enter your email address e.g. you@example.com" class="validate required" type="email" id="email" name="email"/>
+ <label for="email" class="active">Email</label>
+ </div>
+ <div class="input-field col s12">
+ <input placeholder="Enter your password" class="validate required" type="password" id="password" name="password"/>
+ <label for="password" class="active">Password</label>
+ </div>
+ <button class="btn waves-effect waves-light right">Log in</button>
+ </form>
+</div>
+</div>
+</div>
+<script>
+ $(function() {
+ $('.nav-wrapper ul.right').hide();
+ });
+</script>
+`
+
+// Login ...
+func Login() ([]byte, error) {
+ html := startAdminHTML + loginAdminHTML + endAdminHTML
+
+ cfg, err := db.Config("name")
+ if err != nil {
+ return nil, err
+ }
+
+ if cfg == nil {
+ cfg = []byte("")
+ }
+
+ a := admin{
+ Logo: string(cfg),
+ }
+
+ buf := &bytes.Buffer{}
+ tmpl := template.Must(template.New("login").Parse(html))
+ err = tmpl.Execute(buf, a)
if err != nil {
return nil, err
}
diff --git a/system/admin/auth.go b/system/admin/auth.go
deleted file mode 100644
index 153a31a..0000000
--- a/system/admin/auth.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package admin
-
-// Session ...
-type Session struct {
- User
- token string
-}
-
-// User ...
-type User struct {
-}
diff --git a/system/admin/config.go b/system/admin/config.go
deleted file mode 100644
index e067299..0000000
--- a/system/admin/config.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package admin
-
-// Config represents the confirgurable options of the system
-type Config struct {
-}
diff --git a/system/admin/config/config.go b/system/admin/config/config.go
new file mode 100644
index 0000000..791510e
--- /dev/null
+++ b/system/admin/config/config.go
@@ -0,0 +1,89 @@
+package config
+
+import (
+ "github.com/nilslice/cms/content"
+ "github.com/nilslice/cms/management/editor"
+)
+
+//Config represents the confirgurable options of the system
+type Config struct {
+ content.Item
+ editor editor.Editor
+
+ Name string `json:"name"`
+ Domain string `json:"domain"`
+ ClientSecret string `json:"client_secret"`
+}
+
+// SetContentID partially implements editor.Editable
+func (c *Config) SetContentID(id int) { c.ID = id }
+
+// ContentID partially implements editor.Editable
+func (c *Config) ContentID() int { return c.ID }
+
+// ContentName partially implements editor.Editable
+func (c *Config) ContentName() string { return c.Name }
+
+// SetSlug partially implements editor.Editable
+func (c *Config) SetSlug(slug string) { c.Slug = slug }
+
+// Editor partially implements editor.Editable
+func (c *Config) Editor() *editor.Editor { return &c.editor }
+
+// MarshalEditor writes a buffer of html to edit a Post and partially implements editor.Editable
+func (c *Config) MarshalEditor() ([]byte, error) {
+ view, err := editor.Form(c,
+ editor.Field{
+ View: editor.Input("Name", c, map[string]string{
+ "label": "Site Name",
+ "placeholder": "Add a name to this site (internal use only)",
+ }),
+ },
+ editor.Field{
+ View: editor.Input("Domain", c, map[string]string{
+ "label": "Domain Name (required for SSL certificate)",
+ "placeholder": "e.g. www.example.com or example.com",
+ }),
+ },
+ editor.Field{
+ View: editor.Input("ClientSecret", c, map[string]string{
+ "label": "Client Secret (used to validate requests)",
+ "disabled": "true",
+ }),
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ open := []byte(`<div class="card"><form action="/admin/configure" method="post">`)
+ close := []byte(`</form></div>`)
+ script := []byte(`
+ <script>
+ $(function() {
+ // hide default fields & labels unecessary for the config
+ var fields = $('.default-fields');
+ fields.css('position', 'relative');
+ fields.find('input:not([type=submit])').remove();
+ fields.find('label').remove();
+ fields.find('button').css({
+ position: 'absolute',
+ top: '-10px',
+ right: '0px'
+ });
+
+ // adjust layout of td so save button is in same location as usual
+ fields.find('td').css('float', 'right');
+
+ // stop some fixed config settings from being modified
+ fields.find('input[name=client_secret]').attr('name', '');
+ });
+ </script>
+ `)
+
+ view = append(open, view...)
+ view = append(view, close...)
+ view = append(view, script...)
+
+ return view, nil
+}
diff --git a/system/admin/handlers.go b/system/admin/handlers.go
new file mode 100644
index 0000000..9ff39c3
--- /dev/null
+++ b/system/admin/handlers.go
@@ -0,0 +1,127 @@
+package admin
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/nilslice/cms/system/admin/user"
+ "github.com/nilslice/cms/system/db"
+ "github.com/nilslice/jwt"
+)
+
+func adminHandler(res http.ResponseWriter, req *http.Request) {
+ view, err := Admin(nil)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ res.Header().Set("Content-Type", "text/html")
+ res.Write(view)
+}
+
+func loginHandler(res http.ResponseWriter, req *http.Request) {
+ if !db.SystemInitComplete() {
+ redir := req.URL.Scheme + req.URL.Host + "/admin/init"
+ http.Redirect(res, req, redir, http.StatusFound)
+ return
+ }
+
+ switch req.Method {
+ case http.MethodGet:
+ if user.IsValid(req) {
+ http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
+ return
+ }
+
+ view, err := Login()
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ res.Header().Set("Content-Type", "text/html")
+ res.Write(view)
+
+ case http.MethodPost:
+ if user.IsValid(req) {
+ http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
+ return
+ }
+
+ err := req.ParseForm()
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ fmt.Println(req.FormValue("email"))
+ fmt.Println(req.FormValue("password"))
+
+ // check email & password
+ j, err := db.User(req.FormValue("email"))
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ if j == nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusBadRequest)
+ fmt.Println("j == nil")
+ return
+ }
+
+ usr := &user.User{}
+ err = json.Unmarshal(j, usr)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ if !user.IsUser(usr, req.FormValue("password")) {
+ res.WriteHeader(http.StatusBadRequest)
+ fmt.Println("!IsUser")
+ return
+ }
+ // create new token
+ week := time.Now().Add(time.Hour * 24 * 7)
+ claims := map[string]interface{}{
+ "exp": week,
+ "user": usr.Email,
+ }
+ token, err := jwt.New(claims)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ // add it to cookie +1 week expiration
+ http.SetCookie(res, &http.Cookie{
+ Name: "_token",
+ Value: token,
+ Expires: week,
+ })
+
+ http.Redirect(res, req, strings.TrimSuffix(req.URL.String(), "/login"), http.StatusFound)
+ }
+}
+
+func logoutHandler(res http.ResponseWriter, req *http.Request) {
+ http.SetCookie(res, &http.Cookie{
+ Name: "_token",
+ Expires: time.Unix(0, 0),
+ Value: "",
+ })
+
+ http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/login", http.StatusFound)
+}
diff --git a/system/admin/server.go b/system/admin/server.go
index 900f47d..92c86c3 100644
--- a/system/admin/server.go
+++ b/system/admin/server.go
@@ -2,6 +2,7 @@ package admin
import (
"bytes"
+ "encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -16,22 +17,15 @@ import (
"github.com/nilslice/cms/content"
"github.com/nilslice/cms/management/editor"
"github.com/nilslice/cms/management/manager"
+ "github.com/nilslice/cms/system/admin/config"
+ "github.com/nilslice/cms/system/admin/user"
"github.com/nilslice/cms/system/db"
+ "github.com/nilslice/jwt"
)
// Run adds Handlers to default http listener for Admin
func Run() {
- http.HandleFunc("/admin", func(res http.ResponseWriter, req *http.Request) {
- adminView, err := Admin(nil)
- if err != nil {
- fmt.Println(err)
- res.WriteHeader(http.StatusInternalServerError)
- return
- }
-
- res.Header().Set("Content-Type", "text/html")
- res.Write(adminView)
- })
+ http.HandleFunc("/admin", user.Auth(adminHandler))
http.HandleFunc("/admin/static/", func(res http.ResponseWriter, req *http.Request) {
path := req.URL.Path
@@ -49,17 +43,151 @@ func Run() {
http.ServeFile(res, req, filepath.Join(filePathParts...))
})
- http.HandleFunc("/admin/configure", func(res http.ResponseWriter, req *http.Request) {
- adminView, err := Admin(nil)
- if err != nil {
- fmt.Println(err)
- res.WriteHeader(http.StatusInternalServerError)
+ http.HandleFunc("/admin/init", func(res http.ResponseWriter, req *http.Request) {
+ if db.SystemInitComplete() {
+ http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
return
}
- res.Header().Set("Content-Type", "text/html")
- res.Write(adminView)
+ switch req.Method {
+ case http.MethodGet:
+ view, err := Init()
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ res.Header().Set("Content-Type", "text/html")
+ res.Write(view)
+ case http.MethodPost:
+ err := req.ParseForm()
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ // get the site name from post to encode and use as secret
+ name := []byte(req.FormValue("name"))
+ secret := base64.StdEncoding.EncodeToString(name)
+ req.Form.Set("client_secret", secret)
+
+ err = db.SetConfig(req.Form)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ email := req.FormValue("email")
+ password := req.FormValue("password")
+ usr := user.NewUser(email, password)
+
+ _, err = db.SetUser(usr)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ // add _token cookie for login persistence
+ week := time.Now().Add(time.Hour * 24 * 7)
+ claims := map[string]interface{}{
+ "exp": week.Unix(),
+ "user": usr.Email,
+ }
+
+ jwt.Secret([]byte(secret))
+ token, err := jwt.New(claims)
+
+ http.SetCookie(res, &http.Cookie{
+ Name: "_token",
+ Value: token,
+ Expires: week,
+ })
+
+ redir := strings.TrimSuffix(req.URL.String(), "/init")
+ http.Redirect(res, req, redir, http.StatusFound)
+
+ default:
+ res.WriteHeader(http.StatusMethodNotAllowed)
+ }
+ })
+
+ http.HandleFunc("/admin/login", loginHandler)
+ http.HandleFunc("/admin/logout", logoutHandler)
+
+ http.HandleFunc("/admin/configure", func(res http.ResponseWriter, req *http.Request) {
+ switch req.Method {
+ case http.MethodGet:
+ data, err := db.ConfigAll()
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ c := &config.Config{}
+
+ err = json.Unmarshal(data, c)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ cfg, err := c.MarshalEditor()
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ adminView, err := Admin(cfg)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ res.Header().Set("Content-Type", "text/html")
+ res.Write(adminView)
+
+ case http.MethodPost:
+ err := req.ParseForm()
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ err = db.SetConfig(req.Form)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(res, req, req.URL.String(), http.StatusFound)
+
+ default:
+ res.WriteHeader(http.StatusMethodNotAllowed)
+ }
+
+ })
+
+ http.HandleFunc("/admin/configure/users", func(res http.ResponseWriter, req *http.Request) {
+ switch req.Method {
+ case http.MethodGet:
+ // list all users and delete buttons
+
+ case http.MethodPost:
+ // create new user
+
+ default:
+ res.WriteHeader(http.StatusMethodNotAllowed)
+ }
})
http.HandleFunc("/admin/posts", func(res http.ResponseWriter, req *http.Request) {
@@ -69,20 +197,20 @@ func Run() {
res.WriteHeader(http.StatusBadRequest)
}
- posts := db.GetAll(t)
+ posts := db.ContentAll(t)
b := &bytes.Buffer{}
p := content.Types[t]().(editor.Editable)
- html := `<div class="col s9">
- <div class="card">
- <ul class="card-content collection posts">
- <div class="card-title">` + t + ` Items</div>`
+ html := `<div class="col s9 card">
+ <div class="card-content row">
+ <div class="card-title">` + t + ` Items</div>
+ <ul class="posts">`
for i := range posts {
json.Unmarshal(posts[i], &p)
- post := `<div class="row collection-item"><li class="col s12 collection-item"><a href="/admin/edit?type=` +
+ post := `<li class="col s12"><a href="/admin/edit?type=` +
t + `&id=` + fmt.Sprintf("%d", p.ContentID()) +
- `">` + p.ContentName() + `</a></li></div>`
+ `">` + p.ContentName() + `</a></li>`
b.Write([]byte(post))
}
@@ -116,7 +244,7 @@ func Run() {
post := contentType()
if i != "" {
- data, err := db.Get(t + ":" + i)
+ data, err := db.Content(t + ":" + i)
if err != nil {
fmt.Println(err)
res.WriteHeader(http.StatusInternalServerError)
@@ -208,7 +336,7 @@ func Run() {
req.PostForm.Del(discardKey)
}
- id, err := db.Set(t+":"+cid, req.PostForm)
+ id, err := db.SetContent(t+":"+cid, req.PostForm)
if err != nil {
fmt.Println(err)
res.WriteHeader(http.StatusInternalServerError)
@@ -221,6 +349,9 @@ func Run() {
sid := fmt.Sprintf("%d", id)
desURL := scheme + host + path + "?type=" + t + "&id=" + sid
http.Redirect(res, req, desURL, http.StatusFound)
+
+ default:
+ res.WriteHeader(http.StatusMethodNotAllowed)
}
})
diff --git a/system/admin/static/dashboard/css/admin.css b/system/admin/static/dashboard/css/admin.css
index cf8a84b..413edd5 100644
--- a/system/admin/static/dashboard/css/admin.css
+++ b/system/admin/static/dashboard/css/admin.css
@@ -8,16 +8,60 @@
margin: auto;
}
+.nav-wrapper a:hover {
+ color: inherit !important;
+}
+
.admin-ui {
width: 95%;
max-width: 1300px;
margin: 1% auto;
}
+.init {
+ float: none !important;
+ margin: auto !important;
+}
+
.manager {
margin-bottom: 2%;
}
+.left-nav .row li a {
+ padding: 10px 0;
+ transition: all 0.3s ease;
+}
+
+.left-nav .card-title {
+ margin-top: 10px !important;
+}
+
+.left-nav .card-title:first-child {
+ margin-top: 0px !important;
+}
+
+ul.posts li {
+ display: block;
+ margin: 0 0 20px 0;
+ padding: 0 0 20px 0 !important;
+ border-bottom: solid 1px #e0e0e0;
+}
+
+ul.posts li:last-child {
+ border-bottom: none;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+li a {
+ transition: all 0.3s ease;
+}
+
+li a:hover {
+ color: #333;
+ transition: all 0.3s ease;
+}
+
a.new-post {
margin: 0.5rem 0 1rem 0.75rem;
}
diff --git a/system/admin/user/auth.go b/system/admin/user/auth.go
new file mode 100644
index 0000000..36f5e40
--- /dev/null
+++ b/system/admin/user/auth.go
@@ -0,0 +1,106 @@
+package user
+
+import (
+ "encoding/base64"
+ "fmt"
+ "net/http"
+
+ "github.com/nilslice/jwt"
+ "github.com/nilslice/rand"
+ "golang.org/x/crypto/bcrypt"
+)
+
+// User defines a admin user in the system
+type User struct {
+ ID int `json:"id"`
+ Email string `json:"email"`
+ Hash string `json:"hash"`
+ Salt string `json:"salt"`
+}
+
+// NewUser creates a user
+func NewUser(email, password string) *User {
+ salt := salt128()
+ hash := encryptPassword([]byte(password), salt)
+
+ user := &User{
+ Email: email,
+ Hash: string(hash),
+ Salt: base64.StdEncoding.EncodeToString(salt),
+ }
+
+ return user
+}
+
+// Auth is HTTP middleware to ensure the request has proper token credentials
+func Auth(next http.HandlerFunc) http.HandlerFunc {
+ return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+ redir := req.URL.Scheme + req.URL.Host + "/admin/login"
+
+ if IsValid(req) {
+ next.ServeHTTP(res, req)
+ } else {
+ http.Redirect(res, req, redir, http.StatusFound)
+ }
+ })
+}
+
+// IsValid checks if the user request is authenticated
+func IsValid(req *http.Request) bool {
+ // check if token exists in cookie
+ cookie, err := req.Cookie("_token")
+ if err != nil {
+ return false
+ }
+ // validate it and allow or redirect request
+ token := cookie.Value
+ return jwt.Passes(token)
+}
+
+// IsUser checks for consistency in email/pass combination
+func IsUser(usr *User, password string) bool {
+ fmt.Println(usr, password)
+ salt, err := base64.StdEncoding.DecodeString(usr.Salt)
+ if err != nil {
+ return false
+ }
+
+ err = comparePassword([]byte(usr.Hash), []byte(password), salt)
+ if err != nil {
+ return false
+ }
+
+ return true
+}
+
+// The following functions are from github.com/sluu99/um -----------------------
+
+// salt128 generates 128 bits of random data.
+func salt128() []byte {
+ x := make([]byte, 16)
+ rand.Read(x)
+ return x
+}
+
+// makePassword makes the actual password from the plain password and salt
+func makePassword(plainPw, salt []byte) []byte {
+ password := make([]byte, 0, len(plainPw)+len(salt))
+ password = append(password, salt...)
+ password = append(password, plainPw...)
+ return password
+}
+
+// encryptPassword uses bcrypt to encrypt a password and salt combination.
+// It returns the encrypted password in hex form.
+func encryptPassword(plainPw, salt []byte) []byte {
+ hash, _ := bcrypt.GenerateFromPassword(makePassword(plainPw, salt), 10)
+ return hash
+}
+
+// comparePassword compares a hash with the plain password and the salt.
+// The function returns nil on success or an error on failure.
+func comparePassword(hash, plainPw, salt []byte) error {
+ return bcrypt.CompareHashAndPassword(hash, makePassword(plainPw, salt))
+}
+
+// End code from github.com/sluu99/um ------------------------------------------