Skip to content

Commit 7f578ee

Browse files
committed
Merge pull request #13 from phillipj/feature/authenticator
Replaced ./auth.js with options.authenticator
2 parents f3c0515 + de5c163 commit 7f578ee

8 files changed

Lines changed: 239 additions & 264 deletions

File tree

Readme.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Small module which helps you query the Plex Media Server HTTP API.
88

99
Instantiate a PlexAPI client.
1010

11-
The parameter can be a string representing the server's hostname, or an object with the following properties:
11+
The parameter can be a string representing the server's hostname, or an object with the following properties:
1212

1313
Options:
1414
- **hostname**: hostname where Plex Server runs
@@ -53,7 +53,7 @@ client.query("/").then(function (result) {
5353

5454
**postQuery(uri) : Send a POST request and retrieve the response**
5555

56-
This is identical to ```query(uri)```, except that the request will be a POST rather than a GET.
56+
This is identical to ```query(uri)```, except that the request will be a POST rather than a GET.
5757

5858
Note that the parameters can only be passed as a query string as part of the uri, which is all Plex requires. (```Content-Length``` will always be zero)
5959

@@ -120,6 +120,45 @@ client.find("/").then(function (directories) {
120120
});
121121
```
122122

123+
## Authenticators
124+
125+
An authenticator is used by plex-api to authenticate its request against Plex Servers with a PlexHome setup. The most common authentication mechanism is by username and password.
126+
127+
You can provide your own custom authentication mechanism, read more about custom authenticators below.
128+
129+
### Credentials: username and password
130+
131+
Comes bundled with plex-api. Just provide `options.username` and `options.password` when creating a PlexAPI instance and you are good to go.
132+
133+
See the [plex-api-credentials](https://www.npmjs.com/package/plex-api-credentials) module for more information about its inner workings.
134+
135+
### Custom authenticator
136+
137+
In its simplest form an `authenticator` is an object with **one required** function `authenticate()` which should return the autentication token needed by plex-api to satisfy Plex Server.
138+
139+
An optional method `initialize()` could be implemented if you need reference to the created PlexAPI instance when it's created.
140+
141+
```js
142+
{
143+
// OPTIONAL
144+
initialize: function(plexApi) {
145+
// plexApi === the PlexAPI instance just created
146+
},
147+
// REQUIRED
148+
authenticate: function(plexApi, callback) {
149+
// plexApi === the PlexAPI instance requesting the authentication token
150+
151+
// invoke callback if something fails
152+
if (somethingFailed) {
153+
return callback(new Error('I haz no cluez about token!'));
154+
}
155+
156+
// or when you have a token
157+
callback(null, 'I-found-this-token');
158+
}
159+
}
160+
```
161+
123162
## HTTP API Documentation
124163
For more information about the API capabilities, see the [unofficial Plex API documentation](https://code.google.com/p/plex-api/w/list).
125164

lib/api.js

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ var url = require('url');
44
var request = require('request');
55
var Q = require('q');
66
var xml2js = require('xml2js');
7+
var headers = require('plex-api-headers');
78

89
var uri = require('./uri');
9-
var auth = require('./auth');
1010

1111
var PLEX_SERVER_PORT = 32400;
1212

@@ -18,6 +18,7 @@ function PlexAPI(options, deprecatedPort) {
1818
this.port = deprecatedPort || opts.port || PLEX_SERVER_PORT;
1919
this.username = opts.username;
2020
this.password = opts.password;
21+
this.authenticator = opts.authenticator || this._credentialsAuthenticator();
2122
this.options = opts.options || {};
2223
this.options.identifier = this.options.identifier || uuid.v4();
2324
this.options.product = this.options.product || 'Node.js App';
@@ -35,6 +36,7 @@ function PlexAPI(options, deprecatedPort) {
3536
}
3637

3738
this.serverUrl = 'http://' + hostname + ':' + this.port;
39+
this._initializeAuthenticator();
3840
}
3941

4042
PlexAPI.prototype.getHostname = function getHostname() {
@@ -91,32 +93,23 @@ PlexAPI.prototype._request = function _request(relativeUrl, method, parseRespons
9193
url: url.parse(reqUrl),
9294
encoding: null,
9395
method: method || 'GET',
94-
headers: {
96+
headers: headers(this, {
9597
'Accept': 'application/json',
96-
'X-Plex-Client-Identifier': self.getIdentifier(),
97-
'X-Plex-Product': self.options.product,
98-
'X-Plex-Version': self.options.version,
99-
'X-Plex-Device': self.options.device,
100-
'X-Plex-Device-Name': self.options.deviceName,
101-
'X-Plex-Platform': self.options.platform,
102-
'X-Plex-Platform-Version': self.options.platformVersion,
103-
'X-Plex-Provides': 'controller'
104-
}
98+
'X-Plex-Token': this.authToken,
99+
'X-Plex-Username': this.username
100+
})
105101
};
106102

107-
if (this.authToken) {
108-
reqOpts.headers['X-Plex-Token'] = this.authToken;
109-
}
110-
111-
if (this.username) {
112-
reqOpts.headers['X-Plex-Username'] = this.username;
113-
}
114-
115103
request(reqOpts, function onResponse(err, response, body) {
116104
if (err) {
117105
return deferred.reject(err);
118106
}
119107
if (response.statusCode === 401) {
108+
if (self.authenticator === undefined) {
109+
return deferred.reject(new Error('Plex Server denied request, you must provide a way to authenticate! ' +
110+
'Read more about plex-api authenticators on https://www.npmjs.com/package/plex-api#authenticators'));
111+
}
112+
120113
return deferred.resolve(self._authenticate()
121114
.then(function() {
122115
return self._request(relativeUrl, method, parseResponse);
@@ -157,15 +150,35 @@ PlexAPI.prototype._authenticate = function _authenticate() {
157150
return deferred.reject(new Error('Permission denied even after attempted authentication :( Wrong username and/or password maybe?'));
158151
}
159152

160-
auth.retrieveAuthToken(self.username, self.password, self.options)
161-
.then(function onAuthResult(token) {
162-
self.authToken = token;
163-
deferred.resolve();
164-
});
153+
this.authenticator.authenticate(this.options, function(err, token) {
154+
if (err) {
155+
return deferred.reject(new Error('Authentication failed, reason: ' + err.message));
156+
}
157+
self.authToken = token;
158+
deferred.resolve();
159+
});
165160

166161
return deferred.promise;
167162
};
168163

164+
PlexAPI.prototype._credentialsAuthenticator = function _credentialsAuthenticator() {
165+
if (this.username && this.password) {
166+
var credentials = require('plex-api-credentials');
167+
168+
return credentials({
169+
username: this.username,
170+
password: this.password
171+
});
172+
}
173+
return undefined;
174+
};
175+
176+
PlexAPI.prototype._initializeAuthenticator = function _initializeAuthenticator() {
177+
if (this.authenticator && typeof this.authenticator.initialize === 'function') {
178+
this.authenticator.initialize(this);
179+
}
180+
};
181+
169182
function filterChildrenByCriterias(children, criterias) {
170183
var context = {
171184
criterias: criterias || {}

lib/auth.js

Lines changed: 0 additions & 51 deletions
This file was deleted.

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"test": "test"
88
},
99
"dependencies": {
10+
"plex-api-credentials": "^2.0.0",
11+
"plex-api-headers": "^1.0.0",
1012
"q": "^1.0.1",
1113
"request": "^2.36.0",
1214
"uuid": "^2.0.1",
@@ -17,10 +19,12 @@
1719
"jscs": "^1.11.3",
1820
"jshint": "^2.6.3",
1921
"mocha": "*",
20-
"nock": "^1.2.1"
22+
"nock": "^1.2.1",
23+
"proxyquire": "^1.6.0",
24+
"sinon": "^1.15.4"
2125
},
2226
"scripts": {
23-
"test": "jshint lib/* && jscs lib/* && mocha --reporter list test/*-test.js"
27+
"test": "jshint lib/* && jscs lib/* && mocha test/*-test.js"
2428
},
2529
"files": [
2630
"lib"

0 commit comments

Comments
 (0)