Merge pull request #124 from KevinMidboe/feature/plex-authentication

Feature plex authentication
This commit is contained in:
2019-12-23 01:39:43 +01:00
committed by GitHub
20 changed files with 597 additions and 81 deletions

View File

@@ -11,6 +11,11 @@
"plex": { "plex": {
"ip": "" "ip": ""
}, },
"tautulli": {
"apiKey": "",
"ip": "",
"port": ""
},
"raven": { "raven": {
"DSN": "" "DSN": ""
}, },

View File

@@ -22,6 +22,7 @@
"body-parser": "~1.18.2", "body-parser": "~1.18.2",
"cross-env": "~5.1.4", "cross-env": "~5.1.4",
"express": "~4.16.0", "express": "~4.16.0",
"form-data": "^2.5.1",
"jsonwebtoken": "^8.2.0", "jsonwebtoken": "^8.2.0",
"km-moviedb": "^0.2.12", "km-moviedb": "^0.2.12",
"node-cache": "^4.1.1", "node-cache": "^4.1.1",

View File

@@ -1,11 +1,19 @@
CREATE TABLE IF NOT EXISTS user ( CREATE TABLE IF NOT EXISTS user (
user_name varchar(127) UNIQUE, user_name varchar(127) UNIQUE,
password varchar(127), password varchar(127),
email varchar(127) UNIQUE,
admin boolean DEFAULT 0, admin boolean DEFAULT 0,
email varchar(127) UNIQUE,
primary key (user_name) primary key (user_name)
); );
CREATE TABLE IF NOT EXISTS settings (
user_name varchar(127) UNIQUE,
dark_mode boolean DEFAULT 0,
plex_userid varchar(127) DEFAULT NULL,
emoji varchar(16) DEFAULT NULL,
foreign key(user_name) REFERENCES user(user_name) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS cache ( CREATE TABLE IF NOT EXISTS cache (
key varchar(255), key varchar(255),
value blob, value blob,
@@ -28,12 +36,13 @@ CREATE TABLE IF NOT EXISTS requests(
year NUMBER, year NUMBER,
poster_path TEXT DEFAULT NULL, poster_path TEXT DEFAULT NULL,
background_path TEXT DEFAULT NULL, background_path TEXT DEFAULT NULL,
requested_by TEXT, requested_by varchar(127) DEFAULT NULL,
ip TEXT, ip TEXT,
date DATE DEFAULT CURRENT_TIMESTAMP, date DATE DEFAULT CURRENT_TIMESTAMP,
status CHAR(25) DEFAULT 'requested' NOT NULL, status CHAR(25) DEFAULT 'requested' NOT NULL,
user_agent CHAR(255) DEFAULT NULL, user_agent CHAR(255) DEFAULT NULL,
type CHAR(50) DEFAULT 'movie' type CHAR(50) DEFAULT 'movie',
foreign key(requested_by) REFERENCES user(user_name) ON DELETE SET NULL
); );
CREATE TABLE IF NOT EXISTS request( CREATE TABLE IF NOT EXISTS request(

View File

@@ -1,4 +1,5 @@
DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS settings;
DROP TABLE IF EXISTS search_history; DROP TABLE IF EXISTS search_history;
DROP TABLE IF EXISTS requests; DROP TABLE IF EXISTS requests;
DROP TABLE IF EXISTS request; DROP TABLE IF EXISTS request;

View File

@@ -6,6 +6,7 @@ class SqliteDatabase {
constructor(host) { constructor(host) {
this.host = host; this.host = host;
this.connection = new sqlite3.Database(this.host); this.connection = new sqlite3.Database(this.host);
this.execute('pragma foreign_keys = on;');
this.schemaDirectory = path.join(__dirname, 'schemas'); this.schemaDirectory = path.join(__dirname, 'schemas');
} }

View File

@@ -86,18 +86,18 @@ class RequestRepository {
* @param {tmdb} tmdb class of movie|show to add * @param {tmdb} tmdb class of movie|show to add
* @returns {Promise} * @returns {Promise}
*/ */
requestFromTmdb(tmdb, ip, user_agent, user) { requestFromTmdb(tmdb, ip, user_agent, username) {
return Promise.resolve() return Promise.resolve()
.then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type]))
.then(row => assert.equal(row, undefined, 'Id has already been requested')) .then(row => assert.equal(row, undefined, 'Id has already been requested'))
.then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, user, ip, user_agent, tmdb.type])) .then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, username, ip, user_agent, tmdb.type]))
.catch((error) => { .catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('been requested')) { if (error.name === 'AssertionError' || error.message.endsWith('been requested')) {
throw new Error('This id is already requested', error.message); throw new Error('This id is already requested', error.message);
} }
console.log('Error @ request.addTmdb:', error); console.log('Error @ request.addTmdb:', error);
throw new Error('Could not add request'); throw new Error('Could not add request');
}); });
} }
/** /**

View File

@@ -28,17 +28,23 @@ class SearchHistory {
/** /**
* Creates a new search entry in the database. * Creates a new search entry in the database.
* @param {User} user a new user * @param {String} username logged in user doing the search
* @param {String} searchQuery the query the user searched for * @param {String} searchQuery the query the user searched for
* @returns {Promise} * @returns {Promise}
*/ */
create(user, searchQuery) { create(username, searchQuery) {
return Promise.resolve() return this.database.run(this.queries.create, [searchQuery, username])
.then(() => this.database.run(this.queries.create, [searchQuery, user])) .catch(error => {
.catch((error) => {
if (error.message.includes('FOREIGN')) { if (error.message.includes('FOREIGN')) {
throw new Error('Could not create search history.'); throw new Error('Could not create search history.');
} }
throw {
success: false,
status: 500,
message: 'An unexpected error occured',
source: 'database'
}
}); });
} }
} }

