mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-31 19:59:31 -05:00
feat:Remove Requirement for Jellyfin Passwords
This commit is contained in:
608
routes/auth.ts
Normal file
608
routes/auth.ts
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
import { Router } from 'express';
|
||||||
|
import { getRepository } from 'typeorm';
|
||||||
|
import JellyfinAPI from '../api/jellyfin';
|
||||||
|
import PlexTvAPI from '../api/plextv';
|
||||||
|
import { MediaServerType } from '../constants/server';
|
||||||
|
import { UserType } from '../constants/user';
|
||||||
|
import { User } from '../entity/User';
|
||||||
|
import { Permission } from '../lib/permissions';
|
||||||
|
import { getSettings } from '../lib/settings';
|
||||||
|
import logger from '../logger';
|
||||||
|
import { isAuthenticated } from '../middleware/auth';
|
||||||
|
|
||||||
|
const authRoutes = Router();
|
||||||
|
|
||||||
|
authRoutes.get('/me', isAuthenticated(), async (req, res) => {
|
||||||
|
const userRepository = getRepository(User);
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(500).json({
|
||||||
|
status: 500,
|
||||||
|
error: 'Please sign in.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const user = await userRepository.findOneOrFail({
|
||||||
|
where: { id: req.user.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
authRoutes.post('/plex', async (req, res, next) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
const userRepository = getRepository(User);
|
||||||
|
const body = req.body as { authToken?: string };
|
||||||
|
|
||||||
|
if (!body.authToken) {
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Authentication token required.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
settings.main.mediaServerType != MediaServerType.PLEX &&
|
||||||
|
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED
|
||||||
|
) {
|
||||||
|
return res.status(500).json({ error: 'Plex login is disabled' });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// First we need to use this auth token to get the user's email from plex.tv
|
||||||
|
const plextv = new PlexTvAPI(body.authToken);
|
||||||
|
const account = await plextv.getUser();
|
||||||
|
|
||||||
|
// Next let's see if the user already exists
|
||||||
|
let user = await userRepository
|
||||||
|
.createQueryBuilder('user')
|
||||||
|
.where('user.plexId = :id', { id: account.id })
|
||||||
|
.orWhere('user.email = :email', {
|
||||||
|
email: account.email.toLowerCase(),
|
||||||
|
})
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
if (!user && !(await userRepository.count())) {
|
||||||
|
user = new User({
|
||||||
|
email: account.email,
|
||||||
|
plexUsername: account.username,
|
||||||
|
plexId: account.id,
|
||||||
|
plexToken: account.authToken,
|
||||||
|
permissions: Permission.ADMIN,
|
||||||
|
avatar: account.thumb,
|
||||||
|
userType: UserType.PLEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
await userRepository.save(user);
|
||||||
|
} else {
|
||||||
|
const mainUser = await userRepository.findOneOrFail({
|
||||||
|
select: ['id', 'plexToken', 'plexId'],
|
||||||
|
order: { id: 'ASC' },
|
||||||
|
});
|
||||||
|
const mainPlexTv = new PlexTvAPI(mainUser.plexToken ?? '');
|
||||||
|
|
||||||
|
if (
|
||||||
|
account.id === mainUser.plexId ||
|
||||||
|
(await mainPlexTv.checkUserAccess(account.id))
|
||||||
|
) {
|
||||||
|
if (user) {
|
||||||
|
if (!user.plexId) {
|
||||||
|
logger.info(
|
||||||
|
'Found matching Plex user; updating user with Plex data',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
email: user.email,
|
||||||
|
userId: user.id,
|
||||||
|
plexId: account.id,
|
||||||
|
plexUsername: account.username,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.plexToken = body.authToken;
|
||||||
|
user.plexId = account.id;
|
||||||
|
user.avatar = account.thumb;
|
||||||
|
user.email = account.email;
|
||||||
|
user.plexUsername = account.username;
|
||||||
|
user.userType = UserType.PLEX;
|
||||||
|
|
||||||
|
await userRepository.save(user);
|
||||||
|
} else if (!settings.main.newPlexLogin) {
|
||||||
|
logger.warn(
|
||||||
|
'Failed sign-in attempt by unimported Plex user with access to the media server',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
email: account.email,
|
||||||
|
plexId: account.id,
|
||||||
|
plexUsername: account.username,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return next({
|
||||||
|
status: 403,
|
||||||
|
message: 'Access denied.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.info(
|
||||||
|
'Sign-in attempt from Plex user with access to the media server; creating new Overseerr user',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
email: account.email,
|
||||||
|
plexId: account.id,
|
||||||
|
plexUsername: account.username,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
user = new User({
|
||||||
|
email: account.email,
|
||||||
|
plexUsername: account.username,
|
||||||
|
plexId: account.id,
|
||||||
|
plexToken: account.authToken,
|
||||||
|
permissions: settings.main.defaultPermissions,
|
||||||
|
avatar: account.thumb,
|
||||||
|
userType: UserType.PLEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
await userRepository.save(user);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
'Failed sign-in attempt by Plex user without access to the media server',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
email: account.email,
|
||||||
|
plexId: account.id,
|
||||||
|
plexUsername: account.username,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return next({
|
||||||
|
status: 403,
|
||||||
|
message: 'Access denied.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set logged in session
|
||||||
|
if (req.session) {
|
||||||
|
req.session.userId = user.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(user?.filter() ?? {});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Something went wrong authenticating with Plex account', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
ip: req.ip,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to authenticate.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
const userRepository = getRepository(User);
|
||||||
|
const body = req.body as {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
hostname?: string;
|
||||||
|
email?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Make sure jellyfin login is enabled, but only if jellyfin is not already configured
|
||||||
|
if (
|
||||||
|
settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
|
||||||
|
settings.jellyfin.hostname !== ''
|
||||||
|
) {
|
||||||
|
return res.status(500).json({ error: 'Jellyfin login is disabled' });
|
||||||
|
} else if (!body.username) {
|
||||||
|
return res.status(500).json({ error: 'You must provide an username' });
|
||||||
|
} else if (settings.jellyfin.hostname !== '' && body.hostname) {
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ error: 'Jellyfin hostname already configured' });
|
||||||
|
} else if (settings.jellyfin.hostname === '' && !body.hostname) {
|
||||||
|
return res.status(500).json({ error: 'No hostname provided.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const hostname =
|
||||||
|
settings.jellyfin.hostname !== ''
|
||||||
|
? settings.jellyfin.hostname
|
||||||
|
: body.hostname;
|
||||||
|
const { externalHostname } = getSettings().jellyfin;
|
||||||
|
|
||||||
|
// Try to find deviceId that corresponds to jellyfin user, else generate a new one
|
||||||
|
let user = await userRepository.findOne({
|
||||||
|
where: { jellyfinUsername: body.username },
|
||||||
|
});
|
||||||
|
|
||||||
|
let deviceId = '';
|
||||||
|
if (user) {
|
||||||
|
deviceId = user.jellyfinDeviceId ?? '';
|
||||||
|
} else {
|
||||||
|
deviceId = Buffer.from(`BOT_overseerr_${body.username ?? ''}`).toString(
|
||||||
|
'base64'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// First we need to attempt to log the user in to jellyfin
|
||||||
|
const jellyfinserver = new JellyfinAPI(hostname ?? '', undefined, deviceId);
|
||||||
|
const jellyfinHost =
|
||||||
|
externalHostname && externalHostname.length > 0
|
||||||
|
? externalHostname
|
||||||
|
: hostname;
|
||||||
|
|
||||||
|
const account = await jellyfinserver.login(body.username, body.password);
|
||||||
|
// Next let's see if the user already exists
|
||||||
|
user = await userRepository.findOne({
|
||||||
|
where: { jellyfinUserId: account.User.Id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
// Let's check if their authtoken is up to date
|
||||||
|
if (user.jellyfinAuthToken !== account.AccessToken) {
|
||||||
|
user.jellyfinAuthToken = account.AccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the users avatar with their jellyfin profile pic (incase it changed)
|
||||||
|
if (account.User.PrimaryImageTag) {
|
||||||
|
user.avatar = `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`;
|
||||||
|
} else {
|
||||||
|
user.avatar = '/os_logo_square.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
user.jellyfinUsername = account.User.Name;
|
||||||
|
|
||||||
|
if (user.username === account.User.Name) {
|
||||||
|
user.username = '';
|
||||||
|
}
|
||||||
|
await userRepository.save(user);
|
||||||
|
} else if (!settings.main.newPlexLogin) {
|
||||||
|
logger.warn(
|
||||||
|
'Failed sign-in attempt by unimported Jellyfin user with access to the media server',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
jellyfinUserId: account.User.Id,
|
||||||
|
jellyfinUsername: account.User.Name,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return next({
|
||||||
|
status: 403,
|
||||||
|
message: 'Access denied.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Here we check if it's the first user. If it is, we create the user with no check
|
||||||
|
// and give them admin permissions
|
||||||
|
const totalUsers = await userRepository.count();
|
||||||
|
if (totalUsers === 0) {
|
||||||
|
logger.info(
|
||||||
|
'Sign-in attempt from Jellyfin user with access to the media server; creating initial admin user for Overseerr',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
jellyfinUsername: account.User.Name,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
user = new User({
|
||||||
|
email: body.email,
|
||||||
|
jellyfinUsername: account.User.Name,
|
||||||
|
jellyfinUserId: account.User.Id,
|
||||||
|
jellyfinDeviceId: deviceId,
|
||||||
|
jellyfinAuthToken: account.AccessToken,
|
||||||
|
permissions: Permission.ADMIN,
|
||||||
|
avatar: account.User.PrimaryImageTag
|
||||||
|
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
|
||||||
|
: '/os_logo_square.png',
|
||||||
|
userType: UserType.JELLYFIN,
|
||||||
|
});
|
||||||
|
await userRepository.save(user);
|
||||||
|
|
||||||
|
//Update hostname in settings if it doesn't exist (initial configuration)
|
||||||
|
//Also set mediaservertype to JELLYFIN
|
||||||
|
if (settings.jellyfin.hostname === '') {
|
||||||
|
settings.main.mediaServerType = MediaServerType.JELLYFIN;
|
||||||
|
settings.jellyfin.hostname = body.hostname ?? '';
|
||||||
|
settings.jellyfin.serverId = account.User.ServerId;
|
||||||
|
settings.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
if (!body.email) {
|
||||||
|
throw new Error('add_email');
|
||||||
|
}
|
||||||
|
|
||||||
|
user = new User({
|
||||||
|
email: body.email,
|
||||||
|
jellyfinUsername: account.User.Name,
|
||||||
|
jellyfinUserId: account.User.Id,
|
||||||
|
jellyfinDeviceId: deviceId,
|
||||||
|
jellyfinAuthToken: account.AccessToken,
|
||||||
|
permissions: settings.main.defaultPermissions,
|
||||||
|
avatar: account.User.PrimaryImageTag
|
||||||
|
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
|
||||||
|
: '/os_logo_square.png',
|
||||||
|
userType: UserType.JELLYFIN,
|
||||||
|
});
|
||||||
|
//initialize Jellyfin/Emby users with local login
|
||||||
|
const passedExplicitPassword =
|
||||||
|
body.password && body.password.length > 0;
|
||||||
|
if (passedExplicitPassword) {
|
||||||
|
await user.setPassword(body.password ?? '');
|
||||||
|
}
|
||||||
|
await userRepository.save(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set logged in session
|
||||||
|
if (req.session) {
|
||||||
|
req.session.userId = user?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(user?.filter() ?? {});
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message === 'Unauthorized') {
|
||||||
|
logger.info(
|
||||||
|
'Failed login attempt from user with incorrect Jellyfin credentials',
|
||||||
|
{
|
||||||
|
label: 'Auth',
|
||||||
|
account: {
|
||||||
|
ip: req.ip,
|
||||||
|
email: body.username,
|
||||||
|
password: '__REDACTED__',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return next({
|
||||||
|
status: 401,
|
||||||
|
message: 'Unauthorized',
|
||||||
|
});
|
||||||
|
} else if (e.message === 'add_email') {
|
||||||
|
return next({
|
||||||
|
status: 406,
|
||||||
|
message: 'CREDENTIAL_ERROR_ADD_EMAIL',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.error(e.message, { label: 'Auth' });
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
authRoutes.post('/local', async (req, res, next) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
const userRepository = getRepository(User);
|
||||||
|
const body = req.body as { email?: string; password?: string };
|
||||||
|
|
||||||
|
if (!settings.main.localLogin) {
|
||||||
|
return res.status(500).json({ error: 'Password sign-in is disabled.' });
|
||||||
|
} else if (!body.email || !body.password) {
|
||||||
|
return res.status(500).json({
|
||||||
|
error: 'You must provide both an email address and a password.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const user = await userRepository
|
||||||
|
.createQueryBuilder('user')
|
||||||
|
.select(['user.id', 'user.email', 'user.password', 'user.plexId'])
|
||||||
|
.where('user.email = :email', { email: body.email.toLowerCase() })
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
if (!user || !(await user.passwordMatch(body.password))) {
|
||||||
|
logger.warn('Failed sign-in attempt using invalid Overseerr password', {
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
email: body.email,
|
||||||
|
userId: user?.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 403,
|
||||||
|
message: 'Access denied.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainUser = await userRepository.findOneOrFail({
|
||||||
|
select: ['id', 'plexToken', 'plexId'],
|
||||||
|
order: { id: 'ASC' },
|
||||||
|
});
|
||||||
|
const mainPlexTv = new PlexTvAPI(mainUser.plexToken ?? '');
|
||||||
|
|
||||||
|
if (!user.plexId) {
|
||||||
|
try {
|
||||||
|
const plexUsersResponse = await mainPlexTv.getUsers();
|
||||||
|
const account = plexUsersResponse.MediaContainer.User.find(
|
||||||
|
(account) =>
|
||||||
|
account.$.email &&
|
||||||
|
account.$.email.toLowerCase() === user.email.toLowerCase()
|
||||||
|
)?.$;
|
||||||
|
|
||||||
|
if (
|
||||||
|
account &&
|
||||||
|
(await mainPlexTv.checkUserAccess(parseInt(account.id)))
|
||||||
|
) {
|
||||||
|
logger.info(
|
||||||
|
'Found matching Plex user; updating user with Plex data',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
email: body.email,
|
||||||
|
userId: user.id,
|
||||||
|
plexId: account.id,
|
||||||
|
plexUsername: account.username,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
user.plexId = parseInt(account.id);
|
||||||
|
user.avatar = account.thumb;
|
||||||
|
user.email = account.email;
|
||||||
|
user.plexUsername = account.username;
|
||||||
|
user.userType = UserType.PLEX;
|
||||||
|
|
||||||
|
await userRepository.save(user);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Something went wrong fetching Plex users', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
user.plexId &&
|
||||||
|
user.plexId !== mainUser.plexId &&
|
||||||
|
!(await mainPlexTv.checkUserAccess(user.plexId))
|
||||||
|
) {
|
||||||
|
logger.warn(
|
||||||
|
'Failed sign-in attempt from Plex user without access to the media server',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
account: {
|
||||||
|
ip: req.ip,
|
||||||
|
email: body.email,
|
||||||
|
userId: user.id,
|
||||||
|
plexId: user.plexId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return next({
|
||||||
|
status: 403,
|
||||||
|
message: 'Access denied.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set logged in session
|
||||||
|
if (user && req.session) {
|
||||||
|
req.session.userId = user.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(user?.filter() ?? {});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
'Something went wrong authenticating with Overseerr password',
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
ip: req.ip,
|
||||||
|
email: body.email,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to authenticate.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
authRoutes.post('/logout', (req, res, next) => {
|
||||||
|
req.session?.destroy((err) => {
|
||||||
|
if (err) {
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ status: 'ok' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
authRoutes.post('/reset-password', async (req, res, next) => {
|
||||||
|
const userRepository = getRepository(User);
|
||||||
|
const body = req.body as { email?: string };
|
||||||
|
|
||||||
|
if (!body.email) {
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Email address required.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await userRepository
|
||||||
|
.createQueryBuilder('user')
|
||||||
|
.where('user.email = :email', { email: body.email.toLowerCase() })
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
await user.resetPassword();
|
||||||
|
userRepository.save(user);
|
||||||
|
logger.info('Successfully sent password reset link', {
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
email: body.email,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.error('Something went wrong sending password reset link', {
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
email: body.email,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ status: 'ok' });
|
||||||
|
});
|
||||||
|
|
||||||
|
authRoutes.post('/reset-password/:guid', async (req, res, next) => {
|
||||||
|
const userRepository = getRepository(User);
|
||||||
|
|
||||||
|
if (!req.body.password || req.body.password?.length < 8) {
|
||||||
|
logger.warn('Failed password reset attempt using invalid new password', {
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
guid: req.params.guid,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Password must be at least 8 characters long.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await userRepository.findOne({
|
||||||
|
where: { resetPasswordGuid: req.params.guid },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
logger.warn('Failed password reset attempt using invalid recovery link', {
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
guid: req.params.guid,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Invalid password reset link.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!user.recoveryLinkExpirationDate ||
|
||||||
|
user.recoveryLinkExpirationDate <= new Date()
|
||||||
|
) {
|
||||||
|
logger.warn('Failed password reset attempt using expired recovery link', {
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
guid: req.params.guid,
|
||||||
|
email: user.email,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Invalid password reset link.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
user.recoveryLinkExpirationDate = null;
|
||||||
|
userRepository.save(user);
|
||||||
|
logger.info('Successfully reset password', {
|
||||||
|
label: 'API',
|
||||||
|
ip: req.ip,
|
||||||
|
guid: req.params.guid,
|
||||||
|
email: user.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ status: 'ok' });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default authRoutes;
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { Field, Form, Formik } from 'formik';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import Button from '../Common/Button';
|
|
||||||
|
|
||||||
import { Field, Form, Formik } from 'formik';
|
|
||||||
import * as Yup from 'yup';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
import * as Yup from 'yup';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
|
import Button from '../Common/Button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
username: 'Username',
|
username: 'Username',
|
||||||
@@ -63,9 +62,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
|||||||
username: Yup.string().required(
|
username: Yup.string().required(
|
||||||
intl.formatMessage(messages.validationusernamerequired)
|
intl.formatMessage(messages.validationusernamerequired)
|
||||||
),
|
),
|
||||||
password: Yup.string().required(
|
password: Yup.string(),
|
||||||
intl.formatMessage(messages.validationpasswordrequired)
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
@@ -201,9 +198,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
|||||||
username: Yup.string().required(
|
username: Yup.string().required(
|
||||||
intl.formatMessage(messages.validationusernamerequired)
|
intl.formatMessage(messages.validationusernamerequired)
|
||||||
),
|
),
|
||||||
password: Yup.string().required(
|
password: Yup.string(),
|
||||||
intl.formatMessage(messages.validationpasswordrequired)
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user