Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-authentication and move authentication to path #33

Merged
merged 9 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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