From 3f04d9bc56b8759a7dbdd908b8525c9c121c7b18 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 16 Jul 2019 18:50:27 +0200 Subject: [PATCH 1/7] Update script for updating all plex statuses of requestes. --- seasoned_api/package.json | 2 +- seasoned_api/src/plex/updateRequestsInPlex.js | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 seasoned_api/src/plex/updateRequestsInPlex.js diff --git a/seasoned_api/package.json b/seasoned_api/package.json index a0e79a1..9c4834d 100644 --- a/seasoned_api/package.json +++ b/seasoned_api/package.json @@ -11,7 +11,7 @@ "test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --recursive test/unit test/system", "coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --recursive test && nyc report --reporter=text-lcov | coveralls", "lint": "./node_modules/.bin/eslint src/", - "update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js" + "update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js", }, "dependencies": { "axios": "^0.18.0", diff --git a/seasoned_api/src/plex/updateRequestsInPlex.js b/seasoned_api/src/plex/updateRequestsInPlex.js new file mode 100644 index 0000000..f1a028a --- /dev/null +++ b/seasoned_api/src/plex/updateRequestsInPlex.js @@ -0,0 +1,41 @@ +const PlexRepository = require('src/plex/plexRepository'); +const configuration = require('src/config/configuration').getInstance(); +const establishedDatabase = require('src/database/database'); + +const plexRepository = new PlexRepository(); + +class UpdateRequestsInPlex { + constructor() { + this.database = establishedDatabase; + this.queries = { + getRequests: `SELECT * FROM requests WHERE status IS 'requested' OR 'downloaded'`, + saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`, + } + } + + getRequests() { + return this.database.all(this.queries.getRequests); + } + + scrub() { + return this.getRequests() + .then((requests) => Promise.all(requests.map(async (movie) => { + return plexRepository.inPlex(movie) + }))) + .then((requests_checkInPlex) => requests_checkInPlex.filter((movie) => movie.matchedInPlex)) + } + + updateStatus(status) { + this.scrub().then((newInPlex) => + newInPlex.map((movie) => { + console.log('updated', movie.title, 'to', status) + // this.database.run(this.queries.saveNewStatus, [status, movie.id, movie.type]) + }) + ) + } +} + +var requestsUpdater = new UpdateRequestsInPlex(); +requestsUpdater.updateStatus('downloaded') + +module.exports = UpdateRequestsInPlex \ No newline at end of file From 8a5ab204e1ea1e7abdcd74c9a75a503c79134d6d Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Wed, 24 Jul 2019 22:54:04 +0200 Subject: [PATCH 2/7] Change node bcrypt package from bcrypt-nodejs to bcrypt. Change response message on invalid username/pass and changed to bcrypt syntax for compare and hash. --- seasoned_api/package.json | 2 +- seasoned_api/src/user/userSecurity.js | 91 +++++++++++++-------------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/seasoned_api/package.json b/seasoned_api/package.json index 9c4834d..dfc9297 100644 --- a/seasoned_api/package.json +++ b/seasoned_api/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "axios": "^0.18.0", - "bcrypt-nodejs": "^0.0.3", + "bcrypt": "^3.0.6", "body-parser": "~1.18.2", "cross-env": "~5.1.4", "express": "~4.16.0", diff --git a/seasoned_api/src/user/userSecurity.js b/seasoned_api/src/user/userSecurity.js index 9cf4ba4..571ec58 100644 --- a/seasoned_api/src/user/userSecurity.js +++ b/seasoned_api/src/user/userSecurity.js @@ -1,73 +1,72 @@ -const bcrypt = require('bcrypt-nodejs'); +const bcrypt = require('bcrypt'); const UserRepository = require('src/user/userRepository'); class UserSecurity { - constructor(database) { - this.userRepository = new UserRepository(database); - } + constructor(database) { + this.userRepository = new UserRepository(database); +} - /** + /** * Create a new user in PlanFlix. * @param {User} user the new user you want to create * @param {String} clearPassword a password of the user * @returns {Promise} */ - createNewUser(user, clearPassword) { - if (user.username.trim() === '') { - throw new Error('The username is empty.'); - } else if (clearPassword.trim() === '') { - throw new Error('The password is empty.'); - } else { - return Promise.resolve() - .then(() => this.userRepository.create(user)) - .then(() => UserSecurity.hashPassword(clearPassword)) - .then(hash => this.userRepository.changePassword(user, hash)); - } - } + createNewUser(user, clearPassword) { + if (user.username.trim() === '') { + throw new Error('The username is empty.'); + } else if (clearPassword.trim() === '') { + throw new Error('The password is empty.'); + } else { + return Promise.resolve() + .then(() => this.userRepository.create(user)) + .then(() => UserSecurity.hashPassword(clearPassword)) + .then(hash => this.userRepository.changePassword(user, hash)); + } + } - /** + /** * Login into PlanFlix. * @param {User} user the user you want to login * @param {String} clearPassword the user's password * @returns {Promise} */ - login(user, clearPassword) { - return Promise.resolve() - .then(() => this.userRepository.retrieveHash(user)) - .then(hash => UserSecurity.compareHashes(hash, clearPassword)) - .catch(() => { throw new Error('Wrong username or password.'); }); - } + login(user, clearPassword) { + return Promise.resolve() + .then(() => this.userRepository.retrieveHash(user)) + .then(hash => UserSecurity.compareHashes(hash, clearPassword)) + .catch(() => { throw new Error('Incorrect username or password.'); }); + } /** - * Compare between a password and a hash password from database. - * @param {String} hash the hash password from database - * @param {String} clearPassword the user's password - * @returns {Promise} - */ - static compareHashes(hash, clearPassword) { - return new Promise((resolve, reject) => { - bcrypt.compare(clearPassword, hash, (error, matches) => { - if (matches === true) { - resolve(); - } else { - reject(); - } - }); + * Compare between a password and a hash password from database. + * @param {String} hash the hash password from database + * @param {String} clearPassword the user's password + * @returns {Promise} + */ + static compareHashes(hash, clearPassword) { + return new Promise((resolve, reject) => { + bcrypt.compare(clearPassword, hash, (error, match) => { + if (match) + resolve() + reject() }); - } + }); + } - /** + /** * Hashes a password. * @param {String} clearPassword the user's password * @returns {Promise} */ - static hashPassword(clearPassword) { - return new Promise((resolve) => { - bcrypt.hash(clearPassword, null, null, (error, hash) => { - resolve(hash); - }); + static hashPassword(clearPassword) { + return new Promise((resolve) => { + const salatRounds = 10; + bcrypt.hash(clearPassword, saltRounds, (error, hash) => { + resolve(hash); }); - } + }); + } } module.exports = UserSecurity; From 12afbf63645ad4725a88da10c5a62360d40c3c99 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 25 Jul 2019 00:13:28 +0200 Subject: [PATCH 3/7] Tokens can also have a admin property. When admin is defined its included in the jwt token. --- seasoned_api/src/user/token.js | 44 +++++++++++-------- .../src/webserver/controllers/user/login.js | 6 +-- .../webserver/controllers/user/register.js | 6 +-- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/seasoned_api/src/user/token.js b/seasoned_api/src/user/token.js index cd8c285..6e904de 100644 --- a/seasoned_api/src/user/token.js +++ b/seasoned_api/src/user/token.js @@ -2,36 +2,44 @@ const User = require('src/user/user'); const jwt = require('jsonwebtoken'); class Token { - constructor(user) { - this.user = user; - } + constructor(user, admin=false) { + this.user = user; + this.admin = admin; + } - /** + /** * Generate a new token. * @param {String} secret a cipher of the token * @returns {String} */ - toString(secret) { - return jwt.sign({ username: this.user.username }, secret); - } + toString(secret) { + const user = this.user.username; + const admin = this.admin; + let data = { user } - /** + if (admin) + data = { ...data, admin } + + return jwt.sign(data, secret, { expiresIn: '90d' }); + } + + /** * Decode a token. * @param {Token} jwtToken an encrypted token * @param {String} secret a cipher of the token * @returns {Token} */ - static fromString(jwtToken, secret) { - let username = null; + static fromString(jwtToken, secret) { + let username = null; - try { - username = jwt.verify(jwtToken, secret).username; - } catch (error) { - throw new Error('The token is invalid.'); - } - const user = new User(username); - return new Token(user); - } + const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 }) + if (token.username === undefined) + throw new Error('Malformed token') + + username = token.username + const user = new User(username) + return new Token(user) + } } module.exports = Token; diff --git a/seasoned_api/src/webserver/controllers/user/login.js b/seasoned_api/src/webserver/controllers/user/login.js index 89722c1..25d8bca 100644 --- a/seasoned_api/src/webserver/controllers/user/login.js +++ b/seasoned_api/src/webserver/controllers/user/login.js @@ -21,9 +21,9 @@ function loginController(req, res) { userSecurity.login(user, password) .then(() => userRepository.checkAdmin(user)) .then((checkAdmin) => { - const token = new Token(user).toString(secret); - const admin_state = checkAdmin === 1 ? true : false; - res.send({ success: true, token, admin: admin_state }); + const isAdmin = checkAdmin === 1 ? true : false; + const token = new Token(user, isAdmin).toString(secret); + res.send({ success: true, token }); }) .catch((error) => { res.status(401).send({ success: false, error: error.message }); diff --git a/seasoned_api/src/webserver/controllers/user/register.js b/seasoned_api/src/webserver/controllers/user/register.js index 280f4be..36d9ff2 100644 --- a/seasoned_api/src/webserver/controllers/user/register.js +++ b/seasoned_api/src/webserver/controllers/user/register.js @@ -21,10 +21,10 @@ function registerController(req, res) { userSecurity.createNewUser(user, password) .then(() => userRepository.checkAdmin(user)) .then((checkAdmin) => { - const token = new Token(user).toString(secret); - const admin_state = checkAdmin === 1 ? true : false; + const isAdmin = checkAdmin === 1 ? true : false; + const token = new Token(user, isAdmin).toString(secret); res.send({ - success: true, message: 'Welcome to Seasoned!', token, admin: admin_state, + success: true, message: 'Welcome to Seasoned!', token }); }) .catch((error) => { From 144b27f128620620fcaa35118211597d83e3462d Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 25 Jul 2019 00:23:32 +0200 Subject: [PATCH 4/7] Renamed token variable from user to username --- seasoned_api/src/user/token.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seasoned_api/src/user/token.js b/seasoned_api/src/user/token.js index 6e904de..0590dc3 100644 --- a/seasoned_api/src/user/token.js +++ b/seasoned_api/src/user/token.js @@ -13,9 +13,9 @@ class Token { * @returns {String} */ toString(secret) { - const user = this.user.username; + const username = this.user.username; const admin = this.admin; - let data = { user } + let data = { username } if (admin) data = { ...data, admin } From e19cfb5870c31c83766166631d3885ec8a148828 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 25 Jul 2019 00:24:04 +0200 Subject: [PATCH 5/7] Updated formatting --- .../webserver/middleware/.tokenToUser.js.swp | Bin 0 -> 12288 bytes .../src/webserver/middleware/tokenToUser.js | 20 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 seasoned_api/src/webserver/middleware/.tokenToUser.js.swp diff --git a/seasoned_api/src/webserver/middleware/.tokenToUser.js.swp b/seasoned_api/src/webserver/middleware/.tokenToUser.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..7b48de57e0a9b164787c569854a6ed534546b227 GIT binary patch literal 12288 zcmeI2J8u&~5XU!^NZ~0&hlU_zJM!Jd1SBU>kf0($kwAGgD1!Cf+FpFOYj*eI1Qe*L zproV&iG~IVDyZQTq~;S)(lLAIJWLWwB+#t&ztukG^*c9~r!}>@un5ht!El^n?A3O2 z?Z=moZ00>e!B5g-DG5XfCS zXpXb#D~&0CdTHV!oISI0$VJ*B0z`la5CI}U1c(3;AOb|-eK#|2x0`w@)(m67>Rg z7ga@7Q0GwJPB8Wr^%`YR4^XS93#gyy=R4{%>J#b%>K*DW>J7@r8K5qsj-zN00U|&I zhyW2F0z`la5CI}U1pYpOOec9NLb1`;##xw$y*A@N9GN-Bw&oZBD6x?hLuZ-BPx&~) z^x*cvoxLvSofsIxRck)lmXA1m0vPcK`>i`2kt`_R6^+T~DiLjojnzeNumv%9aTlt> z7;S3sq_98KvM4*u(M@Rz5Nl;!i32?^`@F)o(biHiUa|i*(}1y zHtb$01DF1G%+RIJzbkkmj175?ec?tv=1yug$M(f8JHGLH74O&4$ksh{SA+TM7>Pv@ z#JNc!kPzI0i5vSJ&E6}@TAP5PFTJa+o1Jc4y*3m1PV@Cfl!3)8}V1xqgzE=gdnxNOT#Zi;fkj&RkAuTUr_@r_`Kl{!?) z+unL;K4Mh@+KX2nMk9ABHSc=?-xF7{@ZXVW5`;~qazP?3Z>0j14*J~iEHKz}Z)Hcp HWW;^}h>=Za literal 0 HcmV?d00001 diff --git a/seasoned_api/src/webserver/middleware/tokenToUser.js b/seasoned_api/src/webserver/middleware/tokenToUser.js index 462d077..069c3e5 100644 --- a/seasoned_api/src/webserver/middleware/tokenToUser.js +++ b/seasoned_api/src/webserver/middleware/tokenToUser.js @@ -8,16 +8,16 @@ const Token = require('src/user/token'); // curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history const tokenToUser = (req, res, next) => { - const rawToken = req.headers.authorization; - if (rawToken) { - try { - const token = Token.fromString(rawToken, secret); - req.loggedInUser = token.user; - } catch (error) { - req.loggedInUser = undefined; - } - } - next(); + const rawToken = req.headers.authorization; + if (rawToken) { + try { + const token = Token.fromString(rawToken, secret); + req.loggedInUser = token.user; + } catch (error) { + req.loggedInUser = undefined; + } + } + next(); }; module.exports = tokenToUser; From 6aba9774c6e42086215980195987142e31aa0953 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 25 Jul 2019 00:47:17 +0200 Subject: [PATCH 6/7] When requesting all request elements we now also return the page as int not string --- seasoned_api/src/request/request.js | 1 + 1 file changed, 1 insertion(+) diff --git a/seasoned_api/src/request/request.js b/seasoned_api/src/request/request.js index 6e17e70..b83882e 100644 --- a/seasoned_api/src/request/request.js +++ b/seasoned_api/src/request/request.js @@ -124,6 +124,7 @@ class RequestRepository { */ fetchAll(page=1, sort_by=undefined, sort_direction='asc', filter=undefined, query=undefined) { // TODO implemented sort and filter + page = parseInt(page) let fetchQuery = this.queries.fetchAll let fetchTotalResults = this.queries.totalRequests let fetchParams = [page] From af7b1f2424eb5063196435f55ab8fca472219f13 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Thu, 25 Jul 2019 00:48:16 +0200 Subject: [PATCH 7/7] Script for updating all requested and downloading request status to downloaded if exist in plex --- seasoned_api/src/plex/updateRequestsInPlex.js | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/seasoned_api/src/plex/updateRequestsInPlex.js b/seasoned_api/src/plex/updateRequestsInPlex.js index f1a028a..c53ff7f 100644 --- a/seasoned_api/src/plex/updateRequestsInPlex.js +++ b/seasoned_api/src/plex/updateRequestsInPlex.js @@ -1,41 +1,39 @@ -const PlexRepository = require('src/plex/plexRepository'); +const Plex = require('src/plex/plex') const configuration = require('src/config/configuration').getInstance(); -const establishedDatabase = require('src/database/database'); - -const plexRepository = new PlexRepository(); +const plex = new Plex(configuration.get('plex', 'ip')) +const establishedDatabase = require('src/database/database'); class UpdateRequestsInPlex { - constructor() { - this.database = establishedDatabase; - this.queries = { - getRequests: `SELECT * FROM requests WHERE status IS 'requested' OR 'downloaded'`, - saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`, - } - } + constructor() { + this.database = establishedDatabase; + this.queries = { + getMovies: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`, +// getMovies: "select * from requests where status is 'reset'", + saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`, + } + } + getByStatus() { + return this.database.all(this.queries.getMovies); + } + scrub() { + return this.getByStatus() + .then((requests) => Promise.all(requests.map(movie => plex.existsInPlex(movie)))) + } - getRequests() { - return this.database.all(this.queries.getRequests); - } + commitNewStatus(status, id, type, title) { + console.log(type, title, 'updated to:', status) + this.database.run(this.queries.saveNewStatus, [status, id, type]) + } - scrub() { - return this.getRequests() - .then((requests) => Promise.all(requests.map(async (movie) => { - return plexRepository.inPlex(movie) - }))) - .then((requests_checkInPlex) => requests_checkInPlex.filter((movie) => movie.matchedInPlex)) - } - - updateStatus(status) { - this.scrub().then((newInPlex) => - newInPlex.map((movie) => { - console.log('updated', movie.title, 'to', status) - // this.database.run(this.queries.saveNewStatus, [status, movie.id, movie.type]) - }) - ) - } + + updateStatus(status) { + this.getByStatus() + .then(requests => Promise.all(requests.map(request => plex.existsInPlex(request)))) + .then(matchedRequests => matchedRequests.filter(request => request.existsInPlex)) + .then(newMatches => newMatches.map(match => this.commitNewStatus(status, match.id, match.type, match.title))) + } } - var requestsUpdater = new UpdateRequestsInPlex(); requestsUpdater.updateStatus('downloaded') -module.exports = UpdateRequestsInPlex \ No newline at end of file +module.exports = UpdateRequestsInPlex