Support for optional emails.

Reduce boilerplate and allocations.
Fix the error shown on AccountEditEmailTokenSubmit when there aren't any emails rows.

Add register_account_email_optional phrase.
Add account_email_none phrase.
This commit is contained in:
Azareal 2019-10-30 08:13:45 +10:00
parent 146e5cff0d
commit d2be6b220e
11 changed files with 103 additions and 92 deletions

View File

@ -20,7 +20,7 @@ type DefaultEmailStore struct {
func NewDefaultEmailStore(acc *qgen.Accumulator) (*DefaultEmailStore, error) { func NewDefaultEmailStore(acc *qgen.Accumulator) (*DefaultEmailStore, error) {
return &DefaultEmailStore{ return &DefaultEmailStore{
getEmailsByUser: acc.Select("emails").Columns("email, validated, token").Where("uid = ?").Prepare(), getEmailsByUser: acc.Select("emails").Columns("email,validated,token").Where("uid=?").Prepare(),
// Need to fix this: Empty string isn't working, it gets set to 1 instead x.x -- Has this been fixed? // Need to fix this: Empty string isn't working, it gets set to 1 instead x.x -- Has this been fixed?
verifyEmail: acc.Update("emails").Set("validated = 1, token = ''").Where("email = ?").Prepare(), verifyEmail: acc.Update("emails").Set("validated = 1, token = ''").Where("email = ?").Prepare(),

View File

@ -91,7 +91,7 @@ func (s *MemoryGroupStore) LoadGroups() error {
s.groupCount = i s.groupCount = i
DebugLog("Binding the Not Loggedin Group") DebugLog("Binding the Not Loggedin Group")
GuestPerms = s.dirtyGetUnsafe(6).Perms GuestPerms = s.dirtyGetUnsafe(6).Perms // ! Race?
TopicListThaw.Thaw() TopicListThaw.Thaw()
return nil return nil
} }
@ -164,30 +164,30 @@ func (s *MemoryGroupStore) Reload(id int) error {
return nil return nil
} }
func (s *MemoryGroupStore) initGroup(group *Group) error { func (s *MemoryGroupStore) initGroup(g *Group) error {
err := json.Unmarshal(group.PermissionsText, &group.Perms) err := json.Unmarshal(g.PermissionsText, &g.Perms)
if err != nil { if err != nil {
log.Printf("group: %+v\n", group) log.Printf("group: %+v\n", g)
log.Print("bad group perms: ", group.PermissionsText) log.Print("bad group perms: ", g.PermissionsText)
return err return err
} }
DebugLogf(group.Name+": %+v\n", group.Perms) DebugLogf(g.Name+": %+v\n", g.Perms)
err = json.Unmarshal(group.PluginPermsText, &group.PluginPerms) err = json.Unmarshal(g.PluginPermsText, &g.PluginPerms)
if err != nil { if err != nil {
log.Printf("group: %+v\n", group) log.Printf("group: %+v\n", g)
log.Print("bad group plugin perms: ", group.PluginPermsText) log.Print("bad group plugin perms: ", g.PluginPermsText)
return err return err
} }
DebugLogf(group.Name+": %+v\n", group.PluginPerms) DebugLogf(g.Name+": %+v\n", g.PluginPerms)
//group.Perms.ExtData = make(map[string]bool) //group.Perms.ExtData = make(map[string]bool)
// TODO: Can we optimise the bit where this cascades down to the user now? // TODO: Can we optimise the bit where this cascades down to the user now?
if group.IsAdmin || group.IsMod { if g.IsAdmin || g.IsMod {
group.IsBanned = false g.IsBanned = false
} }
err = s.userCount.QueryRow(group.ID).Scan(&group.UserCount) err = s.userCount.QueryRow(g.ID).Scan(&g.UserCount)
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
return err return err
} }
@ -209,9 +209,9 @@ func (s *MemoryGroupStore) SetCanSee(gid int, canSee []int) error {
return nil return nil
} }
func (s *MemoryGroupStore) CacheSet(group *Group) error { func (s *MemoryGroupStore) CacheSet(g *Group) error {
s.Lock() s.Lock()
s.groups[group.ID] = group s.groups[g.ID] = g
s.Unlock() s.Unlock()
return nil return nil
} }
@ -234,7 +234,7 @@ func (s *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod b
} }
defer tx.Rollback() defer tx.Rollback()
insertTx, err := qgen.Builder.SimpleInsertTx(tx, "users_groups", "name, tag, is_admin, is_mod, is_banned, permissions, plugin_perms", "?,?,?,?,?,?,'{}'") insertTx, err := qgen.Builder.SimpleInsertTx(tx, "users_groups", "name,tag,is_admin,is_mod,is_banned,permissions,plugin_perms", "?,?,?,?,?,?,'{}'")
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -242,7 +242,6 @@ func (s *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod b
if err != nil { if err != nil {
return 0, err return 0, err
} }
gid64, err := res.LastInsertId() gid64, err := res.LastInsertId()
if err != nil { if err != nil {
return 0, err return 0, err

View File

@ -90,12 +90,13 @@ type DefaultPageStore struct {
} }
func NewDefaultPageStore(acc *qgen.Accumulator) (*DefaultPageStore, error) { func NewDefaultPageStore(acc *qgen.Accumulator) (*DefaultPageStore, error) {
pa := "pages"
return &DefaultPageStore{ return &DefaultPageStore{
get: acc.Select("pages").Columns("name, title, body, allowedGroups, menuID").Where("pid = ?").Prepare(), get: acc.Select(pa).Columns("name, title, body, allowedGroups, menuID").Where("pid = ?").Prepare(),
getByName: acc.Select("pages").Columns("pid, name, title, body, allowedGroups, menuID").Where("name = ?").Prepare(), getByName: acc.Select(pa).Columns("pid, name, title, body, allowedGroups, menuID").Where("name = ?").Prepare(),
getOffset: acc.Select("pages").Columns("pid, name, title, body, allowedGroups, menuID").Orderby("pid DESC").Limit("?,?").Prepare(), getOffset: acc.Select(pa).Columns("pid, name, title, body, allowedGroups, menuID").Orderby("pid DESC").Limit("?,?").Prepare(),
count: acc.Count("pages").Prepare(), count: acc.Count(pa).Prepare(),
delete: acc.Delete("pages").Where("pid = ?").Prepare(), delete: acc.Delete(pa).Where("pid = ?").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
@ -122,23 +123,23 @@ func (s *DefaultPageStore) parseAllowedGroups(raw string, page *CustomPage) erro
} }
func (s *DefaultPageStore) Get(id int) (*CustomPage, error) { func (s *DefaultPageStore) Get(id int) (*CustomPage, error) {
page := &CustomPage{ID: id} p := &CustomPage{ID: id}
rawAllowedGroups := "" rawAllowedGroups := ""
err := s.get.QueryRow(id).Scan(&page.Name, &page.Title, &page.Body, &rawAllowedGroups, &page.MenuID) err := s.get.QueryRow(id).Scan(&p.Name, &p.Title, &p.Body, &rawAllowedGroups, &p.MenuID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return page, s.parseAllowedGroups(rawAllowedGroups, page) return p, s.parseAllowedGroups(rawAllowedGroups, p)
} }
func (s *DefaultPageStore) GetByName(name string) (*CustomPage, error) { func (s *DefaultPageStore) GetByName(name string) (*CustomPage, error) {
page := BlankCustomPage() p := BlankCustomPage()
rawAllowedGroups := "" rawAllowedGroups := ""
err := s.getByName.QueryRow(name).Scan(&page.ID, &page.Name, &page.Title, &page.Body, &rawAllowedGroups, &page.MenuID) err := s.getByName.QueryRow(name).Scan(&p.ID, &p.Name, &p.Title, &p.Body, &rawAllowedGroups, &p.MenuID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return page, s.parseAllowedGroups(rawAllowedGroups, page) return p, s.parseAllowedGroups(rawAllowedGroups, p)
} }
func (s *DefaultPageStore) GetOffset(offset int, perPage int) (pages []*CustomPage, err error) { func (s *DefaultPageStore) GetOffset(offset int, perPage int) (pages []*CustomPage, err error) {
@ -149,17 +150,17 @@ func (s *DefaultPageStore) GetOffset(offset int, perPage int) (pages []*CustomPa
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
page := &CustomPage{ID: 0} p := &CustomPage{ID: 0}
rawAllowedGroups := "" rawAllowedGroups := ""
err := rows.Scan(&page.ID, &page.Name, &page.Title, &page.Body, &rawAllowedGroups, &page.MenuID) err := rows.Scan(&p.ID, &p.Name, &p.Title, &p.Body, &rawAllowedGroups, &p.MenuID)
if err != nil { if err != nil {
return pages, err return pages, err
} }
err = s.parseAllowedGroups(rawAllowedGroups, page) err = s.parseAllowedGroups(rawAllowedGroups, p)
if err != nil { if err != nil {
return pages, err return pages, err
} }
pages = append(pages, page) pages = append(pages, p)
} }
return pages, rows.Err() return pages, rows.Err()
} }

View File

@ -339,7 +339,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
} }
t.AddStd("login", "c.Page", Page{htitle("Login Page"), tList, nil}) t.AddStd("login", "c.Page", Page{htitle("Login Page"), tList, nil})
t.AddStd("register", "c.Page", Page{htitle("Registration Page"), tList, "nananana"}) t.AddStd("register", "c.Page", Page{htitle("Registration Page"), tList, false})
t.AddStd("error", "c.ErrorPage", ErrorPage{htitle("Error"), "A problem has occurred in the system."}) t.AddStd("error", "c.ErrorPage", ErrorPage{htitle("Error"), "A problem has occurred in the system."})
ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: &user2}, "::1"} ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: &user2}, "::1"}

