diff --git a/pixelapi/pixelapi.go b/pixelapi/pixelapi.go index 4874b16..ab2ca91 100644 --- a/pixelapi/pixelapi.go +++ b/pixelapi/pixelapi.go @@ -44,7 +44,8 @@ func (e Error) Error() string { return e.Value } // SuccessResponse is a generic response the API returns when the action was // successful and there is nothing interesting to report type SuccessResponse struct { - Success bool `json:"success"` + Success bool `json:"success"` + Message string `json:"message"` } func (p *PixelAPI) jsonRequest(method, url string, target interface{}) error { @@ -72,7 +73,7 @@ func (p *PixelAPI) jsonRequest(method, url string, target interface{}) error { } defer resp.Body.Close() - return parseJSONResponse(resp, target) + return parseJSONResponse(resp, target, true) } func (p *PixelAPI) getString(url string) (string, error) { @@ -84,8 +85,6 @@ func (p *PixelAPI) getString(url string) (string, error) { req.SetBasicAuth("", p.apiKey) } - client := &http.Client{} - resp, err := client.Do(req) if err != nil { return "", err @@ -115,10 +114,16 @@ func (p *PixelAPI) getRaw(url string) (io.ReadCloser, error) { return resp.Body, err } -func (p *PixelAPI) postForm(url string, vals url.Values, target interface{}) error { - req, err := http.NewRequest("POST", url, strings.NewReader(vals.Encode())) +func (p *PixelAPI) form( + method string, + url string, + vals url.Values, + target interface{}, + catchErrors bool, +) error { + req, err := http.NewRequest(method, url, strings.NewReader(vals.Encode())) if err != nil { - return &Error{ + return Error{ ReqError: true, Success: false, Value: err.Error(), @@ -129,9 +134,11 @@ func (p *PixelAPI) postForm(url string, vals url.Values, target interface{}) err req.SetBasicAuth("", p.apiKey) } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := client.Do(req) if err != nil { - return &Error{ + return Error{ ReqError: true, Success: false, Value: err.Error(), @@ -140,22 +147,22 @@ func (p *PixelAPI) postForm(url string, vals url.Values, target interface{}) err } defer resp.Body.Close() - return parseJSONResponse(resp, target) + return parseJSONResponse(resp, target, catchErrors) } -func parseJSONResponse(resp *http.Response, target interface{}) error { +func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool) error { var jdec = json.NewDecoder(resp.Body) var err error // Test for client side and server side errors - if resp.StatusCode >= 400 { - var errResp = &Error{ + if catchErrors && resp.StatusCode >= 400 { + var errResp = Error{ ReqError: false, } err = jdec.Decode(&errResp) if err != nil { log.Error("Can't decode this: %v", err) - return &Error{ + return Error{ ReqError: true, Success: false, Value: err.Error(), @@ -169,7 +176,7 @@ func parseJSONResponse(resp *http.Response, target interface{}) error { if err != nil { r, _ := ioutil.ReadAll(resp.Body) log.Error("Can't decode this: %v. %s", err, r) - return &Error{ + return Error{ ReqError: true, Success: false, Value: err.Error(), diff --git a/pixelapi/user.go b/pixelapi/user.go index d08e23a..7df6bd4 100644 --- a/pixelapi/user.go +++ b/pixelapi/user.go @@ -5,22 +5,24 @@ import ( "net/url" ) +// Registration is the response to the UserRegister API. The register API can +// return multiple errors, which will be stored in the Errors array. Check for +// len(Errors) == 0 to see if an error occurred type Registration struct { - Success bool `json:"success"` - Message string `json:"message,omitempty"` - Errors []RegistrationError `json:"errors,omitempty"` -} - -type RegistrationError struct { - Code string `json:"error_code"` - Message string `json:"message"` + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Errors []Error `json:"errors,omitempty"` } // UserRegister registers a new user on the Pixeldrain server. username and -// password are always required. email is optional, but without it you will -// never be able to reset your password in case you forget it. captcha depends -// on whether reCaptcha is enabled on the Pixeldrain server, this can be checked +// password are always required. email is optional, but without it you will not +// be able to reset your password in case you forget it. captcha depends on +// whether reCaptcha is enabled on the Pixeldrain server, this can be checked // through the GetRecaptcha function. +// +// The register API can return multiple errors, which will be stored in the +// Errors array. Check for len(Errors) == 0 to see if an error occurred. If err +// != nil it means a connection error occurred func (p *PixelAPI) UserRegister(username, email, password, captcha string) (resp *Registration, err error) { resp = &Registration{} var form = url.Values{} @@ -28,13 +30,38 @@ func (p *PixelAPI) UserRegister(username, email, password, captcha string) (resp form.Add("email", email) form.Add("password", password) form.Add("recaptcha_response", captcha) - err = p.postForm(p.apiEndpoint+"/user/register", form, resp) + err = p.form("POST", p.apiEndpoint+"/user/register", form, resp, false) if err != nil { return nil, err } return resp, nil } +// Login is the success response to the `user/login` API +type Login struct { + Success bool `json:"success"` + APIKey string `json:"api_key"` +} + +// UserLogin logs a user in with the provided credentials. The response will +// contain the returned API key. If saveKey is true the API key will also be +// saved in the client and following requests with this client will be +// autenticated +func (p *PixelAPI) UserLogin(username, password string, saveKey bool) (resp *Login, err error) { + resp = &Login{} + var form = url.Values{} + form.Add("username", username) + form.Add("password", password) + err = p.form("POST", p.apiEndpoint+"/user/login", form, resp, true) + if err != nil { + return nil, err + } + if saveKey { + p.apiKey = resp.APIKey + } + return resp, nil +} + // UserInfo contains information about the logged in user type UserInfo struct { Success bool `json:"success"` @@ -97,3 +124,15 @@ func (p *PixelAPI) UserLists(page, limit int) (resp *UserLists, err error) { } return resp, nil } + +func (p *PixelAPI) UserPasswordSet(oldPW, newPW string) (resp *SuccessResponse, err error) { + resp = &SuccessResponse{} + var form = url.Values{} + form.Add("old_password", oldPW) + form.Add("new_password", newPW) + err = p.form("PUT", p.apiEndpoint+"/user/password", form, resp, true) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/res/static/style/layout.css b/res/static/style/layout.css index 236b09f..3590028 100644 --- a/res/static/style/layout.css +++ b/res/static/style/layout.css @@ -103,7 +103,7 @@ body{ font-family: "Lato Thin", sans-serif; font-weight: bold; font-size: 1.8em; - transition: box-shadow 2s; + transition: box-shadow 5s; } .navigation a:hover { background: linear-gradient(var(--highlight_color), var(--highlight_color_dark)); diff --git a/res/template/account/login.html b/res/template/account/login.html deleted file mode 100644 index 4007995..0000000 --- a/res/template/account/login.html +++ /dev/null @@ -1,69 +0,0 @@ -{{define "login"}} - - - - {{template "meta_tags" "Login"}} - {{template "user_style" .}} - - - -
- {{template "menu" .}} - -

