add user config page and admin menu

This commit is contained in:
2019-12-17 19:28:30 +01:00
parent 4c33b0841e
commit 7653470a7c
16 changed files with 648 additions and 107 deletions

View File

@@ -0,0 +1,97 @@
package webcontroller
import (
"fmt"
"html/template"
"net/http"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
)
func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f forms.Form) {
if isAdmin, err := td.PixelAPI.UserIsAdmin(); err != nil {
td.Title = err.Error()
return forms.Form{Title: td.Title}
} else if !isAdmin.IsAdmin {
td.Title = ";)"
return forms.Form{Title: td.Title}
}
td.Title = "Pixeldrain global configuration"
f = forms.Form{
Name: "admin_globals",
Title: td.Title,
PreFormHTML: template.HTML("<p>Careful! The slightest typing error could bring the whole website down</p>"),
BackLink: "/admin",
SubmitLabel: "Submit",
}
globals, err := td.PixelAPI.AdminGetGlobals()
if err != nil {
f.SubmitMessages = []template.HTML{template.HTML(err.Error())}
return f
}
var globalsMap = make(map[string]string)
for _, v := range globals.Globals {
f.Fields = append(f.Fields, forms.Field{
Name: v.Key,
DefaultValue: v.Value,
Label: v.Key,
Type: func() forms.FieldType {
switch v.Key {
case
"email_address_change_body",
"email_password_reset_body":
return forms.FieldTypeTextarea
case
"api_ratelimit_limit",
"api_ratelimit_rate",
"cron_interval_seconds",
"file_inactive_expiry_days",
"max_file_size",
"pixelstore_min_redundancy":
return forms.FieldTypeNumber
default:
return forms.FieldTypeText
}
}(),
})
globalsMap[v.Key] = v.Value
}
if f.ReadInput(r) {
var successfulUpdates = 0
for k, v := range f.Fields {
if v.EnteredValue == globalsMap[v.Name] {
continue // Change changes, no need to update
}
// Value changed, try to update global setting
if _, err = td.PixelAPI.AdminSetGlobals(v.Name, v.EnteredValue); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = append(f.SubmitMessages, template.HTML(apiErr.Message))
} else {
log.Error("%s", err)
f.SubmitMessages = append(f.SubmitMessages, template.HTML(
fmt.Sprintf("Failed to set '%s': %s", v.Name, err),
))
return f
}
} else {
f.Fields[k].DefaultValue = v.EnteredValue
successfulUpdates++
}
}
if len(f.SubmitMessages) == 0 {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{template.HTML(
fmt.Sprintf("Success! %d values updated", successfulUpdates),
)}
}
}
return f
}

View File

@@ -79,6 +79,8 @@ type FieldType string
// Fields which can be in a form
const (
FieldTypeText FieldType = "text"
FieldTypeTextarea FieldType = "textarea"
FieldTypeNumber FieldType = "number"
FieldTypeUsername FieldType = "username"
FieldTypeEmail FieldType = "email"
FieldTypeCurrentPassword FieldType = "current-password"

View File

@@ -16,6 +16,7 @@ import (
type TemplateData struct {
Authenticated bool
Username string
Email string
UserAgent string
UserStyle template.CSS
APIEndpoint template.URL
@@ -67,6 +68,7 @@ func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request)
// Authentication succeeded
t.Authenticated = true
t.Username = uinf.Username
t.Email = uinf.Email
} else {
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "")
}

View File

