Static CSS files are now processed by the template system. This will be most likely be superseded by a more sophisticated system in the future.

Added the authentication interface and associated struct to better organise / escapsulate the authentication logic.
Added the LogError function. We'll put together a custom logger at some point which will supersede this.
Reorganised things a little.
Moved two queries into the query generator and inlined four with the query builder.
Added SimpleInsertLeftJoin to the query generator.
Added SimpleInsertSelect, SimpleInsertLeftJoin, and SimpleInsertInnerJoin to the query builder.
Removed the undocumented password reset mechanism for security reasons. We have a proper interface for this in the control panel.
Made one of the template compiler errors less cryptic.
Fixed the CSS on widget_simple.
Fixed a glitch in the weak password detector regarding unique character detection.
Moved the responsive CSS in media.partial.css for Tempra Conflux, and Tempra Simple.
Added the CreateUser method to the UserStore.
Users are no longer logged out on all devices when logging out.

Took the first step towards SEO URLs.
This commit is contained in:
Azareal 2017-06-25 10:56:39 +01:00
parent 1cc1e47582
commit 21d623cba4
38 changed files with 855 additions and 635 deletions

117
auth.go Normal file
View File

@ -0,0 +1,117 @@
/* Work in progress */
package main
import "log"
import "errors"
import "strconv"
import "net/http"
import "database/sql"
import "./query_gen/lib"
import "golang.org/x/crypto/bcrypt"
var auth Auth
var ErrMismatchedHashAndPassword = bcrypt.ErrMismatchedHashAndPassword
type Auth interface
{
Authenticate(username string, password string) (int,error)
Logout(w http.ResponseWriter, uid int)
ForceLogout(uid int) error
SetCookies(w http.ResponseWriter, uid int, session string)
CreateSession(uid int) (string, error)
}
type DefaultAuth struct
{
login *sql.Stmt
logout *sql.Stmt
}
func NewDefaultAuth() *DefaultAuth {
login_stmt, err := qgen.Builder.SimpleSelect("users","uid, name, password, salt","name = ?","","")
if err != nil {
log.Fatal(err)
}
logout_stmt, err := qgen.Builder.SimpleUpdate("users","session = ''","uid = ?")
if err != nil {
log.Fatal(err)
}
return &DefaultAuth{
login: login_stmt,
logout: logout_stmt,
}
}
func (auth *DefaultAuth) Authenticate(username string, password string) (uid int, err error) {
var real_password, salt string
err = auth.login.QueryRow(username).Scan(&uid, &username, &real_password, &salt)
if err == sql.ErrNoRows {
return 0, errors.New("We couldn't find an account with that username.")
} else if err != nil {
LogError(err)
return 0, errors.New("There was a glitch in the system. Please contact the system administrator.")
}
if salt == "" {
// Send an email to admin for this?
LogError(errors.New("Missing salt for user #" + strconv.Itoa(uid) + ". Potential security breach."))
return 0, errors.New("There was a glitch in the system. Please contact the system administrator.")
}
err = CheckPassword(real_password,password,salt)
if err == ErrMismatchedHashAndPassword {
return 0, errors.New("That's not the correct password.")
} else if err != nil {
LogError(err)
return 0, errors.New("There was a glitch in the system. Please contact the system administrator.")
}
return uid, nil
}
func (auth *DefaultAuth) ForceLogout(uid int) error {
_, err := auth.logout.Exec(uid)
if err != nil {
LogError(err)
return errors.New("There was a glitch in the system. Please contact the system administrator.")
}
// Flush the user out of the cache and reload
err = users.Load(uid)
if err != nil {
return errors.New("Your account no longer exists!")
}
return nil
}
func (auth *DefaultAuth) Logout(w http.ResponseWriter, _ int) {
cookie := http.Cookie{Name:"uid",Value:"",Path:"/",MaxAge: year}
http.SetCookie(w,&cookie)
cookie = http.Cookie{Name:"session",Value:"",Path:"/",MaxAge: year}
http.SetCookie(w,&cookie)
}
func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session string) {
cookie := http.Cookie{Name: "uid",Value: strconv.Itoa(uid),Path: "/",MaxAge: year}
http.SetCookie(w,&cookie)
cookie = http.Cookie{Name: "session",Value: session,Path: "/",MaxAge: year}
http.SetCookie(w,&cookie)
}
func(auth *DefaultAuth) CreateSession(uid int) (string, error) {
session, err := GenerateSafeString(sessionLength)
if err != nil {
return "", err
}
_, err = update_session_stmt.Exec(session, uid)
if err != nil {
return "", err
}
// Reload the user data
_ = users.Load(uid)
return session, nil
}

View File

