summaryrefslogtreecommitdiff
path: root/system
diff options
context:
space:
mode:
Diffstat (limited to 'system')
-rw-r--r--system/admin/admin.go115
-rw-r--r--system/admin/handlers.go182
-rw-r--r--system/admin/server.go4
-rw-r--r--system/db/user.go55
4 files changed, 352 insertions, 4 deletions
diff --git a/system/admin/admin.go b/system/admin/admin.go
index 8c13582..e0689b3 100644
--- a/system/admin/admin.go
+++ b/system/admin/admin.go
@@ -205,7 +205,8 @@ var loginAdminHTML = `
</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>
+ <a href="/admin/recover" class="right">Forgot password?</a>
+ <label for="password" class="active">Password</label>
</div>
<button class="btn waves-effect waves-light right">Log in</button>
</form>
@@ -246,6 +247,118 @@ func Login() ([]byte, error) {
return buf.Bytes(), nil
}
+var forgotPasswordHTML = `
+<div class="init col s5">
+<div class="card">
+<div class="card-content">
+ <div class="card-title">Account Recovery</div>
+ <blockquote>Please enter the email for your account and a recovery message will be sent to you at this address. Check your spam folder in case the message was flagged.</blockquote>
+ <form method="post" action="/admin/recover" 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>
+
+ <button class="btn waves-effect waves-light right">Send Recovery Email</button>
+ </form>
+</div>
+</div>
+</div>
+<script>
+ $(function() {
+ $('.nav-wrapper ul.right').hide();
+ });
+</script>
+`
+
+// ForgotPassword ...
+func ForgotPassword() ([]byte, error) {
+ html := startAdminHTML + forgotPasswordHTML + 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("forgotPassword").Parse(html))
+ err = tmpl.Execute(buf, a)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+var recoveryKeyHTML = `
+<div class="init col s5">
+<div class="card">
+<div class="card-content">
+ <div class="card-title">Account Recovery</div>
+ <blockquote>Please check for your recovery key inside an email sent to the address you provided. Check your spam folder in case the message was flagged.</blockquote>
+ <form method="post" action="/admin/recover/key" class="row">
+ <div class="input-field col s12">
+ <input placeholder="Enter your recovery key" class="validate required" type="text" id="key" name="key"/>
+ <label for="key" class="active">Recovery Key</label>
+ </div>
+
+ <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">New Password</label>
+ </div>
+
+ <button class="btn waves-effect waves-light right">Update Account</button>
+ </form>
+</div>
+</div>
+</div>
+<script>
+ $(function() {
+ $('.nav-wrapper ul.right').hide();
+ });
+</script>
+`
+
+// RecoveryKey ...
+func RecoveryKey() ([]byte, error) {
+ html := startAdminHTML + recoveryKeyHTML + 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("recoveryKey").Parse(html))
+ err = tmpl.Execute(buf, a)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
// UsersList ...
func UsersList(req *http.Request) ([]byte, error) {
html := `
diff --git a/system/admin/handlers.go b/system/admin/handlers.go
index fe04601..998db6f 100644
--- a/system/admin/handlers.go
+++ b/system/admin/handlers.go
@@ -21,6 +21,7 @@ import (
"github.com/bosssauce/ponzu/system/db"
"github.com/gorilla/schema"
+ emailer "github.com/nilslice/email"
"github.com/nilslice/jwt"
)
@@ -521,12 +522,187 @@ func logoutHandler(res http.ResponseWriter, req *http.Request) {
http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/login", http.StatusFound)
}
+func forgotPasswordHandler(res http.ResponseWriter, req *http.Request) {
+ switch req.Method {
+ case http.MethodGet:
+ view, err := ForgotPassword()
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ res.Write(view)
+
+ case http.MethodPost:
+ err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
+ if err != nil {
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error400()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ // check email for user, if no user return Error
+ email := strings.ToLower(req.FormValue("email"))
+ if email == "" {
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error400()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ err, u := db.User(email)
+ if err.Error() == db.ErrNoUserExists {
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error400()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ if err.Error() != db.ErrNoUserExists && err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ // create temporary key to verify user
+ key, err := db.SetRecoveryKey(email)
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ domain := db.ConfigCache("domain")
+ body := fmt.Sprintf(`
+ There has been an account recovery request made for the user with email:
+ %s
+
+ To recover your account, please go to http://%s/admin/recover/key and enter
+ this email address along with the following secret key:
+
+ %s
+
+ If you did not make the request, ignore this message and your password
+ will remain as-is.
+
+
+ Thank you,
+ Ponzu CMS at %s
+
+ `, email, domain, key, domain)
+ msg := emailer.Message{
+ To: email,
+ From: fmt.Sprintf("Ponzu CMS <ponzu-cms@%s", domain),
+ Subject: fmt.Sprintf("Account Recovery [%s]", domain),
+ Body: body,
+ }
+
+ /*
+ err = msg.Send()
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+ */
+ fmt.Println(msg)
+
+ // redirect to /admin/recover/key and send email with key and URL
+ http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/recover/key", http.StatusFound)
+
+ default:
+ res.WriteHeader(http.StatusMethodNotAllowed)
+ errView, err := Error405()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+}
+
+func recoveryKeyHandler(res http.ResponseWriter, req *http.Request) {
+ switch req.Method {
+ case http.MethodGet:
+ view, err := RecoveryKey()
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ res.Write(view)
+
+ case http.MethodPost:
+
+ // check for email & key match
+
+ // set user with new password
+
+ // redirect to /admin/login
+
+ default:
+ res.WriteHeader(http.StatusMethodNotAllowed)
+ errView, err := Error405()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+}
+
+func recoveryEditHandler(res http.ResponseWriter, req *http.Request) {
+
+}
+
func postsHandler(res http.ResponseWriter, req *http.Request) {
q := req.URL.Query()
t := q.Get("type")
if t == "" {
res.WriteHeader(http.StatusBadRequest)
- errView, err := Error405()
+ errView, err := Error400()
if err != nil {
return
}
@@ -837,9 +1013,9 @@ func approvePostHandler(res http.ResponseWriter, req *http.Request) {
}
// check if we have a Mergeable
- m, ok := post.(content.Mergeable)
+ m, ok := post.(editor.Mergeable)
if !ok {
- log.Println("Content type", t, "must implement api.Mergable before it can bee approved.")
+ log.Println("Content type", t, "must implement editor.Mergable before it can bee approved.")
res.WriteHeader(http.StatusBadRequest)
errView, err := Error400()
if err != nil {
diff --git a/system/admin/server.go b/system/admin/server.go
index 5d93d84..ef2ae4b 100644
--- a/system/admin/server.go
+++ b/system/admin/server.go
@@ -18,6 +18,10 @@ func Run() {
http.HandleFunc("/admin/login", loginHandler)
http.HandleFunc("/admin/logout", logoutHandler)
+ http.HandleFunc("/admin/recover", forgotPasswordHandler)
+ http.HandleFunc("/admin/recover/key", recoveryKeyHandler)
+ http.HandleFunc("/admin/recover/edit", recoveryEditHandler)
+
http.HandleFunc("/admin/configure", user.Auth(configHandler))
http.HandleFunc("/admin/configure/users", user.Auth(configUsersHandler))
http.HandleFunc("/admin/configure/users/edit", user.Auth(configUsersEditHandler))
diff --git a/system/db/user.go b/system/db/user.go
index d2dc3a9..3386dc2 100644
--- a/system/db/user.go
+++ b/system/db/user.go
@@ -11,6 +11,7 @@ import (
"github.com/boltdb/bolt"
"github.com/nilslice/jwt"
+ "github.com/nilslice/rand"
)
// ErrUserExists is used for the db to report to admin user of existing user
@@ -135,6 +136,10 @@ func User(email string) ([]byte, error) {
return nil, err
}
+ if val.Bytes() == nil {
+ return nil, ErrNoUserExists
+ }
+
return val.Bytes(), nil
}
@@ -184,3 +189,53 @@ func CurrentUser(req *http.Request) ([]byte, error) {
return usr, nil
}
+
+// SetRecoveryKey generates and saves a random secret key to verify an email
+// address submitted in order to recover/reset an account password
+func SetRecoveryKey(email string) (string, error) {
+ key := fmt.Sprintf("%d", rand.Int63())
+
+ err := store.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucketIfNotExists([]byte("_recoveryKeys"))
+ if err != nil {
+ return err
+ }
+
+ err = b.Put([]byte(email), []byte(key))
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return "", err
+ }
+
+ return key, nil
+}
+
+// RecoveryKey generates and saves a random secret key to verify an email
+// address submitted in order to recover/reset an account password
+func RecoveryKey(email string) (string, error) {
+ key := &bytes.Buffer{}
+
+ err := store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("_recoveryKeys"))
+ if b == nil {
+ return errors.New("No database found for checking keys.")
+ }
+
+ _, err := key.Write(b.Get([]byte("email")))
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return "", err
+ }
+
+ return key.String(), nil
+}