Merge branch 'easy-forms'

This commit is contained in:
2019-03-31 22:35:01 +02:00
13 changed files with 646 additions and 119 deletions

View File

@@ -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(),

View File

@@ -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
}

View File

@@ -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));

View File

@@ -1,69 +0,0 @@
{{define "login"}}
<!DOCTYPE html>
<html>
<head>
{{template "meta_tags" "Login"}}
{{template "user_style" .}}
<script type="text/javascript">var apiEndpoint = '{{.APIEndpoint}}';</script>
</head>
<body>
<div id='body' class="body">
{{template "menu" .}}
<h1>Log in to your PixelDrain account</h1>
<div id="submit_result"></div>
<form onSubmit="return submitForm();" class="highlight_dark border_top border_bottom">
<table class="form">
<tr class="form">
<td>Username / e-mail</td>
<td><input id="username" name="username" type="text" autocomplete="username" value="" class="form_input"/></td>
</tr>
<tr class="form">
<td>Password</td>
<td><input id="password" name="password" type="password" autocomplete="current-password" class="form_input"/></td>
</tr>
<tr class="form">
<td colspan=2 style="text-align: right;"><input type="submit" value="Login" class="button_highlight"/></td>
</tr>
</table>
</form>
<br/>
If you don't have a PixelDrain account yet, you can <a href="/register">register here</a>. No e-mail address is required.<br/>
{{template "footer"}}
</div>
<script type="text/javascript">
function submitForm(){
var req = new XMLHttpRequest();
req.onreadystatechange = function(){
if (this.readyState === 4) {
var response = JSON.parse(req.responseText);
var resultDiv = document.getElementById("submit_result");
if (response.success) {
resultDiv.className = "border_top border_bottom highlight_green";
resultDiv.innerHTML = 'Success! Proceeding to user portal...<br/>'
+'<a href="/user">Click here if you are not redirected automatically</a>';
window.location.href = "/user";
} else {
resultDiv.className = "border_top border_bottom highlight_red";
resultDiv.innerHTML = response.message;
}
console.log(response);
}
}
var data = new FormData();
data.append("username", document.getElementById("username").value);
data.append("password", document.getElementById("password").value);
req.open("POST", apiEndpoint+"/user/login", true);
req.send(data);
return false;
}
</script>
{{template "analytics"}}
</body>
</html>
{{end}}

View File

@@ -8,18 +8,18 @@
<div id='body' class="body">
{{template "menu" .}}
<h1>Please confirm that you want to log out of your Pixeldrain account</h1>
<h1>Please confirm that you want to log out of your pixeldrain account</h1>
<form method="POST" action="/logout" class="highlight_light border_top border_bottom">
<input type="submit" value="I want to log out of pixeldrain on this computer" class="button_highlight"/>
</form>
<br/>
<h2>Why do I need to confirm my logout?</h2>
<p>
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.
</p>
<p>
To prevent this from happening we're verifying that you actually

View File

@@ -11,6 +11,13 @@
{{template "menu" .}}
<h1 class="highlight_middle border_bottom">Welcome home, {{.Username}}!</h1>
<h2>Actions</h2>
<ul>
<li><a href="/user/change_password">Change my password</a></li>
<li><a href="/logout">Log out</a></li>
</ul>
<h2>Your most recently uploaded files:</h2>
<div class="highlight_dark border_top border_bottom">
{{$files := .PixelAPI.UserFiles 0 18}}

View File

@@ -10,8 +10,10 @@
<div id='body' class="body">
{{template "menu" .}}
<h1>User configuration</h1>
<p>What would you like to do?</p>
<ul>
<li><a href="/user/change_password">Change my password</a></li>
</ul>
{{template "footer"}}
</div>

21
res/template/admin.html Normal file
View File

@@ -0,0 +1,21 @@
{{define "widgets"}}
<!DOCTYPE html>
<html>
<head>
{{template "meta_tags" "Administrator panel"}}
{{template "user_style" .}}
<script type="text/javascript">var apiEndpoint = '{{.APIEndpoint}}';</script>
</head>
<body>
<img id="header_image" class="header_image" src="/res/img/header_neuropol.png" alt="Header image"/>
<br/>
<div id="body" class="body">
{{template "menu" .}}
<h1>System statistics</h1>
{{template "footer"}}
</div>
</body>
</html>
{{end}}

