Skip to content

Commit

Permalink
Merge pull request #33 from westsurname/plex-request-nginx
Browse files Browse the repository at this point in the history
Auto-authentication and move authentication to path
  • Loading branch information
westsurname authored Aug 7, 2024
2 parents c9b54b0 + 88140a0 commit fd38c95
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 60 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ docker-compose.yml
Dockerfile.*
README.md
shared/tokens.json
sockets/
7 changes: 2 additions & 5 deletions Dockerfile.plex_authentication
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,5 @@ RUN grep -E "#.*($SERVICE_NAME|all)" requirements.txt | awk '{print $0}' > servi
# Copy the rest of the application
COPY . .

# Expose port 8000 to the outside world
EXPOSE 8000

# Run gunicorn on port 8000, add more workers if required
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "plex_authentication_wsgi:app"]
# Run gunicorn using Unix socket
CMD ["gunicorn", "--bind", "unix:/app/sockets/plex_authentication.sock", "plex_authentication_wsgi:app"]
7 changes: 2 additions & 5 deletions Dockerfile.plex_request
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,5 @@ RUN grep -E "#.*($SERVICE_NAME|all)" requirements.txt | awk '{print $0}' > servi
# Copy the rest of the application
COPY . .

# Expose port 8000 to the outside world
EXPOSE 8000

# Run gunicorn on port 8000, add more workers if required
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "plex_request_wsgi:app"]
# Run gunicorn using Unix socket
CMD ["gunicorn", "--bind", "unix:/app/sockets/plex_request.sock", "plex_request_wsgi:app"]
8 changes: 4 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ services:
pull_policy: always
volumes:
- ./shared/tokens.json:/app/shared/tokens.json
ports:
- 8010:8000
- ./sockets:/app/sockets
env_file:
- .env
restart: unless-stopped
Expand All @@ -175,8 +174,7 @@ services:
pull_policy: always
volumes:
- ./shared/tokens.json:/app/shared/tokens.json
ports:
- 8011:8000
- ./sockets:/app/sockets
env_file:
- .env
restart: unless-stopped
Expand All @@ -191,6 +189,7 @@ services:
pull_policy: always
volumes:
- ${PLEX_REQUEST_SSL_PATH:-/dev/null}:${PLEX_REQUEST_SSL_PATH:-/dev/null}:ro
- ./sockets:/app/sockets
ports:
- 8012:8000
env_file:
Expand All @@ -199,6 +198,7 @@ services:
profiles: [plex_request, all]
depends_on:
- plex_request
- plex_authentication

networks:
default:
Expand Down
52 changes: 34 additions & 18 deletions plex_authentication.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import requests
import json
import urllib.parse
from flask import Flask, jsonify, redirect, url_for
from flask import Flask, jsonify, redirect, url_for, request
from shared.shared import watchlist, plexHeaders, tokensFilename
from shared.overseerr import getUserForPlexToken
from shared.plex import getServerToken
from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.middleware.dispatcher import DispatcherMiddleware

# instantiate the app
app = Flask(__name__)
Expand All @@ -32,29 +33,44 @@ def setupComplete(pin):
authToken = pinRequest.json()['authToken']

if authToken:
user = getUserForPlexToken(authToken)
serverToken = getServerToken(authToken)
userId = user['id']
return handleToken(authToken)

with open(tokensFilename, 'r+') as tokensFile:
tokens = json.load(tokensFile)
token = tokens.get(userId, { 'etag': '' })
token['token'] = authToken
token['serverToken'] = serverToken
return jsonify('There was an error, please try again.')

if serverToken == authToken:
token['owner'] = True
@app.route('/token', methods=['POST'])
def receiveToken():
token = request.json.get('token')
if token:
return handleToken(token)
else:
return createResponse({'error': 'No token provided'}, 400)

tokens[userId] = token
tokensFile.seek(0)
json.dump(tokens, tokensFile)
tokensFile.truncate()
def handleToken(token):
user = getUserForPlexToken(token)
serverToken = getServerToken(token)
userId = user['id']

return jsonify('Successfully authenticated!')
updateTokensFile(userId, token, serverToken)

