[FEAT] Automated audit logging (#667)
* WIP event logging - new table for events and new settings view for viewing
* WIP add logging
* UI for log rows
* rename files to Logging to prevent getting gitignore
* add metadata for all logging events and colored badges in logs page
* remove unneeded comment
* cleanup namespace for logging
* clean up backend calls
* update logging to show to => from settings changes
* add logging for invitations, created, deleted, and accepted
* add logging for user created, updated, suspended, or removed
* add logging for workspace deleted
* add logging for chat logs exported
* add logging for API keys, LLM, embedder, vector db, embed chat, and reset button
* modify event logs
* update to event log types
* simplify rendering of event badges
---------
Co-authored-by: timothycarambat <rambat1010@gmail.com>
2024-02-07 00:21:40 +01:00
const { EventLogs } = require ( "../../../models/eventLogs" ) ;
2023-08-24 04:15:07 +02:00
const { Invite } = require ( "../../../models/invite" ) ;
const { SystemSettings } = require ( "../../../models/systemSettings" ) ;
const { User } = require ( "../../../models/user" ) ;
const { Workspace } = require ( "../../../models/workspace" ) ;
const { WorkspaceChats } = require ( "../../../models/workspaceChats" ) ;
2024-01-22 23:14:01 +01:00
const { canModifyAdmin } = require ( "../../../utils/helpers/admin" ) ;
2023-08-24 04:15:07 +02:00
const { multiUserMode , reqBody } = require ( "../../../utils/http" ) ;
const { validApiKey } = require ( "../../../utils/middleware/validApiKey" ) ;
function apiAdminEndpoints ( app ) {
if ( ! app ) return ;
app . get ( "/v1/admin/is-multi-user-mode" , [ validApiKey ] , ( _ , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'Check to see if the instance is in multi-user-mode first. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
"isMultiUser" : true
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
* /
const isMultiUser = multiUserMode ( response ) ;
response . status ( 200 ) . json ( { isMultiUser } ) ;
} ) ;
app . get ( "/v1/admin/users" , [ validApiKey ] , async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'Check to see if the instance is in multi-user-mode first. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
"users" : [
{
username : "sample-sam" ,
role : 'default' ,
}
]
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
2024-05-22 19:32:39 +02:00
const users = await User . where ( ) ;
2023-08-24 04:15:07 +02:00
response . status ( 200 ) . json ( { users } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
} ) ;
app . post ( "/v1/admin/users/new" , [ validApiKey ] , async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'Create a new user with username and password. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . requestBody = {
description : 'Key pair object that will define the new user to add to the system.' ,
required : true ,
type : 'object' ,
content : {
"application/json" : {
example : {
username : "sample-sam" ,
password : 'hunter2' ,
role : 'default | admin'
}
}
}
}
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
user : {
id : 1 ,
username : 'sample-sam' ,
role : 'default' ,
} ,
error : null ,
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
const newUserParams = reqBody ( request ) ;
const { user : newUser , error } = await User . create ( newUserParams ) ;
response . status ( 200 ) . json ( { user : newUser , error } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
} ) ;
app . post ( "/v1/admin/users/:id" , [ validApiKey ] , async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . path = '/v1/admin/users/{id}'
# swagger . parameters [ 'id' ] = {
in : 'path' ,
description : 'id of the user in the database.' ,
required : true ,
type : 'string'
}
# swagger . description = 'Update existing user settings. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . requestBody = {
description : 'Key pair object that will update the found user. All fields are optional and will not update unless specified.' ,
required : true ,
type : 'object' ,
content : {
"application/json" : {
example : {
username : "sample-sam" ,
password : 'hunter2' ,
role : 'default | admin' ,
suspended : 0 ,
}
}
}
}
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
success : true ,
error : null ,
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
const { id } = request . params ;
const updates = reqBody ( request ) ;
2023-11-14 23:43:40 +01:00
const user = await User . get ( { id : Number ( id ) } ) ;
2024-01-22 23:14:01 +01:00
const validAdminRoleModification = await canModifyAdmin ( user , updates ) ;
2023-11-14 23:43:40 +01:00
2024-01-22 23:14:01 +01:00
if ( ! validAdminRoleModification . valid ) {
response
. status ( 200 )
. json ( { success : false , error : validAdminRoleModification . error } ) ;
return ;
2023-11-14 23:43:40 +01:00
}
2023-08-24 04:15:07 +02:00
const { success , error } = await User . update ( id , updates ) ;
response . status ( 200 ) . json ( { success , error } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
} ) ;
app . delete (
"/v1/admin/users/:id" ,
[ validApiKey ] ,
async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'Delete existing user by id. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . path = '/v1/admin/users/{id}'
# swagger . parameters [ 'id' ] = {
in : 'path' ,
description : 'id of the user in the database.' ,
required : true ,
type : 'string'
}
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
success : true ,
error : null ,
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
const { id } = request . params ;
[FEAT] Automated audit logging (#667)
* WIP event logging - new table for events and new settings view for viewing
* WIP add logging
* UI for log rows
* rename files to Logging to prevent getting gitignore
* add metadata for all logging events and colored badges in logs page
* remove unneeded comment
* cleanup namespace for logging
* clean up backend calls
* update logging to show to => from settings changes
* add logging for invitations, created, deleted, and accepted
* add logging for user created, updated, suspended, or removed
* add logging for workspace deleted
* add logging for chat logs exported
* add logging for API keys, LLM, embedder, vector db, embed chat, and reset button
* modify event logs
* update to event log types
* simplify rendering of event badges
---------
Co-authored-by: timothycarambat <rambat1010@gmail.com>
2024-02-07 00:21:40 +01:00
const user = await User . get ( { id : Number ( id ) } ) ;
await User . delete ( { id : user . id } ) ;
await EventLogs . logEvent ( "api_user_deleted" , {
userName : user . username ,
} ) ;
2023-08-24 04:15:07 +02:00
response . status ( 200 ) . json ( { success : true , error : null } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
}
) ;
app . get ( "/v1/admin/invites" , [ validApiKey ] , async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'List all existing invitations to instance regardless of status. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
"invites" : [
{
id : 1 ,
status : "pending" ,
code : 'abc-123' ,
claimedBy : null
}
]
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
const invites = await Invite . whereWithUsers ( ) ;
response . status ( 200 ) . json ( { invites } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
} ) ;
app . post ( "/v1/admin/invite/new" , [ validApiKey ] , async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'Create a new invite code for someone to use to register with instance. Methods are disabled until multi user mode is enabled via the UI.'
2024-03-27 00:38:32 +01:00
# swagger . requestBody = {
description : 'Request body for creation parameters of the invitation' ,
required : false ,
type : 'object' ,
content : {
"application/json" : {
example : {
workspaceIds : [ 1 , 2 , 45 ] ,
}
}
}
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
invite : {
id : 1 ,
status : "pending" ,
code : 'abc-123' ,
} ,
error : null ,
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
2024-03-27 00:38:32 +01:00
const body = reqBody ( request ) ;
const { invite , error } = await Invite . create ( {
workspaceIds : body ? . workspaceIds ? ? [ ] ,
} ) ;
2023-08-24 04:15:07 +02:00
response . status ( 200 ) . json ( { invite , error } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
} ) ;
app . delete (
"/v1/admin/invite/:id" ,
[ validApiKey ] ,
async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'Deactivates (soft-delete) invite by id. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . path = '/v1/admin/invite/{id}'
# swagger . parameters [ 'id' ] = {
in : 'path' ,
description : 'id of the invite in the database.' ,
required : true ,
type : 'string'
}
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
success : true ,
error : null ,
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
const { id } = request . params ;
const { success , error } = await Invite . deactivate ( id ) ;
response . status ( 200 ) . json ( { success , error } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
}
) ;
2024-06-17 22:52:01 +02:00
app . get (
"/v1/admin/workspaces/:workspaceId/users" ,
[ validApiKey ] ,
async ( request , response ) => {
/ *
# swagger . tags = [ 'Admin' ]
# swagger . path = '/v1/admin/workspaces/{workspaceId}/users'
# swagger . parameters [ 'workspaceId' ] = {
in : 'path' ,
description : 'id of the workspace.' ,
required : true ,
type : 'string'
}
# swagger . description = 'Retrieve a list of users with permissions to access the specified workspace.'
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
users : [
{ "userId" : 1 , "role" : "admin" } ,
{ "userId" : 2 , "role" : "member" }
]
}
}
}
}
}
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
2023-08-24 04:15:07 +02:00
2024-06-17 22:52:01 +02:00
const workspaceId = request . params . workspaceId ;
const users = await Workspace . workspaceUsers ( workspaceId ) ;
response . status ( 200 ) . json ( { users } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
} ) ;
2023-08-24 04:15:07 +02:00
app . post (
"/v1/admin/workspaces/:workspaceId/update-users" ,
[ validApiKey ] ,
async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . path = '/v1/admin/workspaces/{workspaceId}/update-users'
# swagger . parameters [ 'workspaceId' ] = {
in : 'path' ,
description : 'id of the workspace in the database.' ,
required : true ,
type : 'string'
}
# swagger . description = 'Overwrite workspace permissions to only be accessible by the given user ids and admins. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . requestBody = {
description : 'Entire array of user ids who can access the workspace. All fields are optional and will not update unless specified.' ,
required : true ,
type : 'object' ,
content : {
"application/json" : {
example : {
userIds : [ 1 , 2 , 4 , 12 ] ,
}
}
}
}
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
success : true ,
error : null ,
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
const { workspaceId } = request . params ;
const { userIds } = reqBody ( request ) ;
const { success , error } = await Workspace . updateUsers (
2023-09-28 23:00:03 +02:00
workspaceId ,
2023-08-24 04:15:07 +02:00
userIds
) ;
response . status ( 200 ) . json ( { success , error } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
}
) ;
app . post (
"/v1/admin/workspace-chats" ,
[ validApiKey ] ,
async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'All chats in the system ordered by most recent. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . requestBody = {
description : 'Page offset to show of workspace chats. All fields are optional and will not update unless specified.' ,
required : false ,
type : 'integer' ,
content : {
"application/json" : {
example : {
offset : 2 ,
}
}
}
}
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
success : true ,
error : null ,
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
* /
try {
2023-11-03 00:12:29 +01:00
const pgSize = 20 ;
2023-08-24 04:15:07 +02:00
const { offset = 0 } = reqBody ( request ) ;
2023-09-12 01:27:04 +02:00
const chats = await WorkspaceChats . whereWithData (
2023-11-03 00:12:29 +01:00
{ } ,
pgSize ,
offset * pgSize ,
{ id : "desc" }
2023-09-12 01:27:04 +02:00
) ;
2023-11-03 00:12:29 +01:00
const hasPages = ( await WorkspaceChats . count ( ) ) > ( offset + 1 ) * pgSize ;
response . status ( 200 ) . json ( { chats : chats , hasPages } ) ;
2023-08-24 04:15:07 +02:00
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
}
) ;
app . get ( "/v1/admin/preferences" , [ validApiKey ] , async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'Show all multi-user preferences for instance. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
settings : {
users _can _delete _workspaces : true ,
limit _user _messages : false ,
message _limit : 10 ,
}
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
const settings = {
users _can _delete _workspaces :
2023-09-28 23:00:03 +02:00
( await SystemSettings . get ( { label : "users_can_delete_workspaces" } ) )
2023-08-24 04:15:07 +02:00
? . value === "true" ,
limit _user _messages :
2023-09-28 23:00:03 +02:00
( await SystemSettings . get ( { label : "limit_user_messages" } ) )
? . value === "true" ,
2023-08-24 04:15:07 +02:00
message _limit :
Number (
2023-09-28 23:00:03 +02:00
( await SystemSettings . get ( { label : "message_limit" } ) ) ? . value
2023-08-24 04:15:07 +02:00
) || 10 ,
} ;
response . status ( 200 ) . json ( { settings } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
} ) ;
app . post (
"/v1/admin/preferences" ,
[ validApiKey ] ,
async ( request , response ) => {
2023-09-28 23:00:03 +02:00
/ *
2023-08-24 04:15:07 +02:00
# swagger . tags = [ 'Admin' ]
# swagger . description = 'Update multi-user preferences for instance. Methods are disabled until multi user mode is enabled via the UI.'
# swagger . requestBody = {
description : 'Object with setting key and new value to set. All keys are optional and will not update unless specified.' ,
required : true ,
type : 'object' ,
content : {
"application/json" : {
example : {
users _can _delete _workspaces : false ,
limit _user _messages : true ,
message _limit : 5 ,
}
}
}
}
# swagger . responses [ 200 ] = {
content : {
"application/json" : {
schema : {
type : 'object' ,
example : {
success : true ,
error : null ,
}
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
}
2023-09-28 23:00:03 +02:00
}
2023-08-24 04:15:07 +02:00
# swagger . responses [ 403 ] = {
schema : {
"$ref" : "#/definitions/InvalidAPIKey"
}
}
# swagger . responses [ 401 ] = {
description : "Instance is not in Multi-User mode. Method denied" ,
}
* /
try {
if ( ! multiUserMode ( response ) ) {
response . sendStatus ( 401 ) . end ( ) ;
return ;
}
const updates = reqBody ( request ) ;
await SystemSettings . updateSettings ( updates ) ;
response . status ( 200 ) . json ( { success : true , error : null } ) ;
} catch ( e ) {
console . error ( e ) ;
response . sendStatus ( 500 ) . end ( ) ;
}
}
) ;
}
module . exports = { apiAdminEndpoints } ;