View File

@@ -0,0 +1,106 @@
{{define "form"}}
<h1>{{.Title}}</h1>
{{.PreFormHTML}}
{{if eq .Submitted true}}
{{if eq .SubmitSuccess true}}
<div id="submit_result" class="highlight_green border_top border_bottom">
{{index .SubmitMessages 0}}
</div>
{{else}}
<div id="submit_result" class="highlight_red border_top border_bottom">
Something went wrong, please correct these errors before continuing:<br/>
<ul>
{{range $msg := .SubmitMessages}}
<li>{{$msg}}</li>
{{end}}
</ul>
</div>
{{end}}
{{end}}
<form class="highlight_dark border_top border_bottom" method="POST">
<input type="text" name="form" value="{{.Name}}" style="display: none;" readonly="readonly"/>
{{if ne .Username ""}}
<!-- The invisible username field is so browsers know which user the form was for -->
<input type="text" autocomplete="username" value="{{.Username}}" style="display: none;" readonly="readonly"/>
{{end}}
<table style="margin-left: auto; margin-right: auto; text-align: left; max-width: 30em;">
{{range $index, $field := .Fields}}
<tr class="form">
<td>{{$field.Label}}</td>
<td>
{{if eq $field.Type "text"}}
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" class="form_input"/>
{{else if eq $field.Type "username"}}
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" autocomplete="username" class="form_input"/>
{{else if eq $field.Type "email"}}
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="email" autocomplete="email" class="form_input"/>
{{else if eq $field.Type "current-password"}}
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="password" autocomplete="current-password" class="form_input"/>
{{else if eq $field.Type "new-password"}}
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="password" autocomplete="new-password" class="form_input"/>
{{else if eq $field.Type "captcha"}}
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-theme="dark" data-sitekey="{{$field.CaptchaSiteKey}}"></div>
{{end}}
</td>
{{if or (ne $field.Description "") (eq $field.Separator true)}}
<tr class="form">
<td colspan="2">
{{$field.Description}}
{{if eq $field.Separator true}}
<hr/>
{{end}}
</td>
</tr>
{{end}}
</tr>
{{end}}
<tr class="form">
{{if eq .BackLink ""}}
<td colspan="2" style="text-align: right;">
{{if eq .SubmitRed true}}
<input type="submit" value="{{.SubmitLabel}}" class="button_red"/>
{{else}}
<input type="submit" value="{{.SubmitLabel}}" class="button_highlight"/>
{{end}}
</td>
{{else}}
<td style="text-align: left;">
<a href="{{.BackLink}}" class="button button_red"/>Back</a>
</td>
<td style="text-align: right;">
{{if eq .SubmitRed true}}
<input type="submit" value="{{.SubmitLabel}}" class="button_red"/>
{{else}}
<input type="submit" value="{{.SubmitLabel}}" class="button_highlight"/>
{{end}}
</td>
{{end}}
</tr>
</table>
</form>
{{.PostFormHTML}}
{{end}}
{{define "form_page"}}
<!DOCTYPE html>
<html>
<head>
{{template "meta_tags" .Title}}
{{template "user_style" .}}
<script type="text/javascript">var apiEndpoint = '{{.APIEndpoint}}';</script>
</head>
<body>
<div id='body' class="body">
{{template "menu" .}}
{{template "form" .Form}}
{{template "footer"}}
</div>
{{template "analytics"}}
</body>
</html>
{{end}}

127
webcontroller/forms/form.go Normal file
View File

@@ -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))
}

View File

@@ -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 {

View File

@@ -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("<p>Welcome to the club!</p>"),
}
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 <a href="/login">log in ` +
`to your account</a>.<br/>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(
`<br/>If you don't have a pixeldrain account yet, you can ` +
`<a href="/register">register here</a>. No e-mail address is ` +
`required.<br/>`,
),
}
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
}

View File

@@ -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)