return jsonify('There was an error, please try again.')
return createResponse({'message': 'Token received and stored successfully'}, 201)

def updateTokensFile(userId, token, serverToken):
with open(tokensFilename, 'r+') as tokensFile:
tokens = json.load(tokensFile)
tokenEntry = tokens.get(userId, {'etag': ''})
tokenEntry['token'] = token
tokenEntry['serverToken'] = serverToken
tokens[userId] = tokenEntry
tokensFile.seek(0)
json.dump(tokens, tokensFile)
tokensFile.truncate()

def createResponse(data, statusCode):
response = jsonify(data), statusCode
return response

app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)

app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/auth': app.wsgi_app})

if __name__ == '__main__':
app.run('127.0.0.1', 12598)
6 changes: 4 additions & 2 deletions plex_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ def all():
urlSuffix = "/children" if mediaTypeNum == mediaTypeNums['season'] else ""
metadataAllRequest = requests.get(f"{plex['metadataHost']}library/metadata/{guid}{urlSuffix}", headers=metadataHeaders, params=args)
if metadataAllRequest.status_code == 200:
libraryId = plex['serverMovieLibraryId'] if mediaType == 'movie' else plex['serverTvShowLibraryId']

additionalMetadata = metadataAllRequest.json()['MediaContainer']['Metadata'][0]
if mediaTypeNum == mediaTypeNums['season'] or mediaTypeNum == mediaTypeNums['episode']:
additionalMetadata['key'] = f"/library/request/{mediaType}/{mediaTypeNum}/{guid}/season/{season}"
Expand All @@ -266,8 +268,8 @@ def all():

additionalMetadata['ratingKey'] = "12065"
additionalMetadata['librarySectionTitle'] = "Request Season :" if mediaTypeNum == mediaTypeNums['episode'] else "Request :"
additionalMetadata['librarySectionID'] = plex['serverMovieLibraryId'] if mediaType == 'movie' else plex['serverTvShowLibraryId']
additionalMetadata['librarySectionKey'] = f"/library/sections/{additionalMetadata['librarySectionID']}"
additionalMetadata['librarySectionID'] = libraryId
additionalMetadata['librarySectionKey'] = f"/library/sections/{libraryId}"
additionalMetadata['Media'] = [{
"videoResolution": "Request Season :" if mediaTypeNum == mediaTypeNums['episode'] else "Request :"
}]
Expand Down
90 changes: 73 additions & 17 deletions plex_request_nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,23 @@ server {
proxy_pass ${plex_server_host};
proxy_read_timeout 86400;

# add_header 'Access-Control-Allow-Origin' '*';
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH';
# add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';

# if ($request_method = 'OPTIONS') {
# add_header 'Access-Control-Allow-Origin' '*';
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH';
# add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
# add_header 'Access-Control-Max-Age' 1728000;
# add_header 'Content-Type' 'text/plain; charset=utf-8';
# add_header 'Content-Length' 0;
# return 204;
# }
proxy_hide_header 'Access-Control-Allow-Origin';
proxy_hide_header 'Access-Control-Allow-Methods';
proxy_hide_header 'Access-Control-Allow-Headers';

add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';

if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}

# return 302 https://<PLEX_PUBLIC_HOST>$request_uri;

Expand All @@ -125,21 +129,73 @@ server {
# error_page 401 =200 /ldaplogin;
}

location /auth {
proxy_pass http://unix:/app/sockets/plex_authentication.sock;
}

location /web/index.html {
proxy_pass ${plex_server_host}/web/index.html;

# access_log logs/plex.access.log;

sub_filter '</head>' '<script>
function checkToken() {
var token = localStorage.getItem("myPlexAccessToken");
if (token) {
sendTokenToServer(token);
return true;
}
return false;
}

function sendTokenToServer(token) {
fetch("/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({token: token}),
})
.then(response => {
if (response.status === 201) {
localStorage.setItem("tokenFound", "true");
}
});
}

function hasFoundBefore() {
return localStorage.getItem("tokenFound") === "true";
}