@ -26,6 +26,10 @@ func init_errors() error {
return nil
}
func LogError(err error) {
log.Fatal(err)
}
func InternalError(err error, w http.ResponseWriter, r *http.Request) {
w.Write(error_internal)
log.Fatal(err)

View File

@ -1,16 +1,17 @@
package main
import "log"
import "bytes"
import "strings"
import "mime"
//import "errors"
import "os"
//import "io"
import "io/ioutil"
import "path/filepath"
import "net/http"
import "compress/gzip"
import (
"log"
"bytes"
"strings"
"mime"
//"errors"
"os"
"io/ioutil"
"path/filepath"
"net/http"
"compress/gzip"
)
type SFile struct
{
@ -24,42 +25,39 @@ type SFile struct
FormattedModTime string
}
/*func (r SFile) Read(b []byte) (n int, err error) {
n = 0
if r.Pos > r.Length {
return n, io.EOF
}
size := cap(b)
if size > 0 {
for n < size {
b[n] = r.Data[r.Pos]
n++
if r.Pos == r.Length {
break
}
r.Pos++
}
}
return n, nil
type CssData struct
{
ComingSoon string
}
func (r SFile) Seek(offset int64, whence int) (int64, error) {
if offset < 0 {
return 0, errors.New("negative position")
func init_static_files() {
log.Print("Loading the static files.")
err := filepath.Walk("./public", func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
return nil
}
path = strings.Replace(path,"\\","/",-1)
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
path = strings.TrimPrefix(path,"public/")
var ext string = filepath.Ext("/public/" + path)
gzip_data := compress_bytes_gzip(data)
static_files["/static/" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)}
if debug {
log.Print("Added the '" + path + "' static file.")
}
return nil
})
if err != nil {
log.Fatal(err)
}
switch whence {
case 0:
r.Pos = offset
case 1:
r.Pos += offset
case 2:
r.Pos = r.Length + offset
default:
return 0, errors.New("invalid whence")
}
return r.Pos, nil
}*/
}
func add_static_file(path string, prefix string) error {
data, err := ioutil.ReadFile(path)
@ -74,20 +72,22 @@ func add_static_file(path string, prefix string) error {
if err != nil {
return err
}
var ext string = filepath.Ext(path)
path = strings.TrimPrefix(path, prefix)
gzip_data := compress_bytes_gzip(data)
static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)}
if debug {
log.Print("Added the '" + path + "' static file")
}
gzip_data := compress_bytes_gzip(data)
static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(filepath.Ext(prefix + path)),f,f.ModTime().UTC().Format(http.TimeFormat)}
return nil
}
func compress_bytes_gzip(in []byte) []byte {
var buff bytes.Buffer
gz := gzip.NewWriter(&buff)
gz := gzip.NewWriter(&buff)
gz.Write(in)
gz.Close()
return buff.Bytes()

View File

@ -9,9 +9,7 @@ import "database/sql"
var get_user_stmt *sql.Stmt
var get_reply_stmt *sql.Stmt
var get_user_reply_stmt *sql.Stmt
var login_stmt *sql.Stmt
var get_password_stmt *sql.Stmt
var username_exists_stmt *sql.Stmt
var get_settings_stmt *sql.Stmt
var get_setting_stmt *sql.Stmt
var get_full_setting_stmt *sql.Stmt
@ -56,7 +54,6 @@ var create_action_reply_stmt *sql.Stmt
var create_like_stmt *sql.Stmt
var add_activity_stmt *sql.Stmt
var notify_one_stmt *sql.Stmt
var register_stmt *sql.Stmt
var add_email_stmt *sql.Stmt
var create_profile_reply_stmt *sql.Stmt
var add_subscription_stmt *sql.Stmt
@ -81,7 +78,6 @@ var stick_topic_stmt *sql.Stmt
var unstick_topic_stmt *sql.Stmt
var update_last_ip_stmt *sql.Stmt
var update_session_stmt *sql.Stmt
var logout_stmt *sql.Stmt
var set_password_stmt *sql.Stmt
var set_avatar_stmt *sql.Stmt
var set_username_stmt *sql.Stmt
@ -111,6 +107,8 @@ var delete_profile_reply_stmt *sql.Stmt
var delete_forum_perms_by_forum_stmt *sql.Stmt
var report_exists_stmt *sql.Stmt
var add_forum_perms_to_forum_admins_stmt *sql.Stmt
var add_forum_perms_to_forum_staff_stmt *sql.Stmt
var add_forum_perms_to_forum_members_stmt *sql.Stmt
var notify_watchers_stmt *sql.Stmt
func gen_mysql() (err error) {
@ -136,24 +134,12 @@ func gen_mysql() (err error) {
return err
}
log.Print("Preparing login statement.")
login_stmt, err = db.Prepare("SELECT `uid`,`name`,`password`,`salt` FROM `users` WHERE `name` = ?")
if err != nil {
return err
}
log.Print("Preparing get_password statement.")
get_password_stmt, err = db.Prepare("SELECT `password`,`salt` FROM `users` WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing username_exists statement.")
username_exists_stmt, err = db.Prepare("SELECT `name` FROM `users` WHERE `name` = ?")
if err != nil {
return err
}
log.Print("Preparing get_settings statement.")
get_settings_stmt, err = db.Prepare("SELECT `name`,`content`,`type` FROM `settings`")
if err != nil {
@ -418,12 +404,6 @@ func gen_mysql() (err error) {
return err
}
log.Print("Preparing register statement.")
register_stmt, err = db.Prepare("INSERT INTO `users`(`name`,`email`,`password`,`salt`,`group`,`is_super_admin`,`session`,`active`,`message`) VALUES (?,?,?,?,?,0,?,?,'')")
if err != nil {
return err
}
log.Print("Preparing add_email statement.")
add_email_stmt, err = db.Prepare("INSERT INTO `emails`(`email`,`uid`,`validated`,`token`) VALUES (?,?,?,?)")
if err != nil {
@ -568,12 +548,6 @@ func gen_mysql() (err error) {
return err
}
log.Print("Preparing logout statement.")
logout_stmt, err = db.Prepare("UPDATE `users` SET `session` = '' WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing set_password statement.")
set_password_stmt, err = db.Prepare("UPDATE `users` SET `password` = ?,`salt` = ? WHERE `uid` = ?")
if err != nil {
@ -748,6 +722,18 @@ func gen_mysql() (err error) {
return err
}
log.Print("Preparing add_forum_perms_to_forum_staff statement.")
add_forum_perms_to_forum_staff_stmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 0 AND `is_mod` = 1")
if err != nil {
return err
}
log.Print("Preparing add_forum_perms_to_forum_members statement.")
add_forum_perms_to_forum_members_stmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 0 AND `is_mod` = 0 AND `is_banned` = 0")
if err != nil {
return err
}
log.Print("Preparing notify_watchers statement.")
notify_watchers_stmt, err = db.Prepare("INSERT INTO `activity_stream_matches`(`watcher`,`asid`) SELECT `activity_subscriptions`.`user`, `activity_stream`.`asid` FROM `activity_stream` INNER JOIN `activity_subscriptions` ON `activity_subscriptions`.`targetType` = `activity_stream`.`elementType` AND `activity_subscriptions`.`targetID` = `activity_stream`.`elementID` AND `activity_subscriptions`.`user` != `activity_stream`.`actor` WHERE `asid` = ?")
if err != nil {

42
main.go
View File

@ -5,13 +5,8 @@ import (
"net/http"
"fmt"
"log"
"mime"
"time"
"strings"
"path/filepath"
"io"
"io/ioutil"
"os"
"html/template"
//"runtime/pprof"
)
@ -26,9 +21,6 @@ const kilobyte int = 1024
const megabyte int = kilobyte * 1024
const gigabyte int = megabyte * 1024
const terabyte int = gigabyte * 1024
//const thousand int = 1000
//const million int = 1_000_000
//const billion int = 1_000_000_000
const saltLength int = 32
const sessionLength int = 80
var enable_websockets bool = false // Don't change this, the value is overwritten by an initialiser
@ -59,8 +51,8 @@ func compile_templates() {
NoticeList:[]string{"test"},
Stylesheets:[]string{"panel"},
Scripts:[]string{"whatever"},
Sidebars:HeaderSidebars{
Left: template.HTML("lalala"),
Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
}
@ -142,33 +134,6 @@ func init_templates() {
template.Must(templates.ParseGlob("pages/*"))
}
func init_static_files() {
log.Print("Loading the static files.")
err := filepath.Walk("./public", func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
return nil
}
path = strings.Replace(path,"\\","/",-1)
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
path = strings.TrimPrefix(path,"public/")
if debug {
log.Print("Added the '" + path + "' static file.")
}
gzip_data := compress_bytes_gzip(data)
static_files["/static/" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(filepath.Ext("/public/" + path)),f,f.ModTime().UTC().Format(http.TimeFormat)}
return nil
})
if err != nil {
log.Fatal(err)
}
}
func main(){
//if profiling {
// f, err := os.Create("startup_cpu.prof")
@ -215,6 +180,9 @@ func main(){
log.Fatal(err)
}
log.Print("Initialising the authentication system")
auth = NewDefaultAuth()
log.Print("Initialising the router")
router := NewGenRouter(http.FileServer(http.Dir("./uploads")))
///router.HandleFunc("/static/", route_static)

View File

@ -9,9 +9,6 @@ import "./query_gen/lib"
var get_activity_feed_by_watcher_stmt *sql.Stmt
var get_activity_count_by_watcher_stmt *sql.Stmt
var add_forum_perms_to_forum_staff_stmt *sql.Stmt
var add_forum_perms_to_forum_members_stmt *sql.Stmt
var update_forum_perms_for_group_stmt *sql.Stmt
var todays_post_count_stmt *sql.Stmt
var todays_topic_count_stmt *sql.Stmt
var todays_report_count_stmt *sql.Stmt
@ -65,18 +62,6 @@ func _init_database() (err error) {
return err
}
log.Print("Preparing add_forum_perms_to_forum_staff statement.")
add_forum_perms_to_forum_staff_stmt, err = db.Prepare("INSERT INTO forums_permissions(gid,fid,preset,permissions) SELECT `gid`,? AS fid,? AS preset,? AS permissions FROM users_groups WHERE is_admin = 0 AND is_mod = 1")
if err != nil {
return err
}
log.Print("Preparing add_forum_perms_to_forum_members statement.")
add_forum_perms_to_forum_members_stmt, err = db.Prepare("INSERT INTO forums_permissions(gid,fid,preset,permissions) SELECT `gid`,? AS fid,? AS preset,? AS permissions FROM users_groups WHERE is_admin = 0 AND is_mod = 0 AND is_banned = 0")
if err != nil {
return err
}
log.Print("Preparing todays_post_count statement.")
todays_post_count_stmt, err = db.Prepare("select count(*) from replies where createdAt BETWEEN (now() - interval 1 day) and now()")
if err != nil {

View File

@ -13,13 +13,13 @@ type HeaderVars struct
NoticeList []string
Scripts []string
Stylesheets []string
Sidebars HeaderSidebars
Widgets PageWidgets
}
type HeaderSidebars struct
type PageWidgets struct
{
Left template.HTML
Right template.HTML
LeftSidebar template.HTML
RightSidebar template.HTML
}
type ExtData struct

View File

@ -1466,6 +1466,7 @@ func route_panel_themes_default(w http.ResponseWriter, r *http.Request, uname st
}
var isDefault bool
fmt.Println("uname",uname)
err := is_theme_default_stmt.QueryRow(uname).Scan(&isDefault)
if err != nil && err != sql.ErrNoRows {
InternalError(err,w,r)
@ -1474,6 +1475,7 @@ func route_panel_themes_default(w http.ResponseWriter, r *http.Request, uname st
has_theme := err != sql.ErrNoRows
if has_theme {
fmt.Println("isDefault",isDefault)
if isDefault {
LocalError("The theme is already active",w,r,user)
return
@ -1511,7 +1513,7 @@ func route_panel_themes_default(w http.ResponseWriter, r *http.Request, uname st
defaultTheme = uname
reset_template_overrides()
add_theme_static_files(uname)
add_theme_static_files(themes[uname])
map_theme_templates(theme)
http.Redirect(w,r,"/panel/themes/",http.StatusSeeOther)

View File

@ -64,6 +64,30 @@ func (build *builder) SimpleInsert(table string, columns string, fields string)
return build.conn.Prepare(res)
}
func (build *builder) SimpleInsertSelect(ins DB_Insert, sel DB_Select) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel)
if err != nil {
return stmt, err
}
return build.conn.Prepare(res)
}
func (build *builder) SimpleInsertLeftJoin(ins DB_Insert, sel DB_Join) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertLeftJoin("_builder", ins, sel)
if err != nil {
return stmt, err
}
return build.conn.Prepare(res)
}
func (build *builder) SimpleInsertInnerJoin(ins DB_Insert, sel DB_Join) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel)
if err != nil {
return stmt, err
}
return build.conn.Prepare(res)
}
func (build *builder) SimpleUpdate(table string, set string, where string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleUpdate("_builder", table, set, where)
if err != nil {

View File

@ -452,6 +452,7 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) {
/* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword
@ -523,8 +524,94 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
/* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(ins.Columns) {
if column.Type == "function" {
querystr += column.Left + ","
} else {
querystr += "`" + column.Left + "`,"
}
}
querystr = querystr[0:len(querystr) - 1] + ") SELECT"
/* Select Portion */
for _, column := range _process_columns(sel.Columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Table != "" {
source = "`" + column.Table + "`.`" + column.Left + "`"
} else if column.Type == "function" {
source = column.Left
} else {
source = "`" + column.Left + "`"
}
if column.Alias != "" {
alias = " AS `" + column.Alias + "`"
}
querystr += " " + source + alias + ","
}
querystr = querystr[0:len(querystr) - 1]
querystr += " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON "
for _, joiner := range _process_joiner(sel.Joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
}
querystr = querystr[0:len(querystr) - 4]
// Add support for BETWEEN x.x
if len(sel.Where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(sel.Where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
halves := strings.Split(token.Contents,".")
if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else {
querystr += " `" + token.Contents + "`"
}
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
if len(sel.Orderby) != 0 {
querystr += " ORDER BY "
for _, column := range _process_orderby(sel.Orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
}
querystr = querystr[0:len(querystr) - 1]
}
if sel.Limit != "" {
querystr += " LIMIT " + sel.Limit
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
/* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword

View File

@ -33,24 +33,6 @@ type DB_Insert struct
Fields string
}
/*type DB_Select struct
{
Name string
Table string
Columns []DB_Column
Where []DB_Where
Orderby []DB_Order
Limit DB_Limit
}
type DB_Insert struct
{
Name string
Table string
Columns []DB_Column
Fields []DB_Field
}*/
type DB_Column struct
{
Table string
@ -111,6 +93,7 @@ type DB_Adapter interface {
SimpleLeftJoin(string,string,string,string,string,string,string,string) (string, error)
SimpleInnerJoin(string,string,string,string,string,string,string,string) (string, error)
SimpleInsertSelect(string,DB_Insert,DB_Select) (string,error)
SimpleInsertLeftJoin(string,DB_Insert,DB_Join) (string,error)
SimpleInsertInnerJoin(string,DB_Insert,DB_Join) (string,error)
SimpleCount(string,string,string,string) (string, error)
Write() error

View File

@ -81,12 +81,8 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("get_reply","replies","tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount","rid = ?","","")
adapter.SimpleSelect("get_user_reply","users_replies","uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress","rid = ?","","")
adapter.SimpleSelect("login","users","uid, name, password, salt","name = ?","","")
adapter.SimpleSelect("get_password","users","password,salt","uid = ?","","")
adapter.SimpleSelect("username_exists","users","name","name = ?","","")
adapter.SimpleSelect("get_password","users","password,salt","uid = ?","","")
adapter.SimpleSelect("get_settings","settings","name, content, type","","","")
@ -189,10 +185,6 @@ func write_inserts(adapter qgen.DB_Adapter) error {
adapter.SimpleInsert("notify_one","activity_stream_matches","watcher,asid","?,?")
// Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO
adapter.SimpleInsert("register","users","name, email, password, salt, group, is_super_admin, session, active, message","?,?,?,?,?,0,?,?,''")
adapter.SimpleInsert("add_email","emails","email, uid, validated, token","?,?,?,?")
adapter.SimpleInsert("create_profile_reply","users_replies","uid, content, parsed_content, createdAt, createdBy, ipaddress","?,?,?,NOW(),?,?")
@ -249,8 +241,6 @@ func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("update_last_ip","users","last_ip = ?","uid = ?")
adapter.SimpleUpdate("update_session","users","session = ?","uid = ?")
adapter.SimpleUpdate("logout","users","session = ''","uid = ?")
adapter.SimpleUpdate("set_password","users","password = ?, salt = ?","uid = ?")
@ -327,6 +317,16 @@ func write_insert_selects(adapter qgen.DB_Adapter) error {
qgen.DB_Select{"users_groups","gid, ? AS fid, ? AS preset, ? AS permissions","is_admin = 1","",""},
)
adapter.SimpleInsertSelect("add_forum_perms_to_forum_staff",
qgen.DB_Insert{"forums_permissions","gid,fid,preset,permissions",""},
qgen.DB_Select{"users_groups","gid, ? AS fid, ? AS preset, ? AS permissions","is_admin = 0 AND is_mod = 1","",""},
)
adapter.SimpleInsertSelect("add_forum_perms_to_forum_members",
qgen.DB_Insert{"forums_permissions","gid,fid,preset,permissions",""},
qgen.DB_Select{"users_groups","gid, ? AS fid, ? AS preset, ? AS permissions","is_admin = 0 AND is_mod = 0 AND is_banned = 0","",""},
)
return nil
}

162
routes.go
View File

@ -190,7 +190,13 @@ func route_topics(w http.ResponseWriter, r *http.Request){
func route_forum(w http.ResponseWriter, r *http.Request, sfid string){
page, _ := strconv.Atoi(r.FormValue("page"))
fid, err := strconv.Atoi(sfid)
// SEO URLs...
halves := strings.Split(sfid,".")
if len(halves) < 2 {
halves = append(halves,halves[0])
}
fid, err := strconv.Atoi(halves[1])
if err != nil {
PreError("The provided ForumID is not a valid number.",w,r)
return
@ -1238,18 +1244,7 @@ func route_account_own_edit_critical_submit(w http.ResponseWriter, r *http.Reque
SetPassword(user.ID, new_password)
// Log the user out as a safety precaution
_, err = logout_stmt.Exec(user.ID)
if err != nil {
InternalError(err,w,r)
return
}
// Reload the user data
err = users.Load(user.ID)
if err != nil {
LocalError("Your account no longer exists!",w,r,user)
return
}
auth.ForceLogout(user.ID)
headerVars.NoticeList = append(headerVars.NoticeList,"Your password was successfully updated")
pi := Page{"Edit Password",user,headerVars,tList,nil}
@ -1542,18 +1537,7 @@ func route_logout(w http.ResponseWriter, r *http.Request) {
LocalError("You can't logout without logging in first.",w,r,user)
return
}
_, err := logout_stmt.Exec(user.ID)
if err != nil {
InternalError(err,w,r)
return
}
err = users.Load(user.ID)
if err != nil {
LocalError("Your account doesn't exist!",w,r,user)
return
}
auth.Logout(w, user.ID)
http.Redirect(w,r, "/", http.StatusSeeOther)
}
@ -1585,79 +1569,24 @@ func route_login_submit(w http.ResponseWriter, r *http.Request) {
return
}
var uid int
var real_password, salt, session string
username := html.EscapeString(r.PostFormValue("username"))
password := r.PostFormValue("password")
err = login_stmt.QueryRow(username).Scan(&uid, &username, &real_password, &salt)
if err == sql.ErrNoRows {
LocalError("That username doesn't exist.",w,r,user)
return
} else if err != nil {
InternalError(err,w,r)
uid, err := auth.Authenticate(html.EscapeString(r.PostFormValue("username")), r.PostFormValue("password"))
if err != nil {
LocalError(err.Error(),w,r,user)
return
}
// Admin password reset mechanism...
if salt == "" {
if password != real_password {
LocalError("That's not the correct password.",w,r,user)
return
}
// Re-encrypt the password
SetPassword(uid, real_password)
// Fe-fetch the user data...
err = login_stmt.QueryRow(username).Scan(&uid, &username, &real_password, &salt)
if err == sql.ErrNoRows {
LocalError("That username doesn't exist anymore.",w,r,user)
return
} else if err != nil {
var session string
if user.Session == "" {
session, err = auth.CreateSession(uid)
if err != nil {
InternalError(err,w,r)
return
}
} else {
session = user.Session
}
password = password + salt
if err != nil {
InternalError(err,w,r)
return
}
err = bcrypt.CompareHashAndPassword([]byte(real_password), []byte(password))
if err == bcrypt.ErrMismatchedHashAndPassword {
LocalError("That's not the correct password.",w,r,user)
return
} else if err != nil {
InternalError(err,w,r)
return
}
session, err = GenerateSafeString(sessionLength)
if err != nil {
InternalError(err,w,r)
return
}
_, err = update_session_stmt.Exec(session, uid)
if err != nil {
InternalError(err,w,r)
return
}
// Reload the user data
err = users.Load(uid)
if err != nil {
LocalError("Your account no longer exists!",w,r,user)
return
}
cookie := http.Cookie{Name:"uid",Value:strconv.Itoa(uid),Path:"/",MaxAge:year}
http.SetCookie(w,&cookie)
cookie = http.Cookie{Name:"session",Value:session,Path:"/",MaxAge:year}
http.SetCookie(w,&cookie)
auth.SetCookies(w,uid,session)
http.Redirect(w,r,"/",http.StatusSeeOther)
}
@ -1726,34 +1655,6 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) {
return
}
// Is this username already taken..?
err = username_exists_stmt.QueryRow(username).Scan(&username)
if err != nil && err != sql.ErrNoRows {
InternalError(err,w,r)
return
} else if err != sql.ErrNoRows {
LocalError("This username isn't available. Try another.",w,r,user)
return
}
salt, err := GenerateSafeString(saltLength)
if err != nil {
InternalError(err,w,r)
return
}
session, err := GenerateSafeString(sessionLength)
if err != nil {
InternalError(err,w,r)
return
}
password = password + salt
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
InternalError(err,w,r)
return
}
var active, group int
switch settings["activation_type"] {
case 1: // Activate All
@ -1763,13 +1664,11 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) {
group = activation_group
}
res, err := register_stmt.Exec(username,email,string(hashed_password),salt,group,session,active)
if err != nil {
InternalError(err,w,r)
uid, err := users.CreateUser(username, password, email, group, active)
if err == err_account_exists {
LocalError("This username isn't available. Try another.",w,r,user)
return
}
lastId, err := res.LastInsertId()
if err != nil {
} else if err != nil {
InternalError(err,w,r)
return
}
@ -1781,7 +1680,7 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) {
InternalError(err,w,r)
return
}
_, err = add_email_stmt.Exec(email, lastId, 0, token)
_, err = add_email_stmt.Exec(email, uid, 0, token)
if err != nil {
InternalError(err,w,r)
return
@ -1793,11 +1692,14 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) {
}
}
cookie := http.Cookie{Name: "uid",Value: strconv.FormatInt(lastId, 10),Path: "/",MaxAge: year}
http.SetCookie(w,&cookie)
cookie = http.Cookie{Name: "session",Value: session,Path: "/",MaxAge: year}
http.SetCookie(w,&cookie)
http.Redirect(w,r, "/", http.StatusSeeOther)
session, err := auth.CreateSession(uid)
if err != nil {
InternalError(err,w,r)
return
}
auth.SetCookies(w,uid,session)
http.Redirect(w,r,"/",http.StatusSeeOther)
}
var phrase_login_alerts []byte = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)

View File

@ -51,7 +51,7 @@ w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_9)
if tmpl_forum_vars.Header.Sidebars.Right != "" {
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_10)
}
w.Write(header_11)
@ -137,9 +137,9 @@ w.Write(forum_31)
}
w.Write(forum_32)
w.Write(footer_0)
if tmpl_forum_vars.Header.Sidebars.Right != "" {
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)
w.Write([]byte(string(tmpl_forum_vars.Header.Sidebars.Right)))
w.Write([]byte(string(tmpl_forum_vars.Header.Widgets.RightSidebar)))
w.Write(footer_2)
}
w.Write(footer_3)

View File

@ -51,7 +51,7 @@ w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_9)
if tmpl_forums_vars.Header.Sidebars.Right != "" {
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_10)
}
w.Write(header_11)
@ -110,9 +110,9 @@ w.Write(forums_20)
}
w.Write(forums_21)
w.Write(footer_0)
if tmpl_forums_vars.Header.Sidebars.Right != "" {
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)
w.Write([]byte(string(tmpl_forums_vars.Header.Sidebars.Right)))
w.Write([]byte(string(tmpl_forums_vars.Header.Widgets.RightSidebar)))
w.Write(footer_2)
}
w.Write(footer_3)

View File

@ -51,7 +51,7 @@ w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_9)
if tmpl_profile_vars.Header.Sidebars.Right != "" {
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_10)
}
w.Write(header_11)
@ -143,9 +143,9 @@ w.Write(profile_37)
}
w.Write(profile_38)
w.Write(footer_0)
if tmpl_profile_vars.Header.Sidebars.Right != "" {
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)
w.Write([]byte(string(tmpl_profile_vars.Header.Sidebars.Right)))
w.Write([]byte(string(tmpl_profile_vars.Header.Widgets.RightSidebar)))
w.Write(footer_2)
}
w.Write(footer_3)