View File

@@ -0,0 +1,58 @@
const fetch = require('node-fetch');
class Tautulli {
constructor(apiKey, ip, port) {
this.apiKey = apiKey;
this.ip = ip;
this.port = port;
}
buildUrlWithCmdAndUserid(cmd, user_id) {
const url = new URL('api/v2', `http://${this.ip}:${this.port}`)
url.searchParams.append('apikey', this.apiKey)
url.searchParams.append('cmd', cmd)
url.searchParams.append('user_id', user_id)
return url
}
getPlaysByDayOfWeek(plex_userid, days, y_axis) {
const url = this.buildUrlWithCmdAndUserid('get_plays_by_dayofweek', plex_userid)
url.searchParams.append('time_range', days)
url.searchParams.append('y_axis', y_axis)
return fetch(url.href)
.then(resp => resp.json())
}
getPlaysByDays(plex_userid, days, y_axis) {
const url = this.buildUrlWithCmdAndUserid('get_plays_by_date', plex_userid)
url.searchParams.append('time_range', days)
url.searchParams.append('y_axis', y_axis)
return fetch(url.href)
.then(resp => resp.json())
}
watchTimeStats(plex_userid) {
const url = this.buildUrlWithCmdAndUserid('get_user_watch_time_stats', plex_userid)
url.searchParams.append('grouping', 0)
return fetch(url.href)
.then(resp => resp.json())
}
viewHistory(plex_userid) {
const url = this.buildUrlWithCmdAndUserid('get_history', plex_userid)
url.searchParams.append('start', 0)
url.searchParams.append('length', 50)
console.log('fetching url', url.href)
return fetch(url.href)
.then(resp => resp.json())
}
}
module.exports = Tautulli;

View File

