From 0a0c0317d215d504a83f03b297178fd4769bc604 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 27 Aug 2023 21:58:26 +0200 Subject: [PATCH] added permission check and swagger doc --- modules/logger/logger.go | 14 +- modules/structs/adminarea.go | 7 + modules/structs/grouptasks.go | 7 + modules/structs/log.go | 12 +- modules/structs/socket.go | 16 +- modules/structs/user.go | 10 + modules/structs/users.go | 1 + modules/utils/globals.go | 4 + public/swagger/swagger.json | 640 +++++++++++++++++ routers/router/api/v1/adminArea/roles.go | 45 ++ routers/router/api/v1/equipment/equipment.go | 17 + routers/router/api/v1/grouptask/grouptask.go | 29 + routers/router/api/v1/logger/logger.go | 35 +- routers/router/api/v1/user/profile.go | 30 +- routers/router/api/v1/user/user.go | 25 +- routers/router/api/v1/users/users.go | 21 + routers/router/router.go | 21 + socketclients/socketclients.go | 25 + socketserver/hub.go | 78 +-- system_lang_log_messages.json | 691 ++++++++++--------- 20 files changed, 1270 insertions(+), 458 deletions(-) create mode 100644 modules/structs/adminarea.go create mode 100644 routers/router/api/v1/adminArea/roles.go diff --git a/modules/logger/logger.go b/modules/logger/logger.go index 3e11e73..0c38f25 100644 --- a/modules/logger/logger.go +++ b/modules/logger/logger.go @@ -70,7 +70,7 @@ func addLog(logMessage structs.LogMessage, systemLog bool) { } } -func ReadLogs(date string, systemLogs bool, language string) []structs.LogMessageResponse { +func ReadLogs(date string, systemLogs bool, language string) []structs.LogListMessage { path := config.Cfg.FolderPaths.LogsSystem if !systemLogs { @@ -88,7 +88,7 @@ func ReadLogs(date string, systemLogs bool, language string) []structs.LogMessag fileScanner := bufio.NewScanner(file) - var logMessages []structs.LogMessageResponse + var logMessages []structs.LogListMessage for fileScanner.Scan() { var logMessage structs.LogMessage @@ -116,8 +116,8 @@ func ReadLogs(date string, systemLogs bool, language string) []structs.LogMessag return logMessages } -func getLogMessage(languageLogMessages []structs.LanguageLogMessages, logMessage structs.LogMessage, language string) structs.LogMessageResponse { - logMessageResponse := structs.LogMessageResponse{ +func getLogMessage(languageLogMessages []structs.LanguageLogMessages, logMessage structs.LogMessage, language string) structs.LogListMessage { + logListMessage := structs.LogListMessage{ Time: logMessage.Time, Type: logMessage.Type, LogData: logMessage.Messages, @@ -125,13 +125,13 @@ func getLogMessage(languageLogMessages []structs.LanguageLogMessages, logMessage for _, systemLanguageLogMessage := range languageLogMessages { if logMessage.Id == systemLanguageLogMessage.Id { - logMessageResponse.Message = getLogLanguageMessage(systemLanguageLogMessage.Languages, language) + logListMessage.Message = getLogLanguageMessage(systemLanguageLogMessage.Languages, language) - return logMessageResponse + return logListMessage } } - return logMessageResponse + return logListMessage } func getLogLanguageMessage(languages []structs.LanguageLogMessagesLanguage, language string) string { diff --git a/modules/structs/adminarea.go b/modules/structs/adminarea.go new file mode 100644 index 0000000..4c452c7 --- /dev/null +++ b/modules/structs/adminarea.go @@ -0,0 +1,7 @@ +package structs + +// swagger:model AdminAreaRolesResponse +type AdminAreaRolesResponse struct { + Roles []Role + RolesPermissions []RolePermissions +} diff --git a/modules/structs/grouptasks.go b/modules/structs/grouptasks.go index f5c14d3..80782b1 100644 --- a/modules/structs/grouptasks.go +++ b/modules/structs/grouptasks.go @@ -103,3 +103,10 @@ type ApiGroupTaskRequest struct { Description string GlobalInputs json.RawMessage } + +// swagger:model GroupTasksResponse +type GroupTasksResponse struct { + CategoryGroups []CategoryGroup + GroupTasks []GroupTasks + GroupTasksSteps []GroupTaskSteps +} diff --git a/modules/structs/log.go b/modules/structs/log.go index 7b26aa8..6d883c1 100644 --- a/modules/structs/log.go +++ b/modules/structs/log.go @@ -26,9 +26,19 @@ type LanguageLogMessagesLanguage struct { Message string } -type LogMessageResponse struct { +type LogListMessage struct { Time time.Time Type uint8 Message string LogData []LogData } + +type LogMessageResponse struct { + Logs []LogListMessage + Dates []string + Users []AllUsers + Roles []Role + CategoryGroups []CategoryGroup + GroupTasks []GroupTasks + GroupTasksSteps []GroupTaskSteps +} diff --git a/modules/structs/socket.go b/modules/structs/socket.go index 737e85c..9709d11 100644 --- a/modules/structs/socket.go +++ b/modules/structs/socket.go @@ -83,8 +83,9 @@ func (socketClient *SocketClient) writeMessage(messageType int, message SendSock return nil } +/* type InitUserSocketConnection struct { - User UserData + User UserProfile CategoryGroups []CategoryGroup // config specific group tasks GroupTasks []GroupTasks // in database saved group tasks GroupTasksSteps []GroupTaskSteps @@ -92,7 +93,7 @@ type InitUserSocketConnection struct { Scanners []Scanner AllRoles []Role AdminArea InitUserSocketConnectionAdminArea -} +} */ type InitUserSocketConnectionAdminArea struct { RolesPermissions []RolePermissions `json:"RolesPermissions,omitempty"` @@ -108,17 +109,6 @@ type AllUsers struct { LastOnline time.Time } -type UserData struct { - Id string - RoleId string - Avatar string - Username string - Email string - Sessions []UserSessionSocket - Permissions []string - ApiKeys []UserApiKey -} - type UserSessionSocket struct { IdForDeletion string UserAgent string diff --git a/modules/structs/user.go b/modules/structs/user.go index 71cd38c..e27b29e 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -56,9 +56,19 @@ type UserApiKey struct { LastUsed time.Time } +// swagger:model UserInfoResponse type UserInfoResponse struct { + UserId string Username string Avatar string Permissions []string AvailableGroupTasks []string + Users []AllUsers +} + +// swagger:model UserProfileResponse +type UserProfileResponse struct { + Email string + Sessions []UserSessionSocket + ApiKeys []UserApiKey } diff --git a/modules/structs/users.go b/modules/structs/users.go index a4e3a9c..39abfc8 100644 --- a/modules/structs/users.go +++ b/modules/structs/users.go @@ -1,5 +1,6 @@ package structs +// swagger:model UsersResponse type UsersResponse struct { RoleId string Users []AllUsers diff --git a/modules/utils/globals.go b/modules/utils/globals.go index c469d1a..2bc1ade 100644 --- a/modules/utils/globals.go +++ b/modules/utils/globals.go @@ -145,6 +145,8 @@ var ( const ( _equipmentDocumentation = "equipment_documentation." PermissionEquipmentDocumentationView = _equipmentDocumentation + "view" + PermissionEquipmentDocumentationEdit = _equipmentDocumentation + "edit" + PermissionEquipmentDocumentationCreate = _equipmentDocumentation + "create" _groupTasks = "group_tasks." PermissionGroupTasksOverviewXYNewTask = _groupTasks + "overview.XY.new_task" PermissionGroupTasksOverviewXYReloadGroupConfig = _groupTasks + "overview.XY.reload_group_config" @@ -172,6 +174,8 @@ const ( var SystemPermissions = []string{ PermissionEquipmentDocumentationView, + PermissionEquipmentDocumentationEdit, + PermissionEquipmentDocumentationCreate, PermissionGroupTasksHistory, PermissionGroupTasksInstallGlobalPythonPackages, PermissionGroupTasksCheckingForCategoryGroupChanges, diff --git a/public/swagger/swagger.json b/public/swagger/swagger.json index 65281b7..370d5ec 100644 --- a/public/swagger/swagger.json +++ b/public/swagger/swagger.json @@ -16,6 +16,42 @@ "host": "jannex", "basePath": "/v1", "paths": { + "/adminarea/roles": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "adminarea" + ], + "summary": "Get all roles", + "operationId": "adminareaGetRoles", + "parameters": [ + { + "description": "You can create a new api key in your user profile", + "name": "X-Api-Key", + "in": "header" + } + ], + "responses": { + "200": { + "description": "All roles", + "schema": { + "$ref": "#/definitions/AdminAreaRolesResponse" + } + }, + "401": { + "description": "No permissions" + }, + "500": { + "description": "Failed to get roles" + } + } + } + }, "/equipment/documentation/create": { "post": { "consumes": [ @@ -239,6 +275,42 @@ } } }, + "/grouptasks": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "grouptask" + ], + "summary": "Get all group tasks", + "operationId": "grouptaskGetGroupTasks", + "parameters": [ + { + "description": "You can create a new api key in your user profile", + "name": "X-Api-Key", + "in": "header" + } + ], + "responses": { + "200": { + "description": "All group tasks", + "schema": { + "$ref": "#/definitions/GroupTasksResponse" + } + }, + "401": { + "description": "No permissions" + }, + "500": { + "description": "Failed to get group tasks" + } + } + } + }, "/grouptasks/start": { "post": { "consumes": [ @@ -404,6 +476,78 @@ } } }, + "/user/info": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get user info", + "operationId": "userInfo", + "parameters": [ + { + "description": "You can create a new api key in your user profile", + "name": "X-Api-Key", + "in": "header" + } + ], + "responses": { + "200": { + "description": "User info", + "schema": { + "$ref": "#/definitions/UserInfoResponse" + } + }, + "401": { + "description": "No permissions" + }, + "500": { + "description": "Failed to get user info" + } + } + } + }, + "/user/profile": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get user profile", + "operationId": "userProfile", + "parameters": [ + { + "description": "You can create a new api key in your user profile", + "name": "X-Api-Key", + "in": "header" + } + ], + "responses": { + "200": { + "description": "User profile", + "schema": { + "$ref": "#/definitions/UserProfileResponse" + } + }, + "401": { + "description": "No permissions" + }, + "500": { + "description": "Failed to get user profile" + } + } + } + }, "/user/session/{idForDeletion}": { "delete": { "consumes": [ @@ -444,9 +588,92 @@ } } } + }, + "/users": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Get all users", + "operationId": "usersGetUsers", + "parameters": [ + { + "description": "You can create a new api key in your user profile", + "name": "X-Api-Key", + "in": "header" + } + ], + "responses": { + "200": { + "description": "All users", + "schema": { + "$ref": "#/definitions/UsersResponse" + } + }, + "401": { + "description": "No permissions" + }, + "500": { + "description": "Failed to get users" + } + } + } } }, "definitions": { + "AdminAreaRolesResponse": { + "type": "object", + "properties": { + "Roles": { + "type": "array", + "items": { + "$ref": "#/definitions/Role" + } + }, + "RolesPermissions": { + "type": "array", + "items": { + "$ref": "#/definitions/RolePermissions" + } + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "AllUsers": { + "type": "object", + "properties": { + "Avatar": { + "type": "string" + }, + "ConnectionStatus": { + "type": "integer", + "format": "uint8" + }, + "Deactivated": { + "type": "boolean" + }, + "Id": { + "type": "string" + }, + "LastOnline": { + "type": "string", + "format": "date-time" + }, + "RoleId": { + "type": "string" + }, + "Username": { + "type": "string" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, "ApiGroupTaskRequest": { "type": "object", "properties": { @@ -465,6 +692,23 @@ }, "x-go-package": "jannex/admin-dashboard-backend/modules/structs" }, + "CategoryGroup": { + "type": "object", + "properties": { + "category": { + "type": "string", + "x-go-name": "Category" + }, + "groups": { + "type": "array", + "items": { + "$ref": "#/definitions/Group" + }, + "x-go-name": "Groups" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, "CreateEquipmentDocumentationRequest": { "type": "object", "properties": { @@ -560,6 +804,336 @@ }, "x-go-package": "jannex/admin-dashboard-backend/modules/structs" }, + "GlobalInputs": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "x-go-name": "DisplayName" + }, + "parameterName": { + "type": "string", + "x-go-name": "ParameterName" + }, + "type": { + "type": "string", + "x-go-name": "Type" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "Group": { + "type": "object", + "properties": { + "category": { + "type": "string", + "x-go-name": "Category" + }, + "globalInputs": { + "type": "array", + "items": { + "$ref": "#/definitions/GlobalInputs" + }, + "x-go-name": "GlobalInputs" + }, + "id": { + "type": "string", + "x-go-name": "Id" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "tasks": { + "type": "array", + "items": { + "$ref": "#/definitions/Task" + }, + "x-go-name": "Tasks" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "GroupTaskSteps": { + "type": "object", + "properties": { + "CreatorUserId": { + "type": "string" + }, + "EndedAt": { + "type": "string", + "format": "date-time" + }, + "Files": { + "type": "string" + }, + "GroupTasksId": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "Inputs": { + "type": "string" + }, + "LockedByUserId": { + "type": "string" + }, + "Log": { + "type": "string" + }, + "StartedAt": { + "type": "string", + "format": "date-time" + }, + "Status": { + "type": "integer", + "format": "uint8" + }, + "Step": { + "type": "integer", + "format": "uint8" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "GroupTasks": { + "type": "object", + "properties": { + "Category": { + "type": "string" + }, + "CreatorUserId": { + "type": "string" + }, + "CurrentTasksStep": { + "type": "integer", + "format": "uint8" + }, + "Description": { + "type": "string" + }, + "EndedAt": { + "type": "string", + "format": "date-time" + }, + "GlobalInputs": { + "type": "string" + }, + "GroupId": { + "type": "string" + }, + "GroupName": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "NumberOfSteps": { + "type": "integer", + "format": "uint8" + }, + "RememberId": { + "type": "string" + }, + "StartedAt": { + "type": "string", + "format": "date-time" + }, + "Status": { + "type": "integer", + "format": "uint8" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "GroupTasksResponse": { + "type": "object", + "properties": { + "CategoryGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/CategoryGroup" + } + }, + "GroupTasks": { + "type": "array", + "items": { + "$ref": "#/definitions/GroupTasks" + } + }, + "GroupTasksSteps": { + "type": "array", + "items": { + "$ref": "#/definitions/GroupTaskSteps" + } + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "Role": { + "type": "object", + "properties": { + "CreatedAt": { + "type": "string", + "format": "date-time" + }, + "Description": { + "type": "string" + }, + "DisplayName": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "Master": { + "type": "boolean" + }, + "SortingOrder": { + "type": "integer", + "format": "int64" + }, + "UpdatedAt": { + "type": "string", + "format": "date-time" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "RolePermissions": { + "type": "object", + "properties": { + "Permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "RoleId": { + "type": "string" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "Task": { + "type": "object", + "properties": { + "name": { + "type": "string", + "x-go-name": "Name" + }, + "onFinish": { + "type": "string", + "x-go-name": "OnFinish" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/definitions/TaskParameter" + }, + "x-go-name": "Parameters" + }, + "repeatPossible": { + "type": "boolean", + "x-go-name": "RepeatPossible" + }, + "scriptPath": { + "type": "string", + "x-go-name": "ScriptPath" + }, + "undoPossible": { + "type": "boolean", + "x-go-name": "UndoPossible" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "TaskParameter": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "x-go-name": "DisplayName" + }, + "global": { + "type": "boolean", + "x-go-name": "Global" + }, + "parameterName": { + "type": "string", + "x-go-name": "ParameterName" + }, + "type": { + "type": "string", + "x-go-name": "Type" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "UserApiKey": { + "type": "object", + "properties": { + "CreatedAt": { + "type": "string", + "format": "date-time" + }, + "Id": { + "type": "string" + }, + "LastUsed": { + "type": "string", + "format": "date-time" + }, + "Name": { + "type": "string" + }, + "Token": { + "type": "string" + }, + "UsageCount": { + "type": "integer", + "format": "uint64" + }, + "UserId": { + "type": "string" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "UserInfoResponse": { + "type": "object", + "properties": { + "AvailableGroupTasks": { + "type": "array", + "items": { + "type": "string" + } + }, + "Avatar": { + "type": "string" + }, + "Permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "UserId": { + "type": "string" + }, + "Username": { + "type": "string" + }, + "Users": { + "type": "array", + "items": { + "$ref": "#/definitions/AllUsers" + } + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, "UserLoginRequest": { "type": "object", "properties": { @@ -580,6 +1154,72 @@ } }, "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "UserProfileResponse": { + "type": "object", + "properties": { + "ApiKeys": { + "type": "array", + "items": { + "$ref": "#/definitions/UserApiKey" + } + }, + "Email": { + "type": "string" + }, + "Sessions": { + "type": "array", + "items": { + "$ref": "#/definitions/UserSessionSocket" + } + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "UserSessionSocket": { + "type": "object", + "properties": { + "ConnectionStatus": { + "type": "integer", + "format": "uint8" + }, + "ExpiresAt": { + "type": "string", + "format": "date-time" + }, + "IdForDeletion": { + "type": "string" + }, + "LastUsed": { + "type": "string", + "format": "date-time" + }, + "UserAgent": { + "type": "string" + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" + }, + "UsersResponse": { + "type": "object", + "properties": { + "RoleId": { + "type": "string" + }, + "Roles": { + "type": "array", + "items": { + "$ref": "#/definitions/Role" + } + }, + "Users": { + "type": "array", + "items": { + "$ref": "#/definitions/AllUsers" + } + } + }, + "x-go-package": "jannex/admin-dashboard-backend/modules/structs" } } } \ No newline at end of file diff --git a/routers/router/api/v1/adminArea/roles.go b/routers/router/api/v1/adminArea/roles.go new file mode 100644 index 0000000..1dd8fb0 --- /dev/null +++ b/routers/router/api/v1/adminArea/roles.go @@ -0,0 +1,45 @@ +package adminarea + +import ( + "jannex/admin-dashboard-backend/modules/structs" + "jannex/admin-dashboard-backend/modules/utils" + "jannex/admin-dashboard-backend/socketclients" + + "github.com/gofiber/fiber/v2" +) + +func GetRoles(c *fiber.Ctx) error { + // swagger:operation GET /adminarea/roles adminarea adminareaGetRoles + // --- + // summary: Get all roles + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: X-Api-Key + // in: header + // description: You can create a new api key in your user profile + // responses: + // '200': + // description: All roles + // schema: + // "$ref": "#/definitions/AdminAreaRolesResponse" + // '401': + // description: No permissions + // '500': + // description: Failed to get roles + + if !socketclients.HasOnePermission(c.Locals("userId").(string), + []string{utils.PermissionAdminAreaCreateNewRole, + utils.PermissionAdminAreaDeleteRole, + utils.PermissionAdminAreaMoveRoleUpDown, + utils.PermissionAdminAreaUpdateRole}) { + return c.SendStatus(fiber.StatusUnauthorized) + } + + return c.JSON(structs.AdminAreaRolesResponse{ + Roles: socketclients.GetAllRoles(), + RolesPermissions: socketclients.GetAdminAreaRolesPermissions(), + }) +} diff --git a/routers/router/api/v1/equipment/equipment.go b/routers/router/api/v1/equipment/equipment.go index 2b55953..ae3b670 100644 --- a/routers/router/api/v1/equipment/equipment.go +++ b/routers/router/api/v1/equipment/equipment.go @@ -4,6 +4,7 @@ import ( "jannex/admin-dashboard-backend/modules/equipment" "jannex/admin-dashboard-backend/modules/structs" "jannex/admin-dashboard-backend/modules/utils" + "jannex/admin-dashboard-backend/socketclients" "github.com/gofiber/fiber/v2" ) @@ -34,6 +35,10 @@ func CreateEquipmentDocumentation(c *fiber.Ctx) error { // '500': // description: Failed to create equipment documentation + if !socketclients.HasPermission(c.Locals("userId").(string), utils.PermissionEquipmentDocumentationCreate) { + return c.SendStatus(fiber.StatusUnauthorized) + } + var body structs.CreateEquipmentDocumentationRequest if err := utils.BodyParserHelper(c, &body); err != nil { @@ -72,6 +77,10 @@ func GetEquipmentDocumentations(c *fiber.Ctx) error { // '500': // description: Failed to get equipment documentations + if !socketclients.HasPermission(c.Locals("userId").(string), utils.PermissionEquipmentDocumentationView) { + return c.SendStatus(fiber.StatusUnauthorized) + } + var params structs.EquipmentRequest if err := utils.ParamsParserHelper(c, ¶ms); err != nil { @@ -115,6 +124,10 @@ func GetEquipmentDocumentation(c *fiber.Ctx) error { // '500': // description: Failed to get equipment documentation + if !socketclients.HasPermission(c.Locals("userId").(string), utils.PermissionEquipmentDocumentationView) { + return c.SendStatus(fiber.StatusUnauthorized) + } + var params structs.GetDocumentationEquipmentRequest if err := utils.ParamsParserHelper(c, ¶ms); err != nil { @@ -150,6 +163,10 @@ func EditEquipmentDocumentation(c *fiber.Ctx) error { // '500': // description: Failed to edit equipment documentation + if !socketclients.HasPermission(c.Locals("userId").(string), utils.PermissionEquipmentDocumentationEdit) { + return c.SendStatus(fiber.StatusUnauthorized) + } + var body structs.EditEquipmentDocumentationRequest if err := utils.BodyParserHelper(c, &body); err != nil { diff --git a/routers/router/api/v1/grouptask/grouptask.go b/routers/router/api/v1/grouptask/grouptask.go index 5aaa4df..c12a5cc 100644 --- a/routers/router/api/v1/grouptask/grouptask.go +++ b/routers/router/api/v1/grouptask/grouptask.go @@ -12,6 +12,35 @@ import ( "github.com/rs/zerolog/log" ) +func GetGroupTasks(c *fiber.Ctx) error { + // swagger:operation GET /grouptasks grouptask grouptaskGetGroupTasks + // --- + // summary: Get all group tasks + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: X-Api-Key + // in: header + // description: You can create a new api key in your user profile + // responses: + // '200': + // description: All group tasks + // schema: + // "$ref": "#/definitions/GroupTasksResponse" + // '401': + // description: No permissions + // '500': + // description: Failed to get group tasks + + return c.JSON(structs.GroupTasksResponse{ + CategoryGroups: cache.GetCategoryGroupsSorted(), + GroupTasks: grouptasks.GetAllGroupTasks(), + GroupTasksSteps: grouptasks.GetAllGroupTasksSteps(), + }) +} + func StartGroupTask(c *fiber.Ctx) error { // swagger:operation POST /grouptasks/start grouptask grouptaskStartGroupTask // --- diff --git a/routers/router/api/v1/logger/logger.go b/routers/router/api/v1/logger/logger.go index 34ed8ad..743943b 100644 --- a/routers/router/api/v1/logger/logger.go +++ b/routers/router/api/v1/logger/logger.go @@ -1,6 +1,8 @@ package logger import ( + "jannex/admin-dashboard-backend/modules/cache" + "jannex/admin-dashboard-backend/modules/grouptasks" "jannex/admin-dashboard-backend/modules/logger" "jannex/admin-dashboard-backend/modules/structs" "jannex/admin-dashboard-backend/modules/utils" @@ -38,22 +40,29 @@ func GetSystemLog(c *fiber.Ctx) error { }, }) - if t == "g" { // grouptasks logs - return c.JSON(struct { - Logs []structs.LogMessageResponse - Dates []string - }{ - Logs: logger.ReadLogs(date, false, lang), - Dates: logger.GetAllLogMessagesDates(false), + // / TODO: remove this by rendering the log message on backend site + + if t == "g" { + // grouptasks logs + return c.JSON(structs.LogMessageResponse{ + Logs: logger.ReadLogs(date, false, lang), + Dates: logger.GetAllLogMessagesDates(false), + Users: socketclients.GetAllUsers(), + Roles: socketclients.GetAllRoles(), + CategoryGroups: cache.GetCategoryGroupsSorted(), + GroupTasks: grouptasks.GetAllGroupTasks(), + GroupTasksSteps: grouptasks.GetAllGroupTasksSteps(), }) } // system logs - return c.JSON(struct { - Logs []structs.LogMessageResponse - Dates []string - }{ - Logs: logger.ReadLogs(date, true, lang), - Dates: logger.GetAllLogMessagesDates(true), + return c.JSON(structs.LogMessageResponse{ + Logs: logger.ReadLogs(date, true, lang), + Dates: logger.GetAllLogMessagesDates(true), + Users: socketclients.GetAllUsers(), + Roles: socketclients.GetAllRoles(), + CategoryGroups: cache.GetCategoryGroupsSorted(), + GroupTasks: grouptasks.GetAllGroupTasks(), + GroupTasksSteps: grouptasks.GetAllGroupTasksSteps(), }) } diff --git a/routers/router/api/v1/user/profile.go b/routers/router/api/v1/user/profile.go index 391442f..35ff060 100644 --- a/routers/router/api/v1/user/profile.go +++ b/routers/router/api/v1/user/profile.go @@ -9,19 +9,35 @@ import ( ) func GetUserProfile(c *fiber.Ctx) error { + // swagger:operation GET /user/profile user userProfile + // --- + // summary: Get user profile + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: X-Api-Key + // in: header + // description: You can create a new api key in your user profile + // responses: + // '200': + // description: User profile + // schema: + // "$ref": "#/definitions/UserProfileResponse" + // '401': + // description: No permissions + // '500': + // description: Failed to get user profile + var user structs.User userId := c.Locals("userId").(string) database.DB.First(&user, "id = ?", userId) - return c.JSON(structs.UserData{ - Id: user.Id, - //RoleId: user.RoleId, - Avatar: user.Avatar, - Username: user.Username, + return c.JSON(structs.UserProfileResponse{ Email: user.Email, Sessions: socketclients.GetUserSessions(userId), - //Permissions: socketclients.GetPermissionsByRoleId(user.RoleId), - ApiKeys: socketclients.GetUserApiKeys(userId), + ApiKeys: socketclients.GetUserApiKeys(userId), }) } diff --git a/routers/router/api/v1/user/user.go b/routers/router/api/v1/user/user.go index cf1e550..384a930 100644 --- a/routers/router/api/v1/user/user.go +++ b/routers/router/api/v1/user/user.go @@ -9,14 +9,37 @@ import ( ) func UserInfo(c *fiber.Ctx) error { + // swagger:operation GET /user/info user userInfo + // --- + // summary: Get user info + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: X-Api-Key + // in: header + // description: You can create a new api key in your user profile + // responses: + // '200': + // description: User info + // schema: + // "$ref": "#/definitions/UserInfoResponse" + // '401': + // description: No permissions + // '500': + // description: Failed to get user info + var user structs.User - database.DB.First(&user, "id = ?", c.Locals("userId").(string)) + database.DB.Select("id, username, avatar", "role_id").First(&user, "id = ?", c.Locals("userId").(string)) return c.JSON(structs.UserInfoResponse{ + UserId: user.Id, Username: user.Username, Avatar: user.Avatar, Permissions: socketclients.GetPermissionsByRoleId(user.RoleId), AvailableGroupTasks: []string{}, + Users: socketclients.GetAllUsers(), }) } diff --git a/routers/router/api/v1/users/users.go b/routers/router/api/v1/users/users.go index 48b9eaa..ef56c50 100644 --- a/routers/router/api/v1/users/users.go +++ b/routers/router/api/v1/users/users.go @@ -9,6 +9,27 @@ import ( ) func GetUsers(c *fiber.Ctx) error { + // swagger:operation GET /users users usersGetUsers + // --- + // summary: Get all users + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: X-Api-Key + // in: header + // description: You can create a new api key in your user profile + // responses: + // '200': + // description: All users + // schema: + // "$ref": "#/definitions/UsersResponse" + // '401': + // description: No permissions + // '500': + // description: Failed to get users + var user structs.User database.DB.Select("role_id").First(&user, "id = ?", c.Locals("userId").(string)) diff --git a/routers/router/router.go b/routers/router/router.go index 157240f..f03164f 100644 --- a/routers/router/router.go +++ b/routers/router/router.go @@ -6,6 +6,7 @@ import ( "jannex/admin-dashboard-backend/modules/logger" "jannex/admin-dashboard-backend/modules/structs" "jannex/admin-dashboard-backend/modules/utils" + adminarea "jannex/admin-dashboard-backend/routers/router/api/v1/adminArea" "jannex/admin-dashboard-backend/routers/router/api/v1/equipment" "jannex/admin-dashboard-backend/routers/router/api/v1/grouptask" log "jannex/admin-dashboard-backend/routers/router/api/v1/logger" @@ -41,6 +42,7 @@ func SetupRoutes(app *fiber.App) { l.Get("/", requestAccessValidation, log.GetSystemLog) g := v1.Group("/grouptasks") + g.Get("/", requestAccessValidation, grouptask.GetGroupTasks) g.Post("/start", requestAccessValidation, grouptask.StartGroupTask) e := v1.Group("/equipment") @@ -50,6 +52,9 @@ func SetupRoutes(app *fiber.App) { e.Post("/documentation/edit", requestAccessValidation, equipment.EditEquipmentDocumentation) e.Get("/thumbnail/:stockItemId", equipment.GetEquipmentThumbnail) + a := v1.Group("/adminarea") + a.Get("/roles", requestAccessValidation, adminarea.GetRoles) + app.Static("/", config.Cfg.FolderPaths.PublicStatic) } @@ -86,6 +91,22 @@ func userApikeyTokenValidation(c *fiber.Ctx) error { return fiber.ErrUnauthorized } + // check if user has permission to use api keys + if !socketclients.HasPermission(apiKey.UserId, utils.PermissionUserProfileApiKeys) { + // delete api key from database + database.DB.Delete(&apiKey) + + logger.AddSystemLog(structs.LogMessage{ + Id: 26, + Type: utils.LogTypeInfo, + Messages: []structs.LogData{ + {Type: "userId", Value: apiKey.UserId}, + }, + }) + + return fiber.ErrUnauthorized + } + lastUsed := time.Now() database.DB.Model(&structs.UserApiKey{}).Where("id = ?", apiKey.Id).Updates(map[string]interface{}{ diff --git a/socketclients/socketclients.go b/socketclients/socketclients.go index 2d52921..50fa3bd 100644 --- a/socketclients/socketclients.go +++ b/socketclients/socketclients.go @@ -714,6 +714,31 @@ func HasPermission(userId string, permission string) bool { return rolePermission.PermissionId == permission } +// This function is used to check if a user has one of the provided permissions +// For example if a user has one of the permissions "user.create" or "user.delete" +func HasOnePermission(userId string, permissions []string) bool { + var user structs.User + + database.DB.Where("id = ?", userId).First(&user) + + var userPermissions []structs.RolePermission + + database.DB.Where("role_id = ?", user.RoleId).Find(&userPermissions) + + // loop through all provided permissions + for _, permission := range permissions { + // loop through all user permissions + for _, userPermission := range userPermissions { + // check if the user has the permission + if userPermission.PermissionId == permission { + return true + } + } + } + + return false +} + func HasXYPermission(userId string, permission string, category string) bool { return HasPermission(userId, systempermissions.ConvertXYPermission(permission, category)) } diff --git a/socketserver/hub.go b/socketserver/hub.go index bb9f2c9..c034cc8 100644 --- a/socketserver/hub.go +++ b/socketserver/hub.go @@ -57,35 +57,6 @@ func RunHub() { ExpiresAt: utils.GetSessionExpiresAtTime(), }) - // init data message - var user structs.User - - database.DB.First(&user, "id = ?", userId) - - newSocketClient.SendMessage(structs.SendSocketMessage{ - Cmd: utils.SentCmdInitUserSocketConnection, - Body: structs.InitUserSocketConnection{ - User: structs.UserData{ - Id: user.Id, - RoleId: user.RoleId, - Username: user.Username, - Email: user.Email, - Sessions: socketclients.GetUserSessions(userId), - Permissions: socketclients.GetPermissionsByRoleId(user.RoleId), - ApiKeys: socketclients.GetUserApiKeys(userId), - }, - CategoryGroups: cache.GetCategoryGroupsSorted(), - GroupTasks: grouptasks.GetAllGroupTasks(), - GroupTasksSteps: grouptasks.GetAllGroupTasksSteps(), - AllUsers: socketclients.GetAllUsers(), - Scanners: socketclients.GetAllScanners(), - AllRoles: socketclients.GetAllRoles(), - AdminArea: structs.InitUserSocketConnectionAdminArea{ - RolesPermissions: socketclients.GetAdminAreaRolesPermissions(), - }, - }, - }) - socketclients.UpdateConnectedUsers(userId) socketclients.UpdateUserSessionsForUser(userId, sessionId) @@ -93,7 +64,7 @@ func RunHub() { Id: 0, Type: utils.LogTypeInfo, Messages: []structs.LogData{ - {Type: "userId", Value: user.Id}}}) + {Type: "userId", Value: userId}}}) case data := <-broadcast: var receivedMessage structs.ReceivedMessage @@ -128,53 +99,6 @@ func RunHub() { GlobalInputs: globalInputsJsonString, RememberId: receivedMessage.Body["rememberId"].(string), }) - - //groupTaskId := uuid.New().String() - - /* - groupTasks := structs.GroupTasks{ - Id: groupTaskId, - CreatorUserId: data.Conn.Locals("userId").(string), - Category: category, - GroupId: groupId, - GroupName: receivedMessage.Body["groupName"].(string), - Description: receivedMessage.Body["description"].(string), - CurrentTasksStep: 1, - NumberOfSteps: uint8(receivedMessage.Body["numberOfSteps"].(float64)), - Status: utils.GroupTasksStatusRunning, - GlobalInputs: globalInputsJsonString, - StartedAt: time.Now(), - RememberId: receivedMessage.Body["rememberId"].(string), - } - - database.DB.Create(&groupTasks) - - socketclients.BroadcastMessage(structs.SendSocketMessage{ - Cmd: utils.SentCmdNewGroupTaskStarted, - Body: groupTasks, - }) - - go grouptasks.RunGroupTask(grouptasks.RunGroupTaskArgs{ - CreatorUserId: data.Conn.Locals("userId").(string), - StartType: grouptasks.RunGroupTaskStartTypeNormal, - GroupTaskId: groupTaskId, - Category: category, - GroupId: groupId, - Step: 1, - TaskStepId: "", - GlobalInputs: globalInputsJsonString, - }) - - logger.AddGroupTasksLog(structs.LogMessage{ - Id: 0, - Type: utils.LogTypeInfo, - Messages: []structs.LogData{ - {Type: "userId", Value: data.Conn.Locals("userId").(string)}, - {Type: "groupTaskId", Value: groupTaskId}, - {Type: "groupTaskName", Value: groupTasks.GroupName}, - }, - }) */ - break case utils.ReceivedCmdTaskFailedTryAgainRunTaskStep: groupTaskArgs := grouptasks.RunGroupTaskArgs{ diff --git a/system_lang_log_messages.json b/system_lang_log_messages.json index 92ea810..b8a85e0 100644 --- a/system_lang_log_messages.json +++ b/system_lang_log_messages.json @@ -1,340 +1,353 @@ [ - { - "id": 0, - "languages": [ - { - "lang": "en", - "message": "%userId% has come online" - }, - { - "lang": "de", - "message": "%userId% ist online gegangen" - } - ] - }, - { - "id": 1, - "languages": [ - { - "lang": "en", - "message": "%userId% has created the role %roleId%" - }, - { - "lang": "de", - "message": "%userId% hat die Rolle %roleId% erstellt" - } - ] - }, - { - "id": 2, - "languages": [ - { - "lang": "en", - "message": "%userId% has updated the role %roleId% with the following changes: %changes%" - }, - { - "lang": "de", - "message": "%userId% hat die Rolle %roleId% mit den folgenden Änderungen bearbeitet: %changes%" - } - ] - }, - { - "id": 3, - "languages": [ - { - "lang": "en", - "message": "%userId% has changed the sorting order of role %roleId% to %sortingOrder%" - }, - { - "lang": "de", - "message": "%userId% hat die Sortierung der Rolle %roleId% zu %sortingOrder% geändert" - } - ] - }, - { - "id": 4, - "languages": [ - { - "lang": "en", - "message": "%userId% has deleted the role %roleId%" - }, - { - "lang": "de", - "message": "%userId% hat die Rolle %roleId% gelöscht" - } - ] - }, - { - "id": 5, - "languages": [ - { - "lang": "en", - "message": "%userId% has assigned the role %roleId% to %targetUserId%" - }, - { - "lang": "de", - "message": "%userId% hat die Rolle %roleId% dem Benutzer %targetUserId% zugewiesen" - } - ] - }, - { - "id": 6, - "languages": [ - { - "lang": "en", - "message": "%userId% has created the user %targetUserId% with the assigned role %roleId%" - }, - { - "lang": "de", - "message": "%userId% hat den Benutzer %targetUserId% mit der zugewiesenen Rolle %roleId% erstellt" - } - ] - }, - { - "id": 7, - "languages": [ - { - "lang": "en", - "message": "%userId% has deleted the user %targetUserId%" - }, - { - "lang": "de", - "message": "%userId% hat den Benutzer %targetUserId% gelöscht" - } - ] - }, - { - "id": 8, - "languages": [ - { - "lang": "en", - "message": "--------- SERVER has started ---------" - }, - { - "lang": "de", - "message": "--------- SERVER gestartet ---------" - } - ] - }, - { - "id": 9, - "languages": [ - { - "lang": "en", - "message": "%userId% has changed the deactivation status of %targetUserId% to %deactivated%" - }, - { - "lang": "de", - "message": "%userId% hat den Deaktivierungsstatus von dem Benutzer %targetUserId% zu %deactivated% aktualisiert" - } - ] - }, - { - "id": 10, - "languages": [ - { - "lang": "en", - "message": "Scanner %scannerId% is now used by %userId%" - }, - { - "lang": "en", - "message": "Scanner %scannerId% ist nun in Verwendung von %userId%" - } - ] - }, - { - "id": 11, - "languages": [ - { - "lang": "en", - "message": "Scanner %scannerId% is not longer used" - }, - { - "lang": "de", - "message": "Scanner %scannerId% ist nicht länger in Verwendung" - } - ] - }, - { - "id": 12, - "languages": [ - { - "lang": "en", - "message": "%userId% has changed his password" - }, - { - "lang": "de", - "message": "%userId% hat sein Passwort geändert" - } - ] - }, - { - "id": 13, - "languages": [ - { - "lang": "en", - "message": "%userId% has updated his account with the following changes: %changes%" - }, - { - "lang": "de", - "message": "%userId% hat seinen Account mit folgenden Veränderungen aktualisiert: %changes%" - } - ] - }, - { - "id": 14, - "languages": [ - { - "lang": "en", - "message": "Scanner %scannerId% %scannerName% was registered" - }, - { - "lang": "de", - "message": "Scanner %scannerId% %scannerName% wurde registriert" - } - ] - }, - { - "id": 15, - "languages": [ - { - "lang": "en", - "message": "Scanner %scannerId% has scanned %scanResult%" - }, - { - "lang": "de", - "message": "Scanner %scannerId% hat %scanResult% gescannt" - } - ] - }, - { - "id": 16, - "languages": [ - { - "lang": "en", - "message": "Scanner %scannerId% was deleted" - }, - { - "lang": "de", - "message": "Scanner %scannerId% wurde gelöscht" - } - ] - }, - { - "id": 17, - "languages": [ - { - "lang": "en", - "message": "%userId% has viewed %logType% logs of the date %logDate%" - }, - { - "lang": "de", - "message": "%userId% hat sich die Logs %logType% vom %logDate% angesehen" - } - ] - }, - { - "id": 18, - "languages": [ - { - "lang": "en", - "message": "%userId% has gone offline" - }, - { - "lang": "de", - "message": "%userId% ist offline gegangen" - } - ] - }, - { - "id": 19, - "languages": [ - { - "lang": "en", - "message": "%userId% has logged in" - }, - { - "lang": "de", - "message": "%userId% hat sich eingeloggt" - } - ] - }, - { - "id": 20, - "languages": [ - { - "lang": "en", - "message": "%userId% has logged out" - }, - { - "lang": "de", - "message": "%userId% hat sich abgemeldet" - } - ] - }, - { - "id": 21, - "languages": [ - { - "lang": "en", - "message": "%userId% has changed his avatar" - }, - { - "lang": "de", - "message": "%userId% hat sein Profilbild aktualisiert" - } - ] - }, - { - "id": 22, - "languages": [ - { - "lang": "en", - "message": "%userId% has logged out one of his account sessions" - }, - { - "lang": "de", - "message": "%userId% hat eine seiner Sitzungen ausgeloggt" - } - ] - }, - { - "id": 23, - "languages": [ - { - "lang": "en", - "message": "%userId% has created an API key" - }, - { - "lang": "de", - "message": "%userId% hat einen API-Schlüssel erstellt" - } - ] - }, - { - "id": 24, - "languages": [ - { - "lang": "en", - "message": "%userId% has deleted one of its api keys" - }, - { - "lang": "de", - "message": "%userId% hat einen seiner Api-Schlüssel gelöscht" - } - ] - }, - { - "id": 25, - "languages": [ - { - "lang": "en", - "message": "%userId% has used one of its api keys" - }, - { - "lang": "de", - "message": "%userId% hat einen seiner Api-Schlüssel verwendet" - } - ] - } -] \ No newline at end of file + { + "id": 0, + "languages": [ + { + "lang": "en", + "message": "%userId% has come online" + }, + { + "lang": "de", + "message": "%userId% ist online gegangen" + } + ] + }, + { + "id": 1, + "languages": [ + { + "lang": "en", + "message": "%userId% has created the role %roleId%" + }, + { + "lang": "de", + "message": "%userId% hat die Rolle %roleId% erstellt" + } + ] + }, + { + "id": 2, + "languages": [ + { + "lang": "en", + "message": "%userId% has updated the role %roleId% with the following changes: %changes%" + }, + { + "lang": "de", + "message": "%userId% hat die Rolle %roleId% mit den folgenden Änderungen bearbeitet: %changes%" + } + ] + }, + { + "id": 3, + "languages": [ + { + "lang": "en", + "message": "%userId% has changed the sorting order of role %roleId% to %sortingOrder%" + }, + { + "lang": "de", + "message": "%userId% hat die Sortierung der Rolle %roleId% zu %sortingOrder% geändert" + } + ] + }, + { + "id": 4, + "languages": [ + { + "lang": "en", + "message": "%userId% has deleted the role %roleId%" + }, + { + "lang": "de", + "message": "%userId% hat die Rolle %roleId% gelöscht" + } + ] + }, + { + "id": 5, + "languages": [ + { + "lang": "en", + "message": "%userId% has assigned the role %roleId% to %targetUserId%" + }, + { + "lang": "de", + "message": "%userId% hat die Rolle %roleId% dem Benutzer %targetUserId% zugewiesen" + } + ] + }, + { + "id": 6, + "languages": [ + { + "lang": "en", + "message": "%userId% has created the user %targetUserId% with the assigned role %roleId%" + }, + { + "lang": "de", + "message": "%userId% hat den Benutzer %targetUserId% mit der zugewiesenen Rolle %roleId% erstellt" + } + ] + }, + { + "id": 7, + "languages": [ + { + "lang": "en", + "message": "%userId% has deleted the user %targetUserId%" + }, + { + "lang": "de", + "message": "%userId% hat den Benutzer %targetUserId% gelöscht" + } + ] + }, + { + "id": 8, + "languages": [ + { + "lang": "en", + "message": "--------- SERVER has started ---------" + }, + { + "lang": "de", + "message": "--------- SERVER gestartet ---------" + } + ] + }, + { + "id": 9, + "languages": [ + { + "lang": "en", + "message": "%userId% has changed the deactivation status of %targetUserId% to %deactivated%" + }, + { + "lang": "de", + "message": "%userId% hat den Deaktivierungsstatus von dem Benutzer %targetUserId% zu %deactivated% aktualisiert" + } + ] + }, + { + "id": 10, + "languages": [ + { + "lang": "en", + "message": "Scanner %scannerId% is now used by %userId%" + }, + { + "lang": "en", + "message": "Scanner %scannerId% ist nun in Verwendung von %userId%" + } + ] + }, + { + "id": 11, + "languages": [ + { + "lang": "en", + "message": "Scanner %scannerId% is not longer used" + }, + { + "lang": "de", + "message": "Scanner %scannerId% ist nicht länger in Verwendung" + } + ] + }, + { + "id": 12, + "languages": [ + { + "lang": "en", + "message": "%userId% has changed his password" + }, + { + "lang": "de", + "message": "%userId% hat sein Passwort geändert" + } + ] + }, + { + "id": 13, + "languages": [ + { + "lang": "en", + "message": "%userId% has updated his account with the following changes: %changes%" + }, + { + "lang": "de", + "message": "%userId% hat seinen Account mit folgenden Veränderungen aktualisiert: %changes%" + } + ] + }, + { + "id": 14, + "languages": [ + { + "lang": "en", + "message": "Scanner %scannerId% %scannerName% was registered" + }, + { + "lang": "de", + "message": "Scanner %scannerId% %scannerName% wurde registriert" + } + ] + }, + { + "id": 15, + "languages": [ + { + "lang": "en", + "message": "Scanner %scannerId% has scanned %scanResult%" + }, + { + "lang": "de", + "message": "Scanner %scannerId% hat %scanResult% gescannt" + } + ] + }, + { + "id": 16, + "languages": [ + { + "lang": "en", + "message": "Scanner %scannerId% was deleted" + }, + { + "lang": "de", + "message": "Scanner %scannerId% wurde gelöscht" + } + ] + }, + { + "id": 17, + "languages": [ + { + "lang": "en", + "message": "%userId% has viewed %logType% logs of the date %logDate%" + }, + { + "lang": "de", + "message": "%userId% hat sich die Logs %logType% vom %logDate% angesehen" + } + ] + }, + { + "id": 18, + "languages": [ + { + "lang": "en", + "message": "%userId% has gone offline" + }, + { + "lang": "de", + "message": "%userId% ist offline gegangen" + } + ] + }, + { + "id": 19, + "languages": [ + { + "lang": "en", + "message": "%userId% has logged in" + }, + { + "lang": "de", + "message": "%userId% hat sich eingeloggt" + } + ] + }, + { + "id": 20, + "languages": [ + { + "lang": "en", + "message": "%userId% has logged out" + }, + { + "lang": "de", + "message": "%userId% hat sich abgemeldet" + } + ] + }, + { + "id": 21, + "languages": [ + { + "lang": "en", + "message": "%userId% has changed his avatar" + }, + { + "lang": "de", + "message": "%userId% hat sein Profilbild aktualisiert" + } + ] + }, + { + "id": 22, + "languages": [ + { + "lang": "en", + "message": "%userId% has logged out one of his account sessions" + }, + { + "lang": "de", + "message": "%userId% hat eine seiner Sitzungen ausgeloggt" + } + ] + }, + { + "id": 23, + "languages": [ + { + "lang": "en", + "message": "%userId% has created an API key" + }, + { + "lang": "de", + "message": "%userId% hat einen API-Schlüssel erstellt" + } + ] + }, + { + "id": 24, + "languages": [ + { + "lang": "en", + "message": "%userId% has deleted one of its api keys" + }, + { + "lang": "de", + "message": "%userId% hat einen seiner Api-Schlüssel gelöscht" + } + ] + }, + { + "id": 25, + "languages": [ + { + "lang": "en", + "message": "%userId% has used one of its api keys" + }, + { + "lang": "de", + "message": "%userId% hat einen seiner Api-Schlüssel verwendet" + } + ] + }, + { + "id": 26, + "languages": [ + { + "lang": "en", + "message": "%userId% has tried to use one of its api keys, but has no longer permission to do so" + }, + { + "lang": "de", + "message": "%userId% hat versucht einen seiner Api-Schlüssel zu verwenden, hat aber keine Berechtigung mehr dazu" + } + ] + } +]