diff options
author | cglatot <[email protected]> | 2020-06-24 19:00:51 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-06-24 19:00:51 +0100 |
commit | e370efc3018ead738df263161c3bec4ba7a9d379 (patch) | |
tree | 666c15116c9308be34019eee4acc1b07355f0614 | |
parent | 712fed87d970c51722e6ba63c66bb7046ae4e71a (diff) | |
parent | 5aef4feb901075b01d5467eb4305314217f20080 (diff) | |
download | pasta-e370efc3018ead738df263161c3bec4ba7a9d379.tar.gz pasta-e370efc3018ead738df263161c3bec4ba7a9d379.zip |
Merge pull request #10 from cglatot/PIN-login1.1.0
Enable PIN based authentication
-rw-r--r-- | css/main.css | 37 | ||||
-rw-r--r-- | index.html | 116 | ||||
-rw-r--r-- | js/main.js | 196 |
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 ==========================*/ @@ -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">×</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"> @@ -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">×</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) { |