Skip to content

Commit

Permalink
Updates to Plex Authentication and User lists and Quality Profiles
Browse files Browse the repository at this point in the history
- Updated PleX authentication method to use the new APIv2
- Plex authentication using Username or Email login now supported for
  all users
- Fixed issue with Quality profiles not being persistent for both Sonarr
  and Radarr (About time!!)
- Added a button to trigger a manual refresh of friends lists
- Cleaned up the Users Ui

Signed-off-by: Ricky Grassmuck <[email protected]>
  • Loading branch information
RickyGrassmuck committed Dec 9, 2017
1 parent 78ec50c commit 8732b49
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 79 deletions.
4 changes: 4 additions & 0 deletions client/lib/stylesheets/_admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,7 @@
background-color: #ccc;
}
}
.item-divider {
margin: inherit;
margin-bottom: 1rem;
}
10 changes: 6 additions & 4 deletions client/templates/admin/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ <h1>Admin</h1>
<br>
<div class="row">
<div class="col-sm-4">
<label class="sr-only" for="plexuser">Plex Username</label>
<input type="email" class="form-control" id="plexuser" placeholder="Plex Username">
<label class="sr-only" for="plexuser">Plex Login</label>
<input type="email" class="form-control" id="plexuser" placeholder="Plex Login">
</div>
<div class="col-sm-4">
<label class="sr-only" for="plexpassword">Password</label>
Expand All @@ -115,6 +115,8 @@ <h1>Admin</h1>
<p class="text-muted">No Approval: This user will always have their requests automatically approved</p>
<p class="text-muted">No Limit: No request limits apply to this user</p>
<p class="text-muted">Banned: Banned users are prevented from logging in</p>
<p><button class="btn btn-secondary-outline" id="refreshFriends">Refresh Friends List</button></p>