@@ -2,64 +2,218 @@ const assert = require('assert');
const establishedDatabase = require('src/database/database'); const establishedDatabase = require('src/database/database');
class UserRepository { class UserRepository {
constructor(database) { constructor(database) {
this.database = database || establishedDatabase; this.database = database || establishedDatabase;
this.queries = { this.queries = {
read: 'select * from user where lower(user_name) = lower(?)', read: 'select * from user where lower(user_name) = lower(?)',
create: 'insert into user (user_name) values (?)', create: 'insert into user (user_name) values (?)',
change: 'update user set password = ? where user_name = ?', change: 'update user set password = ? where user_name = ?',
retrieveHash: 'select * from user where user_name = ?', retrieveHash: 'select * from user where user_name = ?',
getAdminStateByUser: 'select admin from user where user_name = ?' getAdminStateByUser: 'select admin from user where user_name = ?',
}; link: 'update settings set plex_userid = ? where user_name = ?',
} unlink: 'update settings set plex_userid = null where user_name = ?',
createSettings: 'insert into settings (user_name) values (?)',
updateSettings: 'update settings set user_name = ?, dark_mode = ?, emoji = ?',
getSettings: 'select * from settings where user_name = ?'
};
}
/** /**
* Create a user in a database. * Create a user in a database.
* @param {User} user the user you want to create * @param {User} user the user you want to create
* @returns {Promise} * @returns {Promise}
*/ */
create(user) { create(user) {
return Promise.resolve() return this.database.get(this.queries.read, user.username)
.then(() => this.database.get(this.queries.read, user.username)) .then(() => this.database.run(this.queries.create, user.username))
.then(() => this.database.run(this.queries.create, user.username)) .catch((error) => {
.catch((error) => { if (error.name === 'AssertionError' || error.message.endsWith('user_name')) {
if (error.name === 'AssertionError' || error.message.endsWith('user_name')) { throw new Error('That username is already registered');
throw new Error('That username is already registered'); }
} throw Error(error)
throw Error(error)
});
}
/**
* Retrieve a password from a database.
* @param {User} user the user you want to retrieve the password
* @returns {Promise}
*/
retrieveHash(user) {
return Promise.resolve()
.then(() => this.database.get(this.queries.retrieveHash, user.username))
.then((row) => {
assert(row, 'The user does not exist.');
return row.password;
})
.catch((err) => { console.log(error); throw new Error('Unable to find your user.'); });
}
/**
* Change a user's password in a database.
* @param {User} user the user you want to create
* @param {String} password the new password you want to change
* @returns {Promise}
*/
changePassword(user, password) {
return Promise.resolve(this.database.run(this.queries.change, [password, user.username]));
}
checkAdmin(user) {
return this.database.get(this.queries.getAdminStateByUser, user.username).then((row) => {
return row.admin;
}); });
} }
/**
* Retrieve a password from a database.
* @param {User} user the user you want to retrieve the password
* @returns {Promise}
*/
retrieveHash(user) {
return this.database.get(this.queries.retrieveHash, user.username)
.then(row => {
assert(row, 'The user does not exist.');
return row.password;
})
.catch(err => { console.log(error); throw new Error('Unable to find your user.'); })
}
/**
* Change a user's password in a database.
* @param {User} user the user you want to create
* @param {String} password the new password you want to change
* @returns {Promise}
*/
changePassword(user, password) {
return this.database.run(this.queries.change, [password, user.username])
}
/**
* Link plex userid with seasoned user
* @param {String} username the user you want to lunk plex userid with
* @param {Number} plexUserID plex unique id
* @returns {Promsie}
*/
linkPlexUserId(username, plexUserID) {
return new Promise((resolve, reject) => {
this.database.run(this.queries.link, [plexUserID, username])
.then(row => resolve(row))
.catch(error => {
// TODO log this unknown db error
console.log('db error', error)
reject({
status: 500,
message: 'An unexpected error occured while linking plex and seasoned accounts',
source: 'seasoned database'
})
})
})
}
/**
* Unlink plex userid with seasoned user
* @param {User} user the user you want to lunk plex userid with
* @returns {Promsie}
*/
unlinkPlexUserId(username) {
return new Promise((resolve, reject) => {
this.database.run(this.queries.unlink, username)
.then(row => resolve(row))
.catch(error => {
// TODO log this unknown db error
console.log('db error', error)
reject({
status: 500,
message: 'An unexpected error occured while unlinking plex and seasoned accounts',
source: 'seasoned database'
})
})
})
}
/**
* Check if the user has boolean flag set for admin in database
* @param {User} user object
* @returns {Promsie}
*/
checkAdmin(user) {
return this.database.get(this.queries.getAdminStateByUser, user.username)
.then((row) => row.admin);
}
/**
* Get settings for user matching string username
* @param {String} username
* @returns {Promsie}
*/
getSettings(username) {
return new Promise((resolve, reject) => {
this.database.get(this.queries.getSettings, username)
.then(async (row) => {
if (row == null) {
console.log(`settings do not exist for user: ${username}. Creating settings entry.`)
const userExistsWithUsername = await this.database.get('select * from user where user_name is ?', username)
if (userExistsWithUsername !== undefined) {
try {
resolve(this.dbCreateSettings(username))
} catch (error) {
reject(error)
}
} else {
reject({
status: 404,
message: 'User not found, no settings to get'
})
}
}
resolve(row)
})
.catch(error => {
console.error('Unexpected error occured while fetching settings for your account. Error:', error)
reject({
status: 500,
message: 'An unexpected error occured while fetching settings for your account',
source: 'seasoned database'
})
})
})
}
/**
* Update settings values for user matching string username
* @param {String} username
* @param {String} dark_mode
* @param {String} emoji
* @returns {Promsie}
*/
updateSettings(username, dark_mode=undefined, emoji=undefined) {
const settings = this.getSettings(username)
dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode
emoji = emoji !== undefined ? emoji : settings.emoji
return this.dbUpdateSettings(username, dark_mode, emoji)
.catch(error => {
if (error.status && error.message) {
return error
}
return {
status: 500,
message: 'An unexpected error occured while updating settings for your account'
}
})
}
/**
* Helper function for creating settings in the database
* @param {String} username
* @returns {Promsie}
*/
dbCreateSettings(username) {
return this.database.run(this.queries.createSettings, username)
.then(() => this.database.get(this.queries.getSettings, username))
.catch(error => rejectUnexpectedDatabaseError('Unexpected error occured while creating settings', 503, error))
}
/**
* Helper function for updating settings in the database
* @param {String} username
* @returns {Promsie}
*/
dbUpdateSettings(username, dark_mode, emoji) {
return new Promise((resolve, reject) =>
this.database.run(this.queries.updateSettings, [username, dark_mode, emoji])
.then(row => resolve(row)))
}
}
const rejectUnexpectedDatabaseError = (message, status, error, reject=null) => {
console.error(error)
const body = {
status,
message,
source: 'seasoned database'
}
if (reject == null) {
return new Promise((resolve, reject) => reject(body))
}
reject(body)
} }
module.exports = UserRepository; module.exports = UserRepository;

