Added a custom router which is twice as fast as the old one.

Added support for SSL.
Added the email system. Not fully tested.
Added Email Verification.
Added the Emails Page in the Account Manager.
Email Activation is now fully supported.
Fixed CustomErrorJSQ() and added CustomError().
Added commented out tests for Vestigo.
Added tests for the new (possibly temporary) custom router.
Swapped some of the custom error blocks for the Account Manager with LocalError calls.
Moved the account menu into it's own template.
This commit is contained in:
Azareal 2017-01-03 07:47:31 +00:00
parent 1b18ab523c
commit 33c2f4ccb0
24 changed files with 830 additions and 191 deletions

View File

@ -8,13 +8,13 @@ Discord Server: https://discord.gg/eyYvtTf
# Features # Features
Basic Forum Functionality Basic Forum Functionality. All of the little things you would expect of any forum software. E.g. Moderation, Custom Themes, Avatars, and so on.
Custom Pages. Under development. Custom Pages. Under development. Mainly the Control Panel portion to come, but you can create them by hand today.
Emojis Emojis. Allow your users to express themselves without resorting to serving tons upon tons of image files.
In-memory static file, forum and group caches. In-memory static file, forum and group caches. We're pondering over extending this solution over to topics, users, etc. to some extent.
A profile system including profile comments and moderation tools for the profile owner. A profile system including profile comments and moderation tools for the profile owner.
@ -22,7 +22,7 @@ A template engine which compiles templates down into machine code. Over ten time
A plugin system. Under development. A plugin system. Under development.
A responsive design. Looks good on mobile phones, tablets, laptops, desktops and more! A responsive design. Looks great on mobile phones, tablets, laptops, desktops and more!
# Dependencies # Dependencies
@ -38,19 +38,15 @@ Instructions on how to do so on Linux: https://downloads.mariadb.org/mariadb/rep
**Run the following commands:** **Run the following commands:**
go get github.com/go-sql-driver/mysql go get -u github.com/go-sql-driver/mysql
go install github.com/go-sql-driver/mysql go get -u golang.org/x/crypto/bcrypt
go get golang.org/x/crypto/bcrypt
go install golang.org/x/crypto/bcrypt
Tweak the config.go file and put your database details in there. Import data.sql into the same database. Comment out the first line (put /* and */ around it), if you've already made a database, and don't want the script to generate it for you. Tweak the config.go file and put your database details in there. Import data.sql into the same database. Comment out the first line (put /* and */ around it), if you've already made a database, and don't want the script to generate it for you.
Set the password column of your user account in the database to what you want your password to be. The system will encrypt your password when you login for the first time. Set the password column of your user account in the database to what you want your password to be. The system will encrypt your password when you login for the first time.
Add -u after go get to update those libraries, if you've already got them installed. You can run these commands again at any time to update these dependencies to their latest versions.
# Run the program # Run the program
@ -67,7 +63,7 @@ go build
Open up cmd.exe Open up cmd.exe
cd to the directory / folder the code is in. E.g. cd /Users/Blah/Documents/gosora cd to the directory / folder the code is in. E.g. `cd /Users/Blah/Documents/gosora`
go build go build
@ -101,9 +97,11 @@ We're looking for ways to clean-up the plugin system so that all of them (except
Oh my, you caught me right at the start of this project. There's nothing to see here yet, asides from the absolute basics. You might want to look again later! Oh my, you caught me right at the start of this project. There's nothing to see here yet, asides from the absolute basics. You might want to look again later!
More moderation features. The various little features which somehow got stuck in the net. Don't worry, I'll get to them!
Add a simple anti-spam measure. More moderation features. E.g. Move, Approval Queue (Posts made by users in certain usergroups will need to be approved by a moderator before they're publically visible), etc.
Add a simple anti-spam measure. I have quite a few ideas in mind, but it'll take a while to implement the more advanced ones, so I'd like to put off some of those to a later date and focus on the basics. E.g. CAPTCHAs, hidden fields, etc.
Add an alert system. Add an alert system.
@ -111,12 +109,18 @@ Add per-forum permissions to finish up the foundations of the permissions system
Add a *better* plugin system. E.g. Allow for plugins written in Javascript and ones written in Go. Also, we need to add many, many, many more plugin hooks. Add a *better* plugin system. E.g. Allow for plugins written in Javascript and ones written in Go. Also, we need to add many, many, many more plugin hooks.
Implement a faster router. I will need to ponder over implementing an even faster router. We don't need one immediately, although it would be nice if we could get one in the near future. It really depends. Ideally, it would be one which can easily integrate with the current structure without much work, although I'm not beyond making some alterations to faciliate it, assuming that we don't get too tightly bound to that specific router.
Allow themes to define their own templates.
Add a friend system. Add a friend system.
Add more administration features. Add more administration features.
Add more features for improving user engagement. Add more features for improving user engagement. I have quite a few of these in mind, but I'm mostly occupied with implementing the essentials right now.
Add a widget system. Add a widget system.
Add support for multi-factor authentication.
Add support for secondary emails for users.

View File

@ -12,16 +12,23 @@ var max_request_size = 5 * megabyte
// Misc // Misc
var default_route = route_topics var default_route = route_topics
var default_group = 3 var default_group = 3 // Should be a setting
var activation_group = 5 var activation_group = 5 // Should be a setting
var staff_css = " background-color: #ffeaff;" var staff_css = " background-color: #ffeaff;"
var uncategorised_forum_visible = true var uncategorised_forum_visible = true
var enable_emails = false var enable_emails = false
var site_email = "" var site_name = "Test Install" // Should be a setting
var site_email = "" // Should be a setting
var smtp_server = "" var smtp_server = ""
var siteurl = "localhost:8080" //var noavatar = "https://api.adorable.io/avatars/{width}/{id}@{site_url}.png"
var noavatar = "https://api.adorable.io/avatars/285/{id}@" + siteurl + ".png" var noavatar = "https://api.adorable.io/avatars/285/{id}@" + site_url + ".png"
var items_per_page = 50 var items_per_page = 40 // Should be a setting
var site_url = "localhost:8080"
var server_port = "8080"
var enable_ssl = false
var ssl_privkey = ""
var ssl_fullchain = ""
// Developer flag // Developer flag
var debug = false var debug = false

View File

@ -32,6 +32,13 @@ CREATE TABLE `users_groups`(
primary key(`gid`) primary key(`gid`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
CREATE TABLE `emails`(
`email` varchar(200) not null,
`uid` int not null,
`validated` tinyint DEFAULT 0 not null,
`token` varchar(200) DEFAULT '' not null
);
CREATE TABLE `forums`( CREATE TABLE `forums`(
`fid` int not null AUTO_INCREMENT, `fid` int not null AUTO_INCREMENT,
`name` varchar(100) not null, `name` varchar(100) not null,
@ -111,6 +118,7 @@ INSERT INTO themes(`uname`,`default`) VALUES ('tempra-simple',1);
INSERT INTO users(`name`,`email`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`,`message`) INSERT INTO users(`name`,`email`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`,`message`)
VALUES ('Admin','admin@localhost',1,1,NOW(),NOW(),''); VALUES ('Admin','admin@localhost',1,1,NOW(),NOW(),'');
INSERT INTO emails(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);
/* /*
The Permissions: The Permissions:

View File

@ -140,15 +140,24 @@ func NotFound(w http.ResponseWriter, r *http.Request, user User) {
fmt.Fprintln(w,errpage) fmt.Fprintln(w,errpage)
} }
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) {
pi := Page{errtitle,"error",user,nList,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(errcode)
fmt.Fprintln(w,errpage)
}
func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, is_js string) { func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, is_js string) {
if is_js == "0" { if is_js == "0" {
pi := Page{errtitle,"error",user,nList,tList,errmsg} pi := Page{errtitle,"error",user,nList,tList,errmsg}
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String() errpage := b.String()
w.WriteHeader(500) w.WriteHeader(errcode)
fmt.Fprintln(w,errpage) fmt.Fprintln(w,errpage)
} else { } else {
http.Error(w,"{'errmsg': '" + errmsg + "'}",500) http.Error(w,"{'errmsg': '" + errmsg + "'}",errcode)
} }
} }

23
experimental/config.json Normal file
View File

@ -0,0 +1,23 @@
{
"dbhost": "127.0.0.1",
"dbuser": "root",
"dbpassword": "password",
"dbname": "gosora",
"dbport": "3306",
"default_group": 3,
"activation_group": 5,
"staff_css": " background-color: #ffeaff;",
"uncategorised_forum_visible": true,
"enable_emails": false,
"smtp_server": "",
"items_per_page": 40,
"site_url": "localhost:8080",
"server_port": "8080",
"enable_ssl": false,
"ssl_privkey": "",
"ssl_fullchain": "",
"debug": false
}

View File

@ -1,5 +1,4 @@
package main package main
//import "fmt"
import "log" import "log"
import "bytes" import "bytes"
import "math/rand" import "math/rand"
@ -8,6 +7,7 @@ import "net/http"
import "net/http/httptest" import "net/http/httptest"
import "io/ioutil" import "io/ioutil"
import "html/template" import "html/template"
//import "github.com/husobee/vestigo"
func BenchmarkTopicTemplate(b *testing.B) { func BenchmarkTopicTemplate(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
@ -107,7 +107,7 @@ func BenchmarkRoute(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
admin_uid_cookie := http.Cookie{Name: "uid",Value: "1",Path: "/",MaxAge: year} admin_uid_cookie := http.Cookie{Name: "uid",Value: "1",Path: "/",MaxAge: year}
// TO-DO: Stop hard-coding this value // TO-DO: Stop hard-coding this value. Seriously.
admin_session_cookie := http.Cookie{Name: "session",Value: "TKBh5Z-qEQhWDBnV6_XVmOhKAowMYPhHeRlrQjjbNc0QRrRiglvWOYFDc1AaMXQIywvEsyA2AOBRYUrZ5kvnGhThY1GhOW6FSJADnRWm_bI=",Path: "/",MaxAge: year} admin_session_cookie := http.Cookie{Name: "session",Value: "TKBh5Z-qEQhWDBnV6_XVmOhKAowMYPhHeRlrQjjbNc0QRrRiglvWOYFDc1AaMXQIywvEsyA2AOBRYUrZ5kvnGhThY1GhOW6FSJADnRWm_bI=",Path: "/",MaxAge: year}
topic_w := httptest.NewRecorder() topic_w := httptest.NewRecorder()
@ -231,7 +231,7 @@ func addEmptyRoutesToMux(routes []string, serveMux *http.ServeMux) {
} }
} }
func BenchmarkRouter(b *testing.B) { func BenchmarkDefaultGoRouter(b *testing.B) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
req := httptest.NewRequest("get","/topics/",bytes.NewReader(nil)) req := httptest.NewRequest("get","/topics/",bytes.NewReader(nil))
routes := make([]string, 0) routes := make([]string, 0)
@ -388,5 +388,332 @@ func BenchmarkRouter(b *testing.B) {
}) })
} }
/*func addEmptyRoutesToVestigo(routes []string, router *vestigo.Router) {
for _, route := range routes {
router.HandleFunc(route, func(_ http.ResponseWriter,_ *http.Request){})
}
}
func BenchmarkVestigoRouter(b *testing.B) {
w := httptest.NewRecorder()
req := httptest.NewRequest("get","/topics/",bytes.NewReader(nil))
routes := make([]string, 0)
routes = append(routes,"/test/")
router := vestigo.NewRouter()
router.HandleFunc("/test/", func(_ http.ResponseWriter,_ *http.Request){})
b.Run("one-route", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
routes = append(routes,"/topic/")
routes = append(routes,"/forums/")
routes = append(routes,"/forum/")
routes = append(routes,"/panel/")
router = vestigo.NewRouter()
addEmptyRoutesToVestigo(routes, router)
b.Run("five-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = vestigo.NewRouter()
routes = append(routes,"/panel/plugins/")
routes = append(routes,"/panel/groups/")
routes = append(routes,"/panel/settings/")
routes = append(routes,"/panel/users/")
routes = append(routes,"/panel/forums/")
addEmptyRoutesToVestigo(routes, router)
b.Run("ten-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = vestigo.NewRouter()
routes = append(routes,"/panel/forums/create/submit/")
routes = append(routes,"/panel/forums/delete/")
routes = append(routes,"/users/ban/")
routes = append(routes,"/panel/users/edit/")
routes = append(routes,"/panel/forums/create/")
routes = append(routes,"/users/unban/")
routes = append(routes,"/pages/")
routes = append(routes,"/users/activate/")
routes = append(routes,"/panel/forums/edit/submit/")
routes = append(routes,"/panel/plugins/activate/")
addEmptyRoutesToVestigo(routes, router)
b.Run("twenty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = vestigo.NewRouter()
routes = append(routes,"/panel/plugins/deactivate/")
routes = append(routes,"/panel/plugins/install/")
routes = append(routes,"/panel/plugins/uninstall/")
routes = append(routes,"/panel/templates/")
routes = append(routes,"/panel/templates/edit/")
routes = append(routes,"/panel/templates/create/")
routes = append(routes,"/panel/templates/delete/")
routes = append(routes,"/panel/templates/edit/submit/")
routes = append(routes,"/panel/themes/")
routes = append(routes,"/panel/themes/edit/")
addEmptyRoutesToVestigo(routes, router)
b.Run("thirty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = vestigo.NewRouter()
routes = append(routes,"/panel/themes/create/")
routes = append(routes,"/panel/themes/delete/")
routes = append(routes,"/panel/themes/delete/submit/")
routes = append(routes,"/panel/templates/create/submit/")
routes = append(routes,"/panel/templates/delete/submit/")
routes = append(routes,"/panel/widgets/")
routes = append(routes,"/panel/widgets/edit/")
routes = append(routes,"/panel/widgets/activate/")
routes = append(routes,"/panel/widgets/deactivate/")
routes = append(routes,"/panel/magical/wombat/path")
addEmptyRoutesToVestigo(routes, router)
b.Run("forty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = vestigo.NewRouter()
routes = append(routes,"/report/")
routes = append(routes,"/report/submit/")
routes = append(routes,"/topic/create/submit/")
routes = append(routes,"/topics/create/")
routes = append(routes,"/overview/")
routes = append(routes,"/uploads/")
routes = append(routes,"/static/")
routes = append(routes,"/reply/edit/submit/")
routes = append(routes,"/reply/delete/submit/")
routes = append(routes,"/topic/edit/submit/")
addEmptyRoutesToVestigo(routes, router)
b.Run("fifty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = vestigo.NewRouter()
routes = append(routes,"/topic/delete/submit/")
routes = append(routes,"/topic/stick/submit/")
routes = append(routes,"/topic/unstick/submit/")
routes = append(routes,"/accounts/login/")
routes = append(routes,"/accounts/create/")
routes = append(routes,"/accounts/logout/")
routes = append(routes,"/accounts/login/submit/")
routes = append(routes,"/accounts/create/submit/")
routes = append(routes,"/user/edit/critical/")
routes = append(routes,"/user/edit/critical/submit/")
addEmptyRoutesToVestigo(routes, router)
b.Run("sixty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = vestigo.NewRouter()
routes = append(routes,"/user/edit/avatar/")
routes = append(routes,"/user/edit/avatar/submit/")
routes = append(routes,"/user/edit/username/")
routes = append(routes,"/user/edit/username/submit/")
routes = append(routes,"/profile/reply/create/")
routes = append(routes,"/profile/reply/edit/submit/")
routes = append(routes,"/profile/reply/delete/submit/")
routes = append(routes,"/arcane/tower/")
routes = append(routes,"/magical/kingdom/")
routes = append(routes,"/insert/name/here/")
addEmptyRoutesToVestigo(routes, router)
b.Run("seventy-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
}*/
func addEmptyRoutesToCustom(routes []string, router *Router) {
for _, route := range routes {
router.HandleFunc(route, func(_ http.ResponseWriter,_ *http.Request){})
}
}
func BenchmarkCustomRouter(b *testing.B) {
w := httptest.NewRecorder()
req := httptest.NewRequest("get","/topics/",bytes.NewReader(nil))
routes := make([]string, 0)
routes = append(routes,"/test/")
router := NewRouter()
router.HandleFunc("/test/", func(_ http.ResponseWriter,_ *http.Request){})
b.Run("one-route", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
routes = append(routes,"/topic/")
routes = append(routes,"/forums/")
routes = append(routes,"/forum/")
routes = append(routes,"/panel/")
router = NewRouter()
addEmptyRoutesToCustom(routes, router)
b.Run("five-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = NewRouter()
routes = append(routes,"/panel/plugins/")
routes = append(routes,"/panel/groups/")
routes = append(routes,"/panel/settings/")
routes = append(routes,"/panel/users/")
routes = append(routes,"/panel/forums/")
addEmptyRoutesToCustom(routes, router)
b.Run("ten-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = NewRouter()
routes = append(routes,"/panel/forums/create/submit/")
routes = append(routes,"/panel/forums/delete/")
routes = append(routes,"/users/ban/")
routes = append(routes,"/panel/users/edit/")
routes = append(routes,"/panel/forums/create/")
routes = append(routes,"/users/unban/")
routes = append(routes,"/pages/")
routes = append(routes,"/users/activate/")
routes = append(routes,"/panel/forums/edit/submit/")
routes = append(routes,"/panel/plugins/activate/")
addEmptyRoutesToCustom(routes, router)
b.Run("twenty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = NewRouter()
routes = append(routes,"/panel/plugins/deactivate/")
routes = append(routes,"/panel/plugins/install/")
routes = append(routes,"/panel/plugins/uninstall/")
routes = append(routes,"/panel/templates/")
routes = append(routes,"/panel/templates/edit/")
routes = append(routes,"/panel/templates/create/")
routes = append(routes,"/panel/templates/delete/")
routes = append(routes,"/panel/templates/edit/submit/")
routes = append(routes,"/panel/themes/")
routes = append(routes,"/panel/themes/edit/")
addEmptyRoutesToCustom(routes, router)
b.Run("thirty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = NewRouter()
routes = append(routes,"/panel/themes/create/")
routes = append(routes,"/panel/themes/delete/")
routes = append(routes,"/panel/themes/delete/submit/")
routes = append(routes,"/panel/templates/create/submit/")
routes = append(routes,"/panel/templates/delete/submit/")
routes = append(routes,"/panel/widgets/")
routes = append(routes,"/panel/widgets/edit/")
routes = append(routes,"/panel/widgets/activate/")
routes = append(routes,"/panel/widgets/deactivate/")
routes = append(routes,"/panel/magical/wombat/path")
addEmptyRoutesToCustom(routes, router)
b.Run("forty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = NewRouter()
routes = append(routes,"/report/")
routes = append(routes,"/report/submit/")
routes = append(routes,"/topic/create/submit/")
routes = append(routes,"/topics/create/")
routes = append(routes,"/overview/")
routes = append(routes,"/uploads/")
routes = append(routes,"/static/")
routes = append(routes,"/reply/edit/submit/")
routes = append(routes,"/reply/delete/submit/")
routes = append(routes,"/topic/edit/submit/")
addEmptyRoutesToCustom(routes, router)
b.Run("fifty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = NewRouter()
routes = append(routes,"/topic/delete/submit/")
routes = append(routes,"/topic/stick/submit/")
routes = append(routes,"/topic/unstick/submit/")
routes = append(routes,"/accounts/login/")
routes = append(routes,"/accounts/create/")
routes = append(routes,"/accounts/logout/")
routes = append(routes,"/accounts/login/submit/")
routes = append(routes,"/accounts/create/submit/")
routes = append(routes,"/user/edit/critical/")
routes = append(routes,"/user/edit/critical/submit/")
addEmptyRoutesToCustom(routes, router)
b.Run("sixty-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
router = NewRouter()
routes = append(routes,"/user/edit/avatar/")
routes = append(routes,"/user/edit/avatar/submit/")
routes = append(routes,"/user/edit/username/")
routes = append(routes,"/user/edit/username/submit/")
routes = append(routes,"/profile/reply/create/")
routes = append(routes,"/profile/reply/edit/submit/")
routes = append(routes,"/profile/reply/delete/submit/")
routes = append(routes,"/arcane/tower/")
routes = append(routes,"/magical/kingdom/")
routes = append(routes,"/insert/name/here/")
addEmptyRoutesToCustom(routes, router)
b.Run("seventy-routes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
req = httptest.NewRequest("get",routes[rand.Intn(len(routes))],bytes.NewReader(nil))
router.ServeHTTP(w,req)
}
})
}
/*func TestRoute(t *testing.T) { /*func TestRoute(t *testing.T) {
}*/ }*/

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

95
main.go
View File

@ -138,7 +138,7 @@ func main(){
init_plugins() init_plugins()
// In a directory to stop it clashing with the other paths // In a directory to stop it clashing with the other paths
http.HandleFunc("/static/", route_static) /*http.HandleFunc("/static/", route_static)
fs_u := http.FileServer(http.Dir("./uploads")) fs_u := http.FileServer(http.Dir("./uploads"))
http.Handle("/uploads/", http.StripPrefix("/uploads/",fs_u)) http.Handle("/uploads/", http.StripPrefix("/uploads/",fs_u))
@ -180,6 +180,7 @@ func main(){
http.HandleFunc("/user/edit/avatar/submit/", route_account_own_edit_avatar_submit) http.HandleFunc("/user/edit/avatar/submit/", route_account_own_edit_avatar_submit)
http.HandleFunc("/user/edit/username/", route_account_own_edit_username) http.HandleFunc("/user/edit/username/", route_account_own_edit_username)
http.HandleFunc("/user/edit/username/submit/", route_account_own_edit_username_submit) http.HandleFunc("/user/edit/username/submit/", route_account_own_edit_username_submit)
http.HandleFunc("/user/edit/email/token/", route_account_own_edit_email_token_submit)
http.HandleFunc("/user/", route_profile) http.HandleFunc("/user/", route_profile)
http.HandleFunc("/profile/reply/create/", route_profile_reply_create) http.HandleFunc("/profile/reply/create/", route_profile_reply_create)
http.HandleFunc("/profile/reply/edit/submit/", route_profile_reply_edit_submit) http.HandleFunc("/profile/reply/edit/submit/", route_profile_reply_edit_submit)
@ -210,8 +211,96 @@ func main(){
http.HandleFunc("/panel/users/edit/submit/", route_panel_users_edit_submit) http.HandleFunc("/panel/users/edit/submit/", route_panel_users_edit_submit)
http.HandleFunc("/panel/groups/", route_panel_groups) http.HandleFunc("/panel/groups/", route_panel_groups)
http.HandleFunc("/", default_route) http.HandleFunc("/", default_route)*/
router := NewRouter()
router.HandleFunc("/static/", route_static)
fs_u := http.FileServer(http.Dir("./uploads"))
router.Handle("/uploads/", http.StripPrefix("/uploads/",fs_u))
router.HandleFunc("/overview/", route_overview)
router.HandleFunc("/topics/create/", route_topic_create)
router.HandleFunc("/topics/", route_topics)
router.HandleFunc("/forums/", route_forums)
router.HandleFunc("/forum/", route_forum)
router.HandleFunc("/topic/create/submit/", route_create_topic)
router.HandleFunc("/topic/", route_topic_id)
router.HandleFunc("/reply/create/", route_create_reply)
//router.HandleFunc("/reply/edit/", route_reply_edit)
//router.HandleFunc("/reply/delete/", route_reply_delete)
router.HandleFunc("/reply/edit/submit/", route_reply_edit_submit)
router.HandleFunc("/reply/delete/submit/", route_reply_delete_submit)
router.HandleFunc("/report/submit/", route_report_submit)
router.HandleFunc("/topic/edit/submit/", route_edit_topic)
router.HandleFunc("/topic/delete/submit/", route_delete_topic)
router.HandleFunc("/topic/stick/submit/", route_stick_topic)
router.HandleFunc("/topic/unstick/submit/", route_unstick_topic)
// Custom Pages
router.HandleFunc("/pages/", route_custom_page)
// Accounts
router.HandleFunc("/accounts/login/", route_login)
router.HandleFunc("/accounts/create/", route_register)
router.HandleFunc("/accounts/logout/", route_logout)
router.HandleFunc("/accounts/login/submit/", route_login_submit)
router.HandleFunc("/accounts/create/submit/", route_register_submit)
//router.HandleFunc("/accounts/list/", route_login) // Redirect /accounts/ and /user/ to here.. // Get a list of all of the accounts on the forum
//router.HandleFunc("/accounts/create/full/", route_logout) // Advanced account creator for admins?
//router.HandleFunc("/user/edit/", route_logout)
router.HandleFunc("/user/edit/critical/", route_account_own_edit_critical) // Password & Email
router.HandleFunc("/user/edit/critical/submit/", route_account_own_edit_critical_submit)
router.HandleFunc("/user/edit/avatar/", route_account_own_edit_avatar)
router.HandleFunc("/user/edit/avatar/submit/", route_account_own_edit_avatar_submit)
router.HandleFunc("/user/edit/username/", route_account_own_edit_username)
router.HandleFunc("/user/edit/username/submit/", route_account_own_edit_username_submit)
router.HandleFunc("/user/edit/email/", route_account_own_edit_email)
router.HandleFunc("/user/edit/email/token/", route_account_own_edit_email_token_submit)
router.HandleFunc("/user/", route_profile)
router.HandleFunc("/profile/reply/create/", route_profile_reply_create)
router.HandleFunc("/profile/reply/edit/submit/", route_profile_reply_edit_submit)
router.HandleFunc("/profile/reply/delete/submit/", route_profile_reply_delete_submit)
//router.HandleFunc("/user/edit/submit/", route_logout)
router.HandleFunc("/users/ban/", route_ban)
router.HandleFunc("/users/ban/submit/", route_ban_submit)
router.HandleFunc("/users/unban/", route_unban)
router.HandleFunc("/users/activate/", route_activate)
// Admin
router.HandleFunc("/panel/", route_panel)
router.HandleFunc("/panel/forums/", route_panel_forums)
router.HandleFunc("/panel/forums/create/", route_panel_forums_create_submit)
router.HandleFunc("/panel/forums/delete/", route_panel_forums_delete)
router.HandleFunc("/panel/forums/delete/submit/", route_panel_forums_delete_submit)
router.HandleFunc("/panel/forums/edit/submit/", route_panel_forums_edit_submit)
router.HandleFunc("/panel/settings/", route_panel_settings)
router.HandleFunc("/panel/settings/edit/", route_panel_setting)
router.HandleFunc("/panel/settings/edit/submit/", route_panel_setting_edit)
router.HandleFunc("/panel/themes/", route_panel_themes)
router.HandleFunc("/panel/themes/default/", route_panel_themes_default)
router.HandleFunc("/panel/plugins/", route_panel_plugins)
router.HandleFunc("/panel/plugins/activate/", route_panel_plugins_activate)
router.HandleFunc("/panel/plugins/deactivate/", route_panel_plugins_deactivate)
router.HandleFunc("/panel/users/", route_panel_users)
router.HandleFunc("/panel/users/edit/", route_panel_users_edit)
router.HandleFunc("/panel/users/edit/submit/", route_panel_users_edit_submit)
router.HandleFunc("/panel/groups/", route_panel_groups)
router.HandleFunc("/", default_route)
defer db.Close() defer db.Close()
http.ListenAndServe(":8080", nil) if !enable_ssl {
if server_port == "" {
server_port = "80"
}
//http.ListenAndServe(":" + server_port, nil)
http.ListenAndServe(":" + server_port, router)
} else {
if server_port == "" {
server_port = "443"
}
http.ListenAndServeTLS(":" + server_port, ssl_fullchain, ssl_privkey, router)
}
} }

View File

@ -488,7 +488,6 @@ func route_activate(w http.ResponseWriter, r *http.Request) {
NoPermissions(w,r,user) NoPermissions(w,r,user)
return return
} }
if r.FormValue("session") != user.Session { if r.FormValue("session") != user.Session {
SecurityError(w,r,user) SecurityError(w,r,user)
return return
@ -515,7 +514,6 @@ func route_activate(w http.ResponseWriter, r *http.Request) {
LocalError("The account you're trying to activate has already been activated.",w,r,user) LocalError("The account you're trying to activate has already been activated.",w,r,user)
return return
} }
_, err = activate_user_stmt.Exec(uid) _, err = activate_user_stmt.Exec(uid)
if err != nil { if err != nil {
InternalError(err,w,r,user) InternalError(err,w,r,user)

View File

@ -27,6 +27,9 @@ var set_password_stmt *sql.Stmt
var get_password_stmt *sql.Stmt var get_password_stmt *sql.Stmt
var set_avatar_stmt *sql.Stmt var set_avatar_stmt *sql.Stmt
var set_username_stmt *sql.Stmt var set_username_stmt *sql.Stmt
var add_email_stmt *sql.Stmt
var update_email_stmt *sql.Stmt
var verify_email_stmt *sql.Stmt
var register_stmt *sql.Stmt var register_stmt *sql.Stmt
var username_exists_stmt *sql.Stmt var username_exists_stmt *sql.Stmt
var change_group_stmt *sql.Stmt var change_group_stmt *sql.Stmt
@ -61,7 +64,7 @@ func init_database(err error) {
} }
log.Print("Preparing get_session statement.") log.Print("Preparing get_session statement.")
get_session_stmt, err = db.Prepare("select `uid`, `name`, `group`, `is_super_admin`, `session`, `avatar`, `message`, `url_prefix`, `url_name` FROM `users` WHERE `uid` = ? AND `session` = ? AND `session` <> ''") get_session_stmt, err = db.Prepare("select `uid`, `name`, `group`, `is_super_admin`, `session`, `email`, `avatar`, `message`, `url_prefix`, `url_name` FROM `users` WHERE `uid` = ? AND `session` = ? AND `session` <> ''")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -195,6 +198,24 @@ func init_database(err error) {
log.Fatal(err) log.Fatal(err)
} }
log.Print("Preparing add_email statement.")
add_email_stmt, err = db.Prepare("INSERT INTO emails(`email`,`uid`,`validated`,`token`) VALUES(?,?,?,?)")
if err != nil {
log.Fatal(err)
}
log.Print("Preparing update_email statement.")
update_email_stmt, err = db.Prepare("UPDATE emails SET email = ?, uid = ?, validated = ?, token = ? WHERE email = ?")
if err != nil {
log.Fatal(err)
}
log.Print("Preparing verify_email statement.")
verify_email_stmt, err = db.Prepare("UPDATE emails SET validated = 1, token = '' WHERE email = ?")
if err != nil {
log.Fatal(err)
}
log.Print("Preparing activate_user statement.") log.Print("Preparing activate_user statement.")
activate_user_stmt, err = db.Prepare("UPDATE users SET active = 1 WHERE uid = ?") activate_user_stmt, err = db.Prepare("UPDATE users SET active = 1 WHERE uid = ?")
if err != nil { if err != nil {
@ -291,7 +312,6 @@ func init_database(err error) {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if !nogrouplog { if !nogrouplog {
fmt.Println(group.Name + ": ") fmt.Println(group.Name + ": ")
fmt.Printf("%+v\n", group.Perms) fmt.Printf("%+v\n", group.Perms)
@ -328,7 +348,6 @@ func init_database(err error) {
forum.LastTopic = "None" forum.LastTopic = "None"
forum.LastTopicTime = "" forum.LastTopicTime = ""
} }
forums[forum.ID] = forum forums[forum.ID] = forum
} }
err = rows.Err() err = rows.Err()
@ -387,7 +406,6 @@ func init_database(err error) {
if !ok { if !ok {
continue continue
} }
plugin.Active = active plugin.Active = active
plugins[uname] = plugin plugins[uname] = plugin
} }

View File

@ -1,31 +1,66 @@
package main package main
/*import "sync" //import "fmt"
import "strings"
import "sync"
import "net/http" import "net/http"
type Router struct { type Router struct {
mu sync.RWMutex mu sync.RWMutex
routes map[string]http.Handler routes map[string]func(http.ResponseWriter, *http.Request)
} }
func (route *Router) ServeHTTP() { func NewRouter() *Router {
route.mu.RLock() return &Router{
defer route.mu.RUnlock() routes: make(map[string]func(http.ResponseWriter, *http.Request)),
}
}
func (router *Router) Handle(pattern string, handle http.Handler) {
router.routes[pattern] = handle.ServeHTTP
}
func (router *Router) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request)) {
router.routes[pattern] = handle
}
func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.mu.RLock()
defer router.mu.RUnlock()
if path[0] != "/" { if req.URL.Path[0] != '/' {
return route.routes["/"] w.WriteHeader(405)
w.Write([]byte(""))
return
} }
// Do something on the path to turn slashes facing the wrong way "\" into "/" slashes. If it's bytes, then alter the bytes in place for the maximum speed // Do something on the path to turn slashes facing the wrong way "\" into "/" slashes. If it's bytes, then alter the bytes in place for the maximum speed
handle := route.routes[path] handle, ok := router.routes[req.URL.Path]
if !ok { if ok {
if path[-1] != "/" { handle(w,req)
handle = route.routes[path + "/"] return
if !ok {
return route.routes["/"]
}
return handle
}
} }
return handle
}*/ if req.URL.Path[len(req.URL.Path) - 1] == '/' {
w.WriteHeader(404)
w.Write([]byte(""))
return
}
handle, ok = router.routes[req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]]
if ok {
handle(w,req)
return
}
//fmt.Println(req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/')])
handle, ok = router.routes[req.URL.Path + "/"]
if ok {
handle(w,req)
return
}
w.WriteHeader(404)
w.Write([]byte(""))
return
}

227
routes.go
View File

@ -877,19 +877,10 @@ func route_account_own_edit_critical(w http.ResponseWriter, r *http.Request) {
if !ok { if !ok {
return return
} }
if !user.Loggedin { if !user.Loggedin {
errmsg := "You need to login to edit your own account." LocalError("You need to login to edit your account.",w,r,user)
pi := Page{"Error","error",user,nList,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(500)
fmt.Fprintln(w,errpage)
return return
} }
pi := Page{"Edit Password","account-own-edit",user,noticeList,tList,0} pi := Page{"Edit Password","account-own-edit",user,noticeList,tList,0}
templates.ExecuteTemplate(w,"account-own-edit.html", pi) templates.ExecuteTemplate(w,"account-own-edit.html", pi)
} }
@ -899,16 +890,8 @@ func route_account_own_edit_critical_submit(w http.ResponseWriter, r *http.Reque
if !ok { if !ok {
return return
} }
if !user.Loggedin { if !user.Loggedin {
errmsg := "You need to login to edit your own account." LocalError("You need to login to edit your account.",w,r,user)
pi := Page{"Error","error",user,nList,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(500)
fmt.Fprintln(w,errpage)
return return
} }
@ -973,8 +956,9 @@ func route_account_own_edit_critical_submit(w http.ResponseWriter, r *http.Reque
return return
} }
pi := Page{"Edit Password","account-own-edit-success",user,noticeList,tList,0} noticeList[len(noticeList)] = "Your password was successfully updated"
templates.ExecuteTemplate(w,"account-own-edit-success.html", pi) pi := Page{"Edit Password","account-own-edit",user,noticeList,tList,0}
templates.ExecuteTemplate(w,"account-own-edit.html", pi)
} }
func route_account_own_edit_avatar(w http.ResponseWriter, r *http.Request) { func route_account_own_edit_avatar(w http.ResponseWriter, r *http.Request) {
@ -982,19 +966,10 @@ func route_account_own_edit_avatar(w http.ResponseWriter, r *http.Request) {
if !ok { if !ok {
return return
} }
if !user.Loggedin { if !user.Loggedin {
errmsg := "You need to login to edit your own account." LocalError("You need to login to edit your account.",w,r,user)
pi := Page{"Error","error",user,nList,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(500)
fmt.Fprintln(w,errpage)
return return
} }
pi := Page{"Edit Avatar","account-own-edit-avatar",user,noticeList,tList,0} pi := Page{"Edit Avatar","account-own-edit-avatar",user,noticeList,tList,0}
templates.ExecuteTemplate(w,"account-own-edit-avatar.html", pi) templates.ExecuteTemplate(w,"account-own-edit-avatar.html", pi)
} }
@ -1011,14 +986,7 @@ func route_account_own_edit_avatar_submit(w http.ResponseWriter, r *http.Request
return return
} }
if !user.Loggedin { if !user.Loggedin {
errmsg := "You need to login to edit your own account." LocalError("You need to login to edit your account.",w,r,user)
pi := Page{"Error","error",user,nList,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(500)
fmt.Fprintln(w,errpage)
return return
} }
@ -1028,7 +996,7 @@ func route_account_own_edit_avatar_submit(w http.ResponseWriter, r *http.Request
return return
} }
var filename string = "" var filename string
var ext string var ext string
for _, fheaders := range r.MultipartForm.File { for _, fheaders := range r.MultipartForm.File {
for _, hdr := range fheaders { for _, hdr := range fheaders {
@ -1087,7 +1055,6 @@ func route_account_own_edit_avatar_submit(w http.ResponseWriter, r *http.Request
InternalError(err,w,r,user) InternalError(err,w,r,user)
return return
} }
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext
noticeList[len(noticeList)] = "Your avatar was successfully updated" noticeList[len(noticeList)] = "Your avatar was successfully updated"
@ -1100,16 +1067,8 @@ func route_account_own_edit_username(w http.ResponseWriter, r *http.Request) {
if !ok { if !ok {
return return
} }
if !user.Loggedin { if !user.Loggedin {
errmsg := "You need to login to edit your own account." LocalError("You need to login to edit your account.",w,r,user)
pi := Page{"Error","error",user,nList,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(500)
fmt.Fprintln(w,errpage)
return return
} }
@ -1122,19 +1081,10 @@ func route_account_own_edit_username_submit(w http.ResponseWriter, r *http.Reque
if !ok { if !ok {
return return
} }
if !user.Loggedin { if !user.Loggedin {
errmsg := "You need to login to edit your own account." LocalError("You need to login to edit your account.",w,r,user)
pi := Page{"Error","error",user,nList,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(500)
fmt.Fprintln(w,errpage)
return return
} }
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
LocalError("Bad Form", w, r, user) LocalError("Bad Form", w, r, user)
@ -1149,10 +1099,131 @@ func route_account_own_edit_username_submit(w http.ResponseWriter, r *http.Reque
} }
user.Name = new_username user.Name = new_username
pi := Page{"Edit Username","account-own-edit-username",user,noticeList,tList,user.Name} noticeList[len(noticeList)] = "Your username was successfully updated"
pi := Page{"Edit Username","account-own-edit-username",user,noticeList,tList,0}
templates.ExecuteTemplate(w,"account-own-edit-username.html", pi) templates.ExecuteTemplate(w,"account-own-edit-username.html", pi)
} }
func route_account_own_edit_email(w http.ResponseWriter, r *http.Request) {
user, noticeList, ok := SessionCheck(w,r)
if !ok {
return
}
if !user.Loggedin {
LocalError("You need to login to edit your account.",w,r,user)
return
}
email := Email{UserID: user.ID}
var emailList []interface{}
rows, err := db.Query("SELECT email, validated FROM emails WHERE uid = ?", user.ID)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&email.Email, &email.Validated)
if err != nil {
log.Fatal(err)
}
if email.Email == user.Email {
email.Primary = true
}
emailList = append(emailList, email)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
// Was this site migrated from another forum software? Most of them don't have multiple emails for a single user. This also applies when the admin switches enable_emails on after having it off for a while
if len(emailList) == 0 {
email.Email = user.Email
email.Validated = false
email.Primary = true
emailList = append(emailList, email)
}
if !enable_emails {
noticeList[len(noticeList)] = "The email system has been turned off. All features involving sending emails have been disabled."
}
pi := Page{"Email Manager","account-own-edit-email",user,noticeList,emailList,0}
templates.ExecuteTemplate(w,"account-own-edit-email.html", pi)
}
func route_account_own_edit_email_token_submit(w http.ResponseWriter, r *http.Request) {
user, noticeList, ok := SessionCheck(w,r)
if !ok {
return
}
if !user.Loggedin {
LocalError("You need to login to edit your account.",w,r,user)
return
}
token := r.URL.Path[len("/user/edit/email/token/"):]
email := Email{UserID: user.ID}
targetEmail := Email{UserID: user.ID}
var emailList []interface{}
rows, err := db.Query("SELECT email, validated, token FROM emails WHERE uid = ?", user.ID)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&email.Email, &email.Validated, &email.Token)
if err != nil {
log.Fatal(err)
}
if email.Email == user.Email {
email.Primary = true
}
if email.Token == token {
targetEmail = email
}
emailList = append(emailList, email)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
if len(emailList) == 0 {
LocalError("A verification email was never sent for you!",w,r,user)
return
}
if targetEmail.Token == "" {
LocalError("That's not a valid token!",w,r,user)
return
}
_, err = verify_email_stmt.Exec(user.Email)
if err != nil {
InternalError(err,w,r,user)
return
}
// If Email Activation is on, then activate the account while we're here
if settings["activation_type"] == 2 {
_, err = activate_user_stmt.Exec(user.ID)
if err != nil {
InternalError(err,w,r,user)
return
}
}
if !enable_emails {
noticeList[len(noticeList)] = "The email system has been turned off. All features involving sending emails have been disabled."
}
noticeList[len(noticeList)] = "Your email was successfully verified"
pi := Page{"Email Manager","account-own-edit-email",user,noticeList,emailList,0}
templates.ExecuteTemplate(w,"account-own-edit-email.html", pi)
}
func route_logout(w http.ResponseWriter, r *http.Request) { func route_logout(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r) user, ok := SimpleSessionCheck(w,r)
if !ok { if !ok {
@ -1312,7 +1383,6 @@ func route_register(w http.ResponseWriter, r *http.Request) {
if !ok { if !ok {
return return
} }
if user.Loggedin { if user.Loggedin {
errmsg := "You're already logged in." errmsg := "You're already logged in."
pi := Page{"Error","error",user,nList,tList,errmsg} pi := Page{"Error","error",user,nList,tList,errmsg}
@ -1346,12 +1416,12 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) {
LocalError("You didn't put in a username.", w, r, user) LocalError("You didn't put in a username.", w, r, user)
return return
} }
email := html.EscapeString(r.PostFormValue("email")) email := html.EscapeString(r.PostFormValue("email"))
if email == "" { if email == "" {
LocalError("You didn't put in an email.", w, r, user) LocalError("You didn't put in an email.", w, r, user)
return return
} }
password := r.PostFormValue("password") password := r.PostFormValue("password")
if password == "" { if password == "" {
LocalError("You didn't put in a password.", w, r, user) LocalError("You didn't put in a password.", w, r, user)
@ -1416,12 +1486,12 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) {
var active int var active int
var group int var group int
if settings["activation_type"] == 1 { switch settings["activation_type"] {
active = 1 case 1: // Activate All
group = default_group active = 1
} else { group = default_group
active = 0 default: // Anything else. E.g. Admin Activation or Email Activation.
group = activation_group group = activation_group
} }
res, err := register_stmt.Exec(username,email,string(hashed_password),salt,group,session,active) res, err := register_stmt.Exec(username,email,string(hashed_password),salt,group,session,active)
@ -1436,6 +1506,25 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) {
return return
} }
// Check if this user actually owns this email, if email activation is on, automatically flip their account to active when the email is validated. Validation is also useful for determining whether this user should receive any alerts, etc. via email
if enable_emails {
token, err := GenerateSafeString(80)
if err != nil {
InternalError(err,w,r,user)
return
}
_, err = add_email_stmt.Exec(email, lastId, 0, token)
if err != nil {
InternalError(err,w,r,user)
return
}
if !SendValidationEmail(username, email, token) {
LocalError("We were unable to send the email for you to confirm that this email address belongs to you. You may not have access to some functionality until you do so. Please ask an administrator for assistance.",w,r,user)
return
}
}
cookie := http.Cookie{Name: "uid",Value: strconv.FormatInt(lastId, 10),Path: "/",MaxAge: year} cookie := http.Cookie{Name: "uid",Value: strconv.FormatInt(lastId, 10),Path: "/",MaxAge: year}
http.SetCookie(w,&cookie) http.SetCookie(w,&cookie)
cookie = http.Cookie{Name: "session",Value: session,Path: "/",MaxAge: year} cookie = http.Cookie{Name: "session",Value: session,Path: "/",MaxAge: year}

View File

@ -1,7 +1,7 @@
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "io"
import "strconv" import "strconv"
import "io"
func init() { func init() {
template_forum_handle = template_forum template_forum_handle = template_forum

View File

@ -1,8 +1,8 @@
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "html/template"
import "io" import "io"
import "strconv" import "strconv"
import "html/template"
func init() { func init() {
template_topic_alt_handle = template_topic_alt template_topic_alt_handle = template_topic_alt

View File

@ -0,0 +1,10 @@
<div class="colblock_left">
<div class="rowitem"><a>My Account</a></div>
<div class="rowitem passive"><a href="/user/edit/avatar/">Change Avatar</a></div>
<div class="rowitem passive"><a href="/user/edit/username/">Change Username</a></div>
<div class="rowitem passive"><a href="/user/edit/critical/">Change Password</a></div>
<div class="rowitem passive"><a href="/user/edit/email/">Change Email</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
</div>

View File

@ -1,13 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colblock_left"> {{template "account-menu.html" . }}
<div class="rowitem"><a>My Account</a></div>
<div class="rowitem passive"><a href="/user/edit/avatar/">Edit Avatar</a></div>
<div class="rowitem passive"><a href="/user/edit/username/">Edit Username</a></div>
<div class="rowitem passive"><a href="/user/edit/critical/">Edit Password</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
</div>
<div class="colblock_right"> <div class="colblock_right">
<div class="rowitem"><a>Edit Avatar</a></div> <div class="rowitem"><a>Edit Avatar</a></div>
</div> </div>

View File

@ -0,0 +1,17 @@
{{template "header.html" . }}
{{template "account-menu.html" . }}
<div class="colblock_right">
<div class="rowitem"><a>Emails</a></div>
</div>
<div class="colblock_right">
{{range .ItemList}}
<div class="rowitem" style="font-weight: normal;">
<a style="text-transform: none;">{{.Email}}</a>
<span style="float: right">
<span class="username">{{if .Primary}}Primary{{else}}Secondary{{end}}</span>
{{if .Validated}}<span class="username" style="color: green;">Verified</span>{{else}}<a class="username" style="color: crimson;">Resend Verification Email</a>{{end}}
</span>
</div>
{{end}}
</div>
{{template "footer.html" . }}

View File

@ -1,34 +0,0 @@
{{template "header.html" . }}
<div class="alert_success">Your data was successfully updated</div>
<div class="colblock_left">
<div class="rowitem"><a>My Account</a></div>
<div class="rowitem passive"><a href="/user/edit/avatar/">Edit Avatar</a></div>
<div class="rowitem passive"><a href="/user/edit/username/">Edit Username</a></div>
<div class="rowitem passive"><a href="/user/edit/critical/">Edit Password</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
</div>
<div class="colblock_right">
<div class="rowitem"><a>Edit Password</a></div>
</div>
<div class="colblock_right">
<form action="/user/edit/critical/submit/" method="post">
<div class="formrow">
<div class="formitem"><a>Current Password</a></div>
<div class="formitem"><input name="account-current-password" type="password" placeholder="*****" /></div>
</div>
<div class="formrow">
<div class="formitem"><a>New Password</a></div>
<div class="formitem"><input name="account-new-password" type="password" placeholder="*****" /></div>
</div>
<div class="formrow">
<div class="formitem"><a>Confirm Password</a></div>
<div class="formitem"><input name="account-confirm-password" type="password" placeholder="*****" /></div>
</div>
<div class="formrow">
<div class="formitem"><button name="account-button" class="formbutton">Update</div></div>
</div>
</form>
</div>
{{template "footer.html" . }}

View File

@ -1,13 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colblock_left"> {{template "account-menu.html" . }}
<div class="rowitem"><a>My Account</a></div>
<div class="rowitem passive"><a href="/user/edit/avatar/">Edit Avatar</a></div>
<div class="rowitem passive"><a href="/user/edit/username/">Edit Username</a></div>
<div class="rowitem passive"><a href="/user/edit/critical/">Edit Password</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
</div>
<div class="colblock_right"> <div class="colblock_right">
<div class="rowitem"><a>Edit Username</a></div> <div class="rowitem"><a>Edit Username</a></div>
</div> </div>
@ -15,7 +7,7 @@
<form action="/user/edit/username/submit/" method="post"> <form action="/user/edit/username/submit/" method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem"><a>Current Username</a></div> <div class="formitem"><a>Current Username</a></div>
<div class="formitem">{{.Something}}</div> <div class="formitem">{{.CurrentUser.Name}}</div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"><a>New Username</a></div> <div class="formitem"><a>New Username</a></div>

View File

@ -1,13 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colblock_left"> {{template "account-menu.html" . }}
<div class="rowitem"><a>My Account</a></div>
<div class="rowitem passive"><a href="/user/edit/avatar/">Edit Avatar</a></div>
<div class="rowitem passive"><a href="/user/edit/username/">Edit Username</a></div>
<div class="rowitem passive"><a href="/user/edit/critical/">Edit Password</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
</div>
<div class="colblock_right"> <div class="colblock_right">
<div class="rowitem"><a>Edit Password</a></div> <div class="rowitem"><a>Edit Password</a></div>
</div> </div>

23
user.go
View File

@ -28,6 +28,15 @@ type User struct
Tag string Tag string
} }
type Email struct
{
UserID int
Email string
Validated bool
Primary bool
Token string
}
func SetPassword(uid int, password string) (error) { func SetPassword(uid int, password string) (error) {
salt, err := GenerateSafeString(saltLength) salt, err := GenerateSafeString(saltLength)
if err != nil { if err != nil {
@ -47,6 +56,18 @@ func SetPassword(uid int, password string) (error) {
return nil return nil
} }
func SendValidationEmail(username string, email string, token string) bool {
var schema string
if enable_ssl {
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.\nClick on the following link to do so. http" + schema + "://" + site_url + "/user/edit/token/" + token + "\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)
}
func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, noticeList map[int]string, success bool) { func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, noticeList map[int]string, success bool) {
noticeList = make(map[int]string) noticeList = make(map[int]string)
@ -71,7 +92,7 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, noticeList
user.Session = cookie.Value user.Session = cookie.Value
// Is this session valid..? // Is this session valid..?
err = get_session_stmt.QueryRow(user.ID,user.Session).Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName) err = get_session_stmt.QueryRow(user.ID,user.Session).Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
user.ID = 0 user.ID = 0
user.Session = "" user.Session = ""

View File

@ -3,6 +3,7 @@ import "fmt"
import "time" import "time"
import "encoding/base64" import "encoding/base64"
import "crypto/rand" import "crypto/rand"
import "net/smtp"
// Generate a cryptographically secure set of random bytes.. // Generate a cryptographically secure set of random bytes..
func GenerateSafeString(length int) (string, error) { func GenerateSafeString(length int) (string, error) {
@ -44,4 +45,45 @@ func relative_time(in string) (string, error) {
default: default:
return fmt.Sprintf("%d hours ago", int(seconds / 60 / 60)), err return fmt.Sprintf("%d hours ago", int(seconds / 60 / 60)), err
} }
} }
func SendEmail(email string, subject string, msg string) bool {
// This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server?
if vhooks["email_send_intercept"] != nil {
return vhooks["email_send_intercept"](email, subject, msg).(bool)
}
body := "Subject: " + subject + "\n\n" + msg + "\n"
con, err := smtp.Dial(smtp_server)
if err != nil {
return false
}
err = con.Mail(site_email)
if err != nil {
return false
}
err = con.Rcpt(email)
if err != nil {
return false
}
email_data, err := con.Data()
if err != nil {
return false
}
_, err = fmt.Fprintf(email_data, body)
if err != nil {
return false
}
err = email_data.Close()
if err != nil {
return false
}
err = con.Quit()
if err != nil {
return false
}
return true
}