Moved plex auth logic into store rather than vue

This commit is contained in:
Travis Shivers 2020-05-24 20:15:31 -05:00
parent 72d299f932
commit d495fa5d52
5 changed files with 300 additions and 292 deletions

View File

@ -234,16 +234,6 @@ export default {
}
}
// Get other settings in order of importance: config -> settings
// Authentication Mechanism setting
if (this.config && this.config.authentication) {
this.$store.commit('SET_AUTHENTICATION', this.config.authentication);
} else {
this.$store.commit('SET_AUTHENTICATION', {
type: 'none',
});
}
// Auto-join if a single server is provided and autoJoinServer is not
if (this.syncloungeServers.length == 1 && !this.$store.autoJoinServer) {
const server = this.syncloungeServers[0];

View File

@ -70,7 +70,7 @@
<script>
import axios from 'axios';
import { mapGetters, mapMutations } from 'vuex';
import { mapActions, mapGetters, mapMutations } from 'vuex';
export default {
name: 'signin',
@ -98,6 +98,9 @@ export default {
};
},
methods: {
...mapActions({
'plexCheckAuth': 'PLEX_CHECK_AUTH'
}),
...mapMutations('settings', [
'SET_HIDEUSERNAME',
'SET_ALTUSERNAME',
@ -143,72 +146,8 @@ export default {
},
async checkAuth(authToken) {
this.checkingAuth = true;
await this.$store.dispatch('PLEX_LOGIN_TOKEN', authToken);
// Get stored authentication settings
const authentication = { ...this.$store.state.authentication };
// Authentication defaults to false
let authenticationPassed = false;
if (authentication) {
// Authenication via Plex mechanism
if (authentication.mechanism === 'plex') {
// Server authorization using server data
if (authentication.type.includes('server')) {
try {
// Retrieve and store the user's servers
await this.$store.dispatch('PLEX_GET_DEVICES', true);
// Get the user's servers
const servers = { ...this.$store.state.plex.servers };
// Compare servers against the authorized list
for (const id in servers) {
const server = servers[id];
if (authentication.authorized.includes(server.clientIdentifier)) {
authenticationPassed = true;
}
}
} catch (e) {
console.error('An error occurred when authenticating with Plex: ', e);
}
}
// Authorization using user data
if (authentication.type.includes('user')) {
// Get the user object
const user = this.$store.state.plex.user;
// Compare the user's email against the authorized list
if (authentication.authorized.includes(user.email)) {
authenticationPassed = true;
}
// Compare the user's name against the authorized list
if (authentication.authorized.includes(user.username)) {
authenticationPassed = true;
}
}
}
// New authentication mechanisms can be added here
// else if (authentication.mechanism == 'new_mech' ) {
// }
// Authenication via an unsupported mechanism
else if (authentication.mechanism != 'none') {
console.error(
`Invalid authentication mechanism provided: '${
authentication.mechanism
}'. Reverting to default.`,
);
this.$store.state.authentication = {
mechanism: 'none',
};
authenticationPassed = true;
}
// Authenication mechanism isn't set. This should only happen when authentication mechanism is set to 'none'.
else {
console.log('No authentication set');
authenticationPassed = true;
}
this.checkingAuth = false;
return authenticationPassed;
}
await plexCheckAuth(authToken);
this.checkingAuth = false;
return null;
},
},
@ -269,7 +208,7 @@ export default {
console.log('--- Check Auth mounted ---')
const authenticated = await this.checkAuth(authToken);
if (authenticated != null) {
if (authenticated == true) {
if (authenticated === true) {
await this.setAuth(authToken);
this.letsGo();
} else {

View File

@ -96,9 +96,6 @@ const mutations = {
SET_PLEX(state, value) {
state.plex = value;
},
SET_AUTHENTICATION(state, value) {
state.authentication = value;
},
SET_AUTOJOIN(state, value) {
state.autoJoin = value;
},
@ -366,7 +363,7 @@ const actions = {
}
state.synclounge._socket.emit('poll', endObj);
}
},
}
};
const persistedState = createPersistedState({

View File

@ -1,26 +1,26 @@
import axios from 'axios';
const state = () => ({
configuration: {}
configuration: {},
});
const getters = {
GET_CONFIG: state => state.configuration
GET_CONFIG: state => state.configuration,
GET_AUTHENTICATION: state => state.configuration.authentication,
};
const mutations = {
SET_CONFIG: (state, data) => {
state.configuration = data;
}
SET_CONFIG: (state, data) => (state.configuration = data),
SET_AUTHENTICATION: (state, auth) => (state.configuration.authentication = auth),
};
const actions = {
fetchConfig: async ({ commit }) => {
const url = window.location.origin + window.location.pathname.replace(/\/+$/, "");
const url = window.location.origin + window.location.pathname.replace(/\/+$/, '');
return axios.get(`${url}/config`).then(({ data }) => {
commit('SET_CONFIG', data);
});
}
},
};
export default {
@ -28,5 +28,5 @@ export default {
state,
mutations,
actions,
getters
};
getters,
};

View File

@ -9,171 +9,184 @@ const PlexClient = require('./helpers/PlexClient.js');
const PlexAuth = new _PlexAuth();
export default {
PLEX_LOGIN_TOKEN: ({ commit, dispatch, rootState }, token) => new Promise((resolve, reject) => {
const options = PlexAuth.getApiOptions('https://plex.tv/users/sign_in.json', token, 5000, 'POST');
options.headers['X-Plex-Client-Identifier'] = rootState.settings.CLIENTIDENTIFIER;
request(options, (error, response, body) => {
if (!error && (response.statusCode === 200 || response.statusCode === 201)) {
const data = JSON.parse(body);
if (!data) {
commit('PLEX_SET_VALUE', ['signedin', false]);
return reject(new Error('No response data from Plex'));
PLEX_LOGIN_TOKEN: ({ commit, dispatch, rootState }, token) =>
new Promise((resolve, reject) => {
const options = PlexAuth.getApiOptions(
'https://plex.tv/users/sign_in.json',
token,
5000,
'POST',
);
options.headers['X-Plex-Client-Identifier'] = rootState.settings.CLIENTIDENTIFIER;
request(options, (error, response, body) => {
if (!error && (response.statusCode === 200 || response.statusCode === 201)) {
const data = JSON.parse(body);
if (!data) {
commit('PLEX_SET_VALUE', ['signedin', false]);
return reject(new Error('No response data from Plex'));
}
commit('PLEX_SET_VALUE', ['user', data.user]);
commit('PLEX_SET_VALUE', ['signedin', true]);
dispatch('PLEX_GET_DEVICES');
// state.signedin = true
return resolve(true);
}
commit('PLEX_SET_VALUE', ['user', data.user]);
commit('PLEX_SET_VALUE', ['signedin', true]);
dispatch('PLEX_GET_DEVICES');
// state.signedin = true
return resolve(true);
}
commit('PLEX_SET_VALUE', ['signedin', false]);
return reject(error);
});
}),
commit('PLEX_SET_VALUE', ['signedin', false]);
return reject(error);
});
}),
PLEX_LOGIN_STANDARD: ({ dispatch, commit }, data) => new Promise((resolve, reject) => {
const { username, password } = data;
const base64encoded = new Buffer(`${username}:${password}`).toString('base64');
const options = {
url: 'https://plex.tv/users/sign_in.json',
headers: {
Authorization: `Basic ${base64encoded}`,
'X-Plex-Client-Identifier': 'PlexTogether',
},
method: 'POST',
};
request(options, (error, response, body) => {
if (!error && (response.statusCode === 200 || response.statusCode === 201)) {
const data = JSON.parse(body);
if (!data) {
PLEX_LOGIN_STANDARD: ({ dispatch, commit }, data) =>
new Promise((resolve, reject) => {
const { username, password } = data;
const base64encoded = new Buffer(`${username}:${password}`).toString('base64');
const options = {
url: 'https://plex.tv/users/sign_in.json',
headers: {
Authorization: `Basic ${base64encoded}`,
'X-Plex-Client-Identifier': 'PlexTogether',
},
method: 'POST',
};
request(options, (error, response, body) => {
if (!error && (response.statusCode === 200 || response.statusCode === 201)) {
const data = JSON.parse(body);
if (!data) {
commit('PLEX_SET_VALUE', ['signedin', false]);
return reject(response.statusCode);
}
commit('PLEX_SET_VALUE', ['user', data.user]);
commit('PLEX_SET_VALUE', ['signedin', true]);
dispatch('PLEX_GET_DEVICES');
} else {
commit('PLEX_SET_VALUE', ['signedin', false]);
return reject(response.statusCode);
}
commit('PLEX_SET_VALUE', ['user', data.user]);
commit('PLEX_SET_VALUE', ['signedin', true]);
dispatch('PLEX_GET_DEVICES');
} else {
commit('PLEX_SET_VALUE', ['signedin', false]);
return reject(response.statusCode);
});
}),
PLEX_GET_DEVICES: ({ state, commit, dispatch }, dontDelete) =>
new Promise((resolve, reject) => {
if (!state.user) {
return reject(new Error('Sign in before getting devices'));
}
});
}),
PLEX_GET_DEVICES: ({ state, commit, dispatch }, dontDelete) => new Promise((resolve, reject) => {
if (!state.user) {
return reject(new Error('Sign in before getting devices'));
}
if (!dontDelete) {
commit('PLEX_SET_VALUE', ['gotDevices', false]);
commit('PLEX_SET_VALUE', ['servers', {}]);
commit('PLEX_SET_VALUE', ['clients', {}]);
}
const options = PlexAuth.getApiOptions('https://plex.tv/api/resources?includeHttps=1', state.user.authToken, 5000, 'GET');
request(options, (error, response, body) => {
if (!error && response.statusCode === 200) {
// Valid response
parseXMLString(body, async (err, result) => {
if (err) {
return reject(err);
}
for (const index in result.MediaContainer.Device) {
// Handle the individual device
const device = result.MediaContainer.Device[index].$;
// Each device can have multiple network connections
// Any of them can be viable routes to interacting with the device
const connections = result.MediaContainer.Device[index].Connection;
const tempConnectionsArray = [];
// Create a temporary array of object:PlexConnection
for (const i in connections) {
const connection = connections[i].$;
// Exclude local IPs starting with 169.254
if (!connection.address.startsWith('169.254')) {
const tempConnection = new PlexConnection();
for (const key in connection) {
tempConnection[key] = connection[key];
}
tempConnectionsArray.push(tempConnection);
if (connection.local === '1' && connection.uri.indexOf('plex') > -1) {
const rawConnection = new PlexConnection();
Object.assign(rawConnection, connection);
rawConnection.uri = `${connection.protocol}://${connection.address}:${connection.port}`;
rawConnection.isManual = true;
tempConnectionsArray.push(rawConnection);
if (!dontDelete) {
commit('PLEX_SET_VALUE', ['gotDevices', false]);
commit('PLEX_SET_VALUE', ['servers', {}]);
commit('PLEX_SET_VALUE', ['clients', {}]);
}
const options = PlexAuth.getApiOptions(
'https://plex.tv/api/resources?includeHttps=1',
state.user.authToken,
5000,
'GET',
);
request(options, (error, response, body) => {
if (!error && response.statusCode === 200) {
// Valid response
parseXMLString(body, async (err, result) => {
if (err) {
return reject(err);
}
for (const index in result.MediaContainer.Device) {
// Handle the individual device
const device = result.MediaContainer.Device[index].$;
// Each device can have multiple network connections
// Any of them can be viable routes to interacting with the device
const connections = result.MediaContainer.Device[index].Connection;
const tempConnectionsArray = [];
// Create a temporary array of object:PlexConnection
for (const i in connections) {
const connection = connections[i].$;
// Exclude local IPs starting with 169.254
if (!connection.address.startsWith('169.254')) {
const tempConnection = new PlexConnection();
for (const key in connection) {
tempConnection[key] = connection[key];
}
tempConnectionsArray.push(tempConnection);
if (connection.local === '1' && connection.uri.indexOf('plex') > -1) {
const rawConnection = new PlexConnection();
Object.assign(rawConnection, connection);
rawConnection.uri = `${connection.protocol}://${connection.address}:${
connection.port
}`;
rawConnection.isManual = true;
tempConnectionsArray.push(rawConnection);
}
}
}
}
// If device is a player
if (device.provides.indexOf('player') !== -1) {
// If device is not Plex Web
if(device.product.indexOf('Plex Web') === -1) {
// This is a Client
// Create a new PlexClient object
const tempClient = new PlexClient();
// If device is a player
if (device.provides.indexOf('player') !== -1) {
// If device is not Plex Web
if (device.product.indexOf('Plex Web') === -1) {
// This is a Client
// Create a new PlexClient object
const tempClient = new PlexClient();
for (const key in device) {
tempClient[key] = device[key];
}
tempClient.accessToken = state.user.authToken;
tempClient.plexConnections = tempConnectionsArray;
dispatch('PLEX_ADD_CLIENT', tempClient);
}
// If device is a server
} else if (device.provides.indexOf('server') !== -1) {
// This is a Server
// Create a new PlexServer object
const tempServer = new PlexServer();
for (const key in device) {
tempClient[key] = device[key];
tempServer[key] = device[key];
}
// Push a manual connection string for when DNS rebind doesnt work
tempServer.plexConnections = tempConnectionsArray;
if (tempServer.accessToken == null) {
tempServer.accessToken = state.user.authToken;
}
tempClient.accessToken = state.user.authToken;
tempClient.plexConnections = tempConnectionsArray;
dispatch('PLEX_ADD_CLIENT', tempClient);
}
// If device is a server
} else if (device.provides.indexOf('server') !== -1) {
// This is a Server
// Create a new PlexServer object
const tempServer = new PlexServer();
for (const key in device) {
tempServer[key] = device[key];
}
// Push a manual connection string for when DNS rebind doesnt work
tempServer.plexConnections = tempConnectionsArray;
if (tempServer.accessToken == null) {
tempServer.accessToken = state.user.authToken;
}
dispatch('PLEX_ADD_SERVER', tempServer);
dispatch('PLEX_ADD_SERVER', tempServer);
}
}
}
// Setup our slPlayer
const ptplayer = new PlexClient();
ptplayer.provides = 'player';
ptplayer.clientIdentifier = 'PTPLAYER9PLUS10';
ptplayer.platform = 'Web';
ptplayer.device = 'Web';
ptplayer.product = 'SyncLounge';
ptplayer.name = 'SyncLounge Player';
ptplayer.labels = [
['Recommended', 'green'],
];
ptplayer.lastSeenAt = Math.round((new Date()).getTime() / 1000);
for (const i in state.clients) {
const client = state.clients[i];
for (const j in client.plexConnections) {
const clientConnection = client.plexConnections[j];
// Check if this URL matches any server connections
for (const x in state.servers) {
const server = state.servers[x];
for (const y in server.plexConnections) {
const serverConnection = server.plexConnections[y];
if (serverConnection.uri === clientConnection.uri) {
client.accessToken = server.accessToken;
// Setup our slPlayer
const ptplayer = new PlexClient();
ptplayer.provides = 'player';
ptplayer.clientIdentifier = 'PTPLAYER9PLUS10';
ptplayer.platform = 'Web';
ptplayer.device = 'Web';
ptplayer.product = 'SyncLounge';
ptplayer.name = 'SyncLounge Player';
ptplayer.labels = [['Recommended', 'green']];
ptplayer.lastSeenAt = Math.round(new Date().getTime() / 1000);
for (const i in state.clients) {
const client = state.clients[i];
for (const j in client.plexConnections) {
const clientConnection = client.plexConnections[j];
// Check if this URL matches any server connections
for (const x in state.servers) {
const server = state.servers[x];
for (const y in server.plexConnections) {
const serverConnection = server.plexConnections[y];
if (serverConnection.uri === clientConnection.uri) {
client.accessToken = server.accessToken;
}
}
}
}
}
}
dispatch('PLEX_ADD_CLIENT', ptplayer);
dispatch('PLEX_ADD_CLIENT', ptplayer);
commit('PLEX_SET_VALUE', ['gotDevices', true]);
dispatch('PLEX_REFRESH_SERVER_CONNECTIONS');
return resolve(true);
});
} else {
// Invalid response
commit('PLEX_SET_VALUE', ['gotDevices', true]);
dispatch('PLEX_REFRESH_SERVER_CONNECTIONS');
return resolve(true);
});
} else {
// Invalid response
commit('PLEX_SET_VALUE', ['gotDevices', true]);
return reject(new Error('Invalid Response'));
}
});
}),
return reject(new Error('Invalid Response'));
}
});
}),
PLEX_REFRESH_SERVER_CONNECTIONS: ({ state, dispatch }) => {
for (const id in state.servers) {
@ -199,78 +212,147 @@ export default {
// if any of them return a valid response we'll set that connection
// as the chosen connection for future use.
/*eslint-disable */
new Promise(async (resolve, reject) => {
new Promise(async (resolve, reject) => {
if (client.clientIdentifier === 'PTPLAYER9PLUS10') {
return resolve(true)
return resolve(true);
}
let resolved = false
let rootResolve = resolve
let resolved = false;
let rootResolve = resolve;
try {
await Promise.all(client.plexConnections.map((connection) => {
return new Promise(async (resolve, reject) => {
try {
await Promise.all(
client.plexConnections.map(connection => {
return new Promise(async (resolve, reject) => {
try {
await client.hitApi('/player/timeline/poll', { wait: 0 }, connection, false, true)
try {
await client.hitApi(
'/player/timeline/poll',
{ wait: 0 },
connection,
false,
true,
);
} catch (e) {
// We dont care about this result, some clients require a poll command before sending a subscription command
}
await client.hitApi('/player/timeline/poll', { wait: 0 }, connection);
console.log('Got good response on', connection);
commit('PLEX_CLIENT_SET_CONNECTION', {
client,
connection,
});
if (!resolved) {
rootResolve();
}
resolved = true;
resolve();
} catch (e) {
// We dont care about this result, some clients require a poll command before sending a subscription command
resolve();
}
await client.hitApi('/player/timeline/poll', { wait: 0 }, connection)
console.log('Got good response on', connection)
commit('PLEX_CLIENT_SET_CONNECTION', {
client,
connection
})
if (!resolved) {
rootResolve()
}
resolved = true
resolve()
} catch (e) {
resolve()
}
});
}))
});
}),
);
if (!resolved) {
console.log('Couldnt find a connection')
return reject()
console.log('Couldnt find a connection');
return reject();
}
console.log('Resolved connection finder')
return resolve()
console.log('Resolved connection finder');
return resolve();
} catch (e) {
console.log('Error connecting to client', e)
reject(e)
console.log('Error connecting to client', e);
reject(e);
}
}), /* eslint-enable */
}) /* eslint-enable */,
PLEX_CLIENT_UPDATETIMELINE: ({}, data) => {
const [client, timeline] = data;
console.log('Updating timeline for', client, 'with', timeline);
},
PLEX_GET_SERVERS: ({ state, commit, dispatch }, token) => new Promise((resolve, reject) => {
if (!state.user) {
return reject(new Error('Sign in before getting devices'));
PLEX_GET_SERVERS: ({ state, commit, dispatch }, token) =>
new Promise((resolve, reject) => {
if (!state.user) {
return reject(new Error('Sign in before getting devices'));
}
const options = PlexAuth.getApiOptions('https://plex.tv/pms/servers.xml', token, 5000, 'GET');
request(options, (error, response, body) => {
if (!error && response.statusCode === 200) {
// Valid response
parseXMLString(body, async (err, result) => {
if (err) {
return reject(err);
}
// Save the servers list associated with the logged in account
state.user.servers = result.MediaContainer.Server;
return resolve(true);
});
}
return reject(error);
});
}),
async PLEX_CHECK_AUTH({ state, dispatch, getters }, authToken) {
await dispatch('PLEX_LOGIN_TOKEN', authToken);
// Get stored authentication settings
const authentication = getters['config/GET_AUTHENTICATION'];
// Authentication defaults to false
let authenticationPassed = false;
if (authentication) {
// Authenication via Plex mechanism
if (authentication.mechanism === 'plex') {
// Server authorization using server data
if (authentication.type.includes('server')) {
try {
// Retrieve and store the user's servers
await dispatch('PLEX_GET_DEVICES', true);
// Get the user's servers
const servers = state.plex.servers;
// Compare servers against the authorized list
for (const id in servers) {
const server = servers[id];
if (authentication.authorized.includes(server.clientIdentifier)) {
authenticationPassed = true;
}
}
} catch (e) {
console.error('An error occurred when authenticating with Plex: ', e);
}
}
// Authorization using user data
if (authentication.type.includes('user')) {
// Get the user object
const user = state.plex.user;
// Compare the user's email against the authorized list
if (authentication.authorized.includes(user.email)) {
authenticationPassed = true;
}
// Compare the user's name against the authorized list
if (authentication.authorized.includes(user.username)) {
authenticationPassed = true;
}
}
}
// New authentication mechanisms can be added here
// else if (authentication.mechanism == 'new_mech' ) {
// }
// Authenication via an unsupported mechanism
else if (authentication.mechanism !== 'none') {
console.error(`Invalid authentication mechanism provided: '${
authentication.mechanism
}'. Reverting to default.`);
authenticationPassed = true;
}
// Authenication mechanism isn't set. This should only happen when authentication mechanism is set to 'none'.
else {
console.log('No authentication set');
authenticationPassed = true;
}
return authenticationPassed;
}
const options = PlexAuth.getApiOptions('https://plex.tv/pms/servers.xml', token, 5000, 'GET');
request(options, (error, response, body) => {
if (!error && response.statusCode === 200) {
// Valid response
parseXMLString(body, async (err, result) => {
if (err) {
return reject(err);
}
// Save the servers list associated with the logged in account
state.user.servers = result.MediaContainer.Server;
return resolve(true);
});
}
return reject(error);
});
}),
return null;
},
};