Log in to your PixelDrain account

-
-
- - - - - - - - - - - - -
Username / e-mail
Password
-
-
- If you don't have a PixelDrain account yet, you can register here. No e-mail address is required.
- {{template "footer"}} -
- - - {{template "analytics"}} - - -{{end}} diff --git a/res/template/account/logout.html b/res/template/account/logout.html index 6aa9a48..e05f4f4 100644 --- a/res/template/account/logout.html +++ b/res/template/account/logout.html @@ -8,18 +8,18 @@
{{template "menu" .}} -

Please confirm that you want to log out of your Pixeldrain account

+

Please confirm that you want to log out of your pixeldrain account


Why do I need to confirm my logout?

- We need you to confirm your action here so we can be sure that - you really requested a logout. If we didn't do this, anyone (or - any website) would be able to send you to this page and you - would automatically get logged out of Pixeldrain, which would be - very annoying. + We need you to confirm your action so we can be sure that you + really requested a logout. If we didn't do this, anyone (or any + website) would be able to send you to this page and you would + automatically get logged out of pixeldrain, which would be very + annoying.

To prevent this from happening we're verifying that you actually diff --git a/res/template/account/user_home.html b/res/template/account/user_home.html index db769a0..9771c01 100644 --- a/res/template/account/user_home.html +++ b/res/template/account/user_home.html @@ -11,6 +11,13 @@ {{template "menu" .}}

