Added a couple of simple anti-spam measures.
Made progress with an internal error logging component for the Control Panel. Made LogWarning less susceptible to races and slightly improved log contention. Revamped the registration page on Cosora. Added the SanitiseSingleLine and SanitiseBody functions to better centralise sanitisation and to ensure more consistent sanitisation. Zero length spaces are no longer permitted in usernames to help prevent impersonation. More to come in this area. Plugins / internal components can now schedule hourly tasks. Reduced the chances of newlines breaking the visual layout in areas which expect none. Added the register_account_anti_spam phrase.
This commit is contained in:
parent
e311e088c2
commit
f85bf51103
|
@ -3,6 +3,7 @@ package common
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log"
|
"log"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"../query_gen/lib"
|
"../query_gen/lib"
|
||||||
|
@ -23,6 +24,9 @@ const Petabyte int = Terabyte * 1024
|
||||||
var StartTime time.Time
|
var StartTime time.Time
|
||||||
var TmplPtrMap = make(map[string]interface{})
|
var TmplPtrMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
// Anti-spam token with rotated key
|
||||||
|
var JSTokenBox atomic.Value // TODO: Move this and some of these other globals somewhere else
|
||||||
|
|
||||||
// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores
|
// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores
|
||||||
var ErrNoRows = sql.ErrNoRows
|
var ErrNoRows = sql.ErrNoRows
|
||||||
|
|
||||||
|
@ -54,6 +58,10 @@ var ExecutableFileExts = StringList{
|
||||||
"exe", "jar", "phar", "shar", "iso",
|
"exe", "jar", "phar", "shar", "iso",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
JSTokenBox.Store("")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Write a test for this
|
// TODO: Write a test for this
|
||||||
func (slice StringList) Contains(needle string) bool {
|
func (slice StringList) Contains(needle string) bool {
|
||||||
for _, item := range slice {
|
for _, item := range slice {
|
||||||
|
|
|
@ -7,10 +7,16 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page?
|
type ErrorItem struct {
|
||||||
|
error
|
||||||
|
Stack []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ! The errorBuffer uses o(n) memory, we should probably do something about that
|
||||||
|
// TODO: Use the errorBuffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page?
|
||||||
// ? - Should we pass Header / HeaderLite rather than forcing the errors to pull the global Header instance?
|
// ? - Should we pass Header / HeaderLite rather than forcing the errors to pull the global Header instance?
|
||||||
var errorBufferMutex sync.RWMutex
|
var errorBufferMutex sync.RWMutex
|
||||||
var errorBuffer []error
|
var errorBuffer []ErrorItem
|
||||||
|
|
||||||
//var notfoundCountPerSecond int
|
//var notfoundCountPerSecond int
|
||||||
//var nopermsCountPerSecond int
|
//var nopermsCountPerSecond int
|
||||||
|
@ -66,11 +72,11 @@ func LogError(err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogWarning(err error) {
|
func LogWarning(err error) {
|
||||||
log.Print(err)
|
stack := debug.Stack()
|
||||||
debug.PrintStack()
|
log.Print(err.Error()+"\n", string(stack))
|
||||||
errorBufferMutex.Lock()
|
errorBufferMutex.Lock()
|
||||||
defer errorBufferMutex.Unlock()
|
defer errorBufferMutex.Unlock()
|
||||||
errorBuffer = append(errorBuffer, err)
|
errorBuffer = append(errorBuffer, ErrorItem{err, stack})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Dump the request?
|
// TODO: Dump the request?
|
||||||
|
|
|
@ -76,6 +76,7 @@ func (forum *Forum) Update(name string, desc string, active bool, preset string)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = forum.Name
|
name = forum.Name
|
||||||
}
|
}
|
||||||
|
// TODO: Do a line sanitise? Does it matter?
|
||||||
preset = strings.TrimSpace(preset)
|
preset = strings.TrimSpace(preset)
|
||||||
_, err := forumStmts.update.Exec(name, desc, active, preset, forum.ID)
|
_, err := forumStmts.update.Exec(name, desc, active, preset, forum.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"html"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -171,9 +170,9 @@ func PreparseMessage(msg string) string {
|
||||||
msg = strings.Replace(msg, "<p>", "\n\n", -1)
|
msg = strings.Replace(msg, "<p>", "\n\n", -1)
|
||||||
msg = strings.Replace(msg, "</p>", "", -1)
|
msg = strings.Replace(msg, "</p>", "", -1)
|
||||||
msg = strings.Replace(msg, "<br>", "\n\n", -1)
|
msg = strings.Replace(msg, "<br>", "\n\n", -1)
|
||||||
msg = strings.TrimSpace(msg) // There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there
|
|
||||||
msg = RunSshook("preparse_preassign", msg)
|
msg = RunSshook("preparse_preassign", msg)
|
||||||
msg = html.EscapeString(msg)
|
// There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there
|
||||||
|
msg = SanitiseBody(msg)
|
||||||
msg = strings.Replace(msg, " ", "", -1)
|
msg = strings.Replace(msg, " ", "", -1)
|
||||||
|
|
||||||
var runes = []rune(msg)
|
var runes = []rune(msg)
|
||||||
|
|
|
@ -22,6 +22,7 @@ type TaskStmts struct {
|
||||||
var ScheduledHalfSecondTasks []func() error
|
var ScheduledHalfSecondTasks []func() error
|
||||||
var ScheduledSecondTasks []func() error
|
var ScheduledSecondTasks []func() error
|
||||||
var ScheduledFifteenMinuteTasks []func() error
|
var ScheduledFifteenMinuteTasks []func() error
|
||||||
|
var ScheduledHourTasks []func() error
|
||||||
var ShutdownTasks []func() error
|
var ShutdownTasks []func() error
|
||||||
var taskStmts TaskStmts
|
var taskStmts TaskStmts
|
||||||
var lastSync time.Time
|
var lastSync time.Time
|
||||||
|
@ -53,6 +54,11 @@ func AddScheduledFifteenMinuteTask(task func() error) {
|
||||||
ScheduledFifteenMinuteTasks = append(ScheduledFifteenMinuteTasks, task)
|
ScheduledFifteenMinuteTasks = append(ScheduledFifteenMinuteTasks, task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddScheduledHourTask is not concurrency safe
|
||||||
|
func AddScheduledHourTask(task func() error) {
|
||||||
|
ScheduledHourTasks = append(ScheduledHourTasks, task)
|
||||||
|
}
|
||||||
|
|
||||||
// AddShutdownTask is not concurrency safe
|
// AddShutdownTask is not concurrency safe
|
||||||
func AddShutdownTask(task func() error) {
|
func AddShutdownTask(task func() error) {
|
||||||
ShutdownTasks = append(ShutdownTasks, task)
|
ShutdownTasks = append(ShutdownTasks, task)
|
||||||
|
@ -86,6 +92,7 @@ func HandleExpiredScheduledGroups() error {
|
||||||
|
|
||||||
// TODO: Use AddScheduledSecondTask
|
// TODO: Use AddScheduledSecondTask
|
||||||
// TODO: Be a little more granular with the synchronisation
|
// TODO: Be a little more granular with the synchronisation
|
||||||
|
// TODO: Synchronise more things
|
||||||
func HandleServerSync() error {
|
func HandleServerSync() error {
|
||||||
// We don't want to run any unnecessary queries when there is nothing to synchronise
|
// We don't want to run any unnecessary queries when there is nothing to synchronise
|
||||||
/*if Config.ServerCount > 1 {
|
/*if Config.ServerCount > 1 {
|
||||||
|
|
|
@ -233,7 +233,7 @@ func CompileTemplates() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPage := Page{"Registration Page", user, header, tList, nil}
|
registerPage := Page{"Registration Page", user, header, tList, "nananana"}
|
||||||
registerTmpl, err := c.Compile("register.html", "templates/", "common.Page", registerPage, varList)
|
registerTmpl, err := c.Compile("register.html", "templates/", "common.Page", registerPage, varList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"../query_gen/lib"
|
"../query_gen/lib"
|
||||||
|
@ -257,10 +256,7 @@ func (topic *Topic) Delete() error {
|
||||||
|
|
||||||
// TODO: Write tests for this
|
// TODO: Write tests for this
|
||||||
func (topic *Topic) Update(name string, content string) error {
|
func (topic *Topic) Update(name string, content string) error {
|
||||||
name = html.EscapeString(strings.Replace(html.UnescapeString(name), "\n", "", -1))
|
name = SanitiseSingleLine(html.UnescapeString(name))
|
||||||
|
|
||||||
// TODO: Stop duplicating this logic?
|
|
||||||
name = strings.TrimSpace(name)
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return ErrNoTitle
|
return ErrNoTitle
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,6 @@ func (mts *DefaultTopicStore) Exists(id int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mts *DefaultTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) {
|
func (mts *DefaultTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) {
|
||||||
topicName = strings.TrimSpace(topicName)
|
|
||||||
if topicName == "" {
|
if topicName == "" {
|
||||||
return 0, ErrNoTitle
|
return 0, ErrNoTitle
|
||||||
}
|
}
|
||||||
|
@ -130,9 +129,8 @@ func (mts *DefaultTopicStore) Create(fid int, topicName string, content string,
|
||||||
return 0, ErrLongTitle
|
return 0, ErrLongTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
content = strings.TrimSpace(content)
|
parsedContent := strings.TrimSpace(ParseMessage(content, fid, "forums"))
|
||||||
parsedContent := ParseMessage(content, fid, "forums")
|
if parsedContent == "" {
|
||||||
if strings.TrimSpace(parsedContent) == "" {
|
|
||||||
return 0, ErrNoBody
|
return 0, ErrNoBody
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -119,9 +120,8 @@ func ConvertByteUnit(bytes float64) (float64, string) {
|
||||||
return bytes / float64(Megabyte), "MB"
|
return bytes / float64(Megabyte), "MB"
|
||||||
case bytes >= float64(Kilobyte):
|
case bytes >= float64(Kilobyte):
|
||||||
return bytes / float64(Kilobyte), "KB"
|
return bytes / float64(Kilobyte), "KB"
|
||||||
default:
|
|
||||||
return bytes, " bytes"
|
|
||||||
}
|
}
|
||||||
|
return bytes, " bytes"
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write a test for this
|
// TODO: Write a test for this
|
||||||
|
@ -387,6 +387,24 @@ func GetLevels(maxLevel int) []float64 {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Write a test for this
|
||||||
|
// SanitiseSingleLine is a generic function for escaping html entities and removing silly characters from usernames and topic titles. It also strips newline characters
|
||||||
|
func SanitiseSingleLine(in string) string {
|
||||||
|
in = strings.Replace(in, "\n", "", -1)
|
||||||
|
in = strings.Replace(in, "\r", "", -1)
|
||||||
|
return SanitiseBody(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Write a test for this
|
||||||
|
// TODO: Add more strange characters
|
||||||
|
// TODO: Strip all sub-32s minus \r and \n?
|
||||||
|
// SanitiseBody is the same as SanitiseSingleLine, but it doesn't strip newline characters
|
||||||
|
func SanitiseBody(in string) string {
|
||||||
|
in = strings.Replace(in, "", "", -1) // Strip Zero length space
|
||||||
|
in = html.EscapeString(in)
|
||||||
|
return strings.TrimSpace(in)
|
||||||
|
}
|
||||||
|
|
||||||
func BuildSlug(slug string, id int) string {
|
func BuildSlug(slug string, id int) string {
|
||||||
if slug == "" || !Config.BuildSlugs {
|
if slug == "" || !Config.BuildSlugs {
|
||||||
return strconv.Itoa(id)
|
return strconv.Itoa(id)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"html"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -254,9 +253,9 @@ func RouteCreateGuildSubmit(w http.ResponseWriter, r *http.Request, user common.
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildActive = true
|
var guildActive = true
|
||||||
var guildName = html.EscapeString(r.PostFormValue("group_name"))
|
var guildName = common.SanitiseSingleLine(r.PostFormValue("group_name"))
|
||||||
// TODO: Allow Markdown / BBCode / Limited HTML in the description?
|
// TODO: Allow Markdown / BBCode / Limited HTML in the description?
|
||||||
var guildDesc = html.EscapeString(r.PostFormValue("group_desc"))
|
var guildDesc = common.SanitiseBody(r.PostFormValue("group_desc"))
|
||||||
var gprivacy = r.PostFormValue("group_privacy")
|
var gprivacy = r.PostFormValue("group_privacy")
|
||||||
|
|
||||||
var guildPrivacy int
|
var guildPrivacy int
|
||||||
|
|
|
@ -574,26 +574,21 @@ func (router *GenRouter) RemoveFunc(pattern string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (router *GenRouter) StripNewlines(data string) string {
|
|
||||||
// TODO: Strip out all sub-32s?
|
|
||||||
return strings.Replace(strings.Replace(data,"\n","",-1),"\r","",-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (router *GenRouter) DumpRequest(req *http.Request, prepend string) {
|
func (router *GenRouter) DumpRequest(req *http.Request, prepend string) {
|
||||||
var heads string
|
var heads string
|
||||||
for key, value := range req.Header {
|
for key, value := range req.Header {
|
||||||
for _, vvalue := range value {
|
for _, vvalue := range value {
|
||||||
heads += "Header '" + router.StripNewlines(key) + "': " + router.StripNewlines(vvalue) + "!!\n"
|
heads += "Header '" + common.SanitiseSingleLine(key) + "': " + common.SanitiseSingleLine(vvalue) + "!!\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.requestLogger.Print(prepend +
|
router.requestLogger.Print(prepend +
|
||||||
"\nUA: " + router.StripNewlines(req.UserAgent()) + "\n" +
|
"\nUA: " + common.SanitiseSingleLine(req.UserAgent()) + "\n" +
|
||||||
"Method: " + router.StripNewlines(req.Method) + "\n" + heads +
|
"Method: " + common.SanitiseSingleLine(req.Method) + "\n" + heads +
|
||||||
"req.Host: " + router.StripNewlines(req.Host) + "\n" +
|
"req.Host: " + common.SanitiseSingleLine(req.Host) + "\n" +
|
||||||
"req.URL.Path: " + router.StripNewlines(req.URL.Path) + "\n" +
|
"req.URL.Path: " + common.SanitiseSingleLine(req.URL.Path) + "\n" +
|
||||||
"req.URL.RawQuery: " + router.StripNewlines(req.URL.RawQuery) + "\n" +
|
"req.URL.RawQuery: " + common.SanitiseSingleLine(req.URL.RawQuery) + "\n" +
|
||||||
"req.Referer(): " + router.StripNewlines(req.Referer()) + "\n" +
|
"req.Referer(): " + common.SanitiseSingleLine(req.Referer()) + "\n" +
|
||||||
"req.RemoteAddr: " + req.RemoteAddr + "\n")
|
"req.RemoteAddr: " + req.RemoteAddr + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -344,6 +344,7 @@
|
||||||
"register_account_email":"Email",
|
"register_account_email":"Email",
|
||||||
"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_submit_button":"Create Account",
|
"register_submit_button":"Create Account",
|
||||||
|
|
||||||
"account_menu_head":"My Account",
|
"account_menu_head":"My Account",
|
||||||
|
|
17
main.go
17
main.go
|
@ -217,6 +217,12 @@ func main() {
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
common.StartTime = time.Now()
|
common.StartTime = time.Now()
|
||||||
|
|
||||||
|
jsToken, err := common.GenerateSafeString(80)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
common.JSTokenBox.Store(jsToken)
|
||||||
|
|
||||||
log.Print("Processing configuration data")
|
log.Print("Processing configuration data")
|
||||||
err = common.ProcessConfig()
|
err = common.ProcessConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -338,7 +344,7 @@ func main() {
|
||||||
halfSecondTicker := time.NewTicker(time.Second / 2)
|
halfSecondTicker := time.NewTicker(time.Second / 2)
|
||||||
secondTicker := time.NewTicker(time.Second)
|
secondTicker := time.NewTicker(time.Second)
|
||||||
fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
|
fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
|
||||||
//hourTicker := time.NewTicker(time.Hour)
|
hourTicker := time.NewTicker(time.Hour)
|
||||||
go func() {
|
go func() {
|
||||||
var runHook = func(name string) {
|
var runHook = func(name string) {
|
||||||
err := common.RunTaskHook(name)
|
err := common.RunTaskHook(name)
|
||||||
|
@ -382,6 +388,15 @@ func main() {
|
||||||
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
|
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
|
||||||
// TODO: Publish scheduled posts.
|
// TODO: Publish scheduled posts.
|
||||||
runHook("after_fifteen_minute_tick")
|
runHook("after_fifteen_minute_tick")
|
||||||
|
case <-hourTicker.C:
|
||||||
|
runHook("before_hour_tick")
|
||||||
|
jsToken, err := common.GenerateSafeString(80)
|
||||||
|
if err != nil {
|
||||||
|
common.LogError(err)
|
||||||
|
}
|
||||||
|
common.JSTokenBox.Store(jsToken)
|
||||||
|
runTasks(common.ScheduledHourTasks)
|
||||||
|
runHook("after_hour_tick")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle the daily clean-up.
|
// TODO: Handle the daily clean-up.
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -189,6 +188,7 @@ func routePanelWordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, u
|
||||||
}
|
}
|
||||||
isJs := (r.PostFormValue("js") == "1")
|
isJs := (r.PostFormValue("js") == "1")
|
||||||
|
|
||||||
|
// ? - We're not doing a full sanitise here, as it would be useful if admins were able to put down rules for replacing things with HTML, etc.
|
||||||
find := strings.TrimSpace(r.PostFormValue("find"))
|
find := strings.TrimSpace(r.PostFormValue("find"))
|
||||||
if find == "" {
|
if find == "" {
|
||||||
return common.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs)
|
return common.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs)
|
||||||
|
@ -573,14 +573,14 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user comm
|
||||||
return common.LocalError("Only administrators can edit the account of other administrators.", w, r, user)
|
return common.LocalError("Only administrators can edit the account of other administrators.", w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
newname := html.EscapeString(strings.Replace(r.PostFormValue("user-name"), "\n", "", -1))
|
newname := common.SanitiseSingleLine(r.PostFormValue("user-name"))
|
||||||
if newname == "" {
|
if newname == "" {
|
||||||
return common.LocalError("You didn't put in a username.", w, r, user)
|
return common.LocalError("You didn't put in a username.", w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: How should activation factor into admin set emails?
|
// TODO: How should activation factor into admin set emails?
|
||||||
// TODO: How should we handle secondary emails? Do we even have secondary emails implemented?
|
// TODO: How should we handle secondary emails? Do we even have secondary emails implemented?
|
||||||
newemail := html.EscapeString(strings.Replace(r.PostFormValue("user-email"), "\n", "", -1))
|
newemail := common.SanitiseSingleLine(r.PostFormValue("user-email"))
|
||||||
if newemail == "" {
|
if newemail == "" {
|
||||||
return common.LocalError("You didn't put in an email address.", w, r, user)
|
return common.LocalError("You didn't put in an email address.", w, r, user)
|
||||||
}
|
}
|
||||||
|
@ -1222,7 +1222,7 @@ func routePanelThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user c
|
||||||
|
|
||||||
func routePanelThemesMenuItemSetters(r *http.Request, menuItem common.MenuItem) common.MenuItem {
|
func routePanelThemesMenuItemSetters(r *http.Request, menuItem common.MenuItem) common.MenuItem {
|
||||||
var getItem = func(name string) string {
|
var getItem = func(name string) string {
|
||||||
return html.EscapeString(strings.Replace(r.PostFormValue("item-"+name), "\n", "", -1))
|
return common.SanitiseSingleLine(r.PostFormValue("item-" + name))
|
||||||
}
|
}
|
||||||
menuItem.Name = getItem("name")
|
menuItem.Name = getItem("name")
|
||||||
menuItem.HTMLID = getItem("htmlid")
|
menuItem.HTMLID = getItem("htmlid")
|
||||||
|
|
|
@ -390,26 +390,21 @@ func (router *GenRouter) RemoveFunc(pattern string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (router *GenRouter) StripNewlines(data string) string {
|
|
||||||
// TODO: Strip out all sub-32s?
|
|
||||||
return strings.Replace(strings.Replace(data,"\n","",-1),"\r","",-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (router *GenRouter) DumpRequest(req *http.Request, prepend string) {
|
func (router *GenRouter) DumpRequest(req *http.Request, prepend string) {
|
||||||
var heads string
|
var heads string
|
||||||
for key, value := range req.Header {
|
for key, value := range req.Header {
|
||||||
for _, vvalue := range value {
|
for _, vvalue := range value {
|
||||||
heads += "Header '" + router.StripNewlines(key) + "': " + router.StripNewlines(vvalue) + "!!\n"
|
heads += "Header '" + common.SanitiseSingleLine(key) + "': " + common.SanitiseSingleLine(vvalue) + "!!\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.requestLogger.Print(prepend +
|
router.requestLogger.Print(prepend +
|
||||||
"\nUA: " + router.StripNewlines(req.UserAgent()) + "\n" +
|
"\nUA: " + common.SanitiseSingleLine(req.UserAgent()) + "\n" +
|
||||||
"Method: " + router.StripNewlines(req.Method) + "\n" + heads +
|
"Method: " + common.SanitiseSingleLine(req.Method) + "\n" + heads +
|
||||||
"req.Host: " + router.StripNewlines(req.Host) + "\n" +
|
"req.Host: " + common.SanitiseSingleLine(req.Host) + "\n" +
|
||||||
"req.URL.Path: " + router.StripNewlines(req.URL.Path) + "\n" +
|
"req.URL.Path: " + common.SanitiseSingleLine(req.URL.Path) + "\n" +
|
||||||
"req.URL.RawQuery: " + router.StripNewlines(req.URL.RawQuery) + "\n" +
|
"req.URL.RawQuery: " + common.SanitiseSingleLine(req.URL.RawQuery) + "\n" +
|
||||||
"req.Referer(): " + router.StripNewlines(req.Referer()) + "\n" +
|
"req.Referer(): " + common.SanitiseSingleLine(req.Referer()) + "\n" +
|
||||||
"req.RemoteAddr: " + req.RemoteAddr + "\n")
|
"req.RemoteAddr: " + req.RemoteAddr + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"html"
|
"encoding/hex"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -45,7 +46,7 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user common.User
|
||||||
return common.LocalError("You're already logged in.", w, r, user)
|
return common.LocalError("You're already logged in.", w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
username := html.EscapeString(strings.Replace(r.PostFormValue("username"), "\n", "", -1))
|
username := common.SanitiseSingleLine(r.PostFormValue("username"))
|
||||||
uid, err := common.Auth.Authenticate(username, r.PostFormValue("password"))
|
uid, err := common.Auth.Authenticate(username, r.PostFormValue("password"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.LocalError(err.Error(), w, r, user)
|
return common.LocalError(err.Error(), w, r, user)
|
||||||
|
@ -91,7 +92,11 @@ func AccountRegister(w http.ResponseWriter, r *http.Request, user common.User) c
|
||||||
if user.Loggedin {
|
if user.Loggedin {
|
||||||
return common.LocalError("You're already logged in.", w, r, user)
|
return common.LocalError("You're already logged in.", w, r, user)
|
||||||
}
|
}
|
||||||
pi := common.Page{common.GetTitlePhrase("register"), user, header, tList, nil}
|
h := sha256.New()
|
||||||
|
h.Write([]byte(common.JSTokenBox.Load().(string)))
|
||||||
|
h.Write([]byte(user.LastIP))
|
||||||
|
jsToken := hex.EncodeToString(h.Sum(nil))
|
||||||
|
pi := common.Page{common.GetTitlePhrase("register"), user, header, tList, jsToken}
|
||||||
if common.RunPreRenderHook("pre_render_register", w, r, &user, &pi) {
|
if common.RunPreRenderHook("pre_render_register", w, r, &user, &pi) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -117,8 +122,19 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user common.U
|
||||||
regErrReason += reason + "|"
|
regErrReason += reason + "|"
|
||||||
}
|
}
|
||||||
|
|
||||||
username := html.EscapeString(strings.Replace(r.PostFormValue("username"), "\n", "", -1))
|
if r.PostFormValue("tos") != "0" {
|
||||||
email := html.EscapeString(strings.Replace(r.PostFormValue("email"), "\n", "", -1))
|
regError("You might be a machine", "trap-question")
|
||||||
|
}
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(common.JSTokenBox.Load().(string)))
|
||||||
|
h.Write([]byte(user.LastIP))
|
||||||
|
if r.PostFormValue("antispam") != hex.EncodeToString(h.Sum(nil)) {
|
||||||
|
regError("You might be a machine", "js-antispam")
|
||||||
|
}
|
||||||
|
|
||||||
|
username := common.SanitiseSingleLine(r.PostFormValue("username"))
|
||||||
|
// TODO: Add a dedicated function for validating emails
|
||||||
|
email := common.SanitiseSingleLine(r.PostFormValue("email"))
|
||||||
if username == "" {
|
if username == "" {
|
||||||
regError("You didn't put in a username.", "no-username")
|
regError("You didn't put in a username.", "no-username")
|
||||||
}
|
}
|
||||||
|
@ -390,7 +406,7 @@ func AccountEditUsernameSubmit(w http.ResponseWriter, r *http.Request, user comm
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
|
|
||||||
newUsername := html.EscapeString(strings.Replace(r.PostFormValue("account-new-username"), "\n", "", -1))
|
newUsername := common.SanitiseSingleLine(r.PostFormValue("account-new-username"))
|
||||||
err := user.ChangeName(newUsername)
|
err := user.ChangeName(newUsername)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.LocalError("Unable to change the username. Does someone else already have this name?", w, r, user)
|
return common.LocalError("Unable to change the username. Does someone else already have this name?", w, r, user)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package routes
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"html"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -157,7 +156,7 @@ func ChangeTheme(w http.ResponseWriter, r *http.Request, user common.User) commo
|
||||||
//headerLite, _ := SimpleUserCheck(w, r, &user)
|
//headerLite, _ := SimpleUserCheck(w, r, &user)
|
||||||
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly?
|
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly?
|
||||||
isJs := (r.PostFormValue("isJs") == "1")
|
isJs := (r.PostFormValue("isJs") == "1")
|
||||||
newTheme := html.EscapeString(r.PostFormValue("newTheme"))
|
newTheme := common.SanitiseSingleLine(r.PostFormValue("newTheme"))
|
||||||
|
|
||||||
theme, ok := common.Themes[newTheme]
|
theme, ok := common.Themes[newTheme]
|
||||||
if !ok || theme.HideFromThemes {
|
if !ok || theme.HideFromThemes {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"../common"
|
"../common"
|
||||||
|
@ -18,7 +17,7 @@ func IPSearch(w http.ResponseWriter, r *http.Request, user common.User) common.R
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reject IP Addresses with illegal characters
|
// TODO: Reject IP Addresses with illegal characters
|
||||||
var ip = html.EscapeString(r.FormValue("ip"))
|
var ip = common.SanitiseSingleLine(r.FormValue("ip"))
|
||||||
uids, err := common.IPSearch.Lookup(ip)
|
uids, err := common.IPSearch.Lookup(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package panel
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"html"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -185,7 +184,7 @@ func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.Use
|
||||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||||
common.DebugLogf("graph: %+v\n", graph)
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
|
|
||||||
pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(route), graph, viewItems, timeRange.Range}
|
pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", common.SanitiseSingleLine(route), graph, viewItems, timeRange.Range}
|
||||||
return panelRenderTemplate("panel_analytics_route_views", w, r, user, &pi)
|
return panelRenderTemplate("panel_analytics_route_views", w, r, user, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +204,7 @@ func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.Use
|
||||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||||
|
|
||||||
// ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff
|
// ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff
|
||||||
agent = html.EscapeString(agent)
|
agent = common.SanitiseSingleLine(agent)
|
||||||
|
|
||||||
common.DebugLog("in panel.AnalyticsAgentViews")
|
common.DebugLog("in panel.AnalyticsAgentViews")
|
||||||
acc := qgen.Builder.Accumulator()
|
acc := qgen.Builder.Accumulator()
|
||||||
|
@ -299,7 +298,7 @@ func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.Us
|
||||||
return common.LocalError(err.Error(), w, r, user)
|
return common.LocalError(err.Error(), w, r, user)
|
||||||
}
|
}
|
||||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||||
system = html.EscapeString(system)
|
system = common.SanitiseSingleLine(system)
|
||||||
|
|
||||||
common.DebugLog("in panel.AnalyticsSystemViews")
|
common.DebugLog("in panel.AnalyticsSystemViews")
|
||||||
acc := qgen.Builder.Accumulator()
|
acc := qgen.Builder.Accumulator()
|
||||||
|
@ -344,7 +343,7 @@ func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common.
|
||||||
return common.LocalError(err.Error(), w, r, user)
|
return common.LocalError(err.Error(), w, r, user)
|
||||||
}
|
}
|
||||||
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
|
||||||
lang = html.EscapeString(lang)
|
lang = common.SanitiseSingleLine(lang)
|
||||||
|
|
||||||
common.DebugLog("in panel.AnalyticsLanguageViews")
|
common.DebugLog("in panel.AnalyticsLanguageViews")
|
||||||
acc := qgen.Builder.Accumulator()
|
acc := qgen.Builder.Accumulator()
|
||||||
|
@ -410,7 +409,7 @@ func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.
|
||||||
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||||
common.DebugLogf("graph: %+v\n", graph)
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
|
|
||||||
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(domain), "", graph, timeRange.Range}
|
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", common.SanitiseSingleLine(domain), "", graph, timeRange.Range}
|
||||||
return panelRenderTemplate("panel_analytics_referrer_views", w, r, user, &pi)
|
return panelRenderTemplate("panel_analytics_referrer_views", w, r, user, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,7 +735,7 @@ func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User
|
||||||
var refItems []common.PanelAnalyticsAgentsItem
|
var refItems []common.PanelAnalyticsAgentsItem
|
||||||
for domain, count := range refMap {
|
for domain, count := range refMap {
|
||||||
refItems = append(refItems, common.PanelAnalyticsAgentsItem{
|
refItems = append(refItems, common.PanelAnalyticsAgentsItem{
|
||||||
Agent: html.EscapeString(domain),
|
Agent: common.SanitiseSingleLine(domain),
|
||||||
Count: count,
|
Count: count,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package panel
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -104,7 +103,7 @@ func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user common.User,
|
||||||
return common.NoPermissions(w, r, user)
|
return common.NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
scontent := html.EscapeString(r.PostFormValue("setting-value"))
|
scontent := common.SanitiseBody(r.PostFormValue("setting-value"))
|
||||||
err := headerLite.Settings.Update(sname, scontent)
|
err := headerLite.Settings.Update(sname, scontent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if common.SafeSettingError(err) {
|
if common.SafeSettingError(err) {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"html"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -142,8 +141,9 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
|
|
||||||
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
|
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
|
||||||
_, exists := pollInputItems[index]
|
_, exists := pollInputItems[index]
|
||||||
if !exists && len(html.EscapeString(value)) != 0 {
|
// TODO: Should we use SanitiseBody instead to keep the newlines?
|
||||||
pollInputItems[index] = html.EscapeString(value)
|
if !exists && len(common.SanitiseSingleLine(value)) != 0 {
|
||||||
|
pollInputItems[index] = common.SanitiseSingleLine(value)
|
||||||
if len(pollInputItems) >= maxPollOptions {
|
if len(pollInputItems) >= maxPollOptions {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"html"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -334,7 +333,7 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
return common.NoPermissions(w, r, user)
|
return common.NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
topicName := html.EscapeString(strings.Replace(r.PostFormValue("topic-name"), "\n", "", -1))
|
topicName := common.SanitiseSingleLine(r.PostFormValue("topic-name"))
|
||||||
content := common.PreparseMessage(r.PostFormValue("topic-content"))
|
content := common.PreparseMessage(r.PostFormValue("topic-content"))
|
||||||
// TODO: Fully parse the post and store it in the parsed column
|
// TODO: Fully parse the post and store it in the parsed column
|
||||||
tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP)
|
tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP)
|
||||||
|
@ -377,9 +376,9 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
|
|
||||||
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
|
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
|
||||||
_, exists := pollInputItems[index]
|
_, exists := pollInputItems[index]
|
||||||
if !exists && len(html.EscapeString(value)) != 0 {
|
// TODO: Should we use SanitiseBody instead to keep the newlines?
|
||||||
pollInputItems[index] = html.EscapeString(value)
|
if !exists && len(common.SanitiseSingleLine(value)) != 0 {
|
||||||
|
pollInputItems[index] = common.SanitiseSingleLine(value)
|
||||||
if len(pollInputItems) >= maxPollOptions {
|
if len(pollInputItems) >= maxPollOptions {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,21 @@
|
||||||
<div class="formitem formlabel"><a id="confirm_password_label">{{lang "register_account_confirm_password"}}</a></div>
|
<div class="formitem formlabel"><a id="confirm_password_label">{{lang "register_account_confirm_password"}}</a></div>
|
||||||
<div class="formitem"><input name="confirm_password" type="password" placeholder="*****" autocomplete="new-password" aria-labelledby="confirm_password_label" required /></div>
|
<div class="formitem"><input name="confirm_password" type="password" placeholder="*****" autocomplete="new-password" aria-labelledby="confirm_password_label" required /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="formrow">
|
<div class="formrow">{{/** This is not a TOS, that text is there to fool the spambots **/}}
|
||||||
|
<div class="formitem formlabel"><a id="accept_tos_label">{{lang "register_account_anti_spam"}}</a></div>
|
||||||
|
<div class="formitem"><select name="tos" aria-labelledby="accept_tos_label" required>
|
||||||
|
<option value="1" selected>{{lang "option_yes"}}</option>
|
||||||
|
<option value="0">{{lang "option_no"}}</option>
|
||||||
|
</select></div>
|
||||||
|
</div>
|
||||||
|
<div class="formrow register_button_row">
|
||||||
<div class="formitem"><button name="register-button" class="formbutton">{{lang "register_submit_button"}}</button></div>
|
<div class="formitem"><button name="register-button" class="formbutton">{{lang "register_submit_button"}}</button></div>
|
||||||
</div>
|
</div>
|
||||||
|
<input id="js-antispam" name="antispam" value="nananana" type="hidden" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
<script>{{/** TODO: Are we sure we want to do this by ID, just in case we reuse this and have multiple antispams on the page? **/}}
|
||||||
|
document.getElementById("js-antispam").value = "{{.Something}}";
|
||||||
|
</script>
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
||||||
|
|
|
@ -1292,7 +1292,7 @@ textarea {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#create_topic_page .close_form, #create_topic_page .formlabel, #login_page .formlabel, #register_page .formlabel {
|
#create_topic_page .close_form, #create_topic_page .formlabel, #login_page .formlabel {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#login_page .formrow:not(:first-child):not(:last-child), #register_page .formrow:not(:first-child):not(:last-child) {
|
#login_page .formrow:not(:first-child):not(:last-child), #register_page .formrow:not(:first-child):not(:last-child) {
|
||||||
|
@ -1301,12 +1301,27 @@ textarea {
|
||||||
#login_page .formrow:not(:first-child), #register_page .formrow:not(:first-child) {
|
#login_page .formrow:not(:first-child), #register_page .formrow:not(:first-child) {
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
#login_page .formrow:not(:last-child), #register_page .formrow:not(:last-child) {
|
#login_page .formrow:not(:last-child) {
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
}
|
}
|
||||||
#login_page .formrow, #register_page .formrow {
|
#login_page .formrow, #register_page .formrow {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
#register_page .formrow:not(:last-child) {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
#register_page .formlabel {
|
||||||
|
display: block;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
#register_page .register_button_row {
|
||||||
|
padding: 12px !important;
|
||||||
|
padding-top: 0px !important;
|
||||||
|
margin-top: -2px !important;
|
||||||
|
}
|
||||||
|
#register_page .register_button_row .formbutton {
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: Add a generic button_row class and add this to them all? */
|
/* TODO: Add a generic button_row class and add this to them all? */
|
||||||
.login_button_row {
|
.login_button_row {
|
||||||
|
|
Loading…
Reference in New Issue