Convert backend to ESM

- About 5 years overdue
- Remove eslint, use bomejs instead
This commit is contained in:
Jamie Curnow
2025-09-02 21:43:00 +10:00
parent 5b6ca1bf00
commit a12553fec7
89 changed files with 4799 additions and 5107 deletions

View File

@@ -1,43 +1,40 @@
const _ = require('lodash');
const error = require('../lib/error');
const utils = require('../lib/utils');
const userModel = require('../models/user');
const userPermissionModel = require('../models/user_permission');
const authModel = require('../models/auth');
const gravatar = require('gravatar');
const internalToken = require('./token');
const internalAuditLog = require('./audit-log');
import gravatar from "gravatar";
import _ from "lodash";
import errs from "../lib/error.js";
import utils from "../lib/utils.js";
import authModel from "../models/auth.js";
import userModel from "../models/user.js";
import userPermissionModel from "../models/user_permission.js";
import internalAuditLog from "./audit-log.js";
import internalToken from "./token.js";
function omissions () {
return ['is_deleted'];
const omissions = () => {
return ["is_deleted"];
}
const internalUser = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let auth = data.auth || null;
const auth = data.auth || null;
delete data.auth;
data.avatar = data.avatar || '';
data.roles = data.roles || [];
data.avatar = data.avatar || "";
data.roles = data.roles || [];
if (typeof data.is_disabled !== 'undefined') {
if (typeof data.is_disabled !== "undefined") {
data.is_disabled = data.is_disabled ? 1 : 0;
}
return access.can('users:create', data)
return access
.can("users:create", data)
.then(() => {
data.avatar = gravatar.url(data.email, {default: 'mm'});
data.avatar = gravatar.url(data.email, { default: "mm" });
return userModel
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
})
.then((user) => {
if (auth) {
@@ -45,45 +42,45 @@ const internalUser = {
.query()
.insert({
user_id: user.id,
type: auth.type,
secret: auth.secret,
meta: {}
type: auth.type,
secret: auth.secret,
meta: {},
})
.then(() => {
return user;
});
} else {
return user;
}
return user;
})
.then((user) => {
// Create permissions row as well
let is_admin = data.roles.indexOf('admin') !== -1;
const is_admin = data.roles.indexOf("admin") !== -1;
return userPermissionModel
.query()
.insert({
user_id: user.id,
visibility: is_admin ? 'all' : 'user',
proxy_hosts: 'manage',
redirection_hosts: 'manage',
dead_hosts: 'manage',
streams: 'manage',
access_lists: 'manage',
certificates: 'manage'
user_id: user.id,
visibility: is_admin ? "all" : "user",
proxy_hosts: "manage",
redirection_hosts: "manage",
dead_hosts: "manage",
streams: "manage",
access_lists: "manage",
certificates: "manage",
})
.then(() => {
return internalUser.get(access, {id: user.id, expand: ['permissions']});
return internalUser.get(access, { id: user.id, expand: ["permissions"] });
});
})
.then((user) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'user',
object_id: user.id,
meta: user
})
return internalAuditLog
.add(access, {
action: "created",
object_type: "user",
object_id: user.id,
meta: user,
})
.then(() => {
return user;
});
@@ -99,62 +96,58 @@ const internalUser = {
* @return {Promise}
*/
update: (access, data) => {
if (typeof data.is_disabled !== 'undefined') {
if (typeof data.is_disabled !== "undefined") {
data.is_disabled = data.is_disabled ? 1 : 0;
}
return access.can('users:update', data.id)
return access
.can("users:update", data.id)
.then(() => {
// Make sure that the user being updated doesn't change their email to another user that is already using it
// 1. get user we want to update
return internalUser.get(access, {id: data.id})
.then((user) => {
return internalUser.get(access, { id: data.id }).then((user) => {
// 2. if email is to be changed, find other users with that email
if (typeof data.email !== "undefined") {
data.email = data.email.toLowerCase().trim();
// 2. if email is to be changed, find other users with that email
if (typeof data.email !== 'undefined') {
data.email = data.email.toLowerCase().trim();
if (user.email !== data.email) {
return internalUser.isEmailAvailable(data.email, data.id)
.then((available) => {
if (!available) {
throw new error.ValidationError('Email address already in use - ' + data.email);
}
return user;
});
}
if (user.email !== data.email) {
return internalUser.isEmailAvailable(data.email, data.id).then((available) => {
if (!available) {
throw new errs.ValidationError(`Email address already in use - ${data.email}`);
}
return user;
});
}
}
// No change to email:
return user;
});
// No change to email:
return user;
});
})
.then((user) => {
if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
throw new errs.InternalValidationError(
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
);
}
data.avatar = gravatar.url(data.email || user.email, {default: 'mm'});
data.avatar = gravatar.url(data.email || user.email, { default: "mm" });
return userModel
.query()
.patchAndFetchById(user.id, data)
.then(utils.omitRow(omissions()));
return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions()));
})
.then(() => {
return internalUser.get(access, {id: data.id});
return internalUser.get(access, { id: data.id });
})
.then((user) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "updated",
object_type: "user",
object_id: user.id,
meta: data,
})
.then(() => {
return user;
});
@@ -170,36 +163,35 @@ const internalUser = {
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
const thisData = data || {};
if (typeof thisData.id === "undefined" || !thisData.id) {
thisData.id = access.token.getUserId(0);
}
if (typeof data.id === 'undefined' || !data.id) {
data.id = access.token.getUserId(0);
}
return access.can('users:get', data.id)
return access
.can("users:get", thisData.id)
.then(() => {
let query = userModel
const query = userModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[permissions]')
.where("is_deleted", 0)
.andWhere("id", thisData.id)
.allowGraph("[permissions]")
.first();
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched('[' + data.expand.join(', ') + ']');
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
}
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(thisData.id);
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit);
if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
return _.omit(row, thisData.omit);
}
return row;
});
@@ -213,20 +205,15 @@ const internalUser = {
* @param user_id
*/
isEmailAvailable: (email, user_id) => {
let query = userModel
.query()
.where('email', '=', email.toLowerCase().trim())
.where('is_deleted', 0)
.first();
const query = userModel.query().where("email", "=", email.toLowerCase().trim()).where("is_deleted", 0).first();
if (typeof user_id !== 'undefined') {
query.where('id', '!=', user_id);
if (typeof user_id !== "undefined") {
query.where("id", "!=", user_id);
}
return query
.then((user) => {
return !user;
});
return query.then((user) => {
return !user;
});
},
/**
@@ -237,33 +224,34 @@ const internalUser = {
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('users:delete', data.id)
return access
.can("users:delete", data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
return internalUser.get(access, { id: data.id });
})
.then((user) => {
if (!user) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(data.id);
}
// Make sure user can't delete themselves
if (user.id === access.token.getUserId(0)) {
throw new error.PermissionError('You cannot delete yourself.');
throw new errs.PermissionError("You cannot delete yourself.");
}
return userModel
.query()
.where('id', user.id)
.where("id", user.id)
.patch({
is_deleted: 1
is_deleted: 1,
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'user',
object_id: user.id,
meta: _.omit(user, omissions())
action: "deleted",
object_type: "user",
object_id: user.id,
meta: _.omit(user, omissions()),
});
});
})
@@ -280,26 +268,26 @@ const internalUser = {
* @returns {*}
*/
getCount: (access, search_query) => {
return access.can('users:list')
return access
.can("users:list")
.then(() => {
let query = userModel
.query()
.count('id as count')
.where('is_deleted', 0)
.first();
const query = userModel.query().count("id as count").where("is_deleted", 0).first();
// Query is used for searching
if (typeof search_query === 'string') {
if (typeof search_query === "string") {
query.where(function () {
this.where('user.name', 'like', '%' + search_query + '%')
.orWhere('user.email', 'like', '%' + search_query + '%');
this.where("user.name", "like", `%${search_query}%`).orWhere(
"user.email",
"like",
`%${search_query}%`,
);
});
}
return query;
})
.then((row) => {
return parseInt(row.count, 10);
return Number.parseInt(row.count, 10);
});
},
@@ -312,29 +300,31 @@ const internalUser = {
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('users:list')
.then(() => {
let query = userModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[permissions]')
.orderBy('name', 'ASC');
return access.can("users:list").then(() => {
const query = userModel
.query()
.where("is_deleted", 0)
.groupBy("id")
.allowGraph("[permissions]")
.orderBy("name", "ASC");
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('name', 'like', '%' + search_query + '%')
.orWhere('email', 'like', '%' + search_query + '%');
});
}
// Query is used for searching
if (typeof search_query === "string") {
query.where(function () {
this.where("name", "like", `%${search_query}%`).orWhere(
"email",
"like",
`%${search_query}%`,
);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
});
return query.then(utils.omitRows(omissions()));
});
},
/**
@@ -345,8 +335,8 @@ const internalUser = {
getUserOmisionsByAccess: (access, id_requested) => {
let response = []; // Admin response
if (!access.token.hasScope('admin') && access.token.getUserId(0) !== id_requested) {
response = ['roles', 'is_deleted']; // Restricted response
if (!access.token.hasScope("admin") && access.token.getUserId(0) !== id_requested) {
response = ["roles", "is_deleted"]; // Restricted response
}
return response;
@@ -361,26 +351,30 @@ const internalUser = {
* @return {Promise}
*/
setPassword: (access, data) => {
return access.can('users:password', data.id)
return access
.can("users:password", data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
return internalUser.get(access, { id: data.id });
})
.then((user) => {
if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
throw new errs.InternalValidationError(
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
);
}
if (user.id === access.token.getUserId(0)) {
// they're setting their own password. Make sure their current password is correct
if (typeof data.current === 'undefined' || !data.current) {
throw new error.ValidationError('Current password was not supplied');
if (typeof data.current === "undefined" || !data.current) {
throw new errs.ValidationError("Current password was not supplied");
}
return internalToken.getTokenFromEmail({
identity: user.email,
secret: data.current
})
return internalToken
.getTokenFromEmail({
identity: user.email,
secret: data.current,
})
.then(() => {
return user;
});
@@ -392,43 +386,36 @@ const internalUser = {
// Get auth, patch if it exists
return authModel
.query()
.where('user_id', user.id)
.andWhere('type', data.type)
.where("user_id", user.id)
.andWhere("type", data.type)
.first()
.then((existing_auth) => {
if (existing_auth) {
// patch
return authModel
.query()
.where('user_id', user.id)
.andWhere('type', data.type)
.patch({
type: data.type, // This is required for the model to encrypt on save
secret: data.secret
});
} else {
// insert
return authModel
.query()
.insert({
user_id: user.id,
type: data.type,
secret: data.secret,
meta: {}
});
return authModel.query().where("user_id", user.id).andWhere("type", data.type).patch({
type: data.type, // This is required for the model to encrypt on save
secret: data.secret,
});
}
// insert
return authModel.query().insert({
user_id: user.id,
type: data.type,
secret: data.secret,
meta: {},
});
})
.then(() => {
// Add to Audit Log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: {
name: user.name,
action: "updated",
object_type: "user",
object_id: user.id,
meta: {
name: user.name,
password_changed: true,
auth_type: data.type
}
auth_type: data.type,
},
});
});
})
@@ -443,14 +430,17 @@ const internalUser = {
* @return {Promise}
*/
setPermissions: (access, data) => {
return access.can('users:permissions', data.id)
return access
.can("users:permissions", data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
return internalUser.get(access, { id: data.id });
})
.then((user) => {
if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
throw new errs.InternalValidationError(
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
);
}
return user;
@@ -459,34 +449,30 @@ const internalUser = {
// Get perms row, patch if it exists
return userPermissionModel
.query()
.where('user_id', user.id)
.where("user_id", user.id)
.first()
.then((existing_auth) => {
if (existing_auth) {
// patch
return userPermissionModel
.query()
.where('user_id', user.id)
.patchAndFetchById(existing_auth.id, _.assign({user_id: user.id}, data));
} else {
// insert
return userPermissionModel
.query()
.insertAndFetch(_.assign({user_id: user.id}, data));
.where("user_id", user.id)
.patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data));
}
// insert
return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data));
})
.then((permissions) => {
// Add to Audit Log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: {
name: user.name,
permissions: permissions
}
action: "updated",
object_type: "user",
object_id: user.id,
meta: {
name: user.name,
permissions: permissions,
},
});
});
})
.then(() => {
@@ -500,14 +486,15 @@ const internalUser = {
* @param {Integer} data.id
*/
loginAs: (access, data) => {
return access.can('users:loginas', data.id)
return access
.can("users:loginas", data.id)
.then(() => {
return internalUser.get(access, data);
})
.then((user) => {
return internalToken.getTokenFromUser(user);
});
}
},
};
module.exports = internalUser;
export default internalUser;