View File

@ -51,7 +51,7 @@ w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_9)
if tmpl_topic_vars.Header.Sidebars.Right != "" {
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_10)
}
w.Write(header_11)
@ -251,9 +251,9 @@ w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_83)
}
w.Write(footer_0)
if tmpl_topic_vars.Header.Sidebars.Right != "" {
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)
w.Write([]byte(string(tmpl_topic_vars.Header.Sidebars.Right)))
w.Write([]byte(string(tmpl_topic_vars.Header.Widgets.RightSidebar)))
w.Write(footer_2)
}
w.Write(footer_3)

View File

@ -51,7 +51,7 @@ w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_9)
if tmpl_topic_alt_vars.Header.Sidebars.Right != "" {
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_10)
}
w.Write(header_11)
@ -257,9 +257,9 @@ w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_87)
}
w.Write(footer_0)
if tmpl_topic_alt_vars.Header.Sidebars.Right != "" {
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)
w.Write([]byte(string(tmpl_topic_alt_vars.Header.Sidebars.Right)))
w.Write([]byte(string(tmpl_topic_alt_vars.Header.Widgets.RightSidebar)))
w.Write(footer_2)
}
w.Write(footer_3)

View File

@ -51,7 +51,7 @@ w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_9)
if tmpl_topics_vars.Header.Sidebars.Right != "" {
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_10)
}
w.Write(header_11)
@ -113,9 +113,9 @@ w.Write(topics_21)
}
w.Write(topics_22)
w.Write(footer_0)
if tmpl_topics_vars.Header.Sidebars.Right != "" {
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)
w.Write([]byte(string(tmpl_topics_vars.Header.Sidebars.Right)))
w.Write([]byte(string(tmpl_topics_vars.Header.Widgets.RightSidebar)))
w.Write(footer_2)
}
w.Write(footer_3)

