113 lines
3.0 KiB
Python
113 lines
3.0 KiB
Python
import argparse
|
|
import base64
|
|
import binascii
|
|
import json
|
|
import secrets
|
|
from urllib.parse import urlencode
|
|
|
|
import requests
|
|
import yaml
|
|
from cryptography.hazmat.primitives import serialization
|
|
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
|
from flask import Flask, redirect, render_template, request, Response, session
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("config", help="Path to configuration file")
|
|
args = parser.parse_args()
|
|
|
|
with open(args.config) as f:
|
|
config = yaml.safe_load(f)
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = config["secret_key"]
|
|
|
|
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
public_key = private_key.public_key().public_bytes(
|
|
serialization.Encoding.PEM,
|
|
serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
).decode()
|
|
|
|
|
|
@app.route("/")
|
|
def index():
|
|
return render_template("index.html", application_name=config["application_name"])
|
|
|
|
|
|
@app.route("/authorize")
|
|
def authorize():
|
|
nonce = secrets.token_hex(16)
|
|
client_id = secrets.token_hex(48)
|
|
session["nonce"] = nonce
|
|
|
|
params = {
|
|
"auth_redirect": config["app_url"] + "/callback",
|
|
"application_name": config["application_name"],
|
|
"scopes": "read",
|
|
"client_id": client_id,
|
|
"nonce": nonce,
|
|
"public_key": public_key,
|
|
}
|
|
return redirect(f"{config['forum_url']}/user-api-key/new?{urlencode(params)}")
|
|
|
|
|
|
@app.route("/callback")
|
|
def callback():
|
|
payload_b64 = request.args.get("payload")
|
|
if not payload_b64:
|
|
return "Missing payload", 400
|
|
|
|
try:
|
|
payload = base64.b64decode(payload_b64)
|
|
except binascii.Error:
|
|
return "Failed to decode payload", 400
|
|
|
|
try:
|
|
data = private_key.decrypt(payload, padding.PKCS1v15())
|
|
response = json.loads(data)
|
|
except (ValueError, json.JSONDecodeError):
|
|
return "Failed to decrypt or decode payload", 400
|
|
|
|
if response.get("nonce") != session.get("nonce"):
|
|
return "Invalid nonce", 400
|
|
|
|
key = response["key"]
|
|
calendar_url = (
|
|
f"{config['app_url']}/calendar?"
|
|
+ urlencode({"order": "desc", "api_key": key})
|
|
)
|
|
|
|
return render_template(
|
|
"result.html",
|
|
application_name=config["application_name"],
|
|
calendar_url=calendar_url,
|
|
)
|
|
|
|
@app.route("/calendar")
|
|
def calendar():
|
|
params = request.args.to_dict()
|
|
user_api_key = params.pop('user_api_key', None)
|
|
if not user_api_key:
|
|
return {"error": "Missing user_api_key parameter"}, 400
|
|
|
|
calendar_url = config['forum_url'] + "/discourse-post-event/events.ics"
|
|
|
|
headers = {
|
|
'User-Api-Key': user_api_key
|
|
}
|
|
|
|
try:
|
|
response = requests.get(calendar_url, params=params, headers=headers)
|
|
|
|
return Response(
|
|
response.content,
|
|
status=response.status_code,
|
|
content_type=response.headers.get('Content-Type')
|
|
)
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
return {"error": f"Backend connection failed: {str(e)}"}, 502
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=8000)
|