kalendasz/app.py

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)