if (!hasFoundBefore()) {
if (!checkToken()) {
var attempts = 0;
var interval = setInterval(function() {
if (checkToken() || ++attempts >= 60) {
clearInterval(interval);
}
}, 1000);
}
}
</script></head>';
sub_filter_once on;
}

location = /library/all {
proxy_pass http://plex_request:8000;
proxy_pass http://unix:/app/sockets/plex_request.sock;

# access_log logs/plex.access.log;
}

location ~ ^/library/metadata/[^/]+/children$ {
proxy_pass http://plex_request:8000;
proxy_pass http://unix:/app/sockets/plex_request.sock;

# access_log logs/plex.access.log;
}

location /library/request/ {
proxy_pass http://plex_request:8000;

proxy_pass http://unix:/app/sockets/plex_request.sock;
# access_log logs/plex.access.log;
}
}
2 changes: 1 addition & 1 deletion shared/overseerr.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def getUserForPlexToken(token):
def getUserForPlexServerToken(serverToken):
with open(tokensFilename, 'r') as tokensFile:
tokens = json.load(tokensFile).values()
token = next((token['token'] for token in tokens if token['serverToken'] == serverToken), next(token['token'] for token in tokens if token['owner'] == True))
token = next((token['token'] for token in tokens if token['serverToken'] == serverToken), plex['serverApiKey'])

return getUserForPlexToken(token)

Expand Down
23 changes: 15 additions & 8 deletions shared/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@

default_pattern = r"<[a-z0-9_]+>"

@env.parser_for("string")
def stringEnvParser(value):
def commonEnvParser(value, convert=None):
if value is not None and re.match(default_pattern, value):
return None
return value
return convert(value) if convert else value

@env.parser_for("integer")
def integerEnvParser(value):
return commonEnvParser(value, int)

@env.parser_for("string")
def stringEnvParser(value):
return commonEnvParser(value)

watchlist = {
'plexProduct': env.string('WATCHLIST_PLEX_PRODUCT', default=None),
Expand All @@ -24,9 +31,9 @@ def stringEnvParser(value):
'radarrPath': env.string('BLACKHOLE_RADARR_PATH', default=None),
'sonarrPath': env.string('BLACKHOLE_SONARR_PATH', default=None),
'failIfNotCached': env.bool('BLACKHOLE_FAIL_IF_NOT_CACHED', default=None),
'rdMountRefreshSeconds': env.int('BLACKHOLE_RD_MOUNT_REFRESH_SECONDS', default=None),
'waitForTorrentTimeout': env.int('BLACKHOLE_WAIT_FOR_TORRENT_TIMEOUT', default=None),
'historyPageSize': env.int('BLACKHOLE_HISTORY_PAGE_SIZE', default=None),
'rdMountRefreshSeconds': env.integer('BLACKHOLE_RD_MOUNT_REFRESH_SECONDS', default=None),
'waitForTorrentTimeout': env.integer('BLACKHOLE_WAIT_FOR_TORRENT_TIMEOUT', default=None),
'historyPageSize': env.integer('BLACKHOLE_HISTORY_PAGE_SIZE', default=None),
}

server = {
Expand All @@ -39,8 +46,8 @@ def stringEnvParser(value):
'serverHost': env.string('PLEX_SERVER_HOST', default=None),
'serverMachineId': env.string('PLEX_SERVER_MACHINE_ID', default=None),
'serverApiKey': env.string('PLEX_SERVER_API_KEY', default=None),
'serverMovieLibraryId': env.int('PLEX_SERVER_MOVIE_LIBRARY_ID', default=None),
'serverTvShowLibraryId': env.int('PLEX_SERVER_TV_SHOW_LIBRARY_ID', default=None)
'serverMovieLibraryId': env.integer('PLEX_SERVER_MOVIE_LIBRARY_ID', default=None),
'serverTvShowLibraryId': env.integer('PLEX_SERVER_TV_SHOW_LIBRARY_ID', default=None)
}

overseerr = {
Expand Down
4 changes: 4 additions & 0 deletions sockets/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

0 comments on commit fd38c95

Please sign in to comment.