Welcome home, {{.Username}}!

+ +

Actions

+ +

Your most recently uploaded files:

{{$files := .PixelAPI.UserFiles 0 18}} diff --git a/res/template/account/user_settings.html b/res/template/account/user_settings.html index ffb037b..eb7e8e0 100644 --- a/res/template/account/user_settings.html +++ b/res/template/account/user_settings.html @@ -10,8 +10,10 @@
{{template "menu" .}}

User configuration

- - +

What would you like to do?

+ {{template "footer"}}
diff --git a/res/template/admin.html b/res/template/admin.html new file mode 100644 index 0000000..5f818af --- /dev/null +++ b/res/template/admin.html @@ -0,0 +1,21 @@ +{{define "widgets"}} + + + + {{template "meta_tags" "Administrator panel"}} + {{template "user_style" .}} + + + + Header image +
+
+ {{template "menu" .}} + +

System statistics

+ + {{template "footer"}} +
+ + +{{end}} diff --git a/res/template/fragments/form.html b/res/template/fragments/form.html new file mode 100644 index 0000000..854b1f5 --- /dev/null +++ b/res/template/fragments/form.html @@ -0,0 +1,106 @@ +{{define "form"}} +

{{.Title}}

+ {{.PreFormHTML}} + {{if eq .Submitted true}} + {{if eq .SubmitSuccess true}} +
+ {{index .SubmitMessages 0}} +
+ {{else}} +
+ Something went wrong, please correct these errors before continuing:
+
    + {{range $msg := .SubmitMessages}} +
  • {{$msg}}
  • + {{end}} +