View File

@ -319,9 +319,7 @@ func HasSuspiciousEmail(email string) bool {
return true return true
} }
var dotCount int var dotCount, shortBits, currentSegmentLength int
var shortBits int
var currentSegmentLength int
for _, char := range lowEmail { for _, char := range lowEmail {
if char == '.' { if char == '.' {
dotCount++ dotCount++
@ -337,25 +335,40 @@ func HasSuspiciousEmail(email string) bool {
return dotCount > 7 || shortBits > 2 return dotCount > 7 || shortBits > 2
} }
var weakPassStrings = []string{"test", "123","6969","password", "qwerty", "fuck", "love"}
// TODO: Write a test for this // TODO: Write a test for this
func WeakPassword(password string, username string, email string) error { func WeakPassword(password string, username string, email string) error {
lowPassword := strings.ToLower(password) lowPassword := strings.ToLower(password)
switch { switch {
case password == "": case password == "":
return errors.New("You didn't put in a password.") return errors.New("You didn't put in a password.")
case strings.Contains(lowPassword, strings.ToLower(username)) && len(username) > 3:
return errors.New("You can't use your username in your password.")
case strings.Contains(lowPassword, strings.ToLower(email)):
return errors.New("You can't use your email in your password.")
case len(password) < 8: case len(password) < 8:
return errors.New("Your password needs to be at-least eight characters long") return errors.New("Your password needs to be at-least eight characters long")
case strings.Contains(lowPassword, strings.ToLower(username)) && len(username) > 3:
return errors.New("You can't use your username in your password.")
case email != "" && strings.Contains(lowPassword, strings.ToLower(email)):
return errors.New("You can't use your email in your password.")
} }
if strings.Contains(lowPassword, "test") || strings.Contains(password, "123") || strings.Contains(lowPassword, "password") || strings.Contains(lowPassword, "qwerty") || strings.Contains(lowPassword, "fuck") || strings.Contains(lowPassword, "love") { for _, passBit := range weakPassStrings {
return errors.New("You may not have 'test', '123', 'password', 'qwerty', 'love' or 'fuck' in your password") if strings.Contains(lowPassword, passBit) {
s := "You may not have "
for i, passBit := range weakPassStrings {
if i > 0 {
if i == len(weakPassStrings)-1 {
s += " or "
} else {
s += ", "
}
}
s += "'" + passBit + "'"
}
return errors.New(s + " in your password")
}
} }
var charMap = make(map[rune]int) charMap := make(map[rune]int)
var numbers, symbols, upper, lower int var numbers, symbols, upper, lower int
for _, char := range password { for _, char := range password {
charItem, ok := charMap[char] charItem, ok := charMap[char]

View File

@ -474,6 +474,7 @@
"register_head":"Create Account", "register_head":"Create Account",
"register_account_name":"Account Name", "register_account_name":"Account Name",
"register_account_email":"Email", "register_account_email":"Email",
"register_account_email_optional":"Email (optional)",
"register_account_password":"Password", "register_account_password":"Password",
"register_account_confirm_password":"Confirm Password", "register_account_confirm_password":"Confirm Password",
"register_account_anti_spam":"Are you a spambot?", "register_account_anti_spam":"Are you a spambot?",
@ -515,6 +516,7 @@
"account_email_secondary":"Secondary", "account_email_secondary":"Secondary",
"account_email_verified":"Verified", "account_email_verified":"Verified",
"account_email_resend_email":"Resend Verification Email", "account_email_resend_email":"Resend Verification Email",
"account_email_none":"No email addresses found.",
"account_password_head":"Edit Password", "account_password_head":"Edit Password",
"account_password_current_password":"Current Password", "account_password_current_password":"Current Password",

View File

@ -166,7 +166,7 @@ func AccountLoginMFAVerifySubmit(w http.ResponseWriter, r *http.Request, user c.
if !mfaVerifySession(provSession, signedSession, uid) { if !mfaVerifySession(provSession, signedSession, uid) {
return c.LocalError("Invalid session", w, r, user) return c.LocalError("Invalid session", w, r, user)
} }
var token = r.PostFormValue("mfa_token") token := r.PostFormValue("mfa_token")
err = c.Auth.ValidateMFAToken(token, uid) err = c.Auth.ValidateMFAToken(token, uid)
if err != nil { if err != nil {
@ -188,7 +188,7 @@ func AccountRegister(w http.ResponseWriter, r *http.Request, user c.User, h *c.H
} }
h.Title = p.GetTitlePhrase("register") h.Title = p.GetTitlePhrase("register")
h.AddScriptAsync("register.js") h.AddScriptAsync("register.js")
return renderTemplate("register", w, r, h, c.Page{h, tList, nil}) return renderTemplate("register", w, r, h, c.Page{h, tList, h.Settings["activation_type"] != 2})
} }
func isNumeric(data string) (numeric bool) { func isNumeric(data string) (numeric bool) {
@ -227,19 +227,19 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
} }
} }
username := c.SanitiseSingleLine(r.PostFormValue("username")) name := c.SanitiseSingleLine(r.PostFormValue("name"))
// TODO: Add a dedicated function for validating emails if name == "" {
email := c.SanitiseSingleLine(r.PostFormValue("email"))
if username == "" {
regError(p.GetErrorPhrase("register_need_username"), "no-username") regError(p.GetErrorPhrase("register_need_username"), "no-username")
} }
if email == "" { // TODO: Add a dedicated function for validating emails
email := c.SanitiseSingleLine(r.PostFormValue("email"))
if headerLite.Settings["activation_type"] == 2 && email == "" {
regError(p.GetErrorPhrase("register_need_email"), "no-email") regError(p.GetErrorPhrase("register_need_email"), "no-email")
} }
// This is so a numeric name won't interfere with mentioning a user by ID, there might be a better way of doing this like perhaps !@ to mean IDs and @ to mean usernames in the pre-parser // This is so a numeric name won't interfere with mentioning a user by ID, there might be a better way of doing this like perhaps !@ to mean IDs and @ to mean usernames in the pre-parser
usernameBits := strings.Split(username, " ") nameBits := strings.Split(name, " ")
if isNumeric(usernameBits[0]) { if isNumeric(nameBits[0]) {
regError(p.GetErrorPhrase("register_first_word_numeric"), "numeric-name") regError(p.GetErrorPhrase("register_first_word_numeric"), "numeric-name")
} }
@ -249,7 +249,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
password := r.PostFormValue("password") password := r.PostFormValue("password")
// ? Move this into Create()? What if we want to programatically set weak passwords for tests? // ? Move this into Create()? What if we want to programatically set weak passwords for tests?
err := c.WeakPassword(password, username, email) err := c.WeakPassword(password, name, email)
if err != nil { if err != nil {
regError(err.Error(), "weak-password") regError(err.Error(), "weak-password")
} else { } else {
@ -260,7 +260,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
} }
} }
regLog := c.RegLogItem{Username: username, Email: email, FailureReason: regErrReason, Success: regSuccess, IP: user.LastIP} regLog := c.RegLogItem{Username: name, Email: email, FailureReason: regErrReason, Success: regSuccess, IP: user.LastIP}
_, err = regLog.Create() _, err = regLog.Create()
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
@ -280,7 +280,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
} }
// TODO: Do the registration attempt logging a little less messily (without having to amend the result after the insert) // TODO: Do the registration attempt logging a little less messily (without having to amend the result after the insert)
uid, err := c.Users.Create(username, password, email, group, active) uid, err := c.Users.Create(name, password, email, group, active)
if err != nil { if err != nil {
regLog.Success = false regLog.Success = false
if err == c.ErrAccountExists { if err == c.ErrAccountExists {
@ -320,12 +320,12 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
} }
// TODO: Add an EmailStore and move this there // TODO: Add an EmailStore and move this there
_, err = qgen.NewAcc().Insert("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Exec(email, uid, 0, token) _, err = qgen.NewAcc().Insert("emails").Columns("email,uid,validated,token").Fields("?,?,?,?").Exec(email, uid, 0, token)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.SendValidationEmail(username, email, token) err = c.SendValidationEmail(name, email, token)
if err != nil { if err != nil {
return c.LocalError(p.GetErrorPhrase("register_email_fail"), w, r, user) return c.LocalError(p.GetErrorPhrase("register_email_fail"), w, r, user)
} }
@ -345,7 +345,6 @@ func accountEditHead(titlePhrase string, w http.ResponseWriter, r *http.Request,
func AccountEdit(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError { func AccountEdit(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError {
accountEditHead("account", w, r, &user, header) accountEditHead("account", w, r, &user, header)
if r.FormValue("avatar_updated") == "1" { if r.FormValue("avatar_updated") == "1" {
header.AddNotice("account_avatar_updated") header.AddNotice("account_avatar_updated")
} else if r.FormValue("username_updated") == "1" { } else if r.FormValue("username_updated") == "1" {
@ -387,12 +386,12 @@ func AccountEditPasswordSubmit(w http.ResponseWriter, r *http.Request, user c.Us
} }
var realPassword, salt string var realPassword, salt string
currentPassword := r.PostFormValue("account-current-password") currentPassword := r.PostFormValue("current-password")
newPassword := r.PostFormValue("account-new-password") newPassword := r.PostFormValue("new-password")
confirmPassword := r.PostFormValue("account-confirm-password") confirmPassword := r.PostFormValue("confirm-password")
// TODO: Use a reusable statement // TODO: Use a reusable statement
err := qgen.NewAcc().Select("users").Columns("password, salt").Where("uid = ?").QueryRow(user.ID).Scan(&realPassword, &salt) err := qgen.NewAcc().Select("users").Columns("password,salt").Where("uid=?").QueryRow(user.ID).Scan(&realPassword, &salt)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("Your account no longer exists.", w, r, user) return c.LocalError("Your account no longer exists.", w, r, user)
} else if err != nil { } else if err != nil {
@ -429,7 +428,6 @@ func AccountEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user c.User
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
ferr = c.ChangeAvatar("." + ext, w, r, user) ferr = c.ChangeAvatar("." + ext, w, r, user)
if ferr != nil { if ferr != nil {
return ferr return ferr
@ -584,7 +582,7 @@ func AccountEditEmail(w http.ResponseWriter, r *http.Request, user c.User, h *c.
// Was this site migrated from another forum software? Most of them don't have multiple emails for a single user. // Was this site migrated from another forum software? Most of them don't have multiple emails for a single user.
// This also applies when the admin switches site.EnableEmails on after having it off for a while. // This also applies when the admin switches site.EnableEmails on after having it off for a while.
if len(emails) == 0 { if len(emails) == 0 && user.Email != "" {
emails = append(emails, c.Email{UserID: user.ID, Email: user.Email, Validated: false, Primary: true}) emails = append(emails, c.Email{UserID: user.ID, Email: user.Email, Validated: false, Primary: true})
} }
@ -612,7 +610,10 @@ func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user c.
targetEmail := c.Email{UserID: user.ID} targetEmail := c.Email{UserID: user.ID}
emails, err := c.Emails.GetEmailsByUser(&user) emails, err := c.Emails.GetEmailsByUser(&user)
if err != nil { if err == sql.ErrNoRows {
return c.LocalError("A verification email was never sent for you!", w, r, user)
} else if err != nil {
// TODO: Better error if we don't have an email or it's not in the emails table for some reason
return c.LocalError("You are not logged in", w, r, user) return c.LocalError("You are not logged in", w, r, user)
} }
for _, email := range emails { for _, email := range emails {
@ -635,8 +636,7 @@ func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user c.
// If Email Activation is on, then activate the account while we're here // If Email Activation is on, then activate the account while we're here
if header.Settings["activation_type"] == 2 { if header.Settings["activation_type"] == 2 {
err = user.Activate() if err = user.Activate(); err != nil {
if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
} }
@ -646,10 +646,9 @@ func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user c.
func AccountLogins(w http.ResponseWriter, r *http.Request, user c.User, h *c.Header) c.RouteError { func AccountLogins(w http.ResponseWriter, r *http.Request, user c.User, h *c.Header) c.RouteError {
accountEditHead("account_logins", w, r, &user, h) accountEditHead("account_logins", w, r, &user, h)
logCount := c.LoginLogs.CountUser(user.ID)
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 12 perPage := 12
offset, page, lastPage := c.PageOffset(logCount, page, perPage) offset, page, lastPage := c.PageOffset(c.LoginLogs.CountUser(user.ID), page, perPage)
logs, err := c.LoginLogs.GetOffset(user.ID, offset, perPage) logs, err := c.LoginLogs.GetOffset(user.ID, offset, perPage)
if err != nil { if err != nil {
@ -779,12 +778,11 @@ func AccountPasswordResetToken(w http.ResponseWriter, r *http.Request, user c.Us
header.AddNotice("password_reset_token_token_verified") header.AddNotice("password_reset_token_token_verified")
}*/ }*/
token := r.FormValue("token")
uid, err := strconv.Atoi(r.FormValue("uid")) uid, err := strconv.Atoi(r.FormValue("uid"))
if err != nil { if err != nil {
return c.LocalError("Invalid uid", w, r, user) return c.LocalError("Invalid uid", w, r, user)
} }
token := r.FormValue("token")
err = c.PasswordResetter.ValidateToken(uid, token) err = c.PasswordResetter.ValidateToken(uid, token)
if err == sql.ErrNoRows || err == c.ErrBadResetToken { if err == sql.ErrNoRows || err == c.ErrBadResetToken {
return c.LocalError("This reset token has expired.", w, r, user) return c.LocalError("This reset token has expired.", w, r, user)
@ -814,8 +812,7 @@ func AccountPasswordResetTokenSubmit(w http.ResponseWriter, r *http.Request, use
return c.LocalError("This reset token has expired.", w, r, user) return c.LocalError("This reset token has expired.", w, r, user)
} }
token := r.FormValue("token") err = c.PasswordResetter.ValidateToken(uid, r.FormValue("token"))
err = c.PasswordResetter.ValidateToken(uid, token)
if err == sql.ErrNoRows || err == c.ErrBadResetToken { if err == sql.ErrNoRows || err == c.ErrBadResetToken {
return c.LocalError("This reset token has expired.", w, r, user) return c.LocalError("This reset token has expired.", w, r, user)
} else if err != nil { } else if err != nil {

View File

@ -2,7 +2,6 @@
<div class="rowitem"><h1>{{lang "account_email_head"}}</h1></div> <div class="rowitem"><h1>{{lang "account_email_head"}}</h1></div>
</div> </div>
<div class="colstack_item rowlist"> <div class="colstack_item rowlist">
<!-- TODO: Do we need this inline CSS? -->
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem"> <div class="rowitem">
<a>{{.Email}}</a> <a>{{.Email}}</a>
@ -11,5 +10,5 @@
{{if .Validated}}<span class="username validated_email">{{lang "account_email_verified"}}</span>{{else}}<a class="username invalid_email">{{lang "account_email_resend_email"}}</a>{{end}} {{if .Validated}}<span class="username validated_email">{{lang "account_email_verified"}}</span>{{else}}<a class="username invalid_email">{{lang "account_email_resend_email"}}</a>{{end}}
</span> </span>
</div> </div>
{{end}} {{else}}<div class="rowitem passive rowmsg">{{lang "account_email_none"}}</div>{{end}}
</div> </div>

View File

@ -1,6 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div id="account_edit_password" class="colstack account"> <div id="account_edit_password" class="colstack account">
{{template "account_menu.html" . }} {{template "account_menu.html" .}}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>{{lang "account_password_head"}}</h1></div> <div class="rowitem"><h1>{{lang "account_password_head"}}</h1></div>
@ -9,15 +9,15 @@
<form action="/user/edit/password/submit/?s={{.CurrentUser.Session}}" method="post"> <form action="/user/edit/password/submit/?s={{.CurrentUser.Session}}" method="post">
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem formlabel"><a>{{lang "account_password_current_password"}}</a></div> <div class="formitem formlabel"><a>{{lang "account_password_current_password"}}</a></div>
<div class="formitem"><input name="account-current-password" type="password" placeholder="*****" autocomplete="current-password" required /></div> <div class="formitem"><input name="current-password" type="password" placeholder="*****" autocomplete="current-password" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "account_password_new_password"}}</a></div> <div class="formitem formlabel"><a>{{lang "account_password_new_password"}}</a></div>
<div class="formitem"><input name="account-new-password" type="password" placeholder="*****" autocomplete="new-password" required /></div> <div class="formitem"><input name="new-password" type="password" placeholder="*****" autocomplete="new-password" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "account_password_confirm_password"}}</a></div> <div class="formitem formlabel"><a>{{lang "account_password_confirm_password"}}</a></div>
<div class="formitem"><input name="account-confirm-password" type="password" placeholder="*****" autocomplete="new-password" required /></div> <div class="formitem"><input name="confirm-password" type="password" placeholder="*****" autocomplete="new-password" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="account-button" class="formbutton form_middle_button">{{lang "account_password_update_button"}}</button></div> <div class="formitem"><button name="account-button" class="formbutton form_middle_button">{{lang "account_password_update_button"}}</button></div>

View File

@ -18,21 +18,21 @@
</nav> </nav>
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>
<main id="forum_topic_list" class="rowblock topic_list" style="position: relative;z-index: 50;"> <main id="forum_topic_list" class="rowblock topic_list" style="position:relative;z-index:50;">
{{range .ItemList}}<div class="rowitem topic_left passive datarow" style="background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{if .Sticky}}background-color: #FFFFCC;{{else if .IsClosed}}background-color: #eaeaea;{{end}}"> {{range .ItemList}}<div class="rowitem topic_left passive datarow" style="background-image:url({{.Creator.Avatar}});background-position:left;background-repeat:no-repeat;background-size:64px;padding-left:72px;{{if .Sticky}}background-color:#FFFFCC;{{else if .IsClosed}}background-color:#eaeaea;{{end}}">
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float:right;">
<span class="replyCount">{{.PostCount}} replies</span><br /> <span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span> <span class="lastReplyAt">{{.LastReplyAt}}</span>
</span> </span>
<span> <span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> <a class="rowtopic" href="{{.Link}}">{{.Title}}</a>
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a> <br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E{{end}}</span> {{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="{{lang "status.closed_tooltip"}}"> | &#x1F512;&#xFE0E{{end}}</span>
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow" style="background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;"> <div class="rowitem topic_right passive datarow" style="background-image:url({{.LastUser.Avatar}});background-position:left;background-repeat:no-repeat;background-size:64px;padding-left:72px;">
<span> <span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br> <a href="{{.LastUser.Link}}" class="lastName" style="font-size:14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span> <span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span>
</span> </span>
</div> </div>

View File

@ -6,12 +6,12 @@
<div class="rowblock the_form"> <div class="rowblock the_form">
<form action="/accounts/create/submit/" method="post"> <form action="/accounts/create/submit/" method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a id="username_label">{{lang "register_account_name"}}</a></div> <div class="formitem formlabel"><a id="name_label">{{lang "register_account_name"}}</a></div>
<div class="formitem"><input name="username" type="text" placeholder="{{lang "register_account_name"}}" aria-labelledby="username_label" required /></div> <div class="formitem"><input name="name" type="text" placeholder="{{lang "register_account_name"}}" aria-labelledby="name_label" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a id="email_label">{{lang "register_account_email"}}</a></div> <div class="formitem formlabel"><a id="email_label">{{if not .Something}}{{lang "register_account_email"}}{{else}}{{lang "register_account_email_optional"}}{{end}}</a></div>
<div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" aria-labelledby="email_label" required /></div> <div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" aria-labelledby="email_label"{{if not .Something}} required{{end}} /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a id="password_label">{{lang "register_account_password"}}</a></div> <div class="formitem formlabel"><a id="password_label">{{lang "register_account_password"}}</a></div>