@@ -18,9 +18,8 @@ func (wc *WebController) serveLogout(
) {
if key, err := wc.getAPIKey(r); err == nil {
var api = pixelapi.New(wc.conf.APIURLInternal, key)
_, err1 := api.UserSessionDestroy(key)
if err1 != nil {
log.Warn("logout failed for session '%s': %s", key, err1)
if err = api.UserSessionDestroy(key); err != nil {
log.Warn("logout failed for session '%s': %s", key, err)
}
}
@@ -151,9 +150,12 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.F
BackLink: "/",
SubmitLabel: "Login",
PostFormHTML: template.HTML(
`<br/>If you don't have a pixeldrain account yet, you can ` +
`<p>If you don't have a pixeldrain account yet, you can ` +
`<a href="/register">register here</a>. No e-mail address is ` +
`required.<br/>`,
`required.</p>` +
`<p>Forgot your password? If your account has a valid e-mail ` +
`address you can <a href="/password_reset">request a new ` +
`password here</a>.</p>`,
),
}
@@ -184,45 +186,35 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.F
return f
}
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) {
td.Title = "Change Password"
func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f forms.Form) {
td.Title = "Recover lost password"
f = forms.Form{
Name: "password_change",
Name: "password_reset",
Title: td.Title,
Fields: []forms.Field{
{
Name: "old_password",
Label: "Old Password",
Type: forms.FieldTypeCurrentPassword,
Name: "email",
Label: "E-mail address",
Description: "we will send a password reset link to this " +
"e-mail address",
Separator: true,
Type: forms.FieldTypeEmail,
}, {
Name: "new_password1",
Label: "New Password",
Type: forms.FieldTypeNewPassword,
}, {
Name: "new_password2",
Label: "New Password verification",
Type: forms.FieldTypeCurrentPassword,
Name: "recaptcha_response",
Label: "Turing test (click the white box)",
Description: "the reCaptcha turing test verifies that you " +
"are not an evil robot that is trying hijack accounts",
Separator: true,
Type: forms.FieldTypeCaptcha,
CaptchaSiteKey: wc.captchaKey(),
},
},
BackLink: "/user",
BackLink: "/login",
SubmitLabel: "Submit",
}
if f.ReadInput(r) {
if f.FieldVal("new_password1") != f.FieldVal("new_password2") {
f.SubmitMessages = []template.HTML{
"Password verification failed. Please enter the same " +
"password in both new password fields"}
return f
}
// Passwords match, send the request and fill in the response in the
// form
_, err := td.PixelAPI.UserPasswordSet(
f.FieldVal("old_password"),
f.FieldVal("new_password1"),
)
if err != nil {
if err := td.PixelAPI.UserPasswordReset(f.FieldVal("email"), f.FieldVal("recaptcha_response")); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
@@ -230,9 +222,8 @@ func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f form
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! Your password has been updated"}
f.SubmitMessages = []template.HTML{"Success! E-mail sent"}
}
}
return f

View File

@@ -0,0 +1,188 @@
package webcontroller
import (
"html/template"
"net/http"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
"github.com/julienschmidt/httprouter"
)
func (wc *WebController) serveUserSettings(
w http.ResponseWriter,
r *http.Request,
p httprouter.Params,
) {
td := wc.newTemplateData(w, r)
if !td.Authenticated {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
td.Title = "Account settings"
td.Other = struct {
PasswordForm forms.Form
EmailForm forms.Form
UsernameForm forms.Form
}{
PasswordForm: wc.passwordForm(td, r),
EmailForm: wc.emailForm(td, r),
UsernameForm: wc.usernameForm(td, r),
}
wc.templates.Get().ExecuteTemplate(w, "user_settings", td)
}
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
Name: "password_change",
Title: "Change password",
Fields: []forms.Field{
{
Name: "old_password",
Label: "Old Password",
Type: forms.FieldTypeCurrentPassword,
}, {
Name: "new_password1",
Label: "New Password",
Type: forms.FieldTypeNewPassword,
}, {
Name: "new_password2",
Label: "New Password again",
Description: "we need you to repeat your password so you " +
"won't be locked out of your account if you make a " +
"typing error",
Type: forms.FieldTypeNewPassword,
},
},
SubmitLabel: "Submit",
}
if f.ReadInput(r) {
if f.FieldVal("new_password1") != f.FieldVal("new_password2") {
f.SubmitMessages = []template.HTML{
"Password verification failed. Please enter the same " +
"password in both new password fields"}
return f
}
// Passwords match, send the request and fill in the response in the
// form
if err := td.PixelAPI.UserPasswordSet(
f.FieldVal("old_password"),
f.FieldVal("new_password1"),
); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! Your password has been updated"}
}
}
return f
}
func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
Name: "email_change",
Title: "Change e-mail address",
Fields: []forms.Field{
{
Name: "new_email",
Label: "New e-mail address",
Description: "we will send an e-mail to the new address to " +
"verify that it's real. The address will be saved once " +
"the link in the message is clicked. If the e-mail " +
"doesn't arrive right away please check your spam box too",
Type: forms.FieldTypeEmail,
},
},
SubmitLabel: "Submit",
}
if f.ReadInput(r) {
if err := td.PixelAPI.UserEmailReset(
f.FieldVal("new_email"),
false,
); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! E-mail sent"}
}
}
return f
}
func (wc *WebController) serveEmailConfirm(
w http.ResponseWriter,
r *http.Request,
p httprouter.Params,
) {
var status string
if key, err := wc.getAPIKey(r); err == nil {
err = pixelapi.New(wc.conf.APIURLInternal, key).UserEmailResetConfirm(r.FormValue("key"))
if err != nil && err.Error() == "not_found" {
status = "not_found"
} else if err != nil {
status = "internal_error"
} else {
status = "success"
}
}
td := wc.newTemplateData(w, r)
td.Other = status
wc.templates.Get().ExecuteTemplate(w, "email_confirm", td)
}
func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
Name: "username_change",
Title: "Change username",
Fields: []forms.Field{
{
Name: "new_username",
Label: "New username",
Description: "changing your username also changes the name " +
"used to log in. If you forget your username you can " +
"still log in using your e-mail address if you have one " +
"configured",
Type: forms.FieldTypeUsername,
},
},
SubmitLabel: "Submit",
}
if f.ReadInput(r) {
if err := td.PixelAPI.UserSetUsername(f.FieldVal("new_username")); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{template.HTML(
"Success! You are now " + f.FieldVal("new_username"),
)}
}
}
return f
}