+
+ {{end}} + {{end}} + +
+ + {{if ne .Username ""}} + + + {{end}} + + {{range $index, $field := .Fields}} + + + + {{if or (ne $field.Description "") (eq $field.Separator true)}} + + + + {{end}} + + {{end}} + + {{if eq .BackLink ""}} + + {{else}} + + + {{end}} + +
{{$field.Label}} + {{if eq $field.Type "text"}} + + {{else if eq $field.Type "username"}} + + {{else if eq $field.Type "email"}} + + {{else if eq $field.Type "current-password"}} + + {{else if eq $field.Type "new-password"}} + + {{else if eq $field.Type "captcha"}} + +
+ {{end}} +
+ {{$field.Description}} + {{if eq $field.Separator true}} +
+ {{end}} +
+ {{if eq .SubmitRed true}} + + {{else}} + + {{end}} + + Back + + {{if eq .SubmitRed true}} + + {{else}} + + {{end}} +
+
+ {{.PostFormHTML}} +{{end}} +{{define "form_page"}} + + + + {{template "meta_tags" .Title}} + {{template "user_style" .}} + + + + +
+ {{template "menu" .}} + + {{template "form" .Form}} + + {{template "footer"}} +
+ + {{template "analytics"}} + + +{{end}} diff --git a/webcontroller/forms/form.go b/webcontroller/forms/form.go new file mode 100644 index 0000000..3f23e41 --- /dev/null +++ b/webcontroller/forms/form.go @@ -0,0 +1,127 @@ +package forms + +import ( + "fmt" + "html/template" + "net/http" +) + +// Form is a form which can be rendered in HTML and submitted +type Form struct { + // Name of the form. When this form is submitted this name will be in the `form` parameter + Name string + + Title string // Shown in a large font above the form + PreFormHTML template.HTML // Content to be rendered above the form + + Fields []Field + + BackLink string // Empty for no back link + SubmitLabel string // Label for the submit button + SubmitRed bool // If the submit button should be red or green + + PostFormHTML template.HTML // Content to be rendered below the form + + // Fields to render if the form has been submitted once + Submitted bool // If the form has been submitted + SubmitSuccess bool // If the submission was a success + SubmitMessages []template.HTML // Messages telling the user the results + + // Used for letting the browser know which user is logged in + Username string + + // Actions to perform when the form is rendered + Extra ExtraActions +} + +// Field is a single input field in a form +type Field struct { + // Used for reading the data. Entered data is POSTed back to the same URL with this name + Name string + + // Is entered in the input field by default. If this is empty when running + // Form.ReadInput() it will be set to the value entered by the user + DefaultValue string + + // The value entered by the user. Filled in when running Form.ReadInput() + EnteredValue string + + // Text next to the input field + Label string + + // Text below the input field + Description string + + // Separates fields with a horizontal rule + Separator bool + + Type FieldType + + // Only used when Type == FieldTypeCaptcha + CaptchaSiteKey string +} + +// ExtraActions contains extra actions to performs when rendering the form +type ExtraActions struct { + // Redirects the browser to a different URL with a HTTP 303: See Other + // status. This is useful for redirecting the user to a different page if + // the form submission was successful + RedirectTo string + + // A cookie to install in the browser when the form is rendered. Useful for + // setting / destroying user sessions or configurations + SetCookie *http.Cookie +} + +// FieldType defines the type a form field has and how it should be rendered +type FieldType string + +// Fields which can be in a form +const ( + FieldTypeText FieldType = "text" + FieldTypeUsername FieldType = "username" + FieldTypeEmail FieldType = "email" + FieldTypeCurrentPassword FieldType = "current-password" + FieldTypeNewPassword FieldType = "new-password" + FieldTypeCaptcha FieldType = "captcha" +) + +// ReadInput reads the form of a request and fills in the values for each field. +// The return value will be true if this form was submitted and false if the +// form was not submitted +func (f *Form) ReadInput(r *http.Request) (success bool) { + if r.FormValue("form") != f.Name { + f.Submitted = false + return false + } + f.Submitted = true + + for i, field := range f.Fields { + field.EnteredValue = r.FormValue(field.Name) + + if field.DefaultValue == "" { + field.DefaultValue = field.EnteredValue + } + + if field.Type == FieldTypeCaptcha && field.EnteredValue == "" { + field.EnteredValue = r.FormValue("g-recaptcha-response") + } + + f.Fields[i] = field // Update the new values in the array + } + + return true +} + +// FieldVal is a utility function for getting the entered value of a field by +// its name. By using this function you don't have to use nondescriptive array +// indexes to get the values. It panics if the field name is not found in the +// form +func (f *Form) FieldVal(name string) (enteredValue string) { + for _, field := range f.Fields { + if field.Name == name { + return field.EnteredValue + } + } + panic(fmt.Errorf("FieldVal called on unregistered field name '%s'", name)) +} diff --git a/webcontroller/template_data.go b/webcontroller/template_data.go index d229e01..e60c614 100644 --- a/webcontroller/template_data.go +++ b/webcontroller/template_data.go @@ -7,6 +7,7 @@ import ( "time" "fornaxian.com/pixeldrain-web/pixelapi" + "fornaxian.com/pixeldrain-web/webcontroller/forms" "github.com/Fornaxian/log" ) @@ -19,12 +20,15 @@ type TemplateData struct { APIEndpoint template.URL PixelAPI *pixelapi.PixelAPI - Other interface{} - URLQuery url.Values - // Only used on file viewer page Title string OGData OGData + + Other interface{} + URLQuery url.Values + + // Only used for pages containing forms + Form forms.Form } func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request) *TemplateData { diff --git a/webcontroller/user_account.go b/webcontroller/user_account.go index f1bdaf1..e0f0c1b 100644 --- a/webcontroller/user_account.go +++ b/webcontroller/user_account.go @@ -1,9 +1,12 @@ package webcontroller import ( + "html/template" "net/http" + "time" "fornaxian.com/pixeldrain-web/pixelapi" + "fornaxian.com/pixeldrain-web/webcontroller/forms" "github.com/Fornaxian/log" "github.com/julienschmidt/httprouter" ) @@ -37,3 +40,213 @@ func (wc *WebController) serveLogout( http.Redirect(w, r, "/", http.StatusSeeOther) } + +func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f forms.Form) { + // This only runs on the first request + if wc.captchaSiteKey == "" { + capt, err := td.PixelAPI.GetRecaptcha() + if err != nil { + log.Error("Error getting recaptcha key: %s", err) + f.SubmitMessages = []template.HTML{ + "An internal server error had occurred. Registration is " + + "unavailable at the moment. Please return later", + } + return f + } + if capt.SiteKey == "" { + wc.captchaSiteKey = "none" + } else { + wc.captchaSiteKey = capt.SiteKey + } + } + + // Construct the form + td.Title = "Register a new Pixeldrain account" + f = forms.Form{ + Name: "register", + Title: td.Title, + Fields: []forms.Field{ + { + Name: "username", + Label: "Username", + Description: "used for logging into your account", + Separator: true, + Type: forms.FieldTypeUsername, + }, { + Name: "e-mail", + Label: "E-mail address", + Description: "not required. your e-mail address will only be " + + "used for password resets and important account " + + "notifications", + Separator: true, + Type: forms.FieldTypeEmail, + }, { + Name: "password1", + Label: "Password", + Type: forms.FieldTypeNewPassword, + }, { + Name: "password2", + Label: "Password verification", + Description: "you need to enter your password twice so we " + + "can verify that no typing errors were made, which would " + + "prevent you from logging into your new account", + Separator: true, + Type: forms.FieldTypeNewPassword, + }, { + 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 to flood the " + + "website with fake accounts", + Separator: true, + Type: forms.FieldTypeCaptcha, + CaptchaSiteKey: wc.captchaKey(), + }, + }, + BackLink: "/", + SubmitLabel: "Register", + PostFormHTML: template.HTML("

