mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-30 18:20:24 -05:00
* Add ESM loader support and update sync-server modules * Update TypeScript configuration and fix bank file import filter in sync-server * Remove deprecated loader and register files, update TypeScript configuration to use ES2021, and add a new script for automatically adding import extensions to JavaScript files. * Update test script in package.json to include a custom loader and clean up import extensions script by removing unused 'stat' import. * feat: Add warning for unresolved imports Co-authored-by: matiss <matiss@mja.lv> * [autofix.ci] apply automated fixes * Remove unused 'import/extensions' rule from ESLint configuration * Refactor import statements in sync-server - Updated import path for migrations to remove file extension. - Added ESLint directive to ignore import extension rule for reset-password script. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
411 lines
9.4 KiB
JavaScript
411 lines
9.4 KiB
JavaScript
import express from 'express';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
import { isAdmin } from './account-db';
|
|
import * as UserService from './services/user-service';
|
|
import {
|
|
errorMiddleware,
|
|
requestLoggerMiddleware,
|
|
validateSessionMiddleware,
|
|
} from './util/middlewares';
|
|
import { validateSession } from './util/validate-user';
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
app.use(requestLoggerMiddleware);
|
|
|
|
export { app as handlers };
|
|
|
|
app.get('/owner-created/', (req, res) => {
|
|
try {
|
|
const ownerCount = UserService.getOwnerCount();
|
|
res.json(ownerCount > 0);
|
|
} catch {
|
|
res.status(500).json({ error: 'Failed to retrieve owner count' });
|
|
}
|
|
});
|
|
|
|
app.get('/users/', validateSessionMiddleware, (req, res) => {
|
|
const users = UserService.getAllUsers();
|
|
res.json(
|
|
users.map(u => ({
|
|
...u,
|
|
owner: u.owner === 1,
|
|
enabled: u.enabled === 1,
|
|
})),
|
|
);
|
|
});
|
|
|
|
app.post('/users', validateSessionMiddleware, async (req, res) => {
|
|
if (!isAdmin(res.locals.user_id)) {
|
|
res.status(403).send({
|
|
status: 'error',
|
|
reason: 'forbidden',
|
|
details: 'permission-not-found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { userName, role, displayName, enabled } = req.body || {};
|
|
|
|
if (!userName || !role) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,
|
|
details: `${!userName ? 'Username' : 'Role'} cannot be empty`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const roleIdFromDb = UserService.validateRole(role);
|
|
if (!roleIdFromDb) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'role-does-not-exists',
|
|
details: 'Selected role does not exist',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const userIdInDb = UserService.getUserByUsername(userName);
|
|
if (userIdInDb) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'user-already-exists',
|
|
details: `User ${userName} already exists`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const userId = uuidv4();
|
|
UserService.insertUser(
|
|
userId,
|
|
userName,
|
|
displayName || null,
|
|
enabled ? 1 : 0,
|
|
);
|
|
|
|
res.status(200).send({ status: 'ok', data: { id: userId } });
|
|
});
|
|
|
|
app.patch('/users', validateSessionMiddleware, async (req, res) => {
|
|
if (!isAdmin(res.locals.user_id)) {
|
|
res.status(403).send({
|
|
status: 'error',
|
|
reason: 'forbidden',
|
|
details: 'permission-not-found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { id, userName, role, displayName, enabled } = req.body || {};
|
|
|
|
if (!userName || !role) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,
|
|
details: `${!userName ? 'Username' : 'Role'} cannot be empty`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const roleIdFromDb = UserService.validateRole(role);
|
|
if (!roleIdFromDb) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'role-does-not-exists',
|
|
details: 'Selected role does not exist',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const userIdInDb = UserService.getUserById(id);
|
|
if (!userIdInDb) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'cannot-find-user-to-update',
|
|
details: `Cannot find user ${userName} to update`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
UserService.updateUserWithRole(
|
|
userIdInDb,
|
|
userName,
|
|
displayName || null,
|
|
enabled ? 1 : 0,
|
|
role,
|
|
);
|
|
|
|
res.status(200).send({ status: 'ok', data: { id: userIdInDb } });
|
|
});
|
|
|
|
app.delete('/users', validateSessionMiddleware, async (req, res) => {
|
|
if (!isAdmin(res.locals.user_id)) {
|
|
res.status(403).send({
|
|
status: 'error',
|
|
reason: 'forbidden',
|
|
details: 'permission-not-found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { ids } = req.body || {};
|
|
let totalDeleted = 0;
|
|
ids.forEach(item => {
|
|
const ownerId = UserService.getOwnerId();
|
|
|
|
if (item === ownerId) return;
|
|
|
|
UserService.deleteUserAccess(item);
|
|
UserService.transferAllFilesFromUser(ownerId, item);
|
|
const usersDeleted = UserService.deleteUser(item);
|
|
totalDeleted += usersDeleted;
|
|
});
|
|
|
|
if (ids.length === totalDeleted) {
|
|
res
|
|
.status(200)
|
|
.send({ status: 'ok', data: { someDeletionsFailed: false } });
|
|
} else {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'not-all-deleted',
|
|
details: '',
|
|
});
|
|
}
|
|
});
|
|
|
|
app.get('/access', validateSessionMiddleware, (req, res) => {
|
|
const fileId = req.query.fileId;
|
|
|
|
const { granted } = UserService.checkFilePermission(
|
|
fileId,
|
|
res.locals.user_id,
|
|
) || {
|
|
granted: 0,
|
|
};
|
|
|
|
if (granted === 0 && !isAdmin(res.locals.user_id)) {
|
|
res.status(403).send({
|
|
status: 'error',
|
|
reason: 'forbidden',
|
|
details: 'permission-not-found',
|
|
});
|
|
return false;
|
|
}
|
|
|
|
const fileIdInDb = UserService.getFileById(fileId);
|
|
if (!fileIdInDb) {
|
|
res.status(404).send({
|
|
status: 'error',
|
|
reason: 'invalid-file-id',
|
|
details: 'File not found at server',
|
|
});
|
|
return false;
|
|
}
|
|
|
|
const accesses = UserService.getUserAccess(
|
|
fileId,
|
|
res.locals.user_id,
|
|
isAdmin(res.locals.user_id),
|
|
);
|
|
|
|
res.json(accesses);
|
|
});
|
|
|
|
app.post('/access', (req, res) => {
|
|
const userAccess = req.body || {};
|
|
const session = validateSession(req, res);
|
|
|
|
if (!session) return;
|
|
|
|
const { granted } = UserService.checkFilePermission(
|
|
userAccess.fileId,
|
|
session.user_id,
|
|
) || {
|
|
granted: 0,
|
|
};
|
|
|
|
if (granted === 0 && !isAdmin(session.user_id)) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'file-denied',
|
|
details: "You don't have permissions over this file",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const fileIdInDb = UserService.getFileById(userAccess.fileId);
|
|
if (!fileIdInDb) {
|
|
res.status(404).send({
|
|
status: 'error',
|
|
reason: 'invalid-file-id',
|
|
details: 'File not found at server',
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!userAccess.userId) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'user-cant-be-empty',
|
|
details: 'User cannot be empty',
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (UserService.countUserAccess(userAccess.fileId, userAccess.userId) > 0) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'user-already-have-access',
|
|
details: 'User already have access',
|
|
});
|
|
return;
|
|
}
|
|
|
|
UserService.addUserAccess(userAccess.userId, userAccess.fileId);
|
|
|
|
res.status(200).send({ status: 'ok', data: {} });
|
|
});
|
|
|
|
app.delete('/access', (req, res) => {
|
|
const fileId = req.query.fileId;
|
|
const session = validateSession(req, res);
|
|
if (!session) return;
|
|
|
|
const { granted } = UserService.checkFilePermission(
|
|
fileId,
|
|
session.user_id,
|
|
) || {
|
|
granted: 0,
|
|
};
|
|
|
|
if (granted === 0 && !isAdmin(session.user_id)) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'file-denied',
|
|
details: "You don't have permissions over this file",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const fileIdInDb = UserService.getFileById(fileId);
|
|
if (!fileIdInDb) {
|
|
res.status(404).send({
|
|
status: 'error',
|
|
reason: 'invalid-file-id',
|
|
details: 'File not found at server',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { ids } = req.body || {};
|
|
const totalDeleted = UserService.deleteUserAccessByFileId(ids, fileId);
|
|
|
|
if (ids.length === totalDeleted) {
|
|
res
|
|
.status(200)
|
|
.send({ status: 'ok', data: { someDeletionsFailed: false } });
|
|
} else {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'not-all-deleted',
|
|
details: '',
|
|
});
|
|
}
|
|
});
|
|
|
|
app.get('/access/users', validateSessionMiddleware, async (req, res) => {
|
|
const fileId = req.query.fileId;
|
|
|
|
const { granted } = UserService.checkFilePermission(
|
|
fileId,
|
|
res.locals.user_id,
|
|
) || {
|
|
granted: 0,
|
|
};
|
|
|
|
if (granted === 0 && !isAdmin(res.locals.user_id)) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'file-denied',
|
|
details: "You don't have permissions over this file",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const fileIdInDb = UserService.getFileById(fileId);
|
|
if (!fileIdInDb) {
|
|
res.status(404).send({
|
|
status: 'error',
|
|
reason: 'invalid-file-id',
|
|
details: 'File not found at server',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const users = UserService.getAllUserAccess(fileId);
|
|
res.json(users);
|
|
});
|
|
|
|
app.post(
|
|
'/access/transfer-ownership/',
|
|
validateSessionMiddleware,
|
|
(req, res) => {
|
|
const newUserOwner = req.body || {};
|
|
|
|
const { granted } = UserService.checkFilePermission(
|
|
newUserOwner.fileId,
|
|
res.locals.user_id,
|
|
) || {
|
|
granted: 0,
|
|
};
|
|
|
|
if (granted === 0 && !isAdmin(res.locals.user_id)) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'file-denied',
|
|
details: "You don't have permissions over this file",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const fileIdInDb = UserService.getFileById(newUserOwner.fileId);
|
|
if (!fileIdInDb) {
|
|
res.status(404).send({
|
|
status: 'error',
|
|
reason: 'invalid-file-id',
|
|
details: 'File not found at server',
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!newUserOwner.newUserId) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'user-cant-be-empty',
|
|
details: 'Username cannot be empty',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const newUserIdFromDb = UserService.getUserById(newUserOwner.newUserId);
|
|
if (newUserIdFromDb === 0) {
|
|
res.status(400).send({
|
|
status: 'error',
|
|
reason: 'new-user-not-found',
|
|
details: 'New user not found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
UserService.updateFileOwner(newUserOwner.newUserId, newUserOwner.fileId);
|
|
|
|
res.status(200).send({ status: 'ok', data: {} });
|
|
},
|
|
);
|
|
|
|
app.use(errorMiddleware);
|