2023-09-28 23:00:03 +02:00
|
|
|
const prisma = require("../utils/prisma");
|
[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("./eventLogs");
|
2023-09-12 01:27:04 +02:00
|
|
|
|
2023-07-25 19:37:04 +02:00
|
|
|
const User = {
|
2024-08-07 20:35:37 +02:00
|
|
|
usernameRegex: new RegExp(/^[a-z0-9_-]+$/),
|
2024-04-27 01:46:04 +02:00
|
|
|
writable: [
|
|
|
|
// Used for generic updates so we can validate keys in request body
|
|
|
|
"username",
|
|
|
|
"password",
|
|
|
|
"pfpFilename",
|
|
|
|
"role",
|
|
|
|
"suspended",
|
|
|
|
],
|
2024-05-22 20:21:26 +02:00
|
|
|
validations: {
|
|
|
|
username: (newValue = "") => {
|
|
|
|
try {
|
|
|
|
if (String(newValue).length > 100)
|
|
|
|
throw new Error("Username cannot be longer than 100 characters");
|
|
|
|
if (String(newValue).length < 2)
|
|
|
|
throw new Error("Username must be at least 2 characters");
|
|
|
|
return String(newValue);
|
|
|
|
} catch (e) {
|
|
|
|
throw new Error(e.message);
|
|
|
|
}
|
|
|
|
},
|
2024-07-17 01:40:05 +02:00
|
|
|
role: (role = "default") => {
|
|
|
|
const VALID_ROLES = ["default", "admin", "manager"];
|
|
|
|
if (!VALID_ROLES.includes(role)) {
|
|
|
|
throw new Error(
|
|
|
|
`Invalid role. Allowed roles are: ${VALID_ROLES.join(", ")}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return String(role);
|
|
|
|
},
|
2024-05-22 20:21:26 +02:00
|
|
|
},
|
2024-04-27 01:46:04 +02:00
|
|
|
// validations for the above writable fields.
|
|
|
|
castColumnValue: function (key, value) {
|
|
|
|
switch (key) {
|
|
|
|
case "suspended":
|
|
|
|
return Number(Boolean(value));
|
|
|
|
default:
|
|
|
|
return String(value);
|
|
|
|
}
|
|
|
|
},
|
2024-05-22 19:32:39 +02:00
|
|
|
|
|
|
|
filterFields: function (user = {}) {
|
|
|
|
const { password, ...rest } = user;
|
|
|
|
return { ...rest };
|
|
|
|
},
|
|
|
|
|
2023-09-28 23:00:03 +02:00
|
|
|
create: async function ({ username, password, role = "default" }) {
|
2023-12-05 18:13:06 +01:00
|
|
|
const passwordCheck = this.checkPasswordComplexity(password);
|
|
|
|
if (!passwordCheck.checkedOK) {
|
|
|
|
return { user: null, error: passwordCheck.error };
|
|
|
|
}
|
|
|
|
|
2023-09-28 23:00:03 +02:00
|
|
|
try {
|
2024-08-07 20:35:37 +02:00
|
|
|
// Do not allow new users to bypass validation
|
|
|
|
if (!this.usernameRegex.test(username))
|
|
|
|
throw new Error(
|
|
|
|
"Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces"
|
|
|
|
);
|
|
|
|
|
2023-12-05 18:13:06 +01:00
|
|
|
const bcrypt = require("bcrypt");
|
2023-09-28 23:00:03 +02:00
|
|
|
const hashedPassword = bcrypt.hashSync(password, 10);
|
|
|
|
const user = await prisma.users.create({
|
|
|
|
data: {
|
2024-05-22 20:21:26 +02:00
|
|
|
username: this.validations.username(username),
|
2023-09-28 23:00:03 +02:00
|
|
|
password: hashedPassword,
|
2024-07-17 01:40:05 +02:00
|
|
|
role: this.validations.role(role),
|
2023-09-28 23:00:03 +02:00
|
|
|
},
|
2023-07-25 19:37:04 +02:00
|
|
|
});
|
2024-05-22 19:32:39 +02:00
|
|
|
return { user: this.filterFields(user), error: null };
|
2023-09-28 23:00:03 +02:00
|
|
|
} catch (error) {
|
|
|
|
console.error("FAILED TO CREATE USER.", error.message);
|
|
|
|
return { user: null, error: error.message };
|
2023-07-25 19:37:04 +02:00
|
|
|
}
|
|
|
|
},
|
[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
|
|
|
// Log the changes to a user object, but omit sensitive fields
|
|
|
|
// that are not meant to be logged.
|
|
|
|
loggedChanges: function (updates, prev = {}) {
|
|
|
|
const changes = {};
|
|
|
|
const sensitiveFields = ["password"];
|
|
|
|
|
|
|
|
Object.keys(updates).forEach((key) => {
|
|
|
|
if (!sensitiveFields.includes(key) && updates[key] !== prev[key]) {
|
|
|
|
changes[key] = `${prev[key]} => ${updates[key]}`;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return changes;
|
|
|
|
},
|
|
|
|
|
2023-09-28 23:00:03 +02:00
|
|
|
update: async function (userId, updates = {}) {
|
|
|
|
try {
|
2024-04-27 01:46:04 +02:00
|
|
|
if (!userId) throw new Error("No user id provided for update");
|
[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 currentUser = await prisma.users.findUnique({
|
|
|
|
where: { id: parseInt(userId) },
|
|
|
|
});
|
2024-04-27 01:46:04 +02:00
|
|
|
if (!currentUser) return { success: false, error: "User not found" };
|
|
|
|
// Removes non-writable fields for generic updates
|
|
|
|
// and force-casts to the proper type;
|
|
|
|
Object.entries(updates).forEach(([key, value]) => {
|
|
|
|
if (this.writable.includes(key)) {
|
2024-05-22 20:21:26 +02:00
|
|
|
if (this.validations.hasOwnProperty(key)) {
|
|
|
|
updates[key] = this.validations[key](
|
|
|
|
this.castColumnValue(key, value)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
updates[key] = this.castColumnValue(key, value);
|
|
|
|
}
|
2024-04-27 01:46:04 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
delete updates[key];
|
|
|
|
});
|
[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
|
|
|
|
2024-04-27 01:46:04 +02:00
|
|
|
if (Object.keys(updates).length === 0)
|
|
|
|
return { success: false, error: "No valid updates applied." };
|
|
|
|
|
|
|
|
// Handle password specific updates
|
2023-12-05 18:13:06 +01:00
|
|
|
if (updates.hasOwnProperty("password")) {
|
|
|
|
const passwordCheck = this.checkPasswordComplexity(updates.password);
|
|
|
|
if (!passwordCheck.checkedOK) {
|
|
|
|
return { success: false, error: passwordCheck.error };
|
|
|
|
}
|
|
|
|
const bcrypt = require("bcrypt");
|
2023-11-27 19:47:07 +01:00
|
|
|
updates.password = bcrypt.hashSync(updates.password, 10);
|
|
|
|
}
|
|
|
|
|
2024-08-07 20:35:37 +02:00
|
|
|
if (
|
|
|
|
updates.hasOwnProperty("username") &&
|
|
|
|
currentUser.username !== updates.username &&
|
|
|
|
!this.usernameRegex.test(updates.username)
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
success: false,
|
|
|
|
error:
|
|
|
|
"Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces",
|
|
|
|
};
|
|
|
|
|
[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 prisma.users.update({
|
2023-09-28 23:00:03 +02:00
|
|
|
where: { id: parseInt(userId) },
|
|
|
|
data: updates,
|
|
|
|
});
|
[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
|
|
|
|
|
|
|
await EventLogs.logEvent(
|
|
|
|
"user_updated",
|
|
|
|
{
|
|
|
|
username: user.username,
|
|
|
|
changes: this.loggedChanges(updates, currentUser),
|
|
|
|
},
|
|
|
|
userId
|
|
|
|
);
|
2023-09-28 23:00:03 +02:00
|
|
|
return { success: true, error: null };
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error.message);
|
|
|
|
return { success: false, error: error.message };
|
2023-07-25 19:37:04 +02:00
|
|
|
}
|
2023-09-28 23:00:03 +02:00
|
|
|
},
|
2023-07-25 19:37:04 +02:00
|
|
|
|
2024-04-27 01:46:04 +02:00
|
|
|
// Explicit direct update of user object.
|
|
|
|
// Only use this method when directly setting a key value
|
|
|
|
// that takes no user input for the keys being modified.
|
|
|
|
_update: async function (id = null, data = {}) {
|
|
|
|
if (!id) throw new Error("No user id provided for update");
|
|
|
|
|
|
|
|
try {
|
|
|
|
const user = await prisma.users.update({
|
|
|
|
where: { id },
|
|
|
|
data,
|
|
|
|
});
|
|
|
|
return { user, message: null };
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error.message);
|
|
|
|
return { user: null, message: error.message };
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-09-28 23:00:03 +02:00
|
|
|
get: async function (clause = {}) {
|
2024-05-22 19:32:39 +02:00
|
|
|
try {
|
|
|
|
const user = await prisma.users.findFirst({ where: clause });
|
|
|
|
return user ? this.filterFields({ ...user }) : null;
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error.message);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// Returns user object with all fields
|
|
|
|
_get: async function (clause = {}) {
|
2023-09-28 23:00:03 +02:00
|
|
|
try {
|
|
|
|
const user = await prisma.users.findFirst({ where: clause });
|
|
|
|
return user ? { ...user } : null;
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error.message);
|
|
|
|
return null;
|
2023-07-25 19:37:04 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-09-28 23:00:03 +02:00
|
|
|
count: async function (clause = {}) {
|
|
|
|
try {
|
|
|
|
const count = await prisma.users.count({ where: clause });
|
|
|
|
return count;
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error.message);
|
|
|
|
return 0;
|
|
|
|
}
|
2023-07-25 19:37:04 +02:00
|
|
|
},
|
|
|
|
|
2023-09-28 23:00:03 +02:00
|
|
|
delete: async function (clause = {}) {
|
|
|
|
try {
|
2023-10-23 22:10:34 +02:00
|
|
|
await prisma.users.deleteMany({ where: clause });
|
2023-09-28 23:00:03 +02:00
|
|
|
return true;
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error.message);
|
|
|
|
return false;
|
|
|
|
}
|
2023-07-25 19:37:04 +02:00
|
|
|
},
|
|
|
|
|
2023-09-28 23:00:03 +02:00
|
|
|
where: async function (clause = {}, limit = null) {
|
|
|
|
try {
|
|
|
|
const users = await prisma.users.findMany({
|
|
|
|
where: clause,
|
|
|
|
...(limit !== null ? { take: limit } : {}),
|
|
|
|
});
|
2024-05-22 19:32:39 +02:00
|
|
|
return users.map((usr) => this.filterFields(usr));
|
2023-09-28 23:00:03 +02:00
|
|
|
} catch (error) {
|
|
|
|
console.error(error.message);
|
|
|
|
return [];
|
|
|
|
}
|
2023-07-25 19:37:04 +02:00
|
|
|
},
|
2023-12-05 18:13:06 +01:00
|
|
|
|
|
|
|
checkPasswordComplexity: function (passwordInput = "") {
|
|
|
|
const passwordComplexity = require("joi-password-complexity");
|
|
|
|
// Can be set via ENV variable on boot. No frontend config at this time.
|
|
|
|
// Docs: https://www.npmjs.com/package/joi-password-complexity
|
|
|
|
const complexityOptions = {
|
|
|
|
min: process.env.PASSWORDMINCHAR || 8,
|
|
|
|
max: process.env.PASSWORDMAXCHAR || 250,
|
|
|
|
lowerCase: process.env.PASSWORDLOWERCASE || 0,
|
|
|
|
upperCase: process.env.PASSWORDUPPERCASE || 0,
|
|
|
|
numeric: process.env.PASSWORDNUMERIC || 0,
|
|
|
|
symbol: process.env.PASSWORDSYMBOL || 0,
|
|
|
|
// reqCount should be equal to how many conditions you are testing for (1-4)
|
|
|
|
requirementCount: process.env.PASSWORDREQUIREMENTS || 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
const complexityCheck = passwordComplexity(
|
|
|
|
complexityOptions,
|
|
|
|
"password"
|
|
|
|
).validate(passwordInput);
|
|
|
|
if (complexityCheck.hasOwnProperty("error")) {
|
|
|
|
let myError = "";
|
|
|
|
let prepend = "";
|
|
|
|
for (let i = 0; i < complexityCheck.error.details.length; i++) {
|
|
|
|
myError += prepend + complexityCheck.error.details[i].message;
|
|
|
|
prepend = ", ";
|
|
|
|
}
|
|
|
|
return { checkedOK: false, error: myError };
|
|
|
|
}
|
|
|
|
|
|
|
|
return { checkedOK: true, error: "No error." };
|
|
|
|
},
|
2023-07-25 19:37:04 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = { User };
|