View File

@@ -4,9 +4,13 @@ const bodyParser = require('body-parser');
const tokenToUser = require('./middleware/tokenToUser'); const tokenToUser = require('./middleware/tokenToUser');
const mustBeAuthenticated = require('./middleware/mustBeAuthenticated'); const mustBeAuthenticated = require('./middleware/mustBeAuthenticated');
const mustBeAdmin = require('./middleware/mustBeAdmin'); const mustBeAdmin = require('./middleware/mustBeAdmin');
const mustHaveAccountLinkedToPlex = require('./middleware/mustHaveAccountLinkedToPlex');
const configuration = require('src/config/configuration').getInstance(); const configuration = require('src/config/configuration').getInstance();
const listController = require('./controllers/list/listController'); const listController = require('./controllers/list/listController');
const tautulli = require('./controllers/user/viewHistory.js');
const SettingsController = require('./controllers/user/settings');
const AuthenticatePlexAccountController = require('./controllers/user/AuthenticatePlexAccount');
// TODO: Have our raven router check if there is a value, if not don't enable raven. // TODO: Have our raven router check if there is a value, if not don't enable raven.
Raven.config(configuration.get('raven', 'DSN')).install(); Raven.config(configuration.get('raven', 'DSN')).install();
@@ -53,8 +57,17 @@ app.use(function onError(err, req, res, next) {
*/ */
router.post('/v1/user', require('./controllers/user/register.js')); router.post('/v1/user', require('./controllers/user/register.js'));
router.post('/v1/user/login', require('./controllers/user/login.js')); router.post('/v1/user/login', require('./controllers/user/login.js'));
router.get('/v1/user/history', mustBeAuthenticated, require('./controllers/user/history.js')); router.get('/v1/user/settings', mustBeAuthenticated, SettingsController.getSettingsController);
router.put('/v1/user/settings', mustBeAuthenticated, SettingsController.updateSettingsController);
router.get('/v1/user/search_history', mustBeAuthenticated, require('./controllers/user/searchHistory.js'));
router.get('/v1/user/requests', mustBeAuthenticated, require('./controllers/user/requests.js')); router.get('/v1/user/requests', mustBeAuthenticated, require('./controllers/user/requests.js'));
router.post('/v1/user/link_plex', mustBeAuthenticated, AuthenticatePlexAccountController.link);
router.post('/v1/user/unlink_plex', mustBeAuthenticated, AuthenticatePlexAccountController.unlink);
router.get('/v1/user/view_history', mustHaveAccountLinkedToPlex, tautulli.userViewHistoryController);
router.get('/v1/user/watch_time', mustHaveAccountLinkedToPlex, tautulli.watchTimeStatsController);
router.get('/v1/user/plays_by_day', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDaysController);
router.get('/v1/user/plays_by_dayofweek', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDayOfWeekController);
/** /**
* Seasoned * Seasoned

View File

@@ -24,7 +24,7 @@ function multiSearchController(req, res) {
const { query, page } = req.query; const { query, page } = req.query;
if (user) { if (user) {
searchHistory.create(user, query) searchHistory.create(user.username, query)
} }
return tmdb.multiSearch(query, page) return tmdb.multiSearch(query, page)

View File

@@ -0,0 +1,87 @@
const UserRepository = require('src/user/userRepository');
const userRepository = new UserRepository();
const fetch = require('node-fetch');
const FormData = require('form-data');
function handleError(error, res) {
let { status, message, source } = error;
if (status && message) {
if (status === 401) {
message = 'Unauthorized. Please check plex credentials.',
source = 'plex'
}
res.status(status).send({ success: false, message, source })
} else {
console.log('caught authenticate plex account controller error', error)
res.status(500).send({
message: 'An unexpected error occured while authenticating your account with plex',
source
})
}
}
function handleResponse(response) {
if (!response.ok) {
throw {
success: false,
status: response.status,
message: response.statusText
}
}
return response.json()
}
function plexAuthenticate(username, password) {
const url = 'https://plex.tv/api/v2/users/signin'
const form = new FormData()
form.append('login', username)
form.append('password', password)
form.append('rememberMe', 'false')
const headers = {
'Accept': 'application/json, text/plain, */*',
'Content-Type': form.getHeaders()['content-type'],
'X-Plex-Client-Identifier': 'seasonedRequest'
}
const options = {
method: 'POST',
headers,
body: form
}
return fetch(url, options)
.then(resp => handleResponse(resp))
}
function link(req, res) {
const user = req.loggedInUser;
const { username, password } = req.body;
return plexAuthenticate(username, password)
.then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id))
.then(response => res.send({
success: true,
message: "Successfully authenticated and linked plex account with seasoned request."
}))
.catch(error => handleError(error, res))
}
function unlink(req, res) {
const user = req.loggedInUser;
return userRepository.unlinkPlexUserId(user.username)
.then(response => res.send({
success: true,
message: "Successfully unlinked plex account from seasoned request."
}))
.catch(error => handleError(error, res))
}
module.exports = {
link,
unlink
};

