From 4210c9b38f9053f6720a6bebaadefd24c542eaa9 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:06:00 +0300 Subject: backend: better auth check, audit logging --- backend/api/auth.go | 19 +++++--- backend/api/routes.go | 98 +++++++++++++++-------------------------- backend/database/functions.sql | 14 ++++++ backend/database/init.sql | 45 ++++++++++++++----- backend/docs/docs.go | 68 ---------------------------- backend/docs/swagger.json | 68 ---------------------------- backend/docs/swagger.yaml | 41 ----------------- backend/handlers/discussions.go | 24 ++-------- backend/handlers/login.go | 3 -- backend/handlers/logs.go | 98 ----------------------------------------- backend/handlers/mod.go | 36 --------------- backend/handlers/record.go | 31 +------------ backend/handlers/user.go | 28 ++---------- 13 files changed, 106 insertions(+), 467 deletions(-) (limited to 'backend') diff --git a/backend/api/auth.go b/backend/api/auth.go index 621a68b..a1f859c 100644 --- a/backend/api/auth.go +++ b/backend/api/auth.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "net/http" "os" "time" @@ -12,7 +13,7 @@ import ( "github.com/golang-jwt/jwt/v4" ) -func CheckAuth(c *gin.Context) { +func IsAuthenticated(c *gin.Context) { tokenString := c.GetHeader("Authorization") // Validate token token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { @@ -22,17 +23,17 @@ func CheckAuth(c *gin.Context) { return []byte(os.Getenv("SECRET_KEY")), nil }) if token == nil { - c.Next() + c.AbortWithStatusJSON(http.StatusOK, models.ErrorResponse("Token is nil.")) return } if err != nil { - c.Next() + c.AbortWithStatusJSON(http.StatusOK, models.ErrorResponse("Token is invalid.")) return } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { // Check exp if float64(time.Now().Unix()) > claims["exp"].(float64) { - c.Next() + c.AbortWithStatusJSON(http.StatusOK, models.ErrorResponse("Token expired.")) return } // Get user from DB @@ -41,7 +42,7 @@ func CheckAuth(c *gin.Context) { &user.SteamID, &user.UserName, &user.AvatarLink, &user.CountryCode, &user.CreatedAt, &user.UpdatedAt) if user.SteamID == "" { - c.Next() + c.AbortWithStatusJSON(http.StatusOK, models.ErrorResponse("Token does not match a user.")) return } // Get user titles from DB @@ -56,11 +57,17 @@ func CheckAuth(c *gin.Context) { } user.Titles = append(user.Titles, title) } + // Set user id variable in db session for audit logging + _, err = database.DB.Exec(fmt.Sprintf("SET app.user_id = '%s';", user.SteamID)) + if err != nil { + c.AbortWithStatusJSON(http.StatusOK, models.ErrorResponse("Session failed to start.")) + return + } c.Set("user", user) c.Set("mod", moderator) c.Next() } else { - c.Next() + c.AbortWithStatusJSON(http.StatusOK, models.ErrorResponse("Token is invalid.")) return } } diff --git a/backend/api/routes.go b/backend/api/routes.go index 81f1ec6..ecfb54b 100644 --- a/backend/api/routes.go +++ b/backend/api/routes.go @@ -8,82 +8,54 @@ import ( ginSwagger "github.com/swaggo/gin-swagger" ) -const ( - apiPath string = "/api" - v1Path string = "/v1" - swaggerPath string = "/swagger/*any" - indexPath string = "/" - tokenPath string = "/token" - loginPath string = "/login" - profilePath string = "/profile" - usersPath string = "/users/:userid" - demosPath string = "/demos" - mapSummaryPath string = "/maps/:mapid/summary" - mapImagePath string = "/maps/:mapid/image" - mapLeaderboardsPath string = "/maps/:mapid/leaderboards" - mapRecordPath string = "/maps/:mapid/record" - mapRecordIDPath string = "/maps/:mapid/record/:recordid" - mapDiscussionsPath string = "/maps/:mapid/discussions" - mapDiscussionIDPath string = "/maps/:mapid/discussions/:discussionid" - rankingsLPHUBPath string = "/rankings/lphub" - rankingsSteamPath string = "/rankings/steam" - searchPath string = "/search" - gamesPath string = "/games" - chaptersPath string = "/games/:gameid" - gameMapsPath string = "/games/:gameid/maps" - chapterMapsPath string = "/chapters/:chapterid" - scoreLogsPath string = "/logs/score" - modLogsPath string = "/logs/mod" -) - func InitRoutes(router *gin.Engine) { - api := router.Group(apiPath) + api := router.Group("/api") { - v1 := api.Group(v1Path) + v1 := api.Group("/v1") // Swagger - v1.GET(swaggerPath, ginSwagger.WrapHandler(swaggerfiles.Handler)) - v1.GET(indexPath, func(c *gin.Context) { + v1.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) + v1.GET("/", func(c *gin.Context) { c.File("docs/index.html") }) // Tokens, login - v1.GET(tokenPath, handlers.GetCookie) - v1.DELETE(tokenPath, handlers.DeleteCookie) - v1.GET(loginPath, handlers.Login) + v1.GET("/token", handlers.GetCookie) + v1.DELETE("/token", handlers.DeleteCookie) + v1.GET("/login", handlers.Login) // Users, profiles - v1.GET(profilePath, CheckAuth, handlers.Profile) - v1.PUT(profilePath, CheckAuth, handlers.UpdateCountryCode) - v1.POST(profilePath, CheckAuth, handlers.UpdateUser) - v1.GET(usersPath, CheckAuth, handlers.FetchUser) + v1.GET("/profile", IsAuthenticated, handlers.Profile) + v1.PUT("/profile", IsAuthenticated, handlers.UpdateCountryCode) + v1.POST("/profile", IsAuthenticated, handlers.UpdateUser) + v1.GET("/users/:userid", IsAuthenticated, handlers.FetchUser) // Maps // - Summary - v1.GET(mapSummaryPath, handlers.FetchMapSummary) - v1.POST(mapSummaryPath, CheckAuth, handlers.CreateMapSummary) - v1.PUT(mapSummaryPath, CheckAuth, handlers.EditMapSummary) - v1.DELETE(mapSummaryPath, CheckAuth, handlers.DeleteMapSummary) - v1.PUT(mapImagePath, CheckAuth, handlers.EditMapImage) + v1.GET("/maps/:mapid/summary", handlers.FetchMapSummary) + v1.POST("/maps/:mapid/summary", IsAuthenticated, handlers.CreateMapSummary) + v1.PUT("/maps/:mapid/summary", IsAuthenticated, handlers.EditMapSummary) + v1.DELETE("/maps/:mapid/summary", IsAuthenticated, handlers.DeleteMapSummary) + v1.PUT("/maps/:mapid/image", IsAuthenticated, handlers.EditMapImage) // - Leaderboards - v1.GET(mapLeaderboardsPath, handlers.FetchMapLeaderboards) - v1.POST(mapRecordPath, CheckAuth, handlers.CreateRecordWithDemo) - v1.DELETE(mapRecordIDPath, CheckAuth, handlers.DeleteRecord) - v1.GET(demosPath, handlers.DownloadDemoWithID) + v1.GET("/maps/:mapid/leaderboards", handlers.FetchMapLeaderboards) + v1.POST("/maps/:mapid/record", IsAuthenticated, handlers.CreateRecordWithDemo) + v1.DELETE("/maps/:mapid/record/:recordid", IsAuthenticated, handlers.DeleteRecord) + v1.GET("/demos", handlers.DownloadDemoWithID) // - Discussions - v1.GET(mapDiscussionsPath, handlers.FetchMapDiscussions) - v1.GET(mapDiscussionIDPath, handlers.FetchMapDiscussion) - v1.POST(mapDiscussionsPath, CheckAuth, handlers.CreateMapDiscussion) - v1.POST(mapDiscussionIDPath, CheckAuth, handlers.CreateMapDiscussionComment) - v1.PUT(mapDiscussionIDPath, CheckAuth, handlers.EditMapDiscussion) - v1.DELETE(mapDiscussionIDPath, CheckAuth, handlers.DeleteMapDiscussion) + v1.GET("/maps/:mapid/discussions", handlers.FetchMapDiscussions) + v1.GET("/maps/:mapid/discussions/:discussionid", handlers.FetchMapDiscussion) + v1.POST("/maps/:mapid/discussions", IsAuthenticated, handlers.CreateMapDiscussion) + v1.POST("/maps/:mapid/discussions/:discussionid", IsAuthenticated, handlers.CreateMapDiscussionComment) + v1.PUT("/maps/:mapid/discussions/:discussionid", IsAuthenticated, handlers.EditMapDiscussion) + v1.DELETE("/maps/:mapid/discussions/:discussionid", IsAuthenticated, handlers.DeleteMapDiscussion) // Rankings, search - v1.GET(rankingsLPHUBPath, handlers.RankingsLPHUB) - v1.GET(rankingsSteamPath, handlers.RankingsSteam) - v1.GET(searchPath, handlers.SearchWithQuery) + v1.GET("/rankings/lphub", handlers.RankingsLPHUB) + v1.GET("/rankings/steam", handlers.RankingsSteam) + v1.GET("/search", handlers.SearchWithQuery) // Games, chapters, maps - v1.GET(gamesPath, handlers.FetchGames) - v1.GET(chaptersPath, handlers.FetchChapters) - v1.GET(chapterMapsPath, handlers.FetchChapterMaps) - v1.GET(gameMapsPath, handlers.FetchMaps) + v1.GET("/games", handlers.FetchGames) + v1.GET("/games/:gameid", handlers.FetchChapters) + v1.GET("/chapters/:chapterid", handlers.FetchChapterMaps) + v1.GET("/games/:gameid/maps", handlers.FetchMaps) // Logs - v1.GET(scoreLogsPath, handlers.ScoreLogs) - v1.GET(modLogsPath, CheckAuth, handlers.ModLogs) + v1.GET("/logs/score", handlers.ScoreLogs) + // v1.GET("/logs/mod", IsAuthenticated, handlers.ModLogs) } } diff --git a/backend/database/functions.sql b/backend/database/functions.sql index ca33a60..6a6f6d2 100644 --- a/backend/database/functions.sql +++ b/backend/database/functions.sql @@ -1,3 +1,17 @@ +CREATE OR REPLACE FUNCTION log_audit() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO audit (table_name, operation_type, old_data, new_data, changed_by) + VALUES ( + TG_TABLE_NAME, + TG_OP, + CASE WHEN TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN row_to_json(OLD) ELSE NULL END, + CASE WHEN TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN row_to_json(NEW) ELSE NULL END, + current_setting('app.user_id')::TEXT + ); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION get_rankings_singleplayer() RETURNS TABLE ( steam_id TEXT, diff --git a/backend/database/init.sql b/backend/database/init.sql index 77a88f5..51bf2af 100644 --- a/backend/database/init.sql +++ b/backend/database/init.sql @@ -12,6 +12,10 @@ CREATE TABLE users ( PRIMARY KEY (steam_id) ); +CREATE TRIGGER "users" +AFTER INSERT OR UPDATE OR DELETE ON "users" +FOR EACH ROW EXECUTE FUNCTION log_audit(); + CREATE TABLE games ( id SERIAL, name TEXT NOT NULL, @@ -72,6 +76,10 @@ CREATE TABLE map_history ( UNIQUE (map_id, category_id, score_count) ); +CREATE TRIGGER "map_history" +AFTER INSERT OR UPDATE OR DELETE ON "map_history" +FOR EACH ROW EXECUTE FUNCTION log_audit(); + CREATE TABLE map_ratings ( id SERIAL, map_id SMALLINT NOT NULL, @@ -98,6 +106,10 @@ CREATE TABLE map_discussions ( FOREIGN KEY (user_id) REFERENCES users(steam_id) ); +CREATE TRIGGER "map_discussions" +AFTER INSERT OR UPDATE OR DELETE ON "map_discussions" +FOR EACH ROW EXECUTE FUNCTION log_audit(); + CREATE TABLE map_discussions_comments ( id SERIAL, discussion_id INT NOT NULL, @@ -109,6 +121,10 @@ CREATE TABLE map_discussions_comments ( FOREIGN KEY (user_id) REFERENCES users(steam_id) ); +CREATE TRIGGER "map_discussions_comments" +AFTER INSERT OR UPDATE OR DELETE ON "map_discussions_comments" +FOR EACH ROW EXECUTE FUNCTION log_audit(); + CREATE TABLE map_discussions_upvotes ( id SERIAL, discussion_id INT NOT NULL, @@ -140,6 +156,10 @@ CREATE TABLE records_sp ( FOREIGN KEY (demo_id) REFERENCES demos(id) ); +CREATE TRIGGER "records_sp" +AFTER INSERT OR UPDATE OR DELETE ON "records_sp" +FOR EACH ROW EXECUTE FUNCTION log_audit(); + CREATE TABLE records_mp ( id SERIAL, map_id SMALLINT NOT NULL, @@ -159,6 +179,10 @@ CREATE TABLE records_mp ( FOREIGN KEY (partner_demo_id) REFERENCES demos(id) ); +CREATE TRIGGER "records_mp" +AFTER INSERT OR UPDATE OR DELETE ON "records_mp" +FOR EACH ROW EXECUTE FUNCTION log_audit(); + CREATE TABLE titles ( id SERIAL, title_name TEXT NOT NULL, @@ -179,13 +203,14 @@ CREATE TABLE countries ( PRIMARY KEY (country_code) ); -CREATE TABLE logs ( - id SERIAL, - user_id TEXT NOT NULL, - type TEXT NOT NULL, - description TEXT NOT NULL, - message TEXT NOT NULL DEFAULT, - date TIMESTAMP NOT NULL DEFAULT now(), - PRIMARY KEY (id), - FOREIGN KEY (user_id) REFERENCES users(steam_id) -); \ No newline at end of file +CREATE TABLE audit ( + id SERIAL, + table_name TEXT NOT NULL, + operation_type TEXT NOT NULL, -- 'INSERT', 'UPDATE', or 'DELETE' + old_data JSONB, + new_data JSONB, + changed_by TEXT NOT NULL, + changed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + FOREIGN KEY (changed_by) REFERENCES users(steam_id) +); diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 71bd68e..44d53e1 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -250,46 +250,6 @@ const docTemplate = `{ } } }, - "/logs/mod": { - "get": { - "description": "Get mod logs.", - "produces": [ - "application/json" - ], - "tags": [ - "logs" - ], - "parameters": [ - { - "type": "string", - "description": "JWT Token", - "name": "Authorization", - "in": "header", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/models.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/handlers.LogsResponse" - } - } - } - ] - } - } - } - } - }, "/logs/score": { "get": { "description": "Get score logs of every player.", @@ -1536,34 +1496,6 @@ const docTemplate = `{ } } }, - "handlers.LogsResponse": { - "type": "object", - "properties": { - "logs": { - "type": "array", - "items": { - "$ref": "#/definitions/handlers.LogsResponseDetails" - } - } - } - }, - "handlers.LogsResponseDetails": { - "type": "object", - "properties": { - "date": { - "type": "string" - }, - "detail": { - "type": "string" - }, - "message": { - "type": "string" - }, - "user": { - "$ref": "#/definitions/models.UserShort" - } - } - }, "handlers.MapDiscussion": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 879f35f..6c10cfc 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -244,46 +244,6 @@ } } }, - "/logs/mod": { - "get": { - "description": "Get mod logs.", - "produces": [ - "application/json" - ], - "tags": [ - "logs" - ], - "parameters": [ - { - "type": "string", - "description": "JWT Token", - "name": "Authorization", - "in": "header", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/models.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/handlers.LogsResponse" - } - } - } - ] - } - } - } - } - }, "/logs/score": { "get": { "description": "Get score logs of every player.", @@ -1530,34 +1490,6 @@ } } }, - "handlers.LogsResponse": { - "type": "object", - "properties": { - "logs": { - "type": "array", - "items": { - "$ref": "#/definitions/handlers.LogsResponseDetails" - } - } - } - }, - "handlers.LogsResponseDetails": { - "type": "object", - "properties": { - "date": { - "type": "string" - }, - "detail": { - "type": "string" - }, - "message": { - "type": "string" - }, - "user": { - "$ref": "#/definitions/models.UserShort" - } - } - }, "handlers.MapDiscussion": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 2dee421..8f33b94 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -106,24 +106,6 @@ definitions: token: type: string type: object - handlers.LogsResponse: - properties: - logs: - items: - $ref: '#/definitions/handlers.LogsResponseDetails' - type: array - type: object - handlers.LogsResponseDetails: - properties: - date: - type: string - detail: - type: string - message: - type: string - user: - $ref: '#/definitions/models.UserShort' - type: object handlers.MapDiscussion: properties: comments: @@ -690,29 +672,6 @@ paths: type: object tags: - login - /logs/mod: - get: - description: Get mod logs. - parameters: - - description: JWT Token - in: header - name: Authorization - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - allOf: - - $ref: '#/definitions/models.Response' - - properties: - data: - $ref: '#/definitions/handlers.LogsResponse' - type: object - tags: - - logs /logs/score: get: description: Get score logs of every player. diff --git a/backend/handlers/discussions.go b/backend/handlers/discussions.go index 604eb39..6267695 100644 --- a/backend/handlers/discussions.go +++ b/backend/handlers/discussions.go @@ -160,11 +160,7 @@ func CreateMapDiscussion(c *gin.Context) { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") var request CreateMapDiscussionRequest if err := c.BindJSON(&request); err != nil { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) @@ -206,11 +202,7 @@ func CreateMapDiscussionComment(c *gin.Context) { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") var request CreateMapDiscussionCommentRequest if err := c.BindJSON(&request); err != nil { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) @@ -258,11 +250,7 @@ func EditMapDiscussion(c *gin.Context) { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") var request EditMapDiscussionRequest if err := c.BindJSON(&request); err != nil { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) @@ -311,11 +299,7 @@ func DeleteMapDiscussion(c *gin.Context) { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") sql := `UPDATE map_discussions SET is_deleted = true WHERE id = $1 AND map_id = $2 AND user_id = $3` result, err := database.DB.Exec(sql, discussionID, mapID, user.(models.User).SteamID) if err != nil { diff --git a/backend/handlers/login.go b/backend/handlers/login.go index 408d950..51c90d0 100644 --- a/backend/handlers/login.go +++ b/backend/handlers/login.go @@ -40,7 +40,6 @@ func Login(c *gin.Context) { default: steamID, err := openID.ValidateAndGetID() if err != nil { - CreateLog(steamID, LogTypeUser, LogDescriptionUserLoginFailValidate, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -51,7 +50,6 @@ func Login(c *gin.Context) { if checkSteamID == 0 { user, err := GetPlayerSummaries(steamID, os.Getenv("API_KEY")) if err != nil { - CreateLog(steamID, LogTypeUser, LogDescriptionUserLoginFailSummary, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -81,7 +79,6 @@ func Login(c *gin.Context) { // Sign and get the complete encoded token as a string using the secret tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY"))) if err != nil { - CreateLog(steamID, LogTypeUser, LogDescriptionUserLoginFailToken, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse("Failed to generate token.")) return } diff --git a/backend/handlers/logs.go b/backend/handlers/logs.go index 76ddac4..693c448 100644 --- a/backend/handlers/logs.go +++ b/backend/handlers/logs.go @@ -1,7 +1,6 @@ package handlers import ( - "fmt" "net/http" "time" @@ -11,42 +10,6 @@ import ( "github.com/gin-gonic/gin" ) -const ( - LogTypeMod string = "Mod" - LogTypeUser string = "User" - LogTypeRecord string = "Record" - - LogDescriptionUserLoginSuccess string = "LoginSuccess" - LogDescriptionUserLoginFailToken string = "LoginTokenFail" - LogDescriptionUserLoginFailValidate string = "LoginValidateFail" - LogDescriptionUserLoginFailSummary string = "LoginSummaryFail" - LogDescriptionUserUpdateSuccess string = "UpdateSuccess" - LogDescriptionUserUpdateFail string = "UpdateFail" - LogDescriptionUserUpdateSummaryFail string = "UpdateSummaryFail" - LogDescriptionUserUpdateCountrySuccess string = "UpdateCountrySuccess" - LogDescriptionUserUpdateCountryFail string = "UpdateCountryFail" - - LogDescriptionMapSummaryCreateSuccess string = "MapSummaryCreateSuccess" - LogDescriptionMapSummaryCreateFail string = "MapSummaryCreateFail" - LogDescriptionMapSummaryEditSuccess string = "MapSummaryEditSuccess" - LogDescriptionMapSummaryEditFail string = "MapSummaryEditFail" - LogDescriptionMapSummaryEditImageSuccess string = "MapSummaryEditImageSuccess" - LogDescriptionMapSummaryEditImageFail string = "MapSummaryEditImageFail" - LogDescriptionMapSummaryDeleteSuccess string = "MapSummaryDeleteSuccess" - LogDescriptionMapSummaryDeleteFail string = "MapSummaryDeleteFail" - - LogDescriptionCreateRecordSuccess string = "CreateRecordSuccess" - LogDescriptionCreateRecordInsertRecordFail string = "InsertRecordFail" - LogDescriptionCreateRecordInsertDemoFail string = "InsertDemoFail" - LogDescriptionCreateRecordProcessDemoFail string = "ProcessDemoFail" - LogDescriptionCreateRecordCreateDemoFail string = "CreateDemoFail" - LogDescriptionCreateRecordOpenDemoFail string = "OpenDemoFail" - LogDescriptionCreateRecordSaveDemoFail string = "SaveDemoFail" - LogDescriptionCreateRecordInvalidRequestFail string = "InvalidRequestFail" - LogDescriptionDeleteRecordSuccess string = "DeleteRecordSuccess" - LogDescriptionDeleteRecordFail string = "DeleteRecordFail" -) - type Log struct { User models.UserShort `json:"user"` Type string `json:"type"` @@ -80,54 +43,6 @@ type ScoreLogsResponseDetails struct { Date time.Time `json:"date"` } -// GET Mod Logs -// -// @Description Get mod logs. -// @Tags logs -// @Produce json -// @Param Authorization header string true "JWT Token" -// @Success 200 {object} models.Response{data=LogsResponse} -// @Router /logs/mod [get] -func ModLogs(c *gin.Context) { - mod, exists := c.Get("mod") - if !exists || !mod.(bool) { - c.JSON(http.StatusOK, models.ErrorResponse("Insufficient permissions.")) - return - } - response := LogsResponse{Logs: []LogsResponseDetails{}} - sql := `SELECT u.user_name, l.user_id, l.type, l.description, l.message, l.date - FROM logs l INNER JOIN users u ON l.user_id = u.steam_id WHERE type != 'Score' - ORDER BY l.date DESC LIMIT 100;` - rows, err := database.DB.Query(sql) - if err != nil { - c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) - return - } - for rows.Next() { - log := Log{} - err = rows.Scan(&log.User.UserName, &log.User.SteamID, &log.Type, &log.Description, &log.Message, &log.Date) - if err != nil { - c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) - return - } - detail := fmt.Sprintf("%s.%s", log.Type, log.Description) - response.Logs = append(response.Logs, LogsResponseDetails{ - User: models.UserShort{ - SteamID: log.User.SteamID, - UserName: log.User.UserName, - }, - Log: detail, - Message: log.Message, - Date: log.Date, - }) - } - c.JSON(http.StatusOK, models.Response{ - Success: true, - Message: "Successfully retrieved logs.", - Data: response, - }) -} - // GET Score Logs // // @Description Get score logs of every player. @@ -186,16 +101,3 @@ func ScoreLogs(c *gin.Context) { Data: response, }) } - -func CreateLog(userID string, logType string, logDescription string, logMessage ...string) (err error) { - message := "-" - if len(logMessage) == 1 { - message = logMessage[0] - } - sql := `INSERT INTO logs (user_id, "type", description, message) VALUES($1, $2, $3, $4)` - _, err = database.DB.Exec(sql, userID, logType, logDescription, message) - if err != nil { - return err - } - return nil -} diff --git a/backend/handlers/mod.go b/backend/handlers/mod.go index 4fdc78a..66e1437 100644 --- a/backend/handlers/mod.go +++ b/backend/handlers/mod.go @@ -1,7 +1,6 @@ package handlers import ( - "fmt" "net/http" "strconv" "time" @@ -49,12 +48,6 @@ type EditMapImageRequest struct { // @Success 200 {object} models.Response{data=CreateMapSummaryRequest} // @Router /maps/{mapid}/summary [post] func CreateMapSummary(c *gin.Context) { - // Check if user exists - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } mod, exists := c.Get("mod") if !exists || !mod.(bool) { c.JSON(http.StatusOK, models.ErrorResponse("Insufficient permissions.")) @@ -69,7 +62,6 @@ func CreateMapSummary(c *gin.Context) { } var request CreateMapSummaryRequest if err := c.BindJSON(&request); err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryCreateFail, fmt.Sprintf("BIND: %s", err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -85,7 +77,6 @@ func CreateMapSummary(c *gin.Context) { sql := `SELECT m.id FROM maps m WHERE m.id = $1` err = database.DB.QueryRow(sql, mapID).Scan(&checkMapID) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryCreateFail, fmt.Sprintf("SELECT#maps: %s", err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -98,7 +89,6 @@ func CreateMapSummary(c *gin.Context) { VALUES ($1,$2,$3,$4,$5,$6,$7)` _, err = tx.Exec(sql, mapID, request.CategoryID, request.UserName, *request.ScoreCount, request.Description, request.Showcase, request.RecordDate) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryCreateFail, fmt.Sprintf("INSERT#map_history: %s", err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -106,7 +96,6 @@ func CreateMapSummary(c *gin.Context) { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryCreateSuccess, fmt.Sprintf("MapID: %d | CategoryID: %d | ScoreCount: %d", mapID, request.CategoryID, *request.ScoreCount)) c.JSON(http.StatusOK, models.Response{ Success: true, Message: "Successfully created map summary.", @@ -125,12 +114,6 @@ func CreateMapSummary(c *gin.Context) { // @Success 200 {object} models.Response{data=EditMapSummaryRequest} // @Router /maps/{mapid}/summary [put] func EditMapSummary(c *gin.Context) { - // Check if user exists - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } mod, exists := c.Get("mod") if !exists || !mod.(bool) { c.JSON(http.StatusOK, models.ErrorResponse("Insufficient permissions.")) @@ -146,7 +129,6 @@ func EditMapSummary(c *gin.Context) { } var request EditMapSummaryRequest if err := c.BindJSON(&request); err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryEditFail, fmt.Sprintf("BIND: %s", err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -161,7 +143,6 @@ func EditMapSummary(c *gin.Context) { sql := `UPDATE map_history SET user_name = $2, score_count = $3, record_date = $4, description = $5, showcase = $6 WHERE id = $1` _, err = tx.Exec(sql, request.RouteID, request.UserName, *request.ScoreCount, request.RecordDate, request.Description, request.Showcase) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryEditFail, fmt.Sprintf("(HistoryID: %d) UPDATE#map_history: %s", request.RouteID, err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -187,12 +168,6 @@ func EditMapSummary(c *gin.Context) { // @Success 200 {object} models.Response{data=DeleteMapSummaryRequest} // @Router /maps/{mapid}/summary [delete] func DeleteMapSummary(c *gin.Context) { - // Check if user exists - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } mod, exists := c.Get("mod") if !exists || !mod.(bool) { c.JSON(http.StatusOK, models.ErrorResponse("Insufficient permissions.")) @@ -208,7 +183,6 @@ func DeleteMapSummary(c *gin.Context) { } var request DeleteMapSummaryRequest if err := c.BindJSON(&request); err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryEditFail, fmt.Sprintf("(RouteID: %d) BIND: %s", request.RouteID, err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -223,7 +197,6 @@ func DeleteMapSummary(c *gin.Context) { sql := `DELETE FROM map_history mh WHERE mh.id = $1` _, err = tx.Exec(sql, request.RouteID) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryDeleteFail, fmt.Sprintf("(HistoryID: %d) DELETE#map_history: %s", request.RouteID, err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -249,12 +222,6 @@ func DeleteMapSummary(c *gin.Context) { // @Success 200 {object} models.Response{data=EditMapImageRequest} // @Router /maps/{mapid}/image [put] func EditMapImage(c *gin.Context) { - // Check if user exists - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } mod, exists := c.Get("mod") if !exists || !mod.(bool) { c.JSON(http.StatusOK, models.ErrorResponse("Insufficient permissions.")) @@ -269,7 +236,6 @@ func EditMapImage(c *gin.Context) { } var request EditMapImageRequest if err := c.BindJSON(&request); err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryEditImageFail, fmt.Sprintf("BIND: %s", err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -277,11 +243,9 @@ func EditMapImage(c *gin.Context) { sql := `UPDATE maps SET image = $2 WHERE id = $1` _, err = database.DB.Exec(sql, mapID, request.Image) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryEditImageFail, fmt.Sprintf("UPDATE#maps: %s", err.Error())) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - CreateLog(user.(models.User).SteamID, LogTypeMod, LogDescriptionMapSummaryEditImageSuccess) c.JSON(http.StatusOK, models.Response{ Success: true, Message: "Successfully updated map image.", diff --git a/backend/handlers/record.go b/backend/handlers/record.go index e43cc61..bedde57 100644 --- a/backend/handlers/record.go +++ b/backend/handlers/record.go @@ -53,12 +53,7 @@ func CreateRecordWithDemo(c *gin.Context) { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - // Check if user exists - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") // Check if map is sp or mp var gameName string var isCoop bool @@ -76,12 +71,10 @@ func CreateRecordWithDemo(c *gin.Context) { // Get record request var record RecordRequest if err := c.ShouldBind(&record); err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordInvalidRequestFail, "BIND: "+err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } if isCoop && record.PartnerDemo == nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordInvalidRequestFail) c.JSON(http.StatusOK, models.ErrorResponse("Missing partner demo for coop submission.")) return } @@ -112,21 +105,18 @@ func CreateRecordWithDemo(c *gin.Context) { // Upload & insert into demos err = c.SaveUploadedFile(header, "parser/"+uuid+".dem") if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordSaveDemoFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } defer os.Remove("parser/" + uuid + ".dem") f, err := os.Open("parser/" + uuid + ".dem") if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordOpenDemoFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } defer f.Close() parserResult, err := parser.ProcessDemo("parser/" + uuid + ".dem") if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordProcessDemoFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse("Error while processing demo: "+err.Error())) return } @@ -139,7 +129,6 @@ func CreateRecordWithDemo(c *gin.Context) { hostSteamID = parserResult.HostSteamID partnerSteamID = parserResult.PartnerSteamID if hostDemoScoreCount == 0 && hostDemoScoreTime == 0 { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordProcessDemoFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse("Processing demo went wrong. Please contact a web admin and provide the demo in question.")) return } @@ -161,7 +150,6 @@ func CreateRecordWithDemo(c *gin.Context) { } file, err := createFile(srv, uuid+".dem", "application/octet-stream", f, os.Getenv("GOOGLE_FOLDER_ID")) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordCreateDemoFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -177,7 +165,6 @@ func CreateRecordWithDemo(c *gin.Context) { _, err = tx.Exec(`INSERT INTO demos (id,location_id) VALUES ($1,$2)`, uuid, file.Id) if err != nil { deleteFile(srv, file.Id) - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordInsertDemoFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -229,7 +216,6 @@ func CreateRecordWithDemo(c *gin.Context) { if err != nil { deleteFile(srv, hostDemoFileID) deleteFile(srv, partnerDemoFileID) - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordInsertRecordFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -239,7 +225,6 @@ func CreateRecordWithDemo(c *gin.Context) { _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, user.(models.User).SteamID, hostDemoUUID) if err != nil { deleteFile(srv, hostDemoFileID) - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordInsertRecordFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -248,7 +233,6 @@ func CreateRecordWithDemo(c *gin.Context) { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionCreateRecordSuccess) c.JSON(http.StatusOK, models.Response{ Success: true, Message: "Successfully created record.", @@ -277,11 +261,7 @@ func DeleteRecord(c *gin.Context) { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") // Validate map var validateMapID int var isCoop bool @@ -302,12 +282,10 @@ func DeleteRecord(c *gin.Context) { sql = `SELECT mp.id FROM records_mp mp WHERE mp.id = $1 AND mp.map_id = $2 AND (mp.host_id = $3 OR mp.partner_id = $3) AND is_deleted = false` err = database.DB.QueryRow(sql, recordID, mapID, user.(models.User).SteamID).Scan(&validateRecordID) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionDeleteRecordFail, "SELECT#records_mp: "+err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } if recordID != validateRecordID { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionDeleteRecordFail, "recordID != validateRecordID") c.JSON(http.StatusOK, models.ErrorResponse("Selected record does not exist.")) return } @@ -315,7 +293,6 @@ func DeleteRecord(c *gin.Context) { sql = `UPDATE records_mp SET is_deleted = true WHERE id = $1` _, err = database.DB.Exec(sql, recordID) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionDeleteRecordFail, "UPDATE#records_mp: "+err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -325,12 +302,10 @@ func DeleteRecord(c *gin.Context) { sql = `SELECT sp.id FROM records_sp sp WHERE sp.id = $1 AND sp.map_id = $2 AND sp.user_id = $3 AND is_deleted = false` err = database.DB.QueryRow(sql, recordID, mapID, user.(models.User).SteamID).Scan(&validateRecordID) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionDeleteRecordFail, "SELECT#records_sp: "+err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } if recordID != validateRecordID { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionDeleteRecordFail, "recordID != validateRecordID") c.JSON(http.StatusOK, models.ErrorResponse("Selected record does not exist.")) return } @@ -338,12 +313,10 @@ func DeleteRecord(c *gin.Context) { sql = `UPDATE records_sp SET is_deleted = true WHERE id = $1` _, err = database.DB.Exec(sql, recordID) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionDeleteRecordFail, "UPDATE#records_sp: "+err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } } - CreateLog(user.(models.User).SteamID, LogTypeRecord, LogDescriptionDeleteRecordSuccess) c.JSON(http.StatusOK, models.Response{ Success: true, Message: "Successfully deleted record.", diff --git a/backend/handlers/user.go b/backend/handlers/user.go index 021a47f..53f0d06 100644 --- a/backend/handlers/user.go +++ b/backend/handlers/user.go @@ -69,12 +69,7 @@ type ScoreResponse struct { // @Success 200 {object} models.Response{data=ProfileResponse} // @Router /profile [get] func Profile(c *gin.Context) { - // Check if user exists - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") // Get user links links := models.Links{} sql := `SELECT u.p2sr, u.steam, u.youtube, u.twitch FROM users u WHERE u.steam_id = $1` @@ -699,15 +694,9 @@ func FetchUser(c *gin.Context) { // @Success 200 {object} models.Response{data=ProfileResponse} // @Router /profile [post] func UpdateUser(c *gin.Context) { - // Check if user exists - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") profile, err := GetPlayerSummaries(user.(models.User).SteamID, os.Getenv("API_KEY")) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeUser, LogDescriptionUserUpdateSummaryFail, err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } @@ -715,11 +704,9 @@ func UpdateUser(c *gin.Context) { sql := `UPDATE users SET user_name = $1, avatar_link = $2, country_code = $3, updated_at = $4 WHERE steam_id = $5` _, err = database.DB.Exec(sql, profile.PersonaName, profile.AvatarFull, profile.LocCountryCode, time.Now().UTC(), user.(models.User).SteamID) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeUser, LogDescriptionUserUpdateFail, "UPDATE#users: "+err.Error()) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - CreateLog(user.(models.User).SteamID, LogTypeUser, LogDescriptionUserUpdateSuccess) c.JSON(http.StatusOK, models.Response{ Success: true, Message: "Successfully updated user.", @@ -744,33 +731,24 @@ func UpdateUser(c *gin.Context) { // @Success 200 {object} models.Response // @Router /profile [put] func UpdateCountryCode(c *gin.Context) { - // Check if user exists - user, exists := c.Get("user") - if !exists { - c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) - return - } + user, _ := c.Get("user") code := c.Query("country_code") if code == "" { - CreateLog(user.(models.User).SteamID, LogTypeUser, LogDescriptionUserUpdateCountryFail) c.JSON(http.StatusOK, models.ErrorResponse("Enter a valid country code.")) return } var validCode string err := database.DB.QueryRow(`SELECT country_code FROM countries WHERE country_code = $1`, code).Scan(&validCode) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeUser, LogDescriptionUserUpdateCountryFail) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } // Valid code, update profile _, err = database.DB.Exec(`UPDATE users SET country_code = $1 WHERE steam_id = $2`, validCode, user.(models.User).SteamID) if err != nil { - CreateLog(user.(models.User).SteamID, LogTypeUser, LogDescriptionUserUpdateCountryFail) c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) return } - CreateLog(user.(models.User).SteamID, LogTypeUser, LogDescriptionUserUpdateCountrySuccess) c.JSON(http.StatusOK, models.Response{ Success: true, Message: "Successfully updated country code.", -- cgit v1.2.3