feat: Initial code drop
All checks were successful
Build Docker / Build Docker (push) Successful in 41s

This commit is contained in:
Nathan Woodburn 2024-03-01 16:58:56 +11:00
parent f011e2fd2d
commit f129e2d722
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
10 changed files with 320 additions and 1 deletions

3
.env.example Normal file
View File

@ -0,0 +1,3 @@
PROXY=http://nathanwoodburn:5000/
TLD=woodburn
RESTRICTED=["admin"]

View File

@ -0,0 +1,38 @@
name: Build Docker
run-name: Build Docker Images
on:
push:
jobs:
Build Docker:
runs-on: [ubuntu-latest, amd] # Add amd to require amd64
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Docker
run : |
echo "Updating apt sources"
echo "deb http://ftp.au.debian.org/debian buster main" > /etc/apt/sources.list
apt-get update --allow-unauthenticated --allow-insecure-repositories
apt-get install docker.io -y
- name: Build Docker image
run : |
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
tag=${tag//\//-}
tag_num=${GITHUB_RUN_NUMBER}
echo "tag_num=$tag_num"
if [[ "$tag" == "main" ]]; then
tag="latest"
else
tag_num="${tag}-${tag_num}"
fi
docker build -t tld_restricted_proxy:$tag_num .
docker tag tld_restricted_proxy:$tag_num git.woodburn.au/nathanwoodburn/tld_restricted_proxy:$tag_num
docker push git.woodburn.au/nathanwoodburn/tld_restricted_proxy:$tag_num
docker tag tld_restricted_proxy:$tag_num git.woodburn.au/nathanwoodburn/tld_restricted_proxy:$tag
docker push git.woodburn.au/nathanwoodburn/tld_restricted_proxy:$tag

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.env
__pycache__/
cookies.json

0
404.html Normal file
View File

17
Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM --platform=$BUILDPLATFORM python:3.10-alpine AS builder
WORKDIR /app
COPY requirements.txt /app
RUN --mount=type=cache,target=/root/.cache/pip \
pip3 install -r requirements.txt
COPY . /app
# Add mount point for data volume
# VOLUME /data
ENTRYPOINT ["python3"]
CMD ["server.py"]
FROM builder as dev-envs

View File

@ -1 +1,7 @@
# g-user-proxy
# TLD Restricted Proxy
Environment variables:
- `PROXY` - The proxy to use for restricted TLDs. Example: `http://localhost:3128`
- `TLD` - The TLD to restrict. Example: `g`
- `RESTRICTED` - List of restricted paths. Example: `["path1", "path2"]` will require the user to have a .g domain to access `path1/*`, `path2/*`

130
main.py Normal file
View File

@ -0,0 +1,130 @@
from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory, render_template_string
import os
import dotenv
import requests
import datetime
import json
import secrets
import threading
app = Flask(__name__)
dotenv.load_dotenv()
URL = os.getenv('PROXY')
RESTRICTED = os.getenv('RESTRICTED')
RESTRICTED = json.loads(RESTRICTED)
RESTRICTED = [f'{i.lower()}/' for i in RESTRICTED]
TLD = os.getenv('TLD')
# Load cookies
cookies = []
if os.path.isfile('cookies.json'):
with open('cookies.json') as file:
cookies = json.load(file)
else:
with open('cookies.json', 'w') as file:
json.dump(cookies, file)
# region Auth
@app.route('/auth', methods=['POST'])
def auth():
global cookies
auth = login(request)
if auth == False:
return render_template_string("Failed to authenticate")
# Make sure user has a correct domain
if not auth.endswith(f'.{TLD}'):
return render_template_string(f"You need to have a domain on .{TLD} to access this content.")
resp = make_response(render_template_string("Success"))
# Gen cookie
auth_cookie = secrets.token_hex(12 // 2)
cookies.append({'name': auth, 'cookie': auth_cookie})
with open('cookies.json', 'w') as file:
json.dump(cookies, file)
resp.set_cookie('auth', auth_cookie, max_age=60*60*24*30)
return resp
@app.route('/logout')
def logout():
global cookies
resp = make_response(redirect('/'))
resp.set_cookie('auth', '', expires=0)
if 'auth' not in request.cookies:
return resp
cookies = [i for i in cookies if i['cookie'] != request.cookies['auth']]
with open('cookies.json', 'w') as file:
json.dump(cookies, file)
return resp
def login(request):
dict = request.form.to_dict()
keys = dict.keys()
keys = list(keys)[0]
keys = json.loads(keys)
auth_request = keys['request']
# return login(auth_request)
r = requests.get(f'https://auth.varo.domains/verify/{auth_request}')
r = r.json()
if r['success'] == False:
return False
if 'data' in r:
data = r['data']
if 'name' in data:
return data['name']
return False
# endregion
# Catch all
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>', methods=['GET', 'POST'])
def catch_all(path):
for i in RESTRICTED:
if path.lower().startswith(i) or path.lower() == i:
# Check if user is logged in
if 'auth' not in request.cookies:
return render_template('auth.html', year=datetime.datetime.now().year,redirect=request.url,tld=TLD)
auth = request.cookies['auth']
if not any(i['cookie'] == auth for i in cookies):
return render_template('auth.html', year=datetime.datetime.now().year,redirect=request.url,tld=TLD)
break
res = requests.request(
method = request.method,
url = request.url.replace(request.host_url, f'{URL}/'),
headers = {k:v for k,v in request.headers if k.lower() != 'host'},
data = request.get_data(),
cookies = request.cookies,
allow_redirects = False,
)
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
headers = [
(k,v) for k,v in res.raw.headers.items()
if k.lower() not in excluded_headers
]
# Replace all instances of the proxy URL with the local URL
# If content type is html
if 'text/html' in res.headers['Content-Type']:
content = res.content.decode('utf-8')
content = content.replace(URL, request.host_url)
# TMP: Replace other domains
content = content.replace('https://alee.freeconcept/', request.host_url)
response = make_response(content, res.status_code, headers)
return response
response = make_response(res.content, res.status_code, headers)
return response
if __name__ == '__main__':
app.run(debug=True, port=5000, host='0.0.0.0')

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
flask
python-dotenv
gunicorn
requests

33
server.py Normal file
View File

@ -0,0 +1,33 @@
from flask import Flask
from main import app
import main
from gunicorn.app.base import BaseApplication
class GunicornApp(BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load_config(self):
for key, value in self.options.items():
if key in self.cfg.settings and value is not None:
self.cfg.set(key.lower(), value)
def load(self):
return self.application
if __name__ == '__main__':
workers = 1
threads = 2
workers = int(workers)
threads = int(threads)
options = {
'bind': '0.0.0.0:5000',
'workers': workers,
'threads': threads,
}
gunicorn_app = GunicornApp(app, options)
print('Starting server with ' + str(workers) + ' workers and ' + str(threads) + ' threads', flush=True)
gunicorn_app.run()

82
templates/auth.html Normal file
View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<style>
body {
background-color: #232429;
color: #fff;
text-align: center;
/* Align in the center vertically */
display: flex;
justify-content: center;
align-items: center;
height: 70vh
}
bold {
text-emphasis: bold;
font-weight: bold;
}
button {
padding: 10px 20px;
background-color: black;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
font-size: 1.25em;
}
h1 {
font-size: 4em;
margin-bottom: 0px;
}
p {
font-size: 1.25em;
}
</style>
<script src="https://auth.varo.domains/v1"></script>
<!-- Import Jquery -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
<div>
<h1 style="color: red;" id="error">
{{error}}
</h1>
<h1>This content is protected</h1>
<p>Please verify you own a <bold>.{{tld}}</bold> domain to access this content</p>
<script>
var varo = new Varo;
</script>
<div id="varo-login">
<button id="varo-login-button">Login</button>
</div>
<script>
var button = document.getElementById('varo-login-button');
button.onclick = function () {
varo.auth().then(auth => {
if (auth.success) {
// handle success by calling your api to update the users session
$.post("/auth", JSON.stringify(auth.data), (response) => {
// If response returned true, redirect to the page
if (response == 'Success')
window.location = '{{redirect}}';
else
// Set error message
document.getElementById('error').innerText = response;
});
}
});
}
</script>
</div>
</body>
</html>