Welcome to the club!

"), + } + + if f.ReadInput(r) { + if f.FieldVal("password1") != f.FieldVal("password2") { + f.SubmitMessages = []template.HTML{ + "Password verification failed. Please enter the same " + + "password in both password fields"} + return f + } + log.Debug("capt: %s", f.FieldVal("recaptcha_response")) + resp, err := td.PixelAPI.UserRegister( + f.FieldVal("username"), + f.FieldVal("e-mail"), + f.FieldVal("password1"), + f.FieldVal("recaptcha_response"), + ) + if 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 if len(resp.Errors) != 0 { + // Registration errors occurred + for _, rerr := range resp.Errors { + f.SubmitMessages = append(f.SubmitMessages, template.HTML(rerr.Message)) + } + } else { + // Request was a success + f.SubmitSuccess = true + f.SubmitMessages = []template.HTML{ + `Registration completed! You can now log in ` + + `to your account.
We're glad to have you on ` + + `board, have fun sharing!`} + } + } + return f +} + +func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.Form) { + td.Title = "Login" + f = forms.Form{ + Name: "login", + Title: "Log in to your pixeldrain account", + Fields: []forms.Field{ + { + Name: "username", + Label: "Username / e-mail", + Type: forms.FieldTypeUsername, + }, { + Name: "password", + Label: "Password", + Type: forms.FieldTypeCurrentPassword, + }, + }, + BackLink: "/", + SubmitLabel: "Login", + PostFormHTML: template.HTML( + `
If you don't have a pixeldrain account yet, you can ` + + `register here. No e-mail address is ` + + `required.
`, + ), + } + + if f.ReadInput(r) { + loginResp, err := td.PixelAPI.UserLogin(f.FieldVal("username"), f.FieldVal("password"), false) + if 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 { + log.Debug("key %s", loginResp.APIKey) + // Request was a success + f.SubmitSuccess = true + f.SubmitMessages = []template.HTML{"Success!"} + f.Extra.SetCookie = &http.Cookie{ + Name: "pd_auth_key", + Value: loginResp.APIKey, + Path: "/", + Expires: time.Now().AddDate(50, 0, 0), + } + f.Extra.RedirectTo = "/user" + } + } + return f +} + +func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) { + td.Title = "Change Password" + f = forms.Form{ + Name: "password_change", + Title: td.Title, + 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 verification", + Type: forms.FieldTypeCurrentPassword, + }, + }, + BackLink: "/user", + 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 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 +} diff --git a/webcontroller/web_controller.go b/webcontroller/web_controller.go index b5cbfe4..303396f 100644 --- a/webcontroller/web_controller.go +++ b/webcontroller/web_controller.go @@ -8,6 +8,7 @@ import ( "fornaxian.com/pixeldrain-web/init/conf" "fornaxian.com/pixeldrain-web/pixelapi" + "fornaxian.com/pixeldrain-web/webcontroller/forms" "github.com/Fornaxian/log" "github.com/julienschmidt/httprouter" ) @@ -48,20 +49,24 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon }) // General navigation - r.GET(p+"/" /* */, wc.serveTemplate("home", false)) - r.GET(p+"/favicon.ico" /* */, wc.serveFile("/favicon.ico")) - r.GET(p+"/api" /* */, wc.serveTemplate("apidoc", false)) - r.GET(p+"/history" /* */, wc.serveTemplate("history_cookies", false)) - r.GET(p+"/u/:id" /* */, wc.serveFileViewer) - r.GET(p+"/u/:id/preview" /* */, wc.serveFilePreview) - r.GET(p+"/l/:id" /* */, wc.serveListViewer) - r.GET(p+"/t" /* */, wc.serveTemplate("paste", false)) - r.GET(p+"/donation" /* */, wc.serveTemplate("donation", false)) - r.GET(p+"/widgets" /* */, wc.serveTemplate("widgets", false)) + r.GET(p+"/" /* */, wc.serveTemplate("home", false)) + r.GET(p+"/favicon.ico" /* */, wc.serveFile("/favicon.ico")) + r.GET(p+"/api" /* */, wc.serveTemplate("apidoc", false)) + r.GET(p+"/history" /* */, wc.serveTemplate("history_cookies", false)) + r.GET(p+"/u/:id" /* */, wc.serveFileViewer) + r.GET(p+"/u/:id/preview" /**/, wc.serveFilePreview) + r.GET(p+"/l/:id" /* */, wc.serveListViewer) + r.GET(p+"/t" /* */, wc.serveTemplate("paste", false)) + r.GET(p+"/donation" /* */, wc.serveTemplate("donation", false)) + r.GET(p+"/widgets" /* */, wc.serveTemplate("widgets", false)) // User account pages - r.GET(p+"/register" /* */, wc.serveRegister) - r.GET(p+"/login" /* */, wc.serveTemplate("login", false)) + r.GET(p+"/register_old" /* */, wc.serveRegister) + 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.GET(p+"/login" /* */, wc.serveTemplate("login", false)) r.GET(p+"/logout" /* */, wc.serveTemplate("logout", true)) r.POST(p+"/logout" /* */, wc.serveLogout) r.GET(p+"/user" /* */, wc.serveTemplate("user_home", true)) @@ -69,6 +74,11 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon r.GET(p+"/user/lists" /* */, wc.serveTemplate("user_lists", true)) 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.NotFound = http.HandlerFunc(wc.serveNotFound) return wc @@ -105,6 +115,66 @@ func (wc *WebController) serveFile(path string) httprouter.Handle { } } +func (wc *WebController) serveForm( + handler func(*TemplateData, *http.Request) forms.Form, + requireAuth bool, +) httprouter.Handle { + return func( + w http.ResponseWriter, + r *http.Request, + p httprouter.Params, + ) { + var td = wc.newTemplateData(w, r) + if requireAuth && !td.Authenticated { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + // The handler retuns the form which will be rendered + td.Form = handler(td, r) + + td.Form.Username = td.Username + + // Execute the extra actions if any + if td.Form.Extra.SetCookie != nil { + http.SetCookie(w, td.Form.Extra.SetCookie) + } + if td.Form.Extra.RedirectTo != "" { + http.Redirect(w, r, td.Form.Extra.RedirectTo, http.StatusSeeOther) + log.Debug("redirect: %s", td.Form.Extra.RedirectTo) + return // Don't need to render a form if the user is redirected + } + + // Remove the recaptcha field if captcha is disabled + if wc.captchaKey() == "none" { + for i, field := range td.Form.Fields { + if field.Type == forms.FieldTypeCaptcha { + td.Form.Fields = append( + td.Form.Fields[:i], + td.Form.Fields[i+1:]..., + ) + } + } + } + + // Clear the entered values if the request was successful + if td.Form.SubmitSuccess { + w.WriteHeader(http.StatusOK) + for i, field := range td.Form.Fields { + field.EnteredValue = "" + td.Form.Fields[i] = field + } + } else { + w.WriteHeader(http.StatusBadRequest) + } + + err := wc.templates.Get().ExecuteTemplate(w, "form_page", td) + if err != nil { + log.Error("Error executing form page: %s", err) + } + } +} + func (wc *WebController) serveNotFound(w http.ResponseWriter, r *http.Request) { log.Debug("Not Found: %s", r.URL) w.WriteHeader(http.StatusNotFound)