</div>
</div>
<div class="row">
Expand Down Expand Up @@ -199,7 +201,7 @@ <h1>Admin</h1>
{{> afQuickField name='radarrSSL'}}
<p class="text-muted">Enabled if you use SSL on your Radarr server.</p>
<br>
{{> afQuickField name='radarrQUALITYPROFILEID' type='select' options=radarrProfiles firstOption=false}}
{{> afQuickField name='radarrQUALITYPROFILEID' type='select' options=radarrProfiles }}
<button class="btn btn-info-outline" id="getRadarrProfiles">Get Profiles</button>
<br>
<br>
Expand Down Expand Up @@ -266,7 +268,7 @@ <h1>Admin</h1>
{{> afQuickField name='sonarrSSL'}}
<p class="text-muted">Enabled if you use SSL on your Sonarr server.</p>
<br>
{{> afQuickField name='sonarrQUALITYPROFILEID' type='select' options=sonarrProfiles firstOption=false}}
{{> afQuickField name='sonarrQUALITYPROFILEID' type='select' options=sonarrProfiles }}
<button class="btn btn-info-outline" id="getSonarrProfiles">Get Profiles</button>
<br>
<br>
Expand Down
32 changes: 32 additions & 0 deletions client/templates/admin/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,22 @@ Template.admin.onCreated(function(){
instance.previousVersion = new ReactiveVar('')
instance.previousNotes = new ReactiveVar('')

Meteor.call('SonarrProfiles', function (error, result) {
if(result) {
instance.sonarrProfiles.set(result)
} else {
logger.debug(error)
}
})

Meteor.call('RadarrProfiles', function (error, result) {
if(result) {
instance.radarrProfiles.set(result)
} else {
logger.debug(error)
}
})

Meteor.call('getBranch', function (error, result) {
if (result) {
instance.branch.set(result)
Expand Down Expand Up @@ -232,6 +248,22 @@ Template.admin.events({
}
},

'click #refreshFriends': function (event) {
event.preventDefault()
var btn = $(event.target)
btn.html('Refreshing Friends List <i class=\'fa fa-spin fa-refresh\'></i>').removeClass().addClass('btn btn-info-outline')
Meteor.call('getPlexFriendlist', function (error, result) {
if (result.length) {
btn.removeClass('btn-info-outline').addClass('btn-success-outline')
btn.html('Updated!')
} else {
btn.removeClass('btn-info-outline').addClass('btn-danger-outline')
btn.html('Error Updating!')
}
})
return false
},

'click #couchPotatoTest' : function (event) {
event.preventDefault()
var btn = $(event.target)
Expand Down
6 changes: 3 additions & 3 deletions client/templates/home/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ <h1>Login</h1>

<p>
Want to watch a movie or tv show but it's not currently on Plex? <br>
Login below with your Plex.tv username{{#if requirePassword}} and password{{/if}}! <span title="Your login details are only used to authenticate your Plex account."><i class="fa fa-question-circle"></i></span>
Login below with your Plex.tv email/username{{#if requirePassword}} and password{{/if}}! <span title="Your login details are only used to authenticate your Plex account."><i class="fa fa-question-circle"></i></span>
</p>

<form class="form col-md-6" id="sign-in">
<fieldset>
<div class="form-group">
<label for="plex-username">Plex.tv Username</label>
<input type="text" class="form-control" id="plex-username" placeholder="Username">
<label for="plex-username">Plex.tv Login</label>
<input type="text" class="form-control" id="plex-username" placeholder="Login">
</div>
{{#if requirePassword}}
<div class="form-group">
Expand Down
143 changes: 71 additions & 72 deletions server/methods/authentication/plexAuthentication.js
Original file line number Diff line number Diff line change
@@ -1,134 +1,133 @@
Meteor.methods({
'checkPlexAuthentication' : function () {

'checkPlexAuthentication': function () {
return Settings.find({}).fetch()[0].plexAuthenticationENABLED
},
'checkPlexAuthenticationPasswords' : function () {

'checkPlexAuthenticationPasswords': function () {
return Settings.find({}).fetch()[0].plexAuthenticationPASSWORDS
},
'checkPlexUser' : function (plexUsername, plexPassword) {

if (Settings.find({}).fetch()[0].plexAuthenticationPASSWORDS) {
// If passwords are required check full login
'plexLogin': function (username, password) {
check(username, String)
check(password, String)

function authHeaderVal(username, password) {
var authString = username + ':' + password
var buffer = new Buffer(authString.toString(), 'binary')
return 'Basic ' + buffer.toString('base64')
}
try {
var requestAuth = Meteor.http.call('POST', 'https://plex.tv/api/v2/users/signin', {
headers: {
'Accept': 'application/json',
'X-Plex-Client-Identifier': 'BGZQ8N25FYP3UHB6',
'X-Plex-Version': '1.2.0',
'X-Plex-Platform': 'Meteor',
'X-Plex-Device-Name': 'Plex Requests'
},
data: {
'login': username,
'password': password,
'rememberMe': false
}

var headers = {
'Authorization': authHeaderVal(plexUsername, plexPassword),
'X-Plex-Client-Identifier': 'BGZQ8N25FYP3UHB6',
'X-Plex-Version': '1.2.0',
'X-Plex-Platform': 'Meteor',
'X-Plex-Device-Name': 'Plex Requests'
}
})

try {
Meteor.http.call('POST', 'https://plex.tv/users/sign_in.json', {headers: headers})
} catch (error) {
logger.warn(plexUsername + ' failed to login')
throw new Meteor.Error(401, JSON.parse(error.message.substring(13)).error)
}

var plexData = JSON.parse(requestAuth.content)


} catch (error) {
logger.error(plexData.errors[0].message)
throw new Meteor.Error(401, plexData.errors.error[0])
}

function isInArray(value, array) {
return array.indexOf(value) > -1
if (requestAuth.statusCode === 201) {
return plexData
}

function checkArray(value, array) {
return array.indexOf(value)
},

'checkPlexUser': function (plexLogin, plexPassword) {
check(plexLogin, String)
check(plexPassword, String)

if (Settings.find({}).fetch()[0].plexAuthenticationPASSWORDS) {
// If passwords are required check full login
var userInfo = Meteor.call('plexLogin', plexLogin, plexPassword)
var plexUsername = userInfo.username
}

//Update users in permissions
Meteor.call('permissionsUpdateUsers')

//Get friendslist and bannedlist
var friendsList = Meteor.call('getPlexFriendlist')
var bannedList = Permissions.find({permBANNED: true}, {fields: {_id: 0, permUSER: 1, permBANNED: 1}}).fetch()

//Remove banned users
for(var i = 0; i < bannedList.length; i++) {
for (var i = 0; i < bannedList.length; i++) {
friendsList.splice(friendsList.indexOf(bannedList[i].permUSER), 1)
}
return (isInArray(plexUsername.toLowerCase(), friendsList))

return (friendsList.indexOf(plexUsername.toLowerCase()) > -1)
},
'getPlexToken' : function (username,password) {

//clean password and username for authentication.
function authHeaderVal(username, password) {
var authString = username + ':' + password
var buffer = new Buffer(authString.toString(), 'binary')
return 'Basic ' + buffer.toString('base64')
}
'getPlexToken': function (plexLogin, plexPassword) {
check(plexLogin, String)
check(plexPassword, String)

try {
var plexstatus = Meteor.http.call('POST', 'https://plex.tv/users/sign_in.xml', {
headers: {
'Authorization': authHeaderVal(username, password),
'X-Plex-Client-Identifier': 'BGZQ8N25FYP3UHB6',
'X-Plex-Version': '1.2.0',
'X-Plex-Platform': 'Meteor',
'X-Plex-Device-Name': 'Plex Requests'
}
})
} catch (error) {
var response = xml2js.parseStringSync(error.response.content)
logger.error(response.errors.error[0])
throw new Meteor.Error(401, response.errors.error[0])
if (plexLogin !== '' && plexPassword !== '') {
var userInfo = Meteor.call('plexLogin', plexLogin, plexPassword)
var plexAuth = userInfo.authToken
} else {
logger.error('Missing login or password')
throw new Meteor.Error(401, 'Must supply a valid login and password')
}


//Bad authentication comes back as 401, will need to add error handles, for now it just assumes that and lets user know
if (plexstatus.statusCode==201) {
var results = xml2js.parseStringSync(plexstatus.content)
var plexAuth = results.user.$.authenticationToken
Settings.update({}, {$set: {plexAuthenticationTOKEN: plexAuth}})
if (plexAuth) {
Settings.update({}, {$set: {plexAuthenticationTOKEN: plexAuth}})
return true
} else {
logger.error('Error getting Plex token')
return false
}
},
'getPlexFriendlist' : function () {

'getPlexFriendlist': function () {
var plexToken = Settings.find({}).fetch()[0].plexAuthenticationTOKEN

try {
var friendsXML = Meteor.http.call('GET', 'https://plex.tv/pms/friends/all?X-Plex-Token='+plexToken)
var accountXML = Meteor.http.call('GET', 'https://plex.tv/users/account?X-Plex-Token='+plexToken)
var friendsXML = Meteor.http.call('GET', 'https://plex.tv/pms/friends/all?X-Plex-Token=' + plexToken)
var accountXML = Meteor.http.call('GET', 'https://plex.tv/users/account?X-Plex-Token=' + plexToken)
} catch (error) {
logger.error('Error checking Plex Users: ' + error.message)
return false
logger.error('Error checking Plex Users: ' + error.message)
return false
}

var users = []
var admintitle = ''

xml2js.parseString(friendsXML.content, {mergeAttrs : true, explicitArray : false} ,function (err, result) {
xml2js.parseString(friendsXML.content, {mergeAttrs: true, explicitArray: false}, function (err, result) {
users = result['MediaContainer']['User']
})

xml2js.parseString(accountXML.content, {mergeAttrs : true, explicitArray : false} ,function (err, result) {
xml2js.parseString(accountXML.content, {mergeAttrs: true, explicitArray: false}, function (err, result) {
admintitle = result['user']['title'].toLowerCase()
})

var friendsList = []

// Check if an array of users or a single user is returned
if (typeof users !== 'undefined'){
if (typeof users !== 'undefined') {
if (users.length) {
for (var i = 0; i < users.length; i++) {
friendsList.push( users[i].title.toLowerCase() )
friendsList.push(users[i].title.toLowerCase())
}
} else if (users.title) {
friendsList.push( users.title.toLowerCase() )
}
friendsList.push(users.title.toLowerCase())
}
}

//Add admin username to the list
friendsList.push(admintitle)

return(friendsList)
return (friendsList)
}
})
2 changes: 2 additions & 0 deletions server/startup/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ Meteor.startup(function () {
Sonarr.port = settings.sonarrPORT
Sonarr.api = settings.sonarrAPI
Sonarr.directory = settings.sonarrDIRECTORY || ''
Sonarr.qualityProfileId = settings.sonarrQUALITYPROFILEID || ''

//set Radarr on start-up
Radarr.url = (settings.radarrSSL) ? 'https://' + settings.radarrURL : 'http://' + settings.radarrURL
Radarr.port = settings.radarrPORT
Radarr.api = settings.radarrAPI
Radarr.directory = settings.radarrDIRECTORY || ''
Radarr.qualityProfileId = settings.radarrQUALITYPROFILEID || ''


Meteor.call('updateSeasons')
Expand Down

0 comments on commit 8732b49

Please sign in to comment.