View File

@@ -8,6 +8,9 @@ const secret = configuration.get('authentication', 'secret');
const userSecurity = new UserSecurity(); const userSecurity = new UserSecurity();
const userRepository = new UserRepository(); const userRepository = new UserRepository();
// TODO look to move some of the token generation out of the reach of the final "catch-all"
// catch including the, maybe sensitive, error message.
/** /**
* Controller: Log in a user provided correct credentials. * Controller: Log in a user provided correct credentials.
* @param {Request} req http request variable * @param {Request} req http request variable

View File

@@ -0,0 +1,42 @@
const UserRepository = require('src/user/userRepository');
const userRepository = new UserRepository();
/**
* Controller: Retrieves settings of a logged in user
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
const getSettingsController = (req, res) => {
const user = req.loggedInUser;
const username = user === undefined ? undefined : user.username;
userRepository.getSettings(username)
.then(settings => {
res.send({ success: true, settings });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
const updateSettingsController = (req, res) => {
const user = req.loggedInUser;
const username = user === undefined ? undefined : user.username;
const idempotencyKey = req.headers('Idempotency-Key'); // TODO implement better transactions
const { dark_mode, emoji } = req.body;
userRepository.updateSettings(username, dark_mode, emoji)
.then(settings => {
res.send({ success: true, settings });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
module.exports = {
getSettingsController,
updateSettingsController
}

View File

@@ -0,0 +1,105 @@
const configuration = require('src/config/configuration').getInstance();
const Tautulli = require('src/tautulli/tautulli');
const apiKey = configuration.get('tautulli', 'apiKey');
const ip = configuration.get('tautulli', 'ip');
const port = configuration.get('tautulli', 'port');
const tautulli = new Tautulli(apiKey, ip, port);
function handleError(error, res) {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
console.log('caught view history controller error', error)
res.status(500).send({ message: 'An unexpected error occured while fetching view history'})
}
}
function watchTimeStatsController(req, res) {
const user = req.loggedInUser;
tautulli.watchTimeStats(user.plex_userid)
.then(data => {
console.log('data', data, JSON.stringify(data.response.data))
return res.send({
success: true,
data: data.response.data,
message: 'watch time successfully fetched from tautulli'
})
})
}
function getPlaysByDayOfWeekController(req, res) {
const user = req.loggedInUser;
const { days, y_axis } = req.query;
tautulli.getPlaysByDayOfWeek(user.plex_userid, days, y_axis)
.then(data => res.send({
success: true,
data: data.response.data,
message: 'play by day of week successfully fetched from tautulli'
})
)
}
function getPlaysByDaysController(req, res) {
const user = req.loggedInUser;
const { days, y_axis } = req.query;
if (days === undefined) {
return res.status(422).send({
success: false,
message: "Missing parameter: days (number)"
})
}
const allowedYAxisDataType = ['plays', 'duration'];
if (!allowedYAxisDataType.includes(y_axis)) {
return res.status(422).send({
success: false,
message: `Y axis parameter must be one of values: [${ allowedYAxisDataType }]`
})
}
tautulli.getPlaysByDays(user.plex_userid, days, y_axis)
.then(data => res.send({
success: true,
data: data.response.data
}))
}
function userViewHistoryController(req, res) {
const user = req.loggedInUser;
console.log('user', user)
// TODO here we should check if we can init tau
// and then return 501 Not implemented
tautulli.viewHistory(user.plex_userid)
.then(data => {
console.log('data', data, JSON.stringify(data.response.data.data))
return res.send({
success: true,
data: data.response.data.data,
message: 'view history successfully fetched from tautulli'
})
})
.catch(error => handleError(error))
// const username = user.username;
}
module.exports = {
watchTimeStatsController,
getPlaysByDaysController,
getPlaysByDayOfWeekController,
userViewHistoryController
};

View File

@@ -6,7 +6,7 @@ const mustBeAdmin = (req, res, next) => {
if (req.loggedInUser === undefined) { if (req.loggedInUser === undefined) {
return res.status(401).send({ return res.status(401).send({
success: false, success: false,
error: 'You must be logged in.', message: 'You must be logged in.',
}); });
} else { } else {
database.get(`SELECT admin FROM user WHERE user_name IS ?`, req.loggedInUser.username) database.get(`SELECT admin FROM user WHERE user_name IS ?`, req.loggedInUser.username)
@@ -15,7 +15,7 @@ const mustBeAdmin = (req, res, next) => {
if (isAdmin.admin == 0) { if (isAdmin.admin == 0) {
return res.status(401).send({ return res.status(401).send({
success: false, success: false,
error: 'You must be logged in as a admin.' message: 'You must be logged in as a admin.'
}) })
} }
}) })

View File

@@ -2,7 +2,7 @@ const mustBeAuthenticated = (req, res, next) => {
if (req.loggedInUser === undefined) { if (req.loggedInUser === undefined) {
return res.status(401).send({ return res.status(401).send({
success: false, success: false,
error: 'You must be logged in.', message: 'You must be logged in.',
}); });
} }
return next(); return next();

View File

@@ -0,0 +1,31 @@
const establishedDatabase = require('src/database/database');
const mustHaveAccountLinkedToPlex = (req, res, next) => {
let database = establishedDatabase;
const loggedInUser = req.loggedInUser;
if (loggedInUser === undefined) {
return res.status(401).send({
success: false,
message: 'You must have your account linked to a plex account.',
});
} else {
database.get(`SELECT plex_userid FROM settings WHERE user_name IS ?`, loggedInUser.username)
.then(row => {
const plex_userid = row.plex_userid;
if (plex_userid === null || plex_userid === undefined) {
return res.status(403).send({
success: false,
message: 'No plex account user id found for your user. Please authenticate your plex account at /user/authenticate.'
})
} else {
req.loggedInUser.plex_userid = plex_userid;
return next();
}
})
}
};
module.exports = mustHaveAccountLinkedToPlex;

View File

@@ -2777,7 +2777,7 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^2.3.1: form-data@^2.3.1, form-data@^2.5.1:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==