@ -15,8 +15,7 @@ authRoutes.get('/me', isAuthenticated(), async (req, res) => {
if ( ! req . user ) {
if ( ! req . user ) {
return res . status ( 500 ) . json ( {
return res . status ( 500 ) . json ( {
status : 500 ,
status : 500 ,
error :
error : 'Please sign in.' ,
'Requested user endpoint without valid authenticated user in session' ,
} ) ;
} ) ;
}
}
const user = await userRepository . findOneOrFail ( {
const user = await userRepository . findOneOrFail ( {
@ -32,10 +31,13 @@ authRoutes.post('/plex', async (req, res, next) => {
const body = req . body as { authToken? : string } ;
const body = req . body as { authToken? : string } ;
if ( ! body . authToken ) {
if ( ! body . authToken ) {
return res . status ( 500 ) . json ( { error : 'You must provide an auth token' } ) ;
return next ( {
status : 500 ,
message : 'Authentication token required.' ,
} ) ;
}
}
try {
try {
// First we need to use this auth token to get the users email from plex.tv
// First we need to use this auth token to get the user ' s email from plex.tv
const plextv = new PlexTvAPI ( body . authToken ) ;
const plextv = new PlexTvAPI ( body . authToken ) ;
const account = await plextv . getUser ( ) ;
const account = await plextv . getUser ( ) ;
@ -48,30 +50,7 @@ authRoutes.post('/plex', async (req, res, next) => {
} )
} )
. getOne ( ) ;
. getOne ( ) ;
if ( user ) {
if ( ! user && ! ( await userRepository . count ( ) ) ) {
// Let's check if their Plex token is up-to-date
if ( user . plexToken !== body . authToken ) {
user . plexToken = body . authToken ;
}
// Update the user's avatar with their Plex thumbnail, in case it changed
user . avatar = account . thumb ;
user . email = account . email ;
user . plexUsername = account . username ;
// In case the user was previously a local account
if ( user . userType === UserType . LOCAL ) {
user . userType = UserType . PLEX ;
user . plexId = account . id ;
}
await userRepository . save ( user ) ;
} 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 ) {
user = new User ( {
user = new User ( {
email : account.email ,
email : account.email ,
plexUsername : account.username ,
plexUsername : account.username ,
@ -81,38 +60,68 @@ authRoutes.post('/plex', async (req, res, next) => {
avatar : account.thumb ,
avatar : account.thumb ,
userType : UserType.PLEX ,
userType : UserType.PLEX ,
} ) ;
} ) ;
await userRepository . save ( user ) ;
await userRepository . save ( user ) ;
}
} else {
const mainUser = await userRepository . findOneOrFail ( {
select : [ 'id' , 'plexToken' , 'plexId' ] ,
order : { id : 'ASC' } ,
} ) ;
const mainPlexTv = new PlexTvAPI ( mainUser . plexToken ? ? '' ) ;
// Double check that we didn't create the first admin user before running this
if (
if ( ! user ) {
account . id === mainUser . plexId ||
if ( ! settings . main . newPlexLogin ) {
( await mainPlexTv . checkUserAccess ( account . id ) )
) {
if ( user ) {
if ( ! user . plexId ) {
logger . info (
logger . info (
'Failed sign-in attempt from user who has not been imported to Overseerr.' ,
'Found matching Plex user; updating user with Plex data ',
{
{
label : 'Auth' ,
label : 'API' ,
account : {
ip : req.ip ,
. . . account ,
email : user.email ,
authentication_token : '__REDACTED__' ,
userId : user.id ,
authToken : '__REDACTED__' ,
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 ( {
return next ( {
status : 403 ,
status : 403 ,
message : 'Access denied.' ,
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 ,
}
}
) ;
// If we get to this point, the user does not already exist so we need to create the
// user _assuming_ they have access to the Plex server
const mainUser = await userRepository . findOneOrFail ( {
select : [ 'id' , 'plexToken' ] ,
order : { id : 'ASC' } ,
} ) ;
const mainPlexTv = new PlexTvAPI ( mainUser . plexToken ? ? '' ) ;
if ( await mainPlexTv . checkUserAccess ( account . id ) ) {
user = new User ( {
user = new User ( {
email : account.email ,
email : account.email ,
plexUsername : account.username ,
plexUsername : account.username ,
@ -122,17 +131,18 @@ authRoutes.post('/plex', async (req, res, next) => {
avatar : account.thumb ,
avatar : account.thumb ,
userType : UserType.PLEX ,
userType : UserType.PLEX ,
} ) ;
} ) ;
await userRepository . save ( user ) ;
await userRepository . save ( user ) ;
}
} else {
} else {
logger . info (
logger . warn (
'Failed sign-in attempt from user without access to the Plex server. ',
'Failed sign-in attempt by Plex user without access to the media server ',
{
{
label : 'Auth' ,
label : 'API' ,
account : {
ip : req.ip ,
. . . account ,
email : account.email ,
authentication_token : '__REDACTED__' ,
plexId : account.id ,
authToken : '__REDACTED__' ,
plexUsername : account.username ,
} ,
}
}
) ;
) ;
return next ( {
return next ( {
@ -141,7 +151,6 @@ authRoutes.post('/plex', async (req, res, next) => {
} ) ;
} ) ;
}
}
}
}
}
// Set logged in session
// Set logged in session
if ( req . session ) {
if ( req . session ) {
@ -150,10 +159,14 @@ authRoutes.post('/plex', async (req, res, next) => {
return res . status ( 200 ) . json ( user ? . filter ( ) ? ? { } ) ;
return res . status ( 200 ) . json ( user ? . filter ( ) ? ? { } ) ;
} catch ( e ) {
} catch ( e ) {
logger . error ( e . message , { label : 'Auth' } ) ;
logger . error ( 'Something went wrong authenticating with Plex account' , {
label : 'API' ,
errorMessage : e.message ,
ip : req.ip ,
} ) ;
return next ( {
return next ( {
status : 500 ,
status : 500 ,
message : 'Something went wrong.' ,
message : ' Unable to authenticate .',
} ) ;
} ) ;
}
}
} ) ;
} ) ;
@ -164,7 +177,7 @@ authRoutes.post('/local', async (req, res, next) => {
const body = req . body as { email? : string ; password? : string } ;
const body = req . body as { email? : string ; password? : string } ;
if ( ! settings . main . localLogin ) {
if ( ! settings . main . localLogin ) {
return res . status ( 500 ) . json ( { error : ' Local user sign-in is disabled.' } ) ;
return res . status ( 500 ) . json ( { error : ' Password sign-in is disabled.' } ) ;
} else if ( ! body . email || ! body . password ) {
} else if ( ! body . email || ! body . password ) {
return res . status ( 500 ) . json ( {
return res . status ( 500 ) . json ( {
error : 'You must provide both an email address and a password.' ,
error : 'You must provide both an email address and a password.' ,
@ -173,28 +186,77 @@ authRoutes.post('/local', async (req, res, next) => {
try {
try {
const user = await userRepository
const user = await userRepository
. createQueryBuilder ( 'user' )
. createQueryBuilder ( 'user' )
. select ( [ 'user.id' , 'user. password'] )
. select ( [ 'user.id' , 'user. email', 'user. password', 'user.plexI d'] )
. where ( 'user.email = :email' , { email : body.email.toLowerCase ( ) } )
. where ( 'user.email = :email' , { email : body.email.toLowerCase ( ) } )
. getOne ( ) ;
. getOne ( ) ;
const isCorrectCredentials = await user ? . passwordMatch ( body . password ) ;
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.' ,
} ) ;
}
// User doesn't exist or credentials are incorrect
const mainUser = await userRepository . findOneOrFail ( {
if ( ! isCorrectCredentials ) {
select : [ 'id' , 'plexToken' , 'plexId' ] ,
logger . info (
order : { id : 'ASC' } ,
'Failed sign-in attempt from user with incorrect credentials.' ,
} ) ;
const mainPlexTv = new PlexTvAPI ( mainUser . plexToken ? ? '' ) ;
if ( ! user . plexId ) {
const plexUsersResponse = await mainPlexTv . getUsers ( ) ;
const account = plexUsersResponse . MediaContainer . User . find (
( account ) = >
account . $ . email &&
account . $ . email . toLowerCase ( ) === user . email . toLowerCase ( )
) ? . $ ;
if ( account ) {
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 ) ;
}
}
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 : 'Auth' ,
label : 'A PI ',
account : {
account : {
ip : req.ip ,
ip : req.ip ,
email : body.email ,
email : body.email ,
password : '__REDACTED__' ,
userId : user.id ,
plexId : user.plexId ,
} ,
} ,
}
}
) ;
) ;
return next ( {
return next ( {
status : 403 ,
status : 403 ,
message : 'Your sign-in credentials are incorrect.' ,
message : ' Access denied .',
} ) ;
} ) ;
}
}
@ -205,13 +267,18 @@ authRoutes.post('/local', async (req, res, next) => {
return res . status ( 200 ) . json ( user ? . filter ( ) ? ? { } ) ;
return res . status ( 200 ) . json ( user ? . filter ( ) ? ? { } ) ;
} catch ( e ) {
} catch ( e ) {
logger . error ( 'Something went wrong while attempting to authenticate.' , {
logger . error (
label : 'Auth' ,
'Something went wrong authenticating with Overseerr password' ,
error : e.message ,
{
} ) ;
label : 'API' ,
errorMessage : e.message ,
ip : req.ip ,
email : body.email ,
}
) ;
return next ( {
return next ( {
status : 500 ,
status : 500 ,
message : 'Something went wrong.' ,
message : ' Unable to authenticate .',
} ) ;
} ) ;
}
}
} ) ;
} ) ;
@ -221,7 +288,7 @@ authRoutes.post('/logout', (req, res, next) => {
if ( err ) {
if ( err ) {
return next ( {
return next ( {
status : 500 ,
status : 500 ,
message : 'Something went wrong while attempting to sign out .',
message : 'Something went wrong .',
} ) ;
} ) ;
}
}
@ -229,14 +296,15 @@ authRoutes.post('/logout', (req, res, next) => {
} ) ;
} ) ;
} ) ;
} ) ;
authRoutes . post ( '/reset-password' , async ( req , res ) = > {
authRoutes . post ( '/reset-password' , async ( req , res , next ) = > {
const userRepository = getRepository ( User ) ;
const userRepository = getRepository ( User ) ;
const body = req . body as { email? : string } ;
const body = req . body as { email? : string } ;
if ( ! body . email ) {
if ( ! body . email ) {
return res
return next ( {
. status ( 500 )
status : 500 ,
. json ( { error : 'You must provide an email address.' } ) ;
message : 'Email address required.' ,
} ) ;
}
}
const user = await userRepository
const user = await userRepository
@ -247,14 +315,16 @@ authRoutes.post('/reset-password', async (req, res) => {
if ( user ) {
if ( user ) {
await user . resetPassword ( ) ;
await user . resetPassword ( ) ;
userRepository . save ( user ) ;
userRepository . save ( user ) ;
logger . info ( 'Successful request made for recovery link.' , {
logger . info ( 'Successfully sent password reset link' , {
label : 'User Management' ,
label : 'API' ,
context : { ip : req.ip , email : body.email } ,
ip : req.ip ,
email : body.email ,
} ) ;
} ) ;
} else {
} else {
logger . info ( 'Failed request made to reset a password.' , {
logger . error ( 'Something went wrong sending password reset link' , {
label : 'User Management' ,
label : 'API' ,
context : { ip : req.ip , email : body.email } ,
ip : req.ip ,
email : body.email ,
} ) ;
} ) ;
}
}
@ -264,15 +334,16 @@ authRoutes.post('/reset-password', async (req, res) => {
authRoutes . post ( '/reset-password/:guid' , async ( req , res , next ) = > {
authRoutes . post ( '/reset-password/:guid' , async ( req , res , next ) = > {
const userRepository = getRepository ( User ) ;
const userRepository = getRepository ( User ) ;
try {
if ( ! req . body . password || req . body . password ? . length < 8 ) {
if ( ! req . body . password || req . body . password ? . length < 8 ) {
const message =
logger . warn ( 'Failed password reset attempt using invalid new password' , {
'Failed to reset password. Password must be at least 8 characters long.' ;
label : 'API' ,
logger . info ( message , {
ip : req.ip ,
label : 'User Management' ,
guid : req.params.guid ,
context : { ip : req.ip , guid : req.params.guid } ,
} ) ;
return next ( {
status : 500 ,
message : 'Password must be at least 8 characters long.' ,
} ) ;
} ) ;
return next ( { status : 500 , message : message } ) ;
}
}
const user = await userRepository . findOne ( {
const user = await userRepository . findOne ( {
@ -280,32 +351,44 @@ authRoutes.post('/reset-password/:guid', async (req, res, next) => {
} ) ;
} ) ;
if ( ! user ) {
if ( ! user ) {
throw new Error ( 'Guid invalid.' ) ;
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 (
if (
! user . recoveryLinkExpirationDate ||
! user . recoveryLinkExpirationDate ||
user . recoveryLinkExpirationDate <= new Date ( )
user . recoveryLinkExpirationDate <= new Date ( )
) {
) {
throw new Error ( 'Recovery link expired.' ) ;
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.' ,
} ) ;
}
}
await user . setPassword ( req . body . password ) ;
await user . setPassword ( req . body . password ) ;
user . recoveryLinkExpirationDate = null ;
user . recoveryLinkExpirationDate = null ;
userRepository . save ( user ) ;
userRepository . save ( user ) ;
logger . info ( ` Successfully reset password ` , {
logger . info ( 'Successfully reset password' , {
label : 'User Management' ,
label : 'API' ,
context : { ip : req.ip , guid : req.params.guid , email : user.email } ,
ip : req.ip ,
guid : req.params.guid ,
email : user.email ,
} ) ;
} ) ;
return res . status ( 200 ) . json ( { status : 'ok' } ) ;
return res . status ( 200 ) . json ( { status : 'ok' } ) ;
} catch ( e ) {
logger . info ( ` Failed to reset password. ${ e . message } ` , {
label : 'User Management' ,
context : { ip : req.ip , guid : req.params.guid } ,
} ) ;
return res . status ( 200 ) . json ( { status : 'ok' } ) ;
}
} ) ;
} ) ;
export default authRoutes ;
export default authRoutes ;