mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
* fix(jellyfin): use the same deviceId for admins This PR will make Jellyseerr use the same deviceId for the admin user everytime he logins to Jellyfin/Emby. The previous behavior with different deviceId was creating new entries on the media at every request. * fix: remove useless check
735 lines
22 KiB
TypeScript
735 lines
22 KiB
TypeScript
import JellyfinAPI from '@server/api/jellyfin';
|
|
import PlexTvAPI from '@server/api/plextv';
|
|
import { ApiErrorCode } from '@server/constants/error';
|
|
import { MediaServerType } from '@server/constants/server';
|
|
import { UserType } from '@server/constants/user';
|
|
import { getRepository } from '@server/datasource';
|
|
import { User } from '@server/entity/User';
|
|
import { UserSettings } from '@server/entity/UserSettings';
|
|
import type {
|
|
UserSettingsGeneralResponse,
|
|
UserSettingsNotificationsResponse,
|
|
} from '@server/interfaces/api/userSettingsInterfaces';
|
|
import { Permission } from '@server/lib/permissions';
|
|
import { getSettings } from '@server/lib/settings';
|
|
import logger from '@server/logger';
|
|
import { isAuthenticated } from '@server/middleware/auth';
|
|
import { ApiError } from '@server/types/error';
|
|
import { getHostname } from '@server/utils/getHostname';
|
|
import { Router } from 'express';
|
|
import net from 'net';
|
|
import { Not } from 'typeorm';
|
|
import { canMakePermissionsChange } from '.';
|
|
|
|
const isOwnProfile = (): Middleware => {
|
|
return (req, res, next) => {
|
|
if (req.user?.id !== Number(req.params.id)) {
|
|
return next({
|
|
status: 403,
|
|
message: "You do not have permission to view this user's settings.",
|
|
});
|
|
}
|
|
next();
|
|
};
|
|
};
|
|
|
|
const isOwnProfileOrAdmin = (): Middleware => {
|
|
const authMiddleware: Middleware = (req, res, next) => {
|
|
if (
|
|
!req.user?.hasPermission(Permission.MANAGE_USERS) &&
|
|
req.user?.id !== Number(req.params.id)
|
|
) {
|
|
return next({
|
|
status: 403,
|
|
message: "You do not have permission to view this user's settings.",
|
|
});
|
|
}
|
|
|
|
next();
|
|
};
|
|
return authMiddleware;
|
|
};
|
|
|
|
const userSettingsRoutes = Router({ mergeParams: true });
|
|
|
|
userSettingsRoutes.get<{ id: string }, UserSettingsGeneralResponse>(
|
|
'/main',
|
|
isOwnProfileOrAdmin(),
|
|
async (req, res, next) => {
|
|
const {
|
|
main: { defaultQuotas },
|
|
} = getSettings();
|
|
const userRepository = getRepository(User);
|
|
|
|
try {
|
|
const user = await userRepository.findOne({
|
|
where: { id: Number(req.params.id) },
|
|
});
|
|
|
|
if (!user) {
|
|
return next({ status: 404, message: 'User not found.' });
|
|
}
|
|
|
|
return res.status(200).json({
|
|
username: user.username,
|
|
email: user.email,
|
|
discordId: user.settings?.discordId,
|
|
locale: user.settings?.locale,
|
|
discoverRegion: user.settings?.discoverRegion,
|
|
streamingRegion: user.settings?.streamingRegion,
|
|
originalLanguage: user.settings?.originalLanguage,
|
|
movieQuotaLimit: user.movieQuotaLimit,
|
|
movieQuotaDays: user.movieQuotaDays,
|
|
tvQuotaLimit: user.tvQuotaLimit,
|
|
tvQuotaDays: user.tvQuotaDays,
|
|
globalMovieQuotaDays: defaultQuotas.movie.quotaDays,
|
|
globalMovieQuotaLimit: defaultQuotas.movie.quotaLimit,
|
|
globalTvQuotaDays: defaultQuotas.tv.quotaDays,
|
|
globalTvQuotaLimit: defaultQuotas.tv.quotaLimit,
|
|
watchlistSyncMovies: user.settings?.watchlistSyncMovies,
|
|
watchlistSyncTv: user.settings?.watchlistSyncTv,
|
|
});
|
|
} catch (e) {
|
|
next({ status: 500, message: e.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.post<
|
|
{ id: string },
|
|
UserSettingsGeneralResponse,
|
|
UserSettingsGeneralResponse
|
|
>('/main', isOwnProfileOrAdmin(), async (req, res, next) => {
|
|
const userRepository = getRepository(User);
|
|
|
|
try {
|
|
const user = await userRepository.findOne({
|
|
where: { id: Number(req.params.id) },
|
|
});
|
|
|
|
if (!user) {
|
|
return next({ status: 404, message: 'User not found.' });
|
|
}
|
|
|
|
// "Owner" user settings cannot be modified by other users
|
|
if (user.id === 1 && req.user?.id !== 1) {
|
|
return next({
|
|
status: 403,
|
|
message: "You do not have permission to modify this user's settings.",
|
|
});
|
|
}
|
|
|
|
const oldEmail = user.email;
|
|
user.username = req.body.username;
|
|
if (user.userType !== UserType.PLEX) {
|
|
user.email = req.body.email || user.jellyfinUsername || user.email;
|
|
}
|
|
|
|
const existingUser = await userRepository.findOne({
|
|
where: { email: user.email, id: Not(user.id) },
|
|
});
|
|
|
|
if (oldEmail !== user.email && existingUser) {
|
|
throw new ApiError(400, ApiErrorCode.InvalidEmail);
|
|
}
|
|
|
|
// Update quota values only if the user has the correct permissions
|
|
if (
|
|
!user.hasPermission(Permission.MANAGE_USERS) &&
|
|
req.user?.id !== user.id
|
|
) {
|
|
user.movieQuotaDays = req.body.movieQuotaDays;
|
|
user.movieQuotaLimit = req.body.movieQuotaLimit;
|
|
user.tvQuotaDays = req.body.tvQuotaDays;
|
|
user.tvQuotaLimit = req.body.tvQuotaLimit;
|
|
}
|
|
|
|
if (!user.settings) {
|
|
user.settings = new UserSettings({
|
|
user: req.user,
|
|
discordId: req.body.discordId,
|
|
locale: req.body.locale,
|
|
discoverRegion: req.body.discoverRegion,
|
|
streamingRegion: req.body.streamingRegion,
|
|
originalLanguage: req.body.originalLanguage,
|
|
watchlistSyncMovies: req.body.watchlistSyncMovies,
|
|
watchlistSyncTv: req.body.watchlistSyncTv,
|
|
});
|
|
} else {
|
|
user.settings.discordId = req.body.discordId;
|
|
user.settings.locale = req.body.locale;
|
|
user.settings.discoverRegion = req.body.discoverRegion;
|
|
user.settings.streamingRegion = req.body.streamingRegion;
|
|
user.settings.originalLanguage = req.body.originalLanguage;
|
|
user.settings.watchlistSyncMovies = req.body.watchlistSyncMovies;
|
|
user.settings.watchlistSyncTv = req.body.watchlistSyncTv;
|
|
}
|
|
|
|
const savedUser = await userRepository.save(user);
|
|
|
|
return res.status(200).json({
|
|
username: savedUser.username,
|
|
discordId: savedUser.settings?.discordId,
|
|
locale: savedUser.settings?.locale,
|
|
discoverRegion: savedUser.settings?.discoverRegion,
|
|
streamingRegion: savedUser.settings?.streamingRegion,
|
|
originalLanguage: savedUser.settings?.originalLanguage,
|
|
watchlistSyncMovies: savedUser.settings?.watchlistSyncMovies,
|
|
watchlistSyncTv: savedUser.settings?.watchlistSyncTv,
|
|
email: savedUser.email,
|
|
});
|
|
} catch (e) {
|
|
if (e.errorCode) {
|
|
return next({
|
|
status: e.statusCode,
|
|
message: e.errorCode,
|
|
});
|
|
}
|
|
return next({ status: 500, message: e.message });
|
|
}
|
|
});
|
|
|
|
userSettingsRoutes.get<{ id: string }, { hasPassword: boolean }>(
|
|
'/password',
|
|
isOwnProfileOrAdmin(),
|
|
async (req, res, next) => {
|
|
const userRepository = getRepository(User);
|
|
|
|
try {
|
|
const user = await userRepository.findOne({
|
|
where: { id: Number(req.params.id) },
|
|
select: ['id', 'password'],
|
|
});
|
|
|
|
if (!user) {
|
|
return next({ status: 404, message: 'User not found.' });
|
|
}
|
|
|
|
return res.status(200).json({ hasPassword: !!user.password });
|
|
} catch (e) {
|
|
next({ status: 500, message: e.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.post<
|
|
{ id: string },
|
|
null,
|
|
{ currentPassword?: string; newPassword: string }
|
|
>('/password', isOwnProfileOrAdmin(), async (req, res, next) => {
|
|
const userRepository = getRepository(User);
|
|
|
|
try {
|
|
const user = await userRepository.findOne({
|
|
where: { id: Number(req.params.id) },
|
|
});
|
|
|
|
const userWithPassword = await userRepository.findOne({
|
|
select: ['id', 'password'],
|
|
where: { id: Number(req.params.id) },
|
|
});
|
|
|
|
if (!user || !userWithPassword) {
|
|
return next({ status: 404, message: 'User not found.' });
|
|
}
|
|
|
|
if (req.body.newPassword.length < 8) {
|
|
return next({
|
|
status: 400,
|
|
message: 'Password must be at least 8 characters.',
|
|
});
|
|
}
|
|
|
|
if (
|
|
(user.id === 1 && req.user?.id !== 1) ||
|
|
(user.hasPermission(Permission.ADMIN) &&
|
|
user.id !== req.user?.id &&
|
|
req.user?.id !== 1)
|
|
) {
|
|
return next({
|
|
status: 403,
|
|
message: "You do not have permission to modify this user's password.",
|
|
});
|
|
}
|
|
|
|
// If the user has the permission to manage users and they are not
|
|
// editing themselves, we will just set the new password
|
|
if (
|
|
req.user?.hasPermission(Permission.MANAGE_USERS) &&
|
|
req.user?.id !== user.id
|
|
) {
|
|
await user.setPassword(req.body.newPassword);
|
|
await userRepository.save(user);
|
|
logger.debug('Password overriden by user.', {
|
|
label: 'User Settings',
|
|
userEmail: user.email,
|
|
changingUser: req.user.email,
|
|
});
|
|
return res.status(204).send();
|
|
}
|
|
|
|
// If the user has a password, we need to check the currentPassword is correct
|
|
if (
|
|
user.password &&
|
|
(!req.body.currentPassword ||
|
|
!(await userWithPassword.passwordMatch(req.body.currentPassword)))
|
|
) {
|
|
logger.debug(
|
|
'Attempt to change password for user failed. Invalid current password provided.',
|
|
{ label: 'User Settings', userEmail: user.email }
|
|
);
|
|
return next({ status: 403, message: 'Current password is invalid.' });
|
|
}
|
|
|
|
await user.setPassword(req.body.newPassword);
|
|
await userRepository.save(user);
|
|
|
|
return res.status(204).send();
|
|
} catch (e) {
|
|
next({ status: 500, message: e.message });
|
|
}
|
|
});
|
|
|
|
userSettingsRoutes.post<{ authToken: string }>(
|
|
'/linked-accounts/plex',
|
|
isOwnProfile(),
|
|
async (req, res) => {
|
|
const settings = getSettings();
|
|
const userRepository = getRepository(User);
|
|
|
|
if (!req.user) {
|
|
return res.status(404).json({ code: ApiErrorCode.Unauthorized });
|
|
}
|
|
// Make sure Plex login is enabled
|
|
if (settings.main.mediaServerType !== MediaServerType.PLEX) {
|
|
return res.status(500).json({ message: 'Plex login is disabled' });
|
|
}
|
|
|
|
// First we need to use this auth token to get the user's email from plex.tv
|
|
const plextv = new PlexTvAPI(req.body.authToken);
|
|
const account = await plextv.getUser();
|
|
|
|
// Do not allow linking of an already linked account
|
|
if (await userRepository.exist({ where: { plexId: account.id } })) {
|
|
return res.status(422).json({
|
|
message: 'This Plex account is already linked to a Jellyseerr user',
|
|
});
|
|
}
|
|
|
|
const user = req.user;
|
|
|
|
// Emails do not match
|
|
if (user.email !== account.email) {
|
|
return res.status(422).json({
|
|
message:
|
|
'This Plex account is registered under a different email address.',
|
|
});
|
|
}
|
|
|
|
// valid plex user found, link to current user
|
|
user.userType = UserType.PLEX;
|
|
user.plexId = account.id;
|
|
user.plexUsername = account.username;
|
|
user.plexToken = account.authToken;
|
|
await userRepository.save(user);
|
|
|
|
return res.status(204).send();
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.delete<{ id: string }>(
|
|
'/linked-accounts/plex',
|
|
isOwnProfileOrAdmin(),
|
|
async (req, res) => {
|
|
const settings = getSettings();
|
|
const userRepository = getRepository(User);
|
|
|
|
// Make sure Plex login is enabled
|
|
if (settings.main.mediaServerType !== MediaServerType.PLEX) {
|
|
return res.status(500).json({ message: 'Plex login is disabled' });
|
|
}
|
|
|
|
try {
|
|
const user = await userRepository
|
|
.createQueryBuilder('user')
|
|
.addSelect('user.password')
|
|
.where({
|
|
id: Number(req.params.id),
|
|
})
|
|
.getOne();
|
|
|
|
if (!user) {
|
|
return res.status(404).json({ message: 'User not found.' });
|
|
}
|
|
|
|
if (user.id === 1) {
|
|
return res.status(400).json({
|
|
message:
|
|
'Cannot unlink media server accounts for the primary administrator.',
|
|
});
|
|
}
|
|
|
|
if (!user.email || !user.password) {
|
|
return res.status(400).json({
|
|
message: 'User does not have a local email or password set.',
|
|
});
|
|
}
|
|
|
|
user.userType = UserType.LOCAL;
|
|
user.plexId = null;
|
|
user.plexUsername = null;
|
|
user.plexToken = null;
|
|
await userRepository.save(user);
|
|
|
|
return res.status(204).send();
|
|
} catch (e) {
|
|
return res.status(500).json({ message: e.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.post<{ username: string; password: string }>(
|
|
'/linked-accounts/jellyfin',
|
|
isOwnProfile(),
|
|
async (req, res) => {
|
|
const settings = getSettings();
|
|
const userRepository = getRepository(User);
|
|
|
|
if (!req.user) {
|
|
return res.status(401).json({ code: ApiErrorCode.Unauthorized });
|
|
}
|
|
// Make sure jellyfin login is enabled
|
|
if (
|
|
settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
|
|
settings.main.mediaServerType !== MediaServerType.EMBY
|
|
) {
|
|
return res
|
|
.status(500)
|
|
.json({ message: 'Jellyfin/Emby login is disabled' });
|
|
}
|
|
|
|
// Do not allow linking of an already linked account
|
|
if (
|
|
await userRepository.exist({
|
|
where: { jellyfinUsername: req.body.username },
|
|
})
|
|
) {
|
|
return res.status(422).json({
|
|
message: 'The specified account is already linked to a Jellyseerr user',
|
|
});
|
|
}
|
|
|
|
const hostname = getHostname();
|
|
const deviceId = Buffer.from(
|
|
req.user?.id === 1
|
|
? 'BOT_jellyseerr'
|
|
: `BOT_jellyseerr_${req.user.username ?? ''}`
|
|
).toString('base64');
|
|
|
|
const jellyfinserver = new JellyfinAPI(hostname, undefined, deviceId);
|
|
|
|
const ip = req.ip;
|
|
let clientIp: string | undefined;
|
|
if (ip) {
|
|
if (net.isIPv4(ip)) {
|
|
clientIp = ip;
|
|
} else if (net.isIPv6(ip)) {
|
|
clientIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
|
|
}
|
|
}
|
|
|
|
try {
|
|
const account = await jellyfinserver.login(
|
|
req.body.username,
|
|
req.body.password,
|
|
clientIp
|
|
);
|
|
|
|
// Do not allow linking of an already linked account
|
|
if (
|
|
await userRepository.exist({
|
|
where: { jellyfinUserId: account.User.Id },
|
|
})
|
|
) {
|
|
return res.status(422).json({
|
|
message:
|
|
'The specified account is already linked to a Jellyseerr user',
|
|
});
|
|
}
|
|
|
|
const user = req.user;
|
|
|
|
// valid jellyfin user found, link to current user
|
|
user.userType =
|
|
settings.main.mediaServerType === MediaServerType.EMBY
|
|
? UserType.EMBY
|
|
: UserType.JELLYFIN;
|
|
user.jellyfinUserId = account.User.Id;
|
|
user.jellyfinUsername = account.User.Name;
|
|
user.jellyfinAuthToken = account.AccessToken;
|
|
user.jellyfinDeviceId = deviceId;
|
|
await userRepository.save(user);
|
|
|
|
return res.status(204).send();
|
|
} catch (e) {
|
|
logger.error('Failed to link account to user.', {
|
|
label: 'API',
|
|
ip: req.ip,
|
|
error: e,
|
|
});
|
|
if (
|
|
e instanceof ApiError &&
|
|
e.errorCode === ApiErrorCode.InvalidCredentials
|
|
) {
|
|
return res.status(401).json({ code: e.errorCode });
|
|
}
|
|
|
|
return res.status(500).send();
|
|
}
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.delete<{ id: string }>(
|
|
'/linked-accounts/jellyfin',
|
|
isOwnProfileOrAdmin(),
|
|
async (req, res) => {
|
|
const settings = getSettings();
|
|
const userRepository = getRepository(User);
|
|
|
|
// Make sure jellyfin login is enabled
|
|
if (
|
|
settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
|
|
settings.main.mediaServerType !== MediaServerType.EMBY
|
|
) {
|
|
return res
|
|
.status(500)
|
|
.json({ message: 'Jellyfin/Emby login is disabled' });
|
|
}
|
|
|
|
try {
|
|
const user = await userRepository
|
|
.createQueryBuilder('user')
|
|
.addSelect('user.password')
|
|
.where({
|
|
id: Number(req.params.id),
|
|
})
|
|
.getOne();
|
|
|
|
if (!user) {
|
|
return res.status(404).json({ message: 'User not found.' });
|
|
}
|
|
|
|
if (user.id === 1) {
|
|
return res.status(400).json({
|
|
message:
|
|
'Cannot unlink media server accounts for the primary administrator.',
|
|
});
|
|
}
|
|
|
|
if (!user.email || !user.password) {
|
|
return res.status(400).json({
|
|
message: 'User does not have a local email or password set.',
|
|
});
|
|
}
|
|
|
|
user.userType = UserType.LOCAL;
|
|
user.jellyfinUserId = null;
|
|
user.jellyfinUsername = null;
|
|
user.jellyfinAuthToken = null;
|
|
user.jellyfinDeviceId = null;
|
|
await userRepository.save(user);
|
|
|
|
return res.status(204).send();
|
|
} catch (e) {
|
|
return res.status(500).json({ message: e.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>(
|
|
'/notifications',
|
|
isOwnProfileOrAdmin(),
|
|
async (req, res, next) => {
|
|
const userRepository = getRepository(User);
|
|
const settings = getSettings()?.notifications.agents;
|
|
|
|
try {
|
|
const user = await userRepository.findOne({
|
|
where: { id: Number(req.params.id) },
|
|
});
|
|
|
|
if (!user) {
|
|
return next({ status: 404, message: 'User not found.' });
|
|
}
|
|
|
|
return res.status(200).json({
|
|
emailEnabled: settings.email.enabled,
|
|
pgpKey: user.settings?.pgpKey,
|
|
discordEnabled:
|
|
settings?.discord.enabled && settings.discord.options.enableMentions,
|
|
discordEnabledTypes:
|
|
settings?.discord.enabled && settings.discord.options.enableMentions
|
|
? settings.discord.types
|
|
: 0,
|
|
discordId: user.settings?.discordId,
|
|
pushbulletAccessToken: user.settings?.pushbulletAccessToken,
|
|
pushoverApplicationToken: user.settings?.pushoverApplicationToken,
|
|
pushoverUserKey: user.settings?.pushoverUserKey,
|
|
pushoverSound: user.settings?.pushoverSound,
|
|
telegramEnabled: settings.telegram.enabled,
|
|
telegramBotUsername: settings.telegram.options.botUsername,
|
|
telegramChatId: user.settings?.telegramChatId,
|
|
telegramMessageThreadId: user.settings?.telegramMessageThreadId,
|
|
telegramSendSilently: user.settings?.telegramSendSilently,
|
|
webPushEnabled: settings.webpush.enabled,
|
|
notificationTypes: user.settings?.notificationTypes ?? {},
|
|
});
|
|
} catch (e) {
|
|
next({ status: 500, message: e.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>(
|
|
'/notifications',
|
|
isOwnProfileOrAdmin(),
|
|
async (req, res, next) => {
|
|
const userRepository = getRepository(User);
|
|
|
|
try {
|
|
const user = await userRepository.findOne({
|
|
where: { id: Number(req.params.id) },
|
|
});
|
|
|
|
if (!user) {
|
|
return next({ status: 404, message: 'User not found.' });
|
|
}
|
|
|
|
// "Owner" user settings cannot be modified by other users
|
|
if (user.id === 1 && req.user?.id !== 1) {
|
|
return next({
|
|
status: 403,
|
|
message: "You do not have permission to modify this user's settings.",
|
|
});
|
|
}
|
|
|
|
if (!user.settings) {
|
|
user.settings = new UserSettings({
|
|
user: req.user,
|
|
pgpKey: req.body.pgpKey,
|
|
discordId: req.body.discordId,
|
|
pushbulletAccessToken: req.body.pushbulletAccessToken,
|
|
pushoverApplicationToken: req.body.pushoverApplicationToken,
|
|
pushoverUserKey: req.body.pushoverUserKey,
|
|
telegramChatId: req.body.telegramChatId,
|
|
telegramMessageThreadId: req.body.telegramMessageThreadId,
|
|
telegramSendSilently: req.body.telegramSendSilently,
|
|
notificationTypes: req.body.notificationTypes,
|
|
});
|
|
} else {
|
|
user.settings.pgpKey = req.body.pgpKey;
|
|
user.settings.discordId = req.body.discordId;
|
|
user.settings.pushbulletAccessToken = req.body.pushbulletAccessToken;
|
|
user.settings.pushoverApplicationToken =
|
|
req.body.pushoverApplicationToken;
|
|
user.settings.pushoverUserKey = req.body.pushoverUserKey;
|
|
user.settings.pushoverSound = req.body.pushoverSound;
|
|
user.settings.telegramChatId = req.body.telegramChatId;
|
|
user.settings.telegramMessageThreadId =
|
|
req.body.telegramMessageThreadId;
|
|
user.settings.telegramSendSilently = req.body.telegramSendSilently;
|
|
user.settings.notificationTypes = Object.assign(
|
|
{},
|
|
user.settings.notificationTypes,
|
|
req.body.notificationTypes
|
|
);
|
|
}
|
|
|
|
userRepository.save(user);
|
|
|
|
return res.status(200).json({
|
|
pgpKey: user.settings.pgpKey,
|
|
discordId: user.settings.discordId,
|
|
pushbulletAccessToken: user.settings.pushbulletAccessToken,
|
|
pushoverApplicationToken: user.settings.pushoverApplicationToken,
|
|
pushoverUserKey: user.settings.pushoverUserKey,
|
|
pushoverSound: user.settings.pushoverSound,
|
|
telegramChatId: user.settings.telegramChatId,
|
|
telegramMessageThreadId: user.settings.telegramMessageThreadId,
|
|
telegramSendSilently: user.settings.telegramSendSilently,
|
|
notificationTypes: user.settings.notificationTypes,
|
|
});
|
|
} catch (e) {
|
|
next({ status: 500, message: e.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.get<{ id: string }, { permissions?: number }>(
|
|
'/permissions',
|
|
isAuthenticated(Permission.MANAGE_USERS),
|
|
async (req, res, next) => {
|
|
const userRepository = getRepository(User);
|
|
|
|
try {
|
|
const user = await userRepository.findOne({
|
|
where: { id: Number(req.params.id) },
|
|
});
|
|
|
|
if (!user) {
|
|
return next({ status: 404, message: 'User not found.' });
|
|
}
|
|
|
|
return res.status(200).json({ permissions: user.permissions });
|
|
} catch (e) {
|
|
next({ status: 500, message: e.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
userSettingsRoutes.post<
|
|
{ id: string },
|
|
{ permissions?: number },
|
|
{ permissions: number }
|
|
>(
|
|
'/permissions',
|
|
isAuthenticated(Permission.MANAGE_USERS),
|
|
async (req, res, next) => {
|
|
const userRepository = getRepository(User);
|
|
|
|
try {
|
|
const user = await userRepository.findOne({
|
|
where: { id: Number(req.params.id) },
|
|
});
|
|
|
|
if (!user) {
|
|
return next({ status: 404, message: 'User not found.' });
|
|
}
|
|
|
|
// "Owner" user permissions cannot be modified, and users cannot set their own permissions
|
|
if (user.id === 1 || req.user?.id === user.id) {
|
|
return next({
|
|
status: 403,
|
|
message: 'You do not have permission to modify this user',
|
|
});
|
|
}
|
|
|
|
if (!canMakePermissionsChange(req.body.permissions, req.user)) {
|
|
return next({
|
|
status: 403,
|
|
message: 'You do not have permission to grant this level of access',
|
|
});
|
|
}
|
|
user.permissions = req.body.permissions;
|
|
|
|
await userRepository.save(user);
|
|
|
|
return res.status(200).json({ permissions: user.permissions });
|
|
} catch (e) {
|
|
next({ status: 500, message: e.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
export default userSettingsRoutes;
|