diff --git a/common/pages.go b/common/pages.go index 8a55499a..a9634c4d 100644 --- a/common/pages.go +++ b/common/pages.go @@ -182,6 +182,7 @@ type PanelAnalyticsRoutesPage struct { Stats PanelStats Zone string ItemList []PanelAnalyticsRoutesItem + TimeRange string } type PanelAnalyticsAgentsItem struct { @@ -196,6 +197,7 @@ type PanelAnalyticsAgentsPage struct { Stats PanelStats Zone string ItemList []PanelAnalyticsAgentsItem + TimeRange string } type PanelAnalyticsRoutePage struct { diff --git a/gen_router.go b/gen_router.go index 0bee6d53..a0b880cb 100644 --- a/gen_router.go +++ b/gen_router.go @@ -97,6 +97,11 @@ var RouteMap = map[string]interface{}{ "routeProfileReplyCreateSubmit": routeProfileReplyCreateSubmit, "routeProfileReplyEditSubmit": routeProfileReplyEditSubmit, "routeProfileReplyDeleteSubmit": routeProfileReplyDeleteSubmit, + "routeLogin": routeLogin, + "routeRegister": routeRegister, + "routeLogout": routeLogout, + "routeLoginSubmit": routeLoginSubmit, + "routeRegisterSubmit": routeRegisterSubmit, "routeDynamic": routeDynamic, "routeUploads": routeUploads, } @@ -185,8 +190,13 @@ var routeMapEnum = map[string]int{ "routeProfileReplyCreateSubmit": 79, "routeProfileReplyEditSubmit": 80, "routeProfileReplyDeleteSubmit": 81, - "routeDynamic": 82, - "routeUploads": 83, + "routeLogin": 82, + "routeRegister": 83, + "routeLogout": 84, + "routeLoginSubmit": 85, + "routeRegisterSubmit": 86, + "routeDynamic": 87, + "routeUploads": 88, } var reverseRouteMapEnum = map[int]string{ 0: "routeAPI", @@ -271,8 +281,13 @@ var reverseRouteMapEnum = map[int]string{ 79: "routeProfileReplyCreateSubmit", 80: "routeProfileReplyEditSubmit", 81: "routeProfileReplyDeleteSubmit", - 82: "routeDynamic", - 83: "routeUploads", + 82: "routeLogin", + 83: "routeRegister", + 84: "routeLogout", + 85: "routeLoginSubmit", + 86: "routeRegisterSubmit", + 87: "routeDynamic", + 88: "routeUploads", } var agentMapEnum = map[string]int{ "unknown": 0, @@ -379,6 +394,19 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { var prefix, extraData string prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1] if req.URL.Path[len(req.URL.Path) - 1] != '/' { + // TODO: Cover more suspicious strings and at a lower layer than this and more efficiently + if strings.Contains(req.URL.Path,"'") || strings.Contains(req.URL.Path,";") || strings.Contains(req.URL.Path,"\"") || strings.Contains(req.URL.Path,"`") || strings.Contains(req.URL.Path,"%") { + log.Print("Suspicious UA: ", req.UserAgent()) + log.Print("Method: ", req.Method) + for key, value := range req.Header { + for _, vvalue := range value { + log.Print("Header '" + key + "': " + vvalue + "!!") + } + } + log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.Referer(): ", req.Referer()) + log.Print("req.RemoteAddr: ", req.RemoteAddr) + } extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:] req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1] } @@ -775,9 +803,21 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { common.RouteViewCounter.Bump(36) err = routePanelAnalyticsViews(w,req,user) case "/panel/analytics/routes/": + err = common.ParseForm(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + common.RouteViewCounter.Bump(37) err = routePanelAnalyticsRoutes(w,req,user) case "/panel/analytics/agents/": + err = common.ParseForm(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + common.RouteViewCounter.Bump(38) err = routePanelAnalyticsAgents(w,req,user) case "/panel/analytics/route/": @@ -1298,6 +1338,51 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if err != nil { router.handleError(err,w,req,user) } + case "/accounts": + switch(req.URL.Path) { + case "/accounts/login/": + common.RouteViewCounter.Bump(82) + err = routeLogin(w,req,user) + case "/accounts/create/": + common.RouteViewCounter.Bump(83) + err = routeRegister(w,req,user) + case "/accounts/logout/": + err = common.NoSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + err = common.MemberOnly(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + common.RouteViewCounter.Bump(84) + err = routeLogout(w,req,user) + case "/accounts/login/submit/": + err = common.ParseForm(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + common.RouteViewCounter.Bump(85) + err = routeLoginSubmit(w,req,user) + case "/accounts/create/submit/": + err = common.ParseForm(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + common.RouteViewCounter.Bump(86) + err = routeRegisterSubmit(w,req,user) + } + if err != nil { + router.handleError(err,w,req,user) + } /*case "/sitemaps": // TODO: Count these views req.URL.Path += extraData err = sitemapSwitch(w,req) @@ -1309,7 +1394,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { common.NotFound(w,req) return } - common.RouteViewCounter.Bump(83) + common.RouteViewCounter.Bump(88) req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? router.UploadHandler(w,req) // TODO: Count these views @@ -1353,7 +1438,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { router.RUnlock() if ok { - common.RouteViewCounter.Bump(82) // TODO: Be more specific about *which* dynamic route it is + common.RouteViewCounter.Bump(87) // TODO: Be more specific about *which* dynamic route it is req.URL.Path += extraData err = handle(w,req,user) if err != nil { diff --git a/main.go b/main.go index bcf60797..3dda626b 100644 --- a/main.go +++ b/main.go @@ -130,7 +130,7 @@ func main() { // TODO: Have a file for each run with the time/date the server started as the file name? // TODO: Log panics with recover() - f, err := os.OpenFile("./operations.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) + f, err := os.OpenFile("./ops.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) if err != nil { log.Fatal(err) } @@ -303,15 +303,6 @@ func main() { // TODO: Move these routes into the new routes list log.Print("Initialising the router") router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) - - // Accounts - router.HandleFunc("/accounts/login/", routeLogin) - router.HandleFunc("/accounts/create/", routeRegister) - router.HandleFunc("/accounts/logout/", routeLogout) - router.HandleFunc("/accounts/login/submit/", routeLoginSubmit) - router.HandleFunc("/accounts/create/submit/", routeRegisterSubmit) - //router.HandleFunc("/accounts/list/", routeLogin) // Redirect /accounts/ and /user/ to here.. // Get a list of all of the accounts on the forum - router.HandleFunc("/ws/", routeWebsockets) log.Print("Initialising the plugins") diff --git a/member_routes.go b/member_routes.go index da0ad5e5..db9810d5 100644 --- a/member_routes.go +++ b/member_routes.go @@ -16,11 +16,25 @@ import ( "./common" ) +// Experimenting +/*func memberRenderTemplate(tmplName string, themeName string, w http.ResponseWriter, r *http.Request, user common.User, pi interface{}) common.RouteError { + if common.PreRenderHooks["pre_render_"+tmplName] != nil { + if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &user, pi) { + return nil + } + } + + err := common.RunThemeTemplate(themeName, tmplName, pi, w) + if err != nil { + return common.InternalError(err, w, r) + } + return nil +}*/ + // ? - Should we add a new permission or permission zone (like per-forum permissions) specifically for profile comment creation // ? - Should we allow banned users to make reports? How should we handle report abuse? // TODO: Add a permission to stop certain users from using custom avatars // ? - Log username changes and put restrictions on this? - func routeTopicCreate(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError { var fid int var err error diff --git a/panel_routes.go b/panel_routes.go index 7744f081..58570b7c 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -834,39 +834,9 @@ func routePanelAnalyticsPosts(w http.ResponseWriter, r *http.Request, user commo headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") - var timeQuantity = 6 - var timeUnit = "hour" - var timeSlices = 12 - var sliceWidth = 60 * 30 - var timeRange = "six-hours" - - switch r.FormValue("timeRange") { - case "one-month": - timeQuantity = 30 - timeUnit = "day" - timeSlices = 30 - sliceWidth = 60 * 60 * 24 - timeRange = "one-month" - case "two-days": // Two days is experimental - timeQuantity = 2 - timeUnit = "day" - timeSlices = 24 - sliceWidth = 60 * 60 * 2 - timeRange = "two-days" - case "one-day": - timeQuantity = 1 - timeUnit = "day" - timeSlices = 24 - sliceWidth = 60 * 60 - timeRange = "one-day" - case "twelve-hours": - timeQuantity = 12 - timeSlices = 24 - timeRange = "twelve-hours" - case "six-hours", "": - timeRange = "six-hours" - default: - return common.LocalError("Unknown time range", w, r, user) + timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) + if err != nil { + return common.LocalError(err.Error(), w, r, user) } var revLabelList []int64 @@ -874,8 +844,8 @@ func routePanelAnalyticsPosts(w http.ResponseWriter, r *http.Request, user commo var viewMap = make(map[int64]int64) var currentTime = time.Now().Unix() - for i := 1; i <= timeSlices; i++ { - var label = currentTime - int64(i*sliceWidth) + for i := 1; i <= timeRange.Slices; i++ { + var label = currentTime - int64(i*timeRange.SliceWidth) revLabelList = append(revLabelList, label) viewMap[label] = 0 } @@ -887,7 +857,7 @@ func routePanelAnalyticsPosts(w http.ResponseWriter, r *http.Request, user commo log.Print("in routePanelAnalyticsPosts") acc := qgen.Builder.Accumulator() - rows, err := acc.Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeQuantity, timeUnit).Query() + rows, err := acc.Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != ErrNoRows { return common.InternalError(err, w, r) } @@ -925,7 +895,7 @@ func routePanelAnalyticsPosts(w http.ResponseWriter, r *http.Request, user commo graph := common.PanelTimeGraph{Series: viewList, Labels: labelList} log.Printf("graph: %+v\n", graph) - pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", graph, viewItems, timeRange} + pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", graph, viewItems, timeRange.Range} return panelRenderTemplate("panel_analytics_posts", w, r, user, &pi) } @@ -936,8 +906,13 @@ func routePanelAnalyticsRoutes(w http.ResponseWriter, r *http.Request, user comm } var routeMap = make(map[string]int) + timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) + if err != nil { + return common.LocalError(err.Error(), w, r, user) + } + acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks").Columns("count, route").Where("route != ''").DateCutoff("createdAt", 1, "day").Query() + rows, err := acc.Select("viewchunks").Columns("count, route").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != ErrNoRows { return common.InternalError(err, w, r) } @@ -969,7 +944,7 @@ func routePanelAnalyticsRoutes(w http.ResponseWriter, r *http.Request, user comm }) } - pi := common.PanelAnalyticsRoutesPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", routeItems} + pi := common.PanelAnalyticsRoutesPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", routeItems, timeRange.Range} return panelRenderTemplate("panel_analytics_routes", w, r, user, &pi) } @@ -980,8 +955,13 @@ func routePanelAnalyticsAgents(w http.ResponseWriter, r *http.Request, user comm } var agentMap = make(map[string]int) + timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) + if err != nil { + return common.LocalError(err.Error(), w, r, user) + } + acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks_agents").Columns("count, browser").DateCutoff("createdAt", 1, "day").Query() + rows, err := acc.Select("viewchunks_agents").Columns("count, browser").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != ErrNoRows { return common.InternalError(err, w, r) } @@ -1013,7 +993,7 @@ func routePanelAnalyticsAgents(w http.ResponseWriter, r *http.Request, user comm }) } - pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", agentItems} + pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", agentItems, timeRange.Range} return panelRenderTemplate("panel_analytics_agents", w, r, user, &pi) } @@ -1505,7 +1485,7 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user common.Use } var groupList []interface{} - for _, group := range groups[1:] { + for _, group := range groups { if !user.Perms.EditUserGroupAdmin && group.IsAdmin { continue } @@ -1600,6 +1580,8 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user comm if newpassword != "" { common.SetPassword(targetUser.ID, newpassword) + // Log the user out as a safety precaution + common.Auth.ForceLogout(targetUser.ID) } targetUser.CacheRemove() diff --git a/router_gen/main.go b/router_gen/main.go index 25c8c51e..b69f569d 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -181,6 +181,7 @@ func main() { for id, agent := range tmplVars.AllAgentNames { tmplVars.AllAgentMap[agent] = id } + var graveSym = "`" var fileData = `// Code generated by. DO NOT EDIT. /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ @@ -284,6 +285,19 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { var prefix, extraData string prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1] if req.URL.Path[len(req.URL.Path) - 1] != '/' { + // TODO: Cover more suspicious strings and at a lower layer than this and more efficiently + if strings.Contains(req.URL.Path,"'") || strings.Contains(req.URL.Path,";") || strings.Contains(req.URL.Path,"\"") || strings.Contains(req.URL.Path,"` + graveSym + `") || strings.Contains(req.URL.Path,"%") { + log.Print("Suspicious UA: ", req.UserAgent()) + log.Print("Method: ", req.Method) + for key, value := range req.Header { + for _, vvalue := range value { + log.Print("Header '" + key + "': " + vvalue + "!!") + } + } + log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.Referer(): ", req.Referer()) + log.Print("req.RemoteAddr: ", req.RemoteAddr) + } extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:] req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1] } diff --git a/router_gen/routes.go b/router_gen/routes.go index 5027c664..f83bd55a 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -29,6 +29,7 @@ func routes() { buildTopicRoutes() buildReplyRoutes() buildProfileReplyRoutes() + buildAccountRoutes() } // TODO: Test the email token route @@ -101,6 +102,19 @@ func buildProfileReplyRoutes() { addRouteGroup(pReplyGroup) } +func buildAccountRoutes() { + //router.HandleFunc("/accounts/list/", routeLogin) // Redirect /accounts/ and /user/ to here.. // Get a list of all of the accounts on the forum + accReplyGroup := newRouteGroup("/accounts/") + accReplyGroup.Routes( + View("routeLogin", "/accounts/login/"), + View("routeRegister", "/accounts/create/"), + Action("routeLogout", "/accounts/logout/"), + AnonAction("routeLoginSubmit", "/accounts/login/submit/"), // TODO: Guard this with a token, maybe the IP hashed with a rotated key? + AnonAction("routeRegisterSubmit", "/accounts/create/submit/"), + ) + addRouteGroup(accReplyGroup) +} + func buildPanelRoutes() { panelGroup := newRouteGroup("/panel/").Before("SuperModOnly") panelGroup.Routes( @@ -138,8 +152,8 @@ func buildPanelRoutes() { Action("routePanelUsersEditSubmit", "/panel/users/edit/submit/", "extraData"), View("routePanelAnalyticsViews", "/panel/analytics/views/").Before("ParseForm"), - View("routePanelAnalyticsRoutes", "/panel/analytics/routes/"), - View("routePanelAnalyticsAgents", "/panel/analytics/agents/"), + View("routePanelAnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"), + View("routePanelAnalyticsAgents", "/panel/analytics/agents/").Before("ParseForm"), View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"), View("routePanelAnalyticsAgentViews", "/panel/analytics/agent/", "extraData"), View("routePanelAnalyticsPosts", "/panel/analytics/posts/").Before("ParseForm"), diff --git a/routes.go b/routes.go index c11491a4..53d54634 100644 --- a/routes.go +++ b/routes.go @@ -746,10 +746,6 @@ func routeLoginSubmit(w http.ResponseWriter, r *http.Request, user common.User) if user.Loggedin { return common.LocalError("You're already logged in.", w, r, user) } - err := r.ParseForm() - if err != nil { - return common.LocalError("Bad Form", w, r, user) - } username := html.EscapeString(strings.Replace(r.PostFormValue("username"), "\n", "", -1)) uid, err := common.Auth.Authenticate(username, r.PostFormValue("password")) @@ -807,10 +803,6 @@ func routeRegister(w http.ResponseWriter, r *http.Request, user common.User) com func routeRegisterSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { headerLite, _ := common.SimpleUserCheck(w, r, &user) - err := r.ParseForm() - if err != nil { - return common.LocalError("Bad Form", w, r, user) - } username := html.EscapeString(strings.Replace(r.PostFormValue("username"), "\n", "", -1)) if username == "" { return common.LocalError("You didn't put in a username.", w, r, user) @@ -831,13 +823,15 @@ func routeRegisterSubmit(w http.ResponseWriter, r *http.Request, user common.Use } // ? Move this into Create()? What if we want to programatically set weak passwords for tests? - err = common.WeakPassword(password) + err := common.WeakPassword(password) if err != nil { return common.LocalError(err.Error(), w, r, user) } confirmPassword := r.PostFormValue("confirm_password") - log.Print("Registration Attempt! common.Username: " + username) // TODO: Add more controls over what is logged when? + if common.Dev.DebugMode { + log.Print("Registration Attempt! Username: " + username) // TODO: Add more controls over what is logged when? + } // Do the two inputted passwords match..? if password != confirmPassword { diff --git a/run.bat b/run.bat index b777ecf7..85f1b775 100644 --- a/run.bat +++ b/run.bat @@ -34,5 +34,5 @@ if %errorlevel% neq 0 ( echo Running Gosora gosora.exe rem Or you could redirect the output to a file -rem gosora.exe > operations.log 2>&1 +rem gosora.exe > ops.log 2>&1 pause \ No newline at end of file diff --git a/templates/panel_analytics_agents.html b/templates/panel_analytics_agents.html index 7ad50e52..662f998d 100644 --- a/templates/panel_analytics_agents.html +++ b/templates/panel_analytics_agents.html @@ -2,9 +2,20 @@
{{template "panel-menu.html" . }}
- +
+
+
+ User Agents + +
+
+
{{range .ItemList}}
diff --git a/templates/panel_analytics_routes.html b/templates/panel_analytics_routes.html index 9293bec1..7f3b1361 100644 --- a/templates/panel_analytics_routes.html +++ b/templates/panel_analytics_routes.html @@ -2,9 +2,20 @@
{{template "panel-menu.html" . }}
- +
+
+
+ Routes + +
+
+
{{range .ItemList}}
diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index 8ce1bbf2..7a3d8d60 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -207,9 +207,12 @@ ul { font-size: 15px; display: flex; } +.alertItem.withAvatar .text { + margin-top: 8px; +} .alertItem.withAvatar:not(:last-child) .text { border-bottom: 1px solid var(--element-border-color); - margin-top: 8px; + padding-bottom: 16px; } .alertItem .bgsub { width: 32px; @@ -217,6 +220,9 @@ ul { border-radius: 30px; margin-right: 12px; } +.alertItem.withAvatar:not(:first-child) { + padding-top: 0px; +} .rowblock, .colstack_head { margin-bottom: 12px; @@ -820,6 +826,18 @@ textarea { margin-right: 8px; } +.topic_item { + display: flex; +} +.topic_item .topic_name_input { + width: 100%; + padding-left: 12px; + margin-right: 12px; +} +.topic_item .formbutton { + margin-top: 0px; +} + .post_item { display: flex; margin-bottom: 16px;