aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorcglatot <[email protected]>2020-06-24 18:53:34 +0100
committercglatot <[email protected]>2020-06-24 18:53:34 +0100
commit5aef4feb901075b01d5467eb4305314217f20080 (patch)
tree666c15116c9308be34019eee4acc1b07355f0614
parent712fed87d970c51722e6ba63c66bb7046ae4e71a (diff)
downloadpasta-5aef4feb901075b01d5467eb4305314217f20080.tar.gz
pasta-5aef4feb901075b01d5467eb4305314217f20080.zip
Enable PIN based authentication
-rw-r--r--css/main.css37
-rw-r--r--index.html116
-rw-r--r--js/main.js196
3 files changed, 301 insertions, 48 deletions
diff --git a/css/main.css b/css/main.css
index 61a8915..8311db7 100644
--- a/css/main.css
+++ b/css/main.css
@@ -58,7 +58,7 @@ a:hover {
color: rgb(190,190,190) !important;
}
-label {
+label, p {
color: rgb(240,240,240);
}
@@ -139,6 +139,19 @@ label {
display: none;
}
+#url-auth-over-container {
+ display: none;
+}
+
+#authed-pin-container {
+ display: none;
+}
+
+#confirmForgetPin {
+ margin-left: 0.25em;
+ display: none;
+}
+
/*==========================
ALERTS
==========================*/
@@ -193,11 +206,11 @@ label {
border-color: #F39C12;
}
-#episodeOrSeriesBtns label {
+#episodeOrSeriesBtns label, #pinOrAuthBtns label {
cursor: pointer;
}
-#episodeOrSeriesBtns label.active {
+#episodeOrSeriesBtns label.active, #pinOrAuthBtns label.active {
cursor: default;
}
@@ -240,29 +253,29 @@ label {
box-shadow: 0 0 0 0.2rem rgba(82,82,82,.5);
}
-#episodeOrSeriesBtns .btn-secondary {
+#episodeOrSeriesBtns .btn-secondary, #pinOrAuthBtns .btn-secondary {
color: rgb(240,240,240);
background-color: #444;
border-color: #444;
}
-#episodeOrSeriesBtns .btn-secondary:hover {
+#episodeOrSeriesBtns .btn-secondary:hover, #pinOrAuthBtns .btn-secondary:hover {
color: rgb(240,240,240);
background-color: #333;
border-color: #2c2c2c;
}
-#episodeOrSeriesBtns .btn-secondary.active {
+#episodeOrSeriesBtns .btn-secondary.active, #pinOrAuthBtns .btn-secondary.active {
color: #fff;
background-color: #F39C12;
border-color: #F39C12;
}
-#episodeOrSeriesBtns .btn-secondary.active.focus {
+#episodeOrSeriesBtns .btn-secondary.active.focus, #pinOrAuthBtns .btn-secondary.active.focus {
box-shadow: 0 0 0 0.2rem rgba(243,156,18,.25);
}
-#episodeOrSeriesBtns .btn-secondary.active:hover {
+#episodeOrSeriesBtns .btn-secondary.active:hover, #pinOrAuthBtns .btn-secondary.active:hover {
color: #fff;
background-color: #d4860b;
border-color: #c87f0a;
@@ -288,6 +301,10 @@ table, td, tr, th {
background-color: transparent;
}
+#serverTable tbody tr {
+ cursor: pointer;
+}
+
#libraryTable tbody tr {
cursor: pointer;
}
@@ -342,6 +359,10 @@ table, td, tr, th {
animation: successFadeOut 1.75s ease-out;
}
+#serverTableContainer {
+ display: none;
+}
+
/*==========================
MEDIA QUERIES
==========================*/
diff --git a/index.html b/index.html
index 5cb292c..c81a97e 100644
--- a/index.html
+++ b/index.html
@@ -181,6 +181,7 @@
<!-- PLEX LOGIN FORM -->
<div class="row mt-3">
<div class="col">
+ <!-- WARNING BOX -->
<div id="insecureWarning" class="alert alert-warning alert-dismissible fade show mt-3" role="alert">
<strong>Warning:</strong> You have loaded this page over <strong>http</strong>, which means that this may not be secure, especially if you are using public or untrusted networks.
<br>If you can only access your Plex server via http, then please continue with caution.
@@ -190,43 +191,104 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
- <div class="form-group">
- <label for="plexUrl">Plex URL</label>
- <input type="text" class="form-control" id="plexUrl"
- placeholder="e.g. http://192.168.0.1:32400" autocomplete="on">
- <small class="form-text text-muted">This must be a local server, or a server
- publicly addressable.</small>
+ <!-- / WARNING BOX -->
+ <!-- SWITCH TOGGLE -->
+ <div class="row mt-4">
+ <div class="col text-center">
+ <div id="pinOrAuthBtns" class="btn-group btn-group-toggle" data-toggle="buttons">
+ <label class="btn btn-secondary active">
+ <input type="radio" name="pinOrAuth" value="showPinControls" id="showPinControls" autocomplete="off"
+ checked> PIN Authentication
+ </label>
+ <label class="btn btn-secondary">
+ <input type="radio" name="pinOrAuth" value="showUrlControls" id="showUrlControls" autocomplete="off">
+ URL / Token
+ </label>
+ </div>
+ </div>
</div>
- <div class="form-group">
- <label for="plexToken">Plex Token</label>
- <input type="text" class="form-control" id="plexToken" placeholder="X-Plex-Token" autocomplete="on">
- <small class="form-text text-muted">
- <a target="_blank"
- href="https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/">Find
- out how to get your X-Plex-token here.</a>
- </small>
+ <!-- / SWITCH TOGGLE -->
+ <!-- PIN AUTHENTICATION -->
+ <div id="pin-auth-over-container" class="mt-4">
+ <div id="new-pin-container">
+ <p>Please go to <a href="https://www.plex.tv/pin" target="_blank">https://www.plex.tv/pin</a> and enter the following PIN:</p>
+ <div class="d-inline-flex flex-row align-items-center">
+ <h1 id="pin-code-holder"></h1>
+ <div class="spinner-border spinner-border-sm text-warning ml-2" role="status" aria-hidden="true"></div>
+ </div>
+ </div>
+ <div id="authed-pin-container">
+ <p>You are authenticated via PIN.
+ <small id="forgetPinDetails">
+ <a href="javascript:void(0)" onclick="forgetPinDetails()">Click here to logout.</a>
+ <i id="confirmForgetPin" class="fas fa-check" style="color: #28a745; font-size: 1.5em"></i>
+ </small>
+ </p>
+ </div>
</div>
- <div class="form-group form-check">
- <input type="checkbox" class="form-check-input" id="rememberDetails">
- <label class="form-check-label" for="rememberDetails">Remember my details<span id="forgetDivider"> | </span></label>
- <small id="forgetDetailsSection">
- <a href="javascript:void(0)" onclick="forgetDetails()">Forget my details</a>
- <i id="confirmForget" class="fas fa-check" style="color: #28a745; font-size: 1.5em"></i>
- </small>
+ <!-- / PIN AUTHENTICATION -->
+ <!-- URL / TOKEN AUTHENTICATION -->
+ <div id="url-auth-over-container" class="mt-4">
+ <div class="form-group">
+ <label for="plexUrl">Plex URL</label>
+ <input type="text" class="form-control" id="plexUrl"
+ placeholder="e.g. http://192.168.0.1:32400" autocomplete="on">
+ <small class="form-text text-muted">This must be a local server, or a server
+ publicly addressable.</small>
+ </div>
+ <div class="form-group">
+ <label for="plexToken">Plex Token</label>
+ <input type="text" class="form-control" id="plexToken" placeholder="X-Plex-Token" autocomplete="on">
+ <small class="form-text text-muted">
+ <a target="_blank"
+ href="https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/">Find
+ out how to get your X-Plex-token here.</a>
+ </small>
+ </div>
+ <div class="form-group form-check">
+ <input type="checkbox" class="form-check-input" id="rememberDetails">
+ <label class="form-check-label" for="rememberDetails">Remember my details<span id="forgetDivider"> | </span></label>
+ <small id="forgetDetailsSection">
+ <a href="javascript:void(0)" onclick="forgetDetails()">Forget my details</a>
+ <i id="confirmForget" class="fas fa-check" style="color: #28a745; font-size: 1.5em"></i>
+ </small>
+ </div>
+ <button id="btnConnectToPlex" class="btn btn-secondary" onclick="connectToPlex()"
+ disabled>Connect to Plex</button>
+ <!-- PLEX AUTHENTICATION WARNING -->
+ <div class="row">
+ <div class="col" id="authWarningText">
+
+ </div>
+ </div>
+ <!-- / PLEX AUTHENTICATION WARNING -->
</div>
- <button id="btnConnectToPlex" class="btn btn-secondary" onclick="connectToPlex()"
- disabled>Connect to Plex</button>
+ <!-- / URL / TOKEN AUTHENTICATION -->
</div>
</div>
<!-- / PLEX LOGIN FORM -->
- <!-- PLEX AUTHENTICATION WARNING -->
- <div class="row">
- <div class="col" id="authWarningText">
+
+ <!-- SERVERS TABLE -->
+ <div id="serverTableContainer" class="row mt-4">
+ <div class="col">
+ <h3>Plex Servers</h3>
+ <div class="table-responsive">
+ <table id="serverTable" class="table table-hover mt-3">
+ <thead>
+ <tr>
+ <th scope="col">Name</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ </tbody>
+ </table>
+ </div>
</div>
</div>
- <!-- / PLEX AUTHENTICATION WARNING -->
+ <!-- / SERVERS TABLE -->
<!-- LIBRARIES TABLE -->
<div class="row mt-4">
diff --git a/js/main.js b/js/main.js
index 384de11..c5f88b7 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,5 +1,10 @@
var plexUrl;
var plexToken;
+var clientIdentifier; // UID for the device being used
+var plexProduct = "PASTA-cglatot";
+var backOffTimer = 0;
+var serverList = []; // save server information for pin login and multiple servers
+
var libraryNumber = ""; // The Library ID that was clicked
var showId = ""; // Stores the Id for the most recently clicked series
var seasonsList = []; // Stores the Ids for all seasons of the most recently clicked series
@@ -34,18 +39,177 @@ $(document).ready(() => {
validateEnableConnectBtn('plexToken');
});
- if (localStorage.plexUrl && localStorage.plexUrl !== "") {
- $('#plexUrl').val(localStorage.plexUrl);
- validateEnableConnectBtn('plexUrl');
- $('#forgetDivider, #forgetDetailsSection').show();
- }
- if (localStorage.plexToken && localStorage.plexToken !== "") {
- $('#plexToken').val(localStorage.plexToken);
- validateEnableConnectBtn('plexToken');
- $('#forgetDivider, #forgetDetailsSection').show();
+ // Setup on change listener for toggle buttons
+ $('input[type=radio][name=pinOrAuth]').change(function() {
+ console.log(this);
+ toggleAuthPages(this.value);
+ });
+
+ // Set the clientID, this might get overridden if one is saved to localstorage
+ clientIdentifier = `PASTA-cglatot-${Date.now()}-${Math.round(Math.random() * 1000)}`;
+
+ if (!localStorage.isPinAuth) {
+ // Not using PIN auth, so must be using url / token
+ if (localStorage.plexUrl && localStorage.plexUrl !== "") {
+ plexUrl = localStorage.plexUrl;
+ $('#plexUrl').val(localStorage.plexUrl);
+ validateEnableConnectBtn('plexUrl');
+ $('#forgetDivider, #forgetDetailsSection').show();
+ }
+ if (localStorage.plexToken && localStorage.plexToken !== "") {
+ plexToken = localStorage.plexToken;
+ $('#plexToken').val(localStorage.plexToken);
+ validateEnableConnectBtn('plexToken');
+ $('#forgetDivider, #forgetDetailsSection').show();
+ }
+
+ // Display a PIN code for that authentication as well
+ $.ajax({
+ "url": `https://plex.tv/pins.xml?X-Plex-Product=${plexProduct}&X-Plex-Client-Identifier=${clientIdentifier}`,
+ "method": "POST",
+ "success": (data) => {
+ let pinId = $(data).find('id')[0].innerHTML;
+ let pinCode = $(data).find('code')[0].innerHTML;
+
+ $('#pin-code-holder').html(pinCode);
+ backOffTimer = Date.now();
+ listenForValidPincode(pinId);
+ },
+ "error": (data) => {
+ console.log("ERROR L59");
+ console.log(data);
+ }
+ });
+ } else {
+ $('#new-pin-container').hide();
+ $('#authed-pin-container').show();
+ // We are using Pin Auth
+ clientIdentifier = localStorage.clientIdentifier;
+ plexToken = localStorage.pinAuthToken;
+ getServers();
}
});
+function toggleAuthPages(value) {
+ if (value == 'showPinControls') {
+ $('#pin-auth-over-container').show();
+ $('#url-auth-over-container').hide();
+ } else {
+ $('#pin-auth-over-container').hide();
+ $('#url-auth-over-container').show();
+
+ if (localStorage.isPinAuth) {
+ $("#authWarningText").html(`<div class="alert alert-warning alert-dismissible fade show mt-3" role="alert">
+ <strong>Warning:</strong> You are currently signed in via PIN. Please <a href="javascript:void(0)" onclick="forgetPinDetails()">sign out of PIN</a> before proceeding to connect using a URL / IP address.
+ <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </div>`);
+ }
+ }
+}
+
+function listenForValidPincode (pinId) {
+ let currentTime = Date.now();
+ if ((currentTime - backOffTimer)/1000 < 180) {
+ $.ajax({
+ "url": `https://plex.tv/pins/${pinId}?X-Plex-Product=${plexProduct}&X-Plex-Client-Identifier=${clientIdentifier}`,
+ "method": "GET",
+ "success": (data) => {
+ if (data.pin.auth_token != null) {
+ plexToken = data.pin.auth_token;
+ // Save to local storage
+ localStorage.isPinAuth = true;
+ localStorage.pinAuthToken = plexToken;
+ localStorage.clientIdentifier = clientIdentifier;
+ $('#new-pin-container').hide();
+ $('#authed-pin-container').show();
+ getServers();
+ } else {
+ setTimeout(() => {
+ listenForValidPincode(pinId);
+ }, 5000);
+ }
+ },
+ "error": (data) => {
+ console.log("ERROR L73");
+ console.log(data);
+ return;
+ }
+ });
+ } else {
+ $('#new-pin-container').html(' <p><i class="far fa-times-circle mr-2" style="color: #e5a00d; font-size: 1.5em; vertical-align: middle;"></i>PIN entry timed out. \
+ Please <a href="javascript:void(0)" onclick="window.location.reload()">refresh the page</a> to get a new PIN.</p>');
+ }
+}
+
+function getServers () {
+ $.ajax({
+ "url": `https://plex.tv/pms/servers.xml?X-Plex-Product=${plexProduct}&X-Plex-Client-Identifier=${clientIdentifier}`,
+ "method": "GET",
+ "headers": {
+ "X-Plex-Token": plexToken
+ },
+ "success": (data) => {
+ let servers = $(data).find('Server');
+ if (servers.length > 1) {
+ displayServers(servers);
+ // Add server info to the list
+ for (let i = 0; i < servers.length; i++) {
+ serverList.push({
+ name: $(servers[i]).attr("name"),
+ accessToken: $(servers[i]).attr("accessToken"),
+ address: $(servers[i]).attr("address"),
+ port: $(servers[i]).attr("port")
+ });
+ }
+ } else {
+ plexToken = $(servers[0]).attr("accessToken");
+ plexUrl = `http://${$(servers[0]).attr("address")}:${$(servers[0]).attr("port")}`;
+ connectToPlex();
+ }
+ },
+ "error": (data) => {
+ console.log("ERROR L59");
+ console.log(data);
+ }
+ });
+}
+
+function displayServers(servers) {
+ $("#serverTable tbody").empty();
+ $("#libraryTable tbody").empty();
+ $("#tvShowsTable tbody").empty();
+ $("#seasonsTable tbody").empty();
+ $("#episodesTable tbody").empty();
+ $("#audioTable tbody").empty();
+ $("#subtitleTable tbody").empty();
+
+ for (let i = 0; i < servers.length; i++) {
+ let rowHTML = `<tr onclick="chooseServer(${i}, this)">
+ <td>${$(servers[i]).attr("name")}</td>
+ </tr>`;
+ $("#serverTable tbody").append(rowHTML);
+ }
+ $("#serverTableContainer").show();
+}
+
+function chooseServer(number, row) {
+ $("#libraryTable tbody").empty();
+ $("#tvShowsTable tbody").empty();
+ $("#seasonsTable tbody").empty();
+ $("#episodesTable tbody").empty();
+ $("#audioTable tbody").empty();
+ $("#subtitleTable tbody").empty();
+
+ $(row).siblings().removeClass("table-active");
+ $(row).addClass("table-active");
+
+ plexToken = serverList[number].accessToken;
+ plexUrl = `http://${serverList[number].address}:${serverList[number].port}`;
+ connectToPlex();
+}
+
function validateEnableConnectBtn(context) {
// Apply validation highlighting to URL field
if (context == 'plexUrl') {
@@ -84,15 +248,21 @@ function forgetDetails() {
});
}
+function forgetPinDetails() {
+ localStorage.removeItem('isPinAuth');
+ localStorage.removeItem('pinAuthToken');
+ localStorage.removeItem('clientIdentifier');
+ window.location.reload();
+}
+
function hideAlertForever() {
$("#insecureWarning").hide();
localStorage.showHttpAlert = 'false';
}
function connectToPlex() {
- plexUrl = $("#plexUrl").val().trim().replace(/\/+$/, '');
- console.log(plexUrl);
- plexToken = $("#plexToken").val().trim();
+ plexUrl = plexUrl || $("#plexUrl").val().trim().replace(/\/+$/, '');
+ plexToken = plexToken || $("#plexToken").val().trim();
if (plexUrl.toLowerCase().indexOf("http") < 0) {
plexUrl = `http://${plexUrl}`
@@ -112,7 +282,7 @@ function connectToPlex() {
localStorage.plexToken = plexToken;
$('#forgetDivider, #forgetDetailsSection').show();
}
- displayLibraries(data)
+ displayLibraries(data);
},
"error": (data) => {
if (data.status == 401) {