View File

@@ -78,7 +78,9 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
r.GET(p+"/register" /* */, wc.serveForm(wc.registerForm, false))
r.POST(p+"/register" /* */, wc.serveForm(wc.registerForm, false))
r.GET(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
r.POST(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
r.POST(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
r.GET(p+"/password_reset" /* */, wc.serveForm(wc.passwordResetForm, false))
r.POST(p+"/password_reset" /* */, wc.serveForm(wc.passwordResetForm, false))
r.GET(p+"/logout" /* */, wc.serveTemplate("logout", true))
r.POST(p+"/logout" /* */, wc.serveLogout)
r.GET(p+"/user" /* */, wc.serveTemplate("user_home", true))
@@ -87,11 +89,14 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
r.GET(p+"/user/filemanager" /**/, wc.serveTemplate("file_manager", true))
// User account settings
r.GET(p+"/user/settings" /* */, wc.serveTemplate("user_settings", true))
r.GET(p+"/user/change_password" /* */, wc.serveForm(wc.passwordForm, true))
r.POST(p+"/user/change_password" /**/, wc.serveForm(wc.passwordForm, true))
r.GET(p+"/user/settings" /* */, wc.serveUserSettings)
r.POST(p+"/user/settings" /* */, wc.serveUserSettings)
r.GET(p+"/user/confirm_email" /**/, wc.serveEmailConfirm)
r.GET(p+"/admin", wc.serveTemplate("admin_panel", true))
// Admin settings
r.GET(p+"/admin" /* */, wc.serveTemplate("admin_panel", true))
r.GET(p+"/admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, true))
r.POST(p+"/admin/globals" /**/, wc.serveForm(wc.adminGlobalsForm, true))
r.NotFound = http.HandlerFunc(wc.serveNotFound)