Fixed the Cosora Alerts CSS.

Fixed the topic edit input CSS.
You can now filter the time range of the routes list.
You can now filter the time range of the agents list.
Renamed operations.log to ops.log
Moved the account routes into the new router.
Log suspicious requests.
Removed some duplicated code in the control panel routes.
Fixed a bug in the User Editor where the Administrator group didn't show up.
Users are now force logged out when an admin changes their password.
This commit is contained in:
Azareal 2018-01-17 11:13:08 +00:00
parent cce87e2d70
commit 0416b1ed91
12 changed files with 215 additions and 79 deletions

View File

@ -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 {

View File

@ -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 {

11
main.go
View File

@ -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")

View File

@ -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

View File

@ -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()

View File

@ -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]
}

View File

@ -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"),

View File

@ -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 {

View File

@ -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

View File

@ -2,9 +2,20 @@
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agents/" method="get">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>User Agents (24 hours)</a></div>
<div class="rowitem">
<a>User Agents</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option>
<option val="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
<option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>
</select>
</div>
</div>
</form>
<div id="panel_analytics_agents" class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">

View File

@ -2,9 +2,20 @@
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes/" method="get">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Routes (24 hours)</a></div>
<div class="rowitem">
<a>Routes</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option>
<option val="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
<option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>
</select>
</div>
</div>
</form>
<div id="panel_analytics_routes" class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">

View File

@ -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;