View File

@ -887,6 +887,9 @@ func (c *CTemplateSet) compile_varsub(varname string, val reflect.Value) string
case reflect.Int64:
return "w.Write([]byte(strconv.FormatInt(" + varname + ", 10)))"
default:
if !val.IsValid() {
panic(varname + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
fmt.Println("Unknown Variable Name:",varname)
fmt.Println("Unknown Kind:",val.Kind())
fmt.Println("Unknown Type:",val.Type().Name())

View File

@ -1,5 +1,5 @@
</div>
{{if .Header.Sidebars.Right}}<div class="sidebar">{{.Header.Sidebars.Right}}</div>{{end}}
{{if .Header.Widgets.RightSidebar}}<div class="sidebar">{{.Header.Widgets.RightSidebar}}</div>{{end}}
<div style="clear: both;"></div>
</div>
</div>

View File

@ -18,5 +18,5 @@
<body>
<div class="container">
{{template "menu.html" .}}
<div id="back"><div id="main" {{if .Header.Sidebars.Right}}class="shrink_main"{{end}}>
<div id="back"><div id="main" {{if .Header.Widgets.RightSidebar}}class="shrink_main"{{end}}>
{{range .Header.NoticeList}}<div class="alert">{{.}}</div>{{end}}

View File

@ -1,5 +1,15 @@
{{template "header.html" . }}
{{template "panel-menu.html" . }}
<div class="colstack_left">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item">
<div class="rowitem passive"><a href="#">Widgets</a></div>
</div>
{{template "panel-inner-menu.html" . }}
</div>
<!-- Stop inlining this x.x -->
<style type="text/css">
.rowitem::after {
content: "";

View File

@ -1,5 +1,5 @@
<div class="rowblock">
<div class="rowitem">{{.Name}}</div>
<div class="rowitem rowhead">{{.Name}}</div>
</div>
<div class="rowblock">
<div class="rowitem">{{.Text}}</div>

View File

@ -6,12 +6,14 @@ import (
"log"
"io"
"os"
"bytes"
"strings"
"mime"
"io/ioutil"
"path/filepath"
"encoding/json"
"net/http"
"text/template"
)
var defaultTheme string
@ -37,6 +39,7 @@ type Theme struct
Templates []TemplateMapping
TemplatesMap map[string]string
Resources []ThemeResource
ResourceTemplates *template.Template
// This variable should only be set and unset by the system, not the theme meta file
Active bool
@ -89,11 +92,14 @@ func LoadThemes() error {
}
}
theme.ResourceTemplates = template.New("")
template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + uname + "/public/*.css"))
if defaultThemeSwitch {
log.Print("Loading the theme '" + theme.Name + "'")
theme.Active = true
defaultTheme = uname
add_theme_static_files(uname)
add_theme_static_files(theme)
map_theme_templates(theme)
} else {
theme.Active = false
@ -147,32 +153,46 @@ func init_themes() {
}
}
func add_theme_static_files(themeName string) {
err := filepath.Walk("./themes/" + themeName + "/public", func(path string, f os.FileInfo, err error) error {
func add_theme_static_files(theme Theme) {
err := filepath.Walk("./themes/" + theme.Name + "/public", func(path string, f os.FileInfo, err error) error {
if debug {
log.Print("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
}
if err != nil {
return err
}
if f.IsDir() {
return nil
}
path = strings.Replace(path,"\\","/",-1)
if debug {
log.Print("Attempting to add static file '" + path + "' for default theme '" + themeName + "'")
}
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
path = strings.TrimPrefix(path,"themes/" + themeName + "/public")
if debug {
log.Print("Added the '" + path + "' static file for default theme " + themeName + ".")
var ext string = filepath.Ext(path)
//log.Print("path ",path)
//log.Print("ext ",ext)
if ext == ".css" {
var b bytes.Buffer
var pieces []string = strings.Split(path,"/")
var filename string = pieces[len(pieces) - 1]
//log.Print("filename ", filename)
err = theme.ResourceTemplates.ExecuteTemplate(&b,filename, CssData{ComingSoon:"We don't have any data to pass you yet!"})
if err != nil {
return err
}
data = b.Bytes()
}
gzip_data := compress_bytes_gzip(data)
static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(filepath.Ext("/themes/" + themeName + "/public" + path)),f,f.ModTime().UTC().Format(http.TimeFormat)}
path = strings.TrimPrefix(path,"themes/" + theme.Name + "/public")
gzip_data := compress_bytes_gzip(data)
static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)}
if debug {
log.Print("Added the '" + path + "' static file for default theme " + theme.Name + ".")
}
return nil
})
if err != nil {

View File

@ -0,0 +1 @@
/* Sample CSS file. Doesn't do anything. */

View File

@ -527,132 +527,4 @@ button.username {
top: -2px;
}
/* The Media Queries */
@media (max-width: 880px) {
li {
height: 29px;
font-size: 15px;
padding-left: 9px;
padding-top: 6px;
padding-bottom: 6px;
}
ul {
height: 30px;
margin-top: 8px;
}
.menu_left, .menu_right { padding-right: 9px; }
.menu_alerts {
padding-left: 7px;
padding-right: 7px;
font-size: 18px;
}
body {
padding-left: 4px;
padding-right: 4px;
margin: 0px !important;
width: 100% !important;
height: 100% !important;
overflow-x: hidden;
}
.container { width: auto; }
.selectedAlert .alertList { top: 37px; right: 4px; }
}
@media (max-width: 680px) {
li {
padding-left: 5px;
padding-top: 3px;
padding-bottom: 2px;
height: 25px;
}
li a { font-size: 14px; }
ul { height: 26px; }
.menu_left, .menu_right { padding-right: 7px; }
.menu_create_topic, .hide_on_mobile { display: none; }
.menu_alerts {
padding-left: 4px;
padding-right: 4px;
font-size: 16px;
padding-top: 1px;
}
.selectedAlert .alertList { top: 33px; }
.prev_button, .next_button { top: auto;bottom: 5px; }
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
.grid_istat { margin-bottom: 0px; }
}
@media (max-width: 470px) {
ul { margin-bottom: 10px; }
.menu_overview, .menu_profile, .hide_on_micro { display: none; }
.selectedAlert .alertList {
width: 156px;
margin-bottom: 5px;
}
.alertItem.withAvatar {
background-size: 36px;
text-align: center;
padding-left: 42px;
height: 46px;
}
.alertItem { padding: 8px; }
.alertItem .text {
height: 30px;
font-size: 10px;
font-weight: bold;
white-space: normal;
}
.post_container { overflow: visible !important; }
.post_item {
background-position: 0px 2px !important;
background-size: 64px 64px !important;
padding-left: 2px !important;
min-height: 96px;
position: relative !important;
}
.post_item > .user_content {
margin-left: 75px !important;
width: 100% !important;
}
.post_item > .mod_button {
float: right !important;
margin-left: 2px !important;
position: relative;
top: -14px;
}
.post_item > .real_username {
position: absolute;
top: 70px;
float: left;
margin-top: 0px;
padding-top: 3px !important;
margin-right: 2px;
width: 60px;
font-size: 15px;
}
.userinfo { width: 70px; }
.userinfo .avatar_item {
background-size: 64px;
width: 64px;
min-height: 64px;
}
.content_container {
margin-left: 73px;
min-height: inherit !important;
}
.user_content { min-height: 76.5px !important; font-size: 15px; }
.user_content.nobuttons { min-height: 100.5px !important; }
.the_name { font-size: 15px; }
.post_tag { font-size: 12px; }
.rowtopic { font-size: 14px; }
.container { width: 100% !important; }
}
@media (max-width: 330px) {
li { padding-left: 6px; }
.menu_left { padding-right: 6px; }
}
{{template "media.partial.css" }}

View File

@ -0,0 +1,143 @@
/* The Media Queries */
@media(min-width: 881px) {
.shrink_main {
float: left;
width: calc(75% - 12px);
}
.sidebar {
float: left;
width: 25%;
margin-left: 12px;
}
}
@media (max-width: 880px) {
li {
height: 29px;
font-size: 15px;
padding-left: 9px;
padding-top: 6px;
padding-bottom: 6px;
}
ul {
height: 30px;
margin-top: 8px;
}
.menu_left, .menu_right { padding-right: 9px; }
.menu_alerts {
padding-left: 7px;
padding-right: 7px;
font-size: 18px;
}
body {
padding-left: 4px;
padding-right: 4px;
margin: 0px !important;
width: 100% !important;
height: 100% !important;
overflow-x: hidden;
}
.container { width: auto; }
.sidebar { display: none; }
.selectedAlert .alertList { top: 37px; right: 4px; }
}
@media (max-width: 680px) {
li {
padding-left: 5px;
padding-top: 3px;
padding-bottom: 2px;
height: 25px;
}
li a { font-size: 14px; }
ul { height: 26px; }
.menu_left, .menu_right { padding-right: 7px; }
.menu_create_topic, .hide_on_mobile { display: none; }
.menu_alerts {
padding-left: 4px;
padding-right: 4px;
font-size: 16px;
padding-top: 1px;
}
.selectedAlert .alertList { top: 33px; }
.prev_button, .next_button { top: auto;bottom: 5px; }
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
.grid_istat { margin-bottom: 0px; }
}
@media (max-width: 470px) {
ul { margin-bottom: 10px; }
.menu_overview, .menu_profile, .hide_on_micro { display: none; }
.selectedAlert .alertList {
width: 156px;
margin-bottom: 5px;
}
.alertItem.withAvatar {
background-size: 36px;
text-align: center;
padding-left: 42px;
height: 46px;
}
.alertItem { padding: 8px; }
.alertItem .text {
height: 30px;
font-size: 10px;
font-weight: bold;
white-space: normal;
}
.post_container { overflow: visible !important; }
.post_item {
background-position: 0px 2px !important;
background-size: 64px 64px !important;
padding-left: 2px !important;
min-height: 96px;
position: relative !important;
}
.post_item > .user_content {
margin-left: 75px !important;
width: 100% !important;
}
.post_item > .mod_button {
float: right !important;
margin-left: 2px !important;
position: relative;
top: -14px;
}
.post_item > .real_username {
position: absolute;
top: 70px;
float: left;
margin-top: 0px;
padding-top: 3px !important;
margin-right: 2px;
width: 60px;
font-size: 15px;
}
.userinfo { width: 70px; }
.userinfo .avatar_item {
background-size: 64px;
width: 64px;
min-height: 64px;
}
.content_container {
margin-left: 73px;
min-height: inherit !important;
}
.user_content { min-height: 76.5px !important; font-size: 15px; }
.user_content.nobuttons { min-height: 100.5px !important; }
.the_name { font-size: 15px; }
.post_tag { font-size: 12px; }
.rowtopic { font-size: 14px; }
.container { width: 100% !important; }
}
@media (max-width: 330px) {
li { padding-left: 6px; }
.menu_left { padding-right: 6px; }
}

View File

@ -6,6 +6,7 @@
"FullImage": "tempra-conflux.png",
"MobileFriendly": true,
"URL": "github.com/Azareal/Gosora",
"Sidebars":"right",
"Templates": [
{
"Name": "topic",

View File

@ -481,9 +481,17 @@ button.username {
font-size: 12px;
}
@media(max-width: 1300px) {
.theme_row {
background-image: none !important;
/* Media Queries */
@media(min-width: 881px) {
.shrink_main {
float: left;
width: calc(75% - 12px);
}
.sidebar {
float: left;
width: 25%;
margin-left: 12px;
}
}
@ -516,6 +524,7 @@ button.username {
overflow-x: hidden;
}
.container { width: auto; }
.sidebar { display: none; }
.selectedAlert .alertList { top: 37px; right: 4px; }
}

View File

@ -65,3 +65,9 @@
.perm_preset_can_moderate:before { content: "Can Moderate"; color: darkblue; }
.perm_preset_custom:before { content: "Custom"; color: black; }
.perm_preset_default:before { content: "Default"; }
@media(max-width: 1300px) {
.theme_row {
background-image: none !important;
}
}

View File

@ -6,5 +6,6 @@
"FullImage": "tempra-cursive.png",
"ForkOf": "tempra-simple",
"MobileFriendly": true,
"URL": "github.com/Azareal/Gosora"
"URL": "github.com/Azareal/Gosora",
"Sidebars":"right"
}

View File

@ -451,165 +451,4 @@ button.username { position: relative; top: -0.25px; }
top: -2px;
}
@media(min-width: 881px) {
.shrink_main {
float: left;
width: calc(75% - 12px);
}
.sidebar {
float: left;
width: 25%;
margin-left: 12px;
}
}
@media (max-width: 880px) {
li {
height: 29px;
font-size: 15px;
padding-left: 9px;
padding-top: 6px;
padding-bottom: 6px;
}
ul {
height: 30px;
margin-top: 8px;
}
.menu_left, .menu_right { padding-right: 9px; }
.menu_alerts {
padding-left: 7px;
padding-right: 7px;
font-size: 18px;
}
body {
padding-left: 4px;
padding-right: 4px;
margin: 0px !important;
width: 100% !important;
height: 100% !important;
overflow-x: hidden;
}
.container { width: auto; }
.sidebar { display: none; }
.selectedAlert .alertList { top: 37px; right: 4px; }
}
@media (max-width: 680px) {
li {
padding-left: 5px;
padding-top: 3px;
padding-bottom: 2px;
height: 25px;
}
li a { font-size: 14px; }
ul { height: 26px; }
.menu_left, .menu_right { padding-right: 7px; }
.menu_create_topic { display: none; }
.menu_alerts {
padding-left: 4px;
padding-right: 4px;
font-size: 16px;
padding-top: 1px;
}
.selectedAlert .alertList { top: 33px; }
.hide_on_mobile { display: none !important; }
.prev_button, .next_button { top: auto; bottom: 5px; }
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
.grid_istat { margin-bottom: 0px; }
}
@media (max-width: 470px) {
.menu_overview, .menu_profile { display: none; }
.selectedAlert .alertList {
width: 135px;
margin-bottom: 5px;
}
.selectedAlert.hasAvatars .alertList { width: calc(100% - 8px); }
.alertItem.withAvatar {
background-size: 36px;
text-align: right;
padding-left: 10px;
height: 46px;
}
.hasAvatars > .alertList > .alertItem.withAvatar {
background-size: 46px;
text-align: inherit;
padding-left: 56px;
height: 42px;
}
.alertItem { padding: 8px; }
.hasAvatars > .alertList > .alertItem { padding-top: 11px; }
.selectedAlert:not(.hasAvatars) > .alertList > .alertItem.withAvatar .text {
width: calc(100% - 20px);
height: 30px;
white-space: normal;
}
.selectedAlert:not(.hasAvatars) > .alertList > .alertItem .text {
font-size: 10px;
font-weight: bold;
margin-left: 0px;
}
.alertActive {
opacity: 0.7;
}
.hide_on_micro { display: none !important; }
.post_container { overflow: visible !important; }
.post_item:not(.action_item) {
background-position: 0px 2px !important;
background-size: 64px auto !important;
padding-left: 2px !important;
min-height: 96px;
position: relative !important;
}
.post_item > .user_content {
margin-left: 75px !important;
width: 100% !important;
min-height: 45px;
}
.post_item > .controls > .mod_button {
float: right !important;
margin-left: 2px !important;
margin-right: 3px;
}
.post_item > .controls > .mod_button > button {
opacity: 1;
padding-left: 3px;
padding-right: 3px;
}
.post_item > .controls > .real_username {
margin-top: 0px;
margin-right: 0px;
font-size: 15px;
color: black;
max-width: 61px;
text-overflow: ellipsis;
}
.post_item > .controls {
margin-top: 0px;
margin-left: 74px;
width: calc(100% - 74px);
}
.rowtopic {
font-size: 14px;
}
.container { width: 100% !important; }
}
@media (max-width: 330px) {
li { padding-left: 6px; }
.menu_left { padding-right: 6px; }
.post_item > .controls > .real_username {
display: inline-block;
overflow: hidden;
margin-right: -3px;
text-overflow: clip;
max-width: 84px;
}
.post_item > .controls { margin-left: 72px; }
.top_post > .post_item > .controls > .real_username { max-width: 57px; }
.top_post > .post_item { padding-right: 4px; }
}
{{template "media.partial.css" }}

View File

@ -0,0 +1,162 @@
@media(min-width: 881px) {
.shrink_main {
float: left;
width: calc(75% - 12px);
}
.sidebar {
float: left;
width: 25%;
margin-left: 12px;
}
}
@media (max-width: 880px) {
li {
height: 29px;
font-size: 15px;
padding-left: 9px;
padding-top: 6px;
padding-bottom: 6px;
}
ul {
height: 30px;
margin-top: 8px;
}
.menu_left, .menu_right { padding-right: 9px; }
.menu_alerts {
padding-left: 7px;
padding-right: 7px;
font-size: 18px;
}
body {
padding-left: 4px;
padding-right: 4px;
margin: 0px !important;
width: 100% !important;
height: 100% !important;
overflow-x: hidden;
}
.container { width: auto; }
.sidebar { display: none; }
.selectedAlert .alertList { top: 37px; right: 4px; }
}
@media (max-width: 680px) {
li {
padding-left: 5px;
padding-top: 3px;
padding-bottom: 2px;
height: 25px;
}
li a { font-size: 14px; }
ul { height: 26px; }
.menu_left, .menu_right { padding-right: 7px; }
.menu_create_topic { display: none; }
.menu_alerts {
padding-left: 4px;
padding-right: 4px;
font-size: 16px;
padding-top: 1px;
}
.selectedAlert .alertList { top: 33px; }
.hide_on_mobile { display: none !important; }
.prev_button, .next_button { top: auto; bottom: 5px; }
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
.grid_istat { margin-bottom: 0px; }
}
@media (max-width: 470px) {
.menu_overview, .menu_profile { display: none; }
.selectedAlert .alertList {
width: 135px;
margin-bottom: 5px;
}
.selectedAlert.hasAvatars .alertList { width: calc(100% - 8px); }
.alertItem.withAvatar {
background-size: 36px;
text-align: right;
padding-left: 10px;
height: 46px;
}
.hasAvatars > .alertList > .alertItem.withAvatar {
background-size: 46px;
text-align: inherit;
padding-left: 56px;
height: 42px;
}
.alertItem { padding: 8px; }
.hasAvatars > .alertList > .alertItem { padding-top: 11px; }
.selectedAlert:not(.hasAvatars) > .alertList > .alertItem.withAvatar .text {
width: calc(100% - 20px);
height: 30px;
white-space: normal;
}
.selectedAlert:not(.hasAvatars) > .alertList > .alertItem .text {
font-size: 10px;
font-weight: bold;
margin-left: 0px;
}
.alertActive {
opacity: 0.7;
}
.hide_on_micro { display: none !important; }
.post_container { overflow: visible !important; }
.post_item:not(.action_item) {
background-position: 0px 2px !important;
background-size: 64px auto !important;
padding-left: 2px !important;
min-height: 96px;
position: relative !important;
}
.post_item > .user_content {
margin-left: 75px !important;
width: 100% !important;
min-height: 45px;
}
.post_item > .controls > .mod_button {
float: right !important;
margin-left: 2px !important;
margin-right: 3px;
}
.post_item > .controls > .mod_button > button {
opacity: 1;
padding-left: 3px;
padding-right: 3px;
}
.post_item > .controls > .real_username {
margin-top: 0px;
margin-right: 0px;
font-size: 15px;
color: black;
max-width: 61px;
text-overflow: ellipsis;
}
.post_item > .controls {
margin-top: 0px;
margin-left: 74px;
width: calc(100% - 74px);
}
.rowtopic {
font-size: 14px;
}
.container { width: 100% !important; }
}
@media (max-width: 330px) {
li { padding-left: 6px; }
.menu_left { padding-right: 6px; }
.post_item > .controls > .real_username {
display: inline-block;
overflow: hidden;
margin-right: -3px;
text-overflow: clip;
max-width: 84px;
}
.post_item > .controls { margin-left: 72px; }
.top_post > .post_item > .controls > .real_username { max-width: 57px; }
.top_post > .post_item { padding-right: 4px; }
}

View File

@ -6,7 +6,7 @@
"FullImage": "tempra-simple.png",
"MobileFriendly": true,
"URL": "github.com/Azareal/Gosora",
"Sidebars":"false",
"Sidebars":"right",
"Resources": [
{
"Name": "sample.css",

15
user.go
View File

@ -52,6 +52,10 @@ type Email struct
Token string
}
func CheckPassword(real_password string, password string, salt string) (err error) {
return bcrypt.CompareHashAndPassword([]byte(real_password), []byte(password + salt))
}
func SetPassword(uid int, password string) (error) {
salt, err := GenerateSafeString(saltLength)
if err != nil {
@ -72,12 +76,13 @@ func SetPassword(uid int, password string) (error) {
}
func SendValidationEmail(username string, email string, token string) bool {
var schema string
var schema string = "http"
if enable_ssl {
schema = "s"
schema += "s"
}
subject := "Validate Your Email @ " + site_name
msg := "Dear " + username + ", following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. http" + schema + "://" + site_url + "/user/edit/token/" + token + "\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
msg := "Dear " + username + ", following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. " + schema + "://" + site_url + "/user/edit/token/" + token + "\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
return SendEmail(email, subject, msg)
}
@ -207,13 +212,11 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, headerVars
if len(docks.RightSidebar) != 0 {
var sbody string
for _, widget := range docks.RightSidebar {
//fmt.Println("widget",widget)
if widget.Enabled && widget.Location == "global" {
sbody += widget.Body
//fmt.Println("sbody",sbody)
}
}
headerVars.Sidebars.Right = template.HTML(sbody)
headerVars.Widgets.RightSidebar = template.HTML(sbody)
}
}

View File

@ -2,12 +2,15 @@ package main
import "log"
import "sync"
import "errors"
import "strings"
import "strconv"
import "database/sql"
import "./query_gen/lib"
import "golang.org/x/crypto/bcrypt"
var users UserStore
var err_account_exists = errors.New("This username is already in use.")
type UserStore interface {
Load(id int) error
@ -22,6 +25,7 @@ type UserStore interface {
//GetConn() interface{}
Remove(id int) error
RemoveUnsafe(id int) error
CreateUser(username string, password string, email string, group int, active int) (int, error)
GetLength() int
GetCapacity() int
}
@ -31,18 +35,35 @@ type MemoryUserStore struct {
length int
capacity int
get *sql.Stmt
register *sql.Stmt
username_exists *sql.Stmt
sync.RWMutex
}
func NewMemoryUserStore(capacity int) *MemoryUserStore {
stmt, err := qgen.Builder.SimpleSelect("users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid = ?","","")
get_stmt, err := qgen.Builder.SimpleSelect("users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid = ?","","")
if err != nil {
log.Fatal(err)
}
// Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO
register_stmt, err := qgen.Builder.SimpleInsert("users","name, email, password, salt, group, is_super_admin, session, active, message","?,?,?,?,?,0,'',?,''")
if err != nil {
log.Fatal(err)
}
username_exists_stmt, err := qgen.Builder.SimpleSelect("users","name","name = ?","","")
if err != nil {
log.Fatal(err)
}
return &MemoryUserStore{
items:make(map[int]*User),
capacity:capacity,
get:stmt,
get:get_stmt,
register:register_stmt,
username_exists:username_exists_stmt,
}
}
@ -178,6 +199,32 @@ func (sus *MemoryUserStore) RemoveUnsafe(id int) error {
return nil
}
func (sus *MemoryUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) {
// Is this username already taken..?
err := sus.username_exists.QueryRow(username).Scan(&username)
if err != sql.ErrNoRows {
return 0, err_account_exists
}
salt, err := GenerateSafeString(saltLength)
if err != nil {
return 0, err
}
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password + salt), bcrypt.DefaultCost)
if err != nil {
return 0, err
}
res, err := sus.register.Exec(username,email,string(hashed_password),salt,group,active)
if err != nil {
return 0, err
}
lastId, err := res.LastInsertId()
return int(lastId), err
}
func (sus *MemoryUserStore) GetLength() int {
return sus.length
}
@ -192,14 +239,33 @@ func (sus *MemoryUserStore) GetCapacity() int {
type SqlUserStore struct {
get *sql.Stmt
register *sql.Stmt
username_exists *sql.Stmt
}
func NewSqlUserStore() *SqlUserStore {
stmt, err := qgen.Builder.SimpleSelect("users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid = ?","","")
get_stmt, err := qgen.Builder.SimpleSelect("users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid = ?","","")
if err != nil {
log.Fatal(err)
}
return &SqlUserStore{stmt}
// Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO
register_stmt, err := qgen.Builder.SimpleInsert("users","name, email, password, salt, group, is_super_admin, session, active, message","?,?,?,?,?,0,'',?,''")
if err != nil {
log.Fatal(err)
}
username_exists_stmt, err := qgen.Builder.SimpleSelect("users","name","name = ?","","")
if err != nil {
log.Fatal(err)
}
return &SqlUserStore{
get:get_stmt,
register:register_stmt,
username_exists:username_exists_stmt,
}
}
func (sus *SqlUserStore) Get(id int) (*User, error) {
@ -273,6 +339,32 @@ func (sus *SqlUserStore) Load(id int) error {
return err
}
func (sus *SqlUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) {
// Is this username already taken..?
err := sus.username_exists.QueryRow(username).Scan(&username)
if err != sql.ErrNoRows {
return 0, err_account_exists
}
salt, err := GenerateSafeString(saltLength)
if err != nil {
return 0, err
}
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password + salt), bcrypt.DefaultCost)
if err != nil {
return 0, err
}
res, err := sus.register.Exec(username,email,string(hashed_password),salt,group,active)
if err != nil {
return 0, err
}
lastId, err := res.LastInsertId()
return int(lastId), err
}
// Placeholder methods, the actual queries are done elsewhere
func (sus *SqlUserStore) Set(item *User) error {
return nil

View File

@ -213,7 +213,7 @@ func weak_password(password string) error {
if lower == 0 {
return errors.New("You don't have any lowercase characters in your password.")
}
if (len(password) / 2) < len(charMap) {
if (len(password) / 2) > len(charMap) {
return errors.New("You don't have enough unique characters in your password.")
}