From a057aab19803f33fadf0a09c4a5e0144507b0f70 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 15 Aug 2023 18:49:45 +0200 Subject: [PATCH] Initial commit for new app store --- .gitignore | 1 + store/app.py | 121 +++++++++++++++++++++++++++++++++ store/requirements.txt | 1 + store/templates/app.html | 9 +++ store/templates/base.html | 123 ++++++++++++++++++++++++++++++++++ store/templates/catalog.html | 113 +++++++++++++++++++++++++++++++ store/templates/index.html | 40 +++++++++++ store/templates/wishlist.html | 103 ++++++++++++++++++++++++++++ 8 files changed, 511 insertions(+) create mode 100644 store/app.py create mode 100644 store/requirements.txt create mode 100644 store/templates/app.html create mode 100644 store/templates/base.html create mode 100644 store/templates/catalog.html create mode 100644 store/templates/index.html create mode 100644 store/templates/wishlist.html diff --git a/.gitignore b/.gitignore index 484a0956..1dc38528 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ tools/autopatches/token __pycache__ app_list_auto_update.log +venv diff --git a/store/app.py b/store/app.py new file mode 100644 index 00000000..d9cf32d9 --- /dev/null +++ b/store/app.py @@ -0,0 +1,121 @@ +from flask import Flask, send_from_directory, render_template, session, redirect, request +import base64 +import hashlib +import hmac +import os +import random +import urllib +import json +from settings import DISCOURSE_SSO_SECRET, DISCOURSE_SSO_ENDPOINT, CALLBACK_URL_AFTER_LOGIN_ON_DISCOURSE +app = Flask(__name__) + +app.debug = True +app.config["DEBUG"] = True +app.config['TEMPLATES_AUTO_RELOAD'] = True + +catalog = json.load(open("apps.json")) +catalog['categories'] = {c['id']:c for c in catalog['categories']} + +category_color = { + "synchronization": "sky", + "publishing": "yellow", + "communication": "amber", + "office": "lime", + "productivity_and_management": "purple", + "small_utilities": "", + "reading": "emerald", + "multimedia": "fuchsia", + "social_media": "rose", + "games": "violet", + "dev": "stone", + "system_tools": "white", + "iot": "orange", + "wat": "teal", +} + +for id_, category in catalog['categories'].items(): + category["color"] = category_color[id_] + +wishlist = json.load(open("wishlist.json")) + +# This is the secret key used for session signing +app.secret_key = ''.join([str(random.randint(0, 9)) for i in range(99)]) + + +@app.route('/login_using_discourse') +def login_using_discourse(): + """ + Send auth request to Discourse: + """ + + nonce, url = create_nonce_and_build_url_to_login_on_discourse_sso() + + session.clear() + session["nonce"] = nonce + + return redirect(url) + + +@app.route('/sso_login_callback') +def sso_login_callback(): + response = base64.b64decode(request.args['sso'].encode()).decode() + user_data = urllib.parse.parse_qs(response) + if user_data['nonce'][0] != session.get("nonce"): + return "Invalid nonce", 401 + else: + session.clear() + session['user'] = { + "id": user_data["external_id"][0], + "username": user_data["username"][0], + "avatar_url": user_data["avatar_url"][0], + } + return redirect("/") + + +@app.route('/logout') +def logout(): + session.clear() + return redirect("/") + + +@app.route('/') +def index(): + return render_template("index.html", user=session.get('user', {}), catalog=catalog) + + +@app.route('/catalog') +def browse_catalog(category_filter=None): + return render_template("catalog.html", user=session.get('user', {}), catalog=catalog) + + +@app.route('/app/') +def app_info(app_id): + infos = catalog["apps"].get(app_id) + if not infos: + return f"App {app_id} not found", 404 + return render_template("app.html", user=session.get('user', {}), app_id=app_id, infos=infos) + + +@app.route('/wishlist') +def browse_wishlist(): + return render_template("wishlist.html", user=session.get('user', {}), wishlist=wishlist) + + + +################################################ + +def create_nonce_and_build_url_to_login_on_discourse_sso(): + """ + Redirect the user to DISCOURSE_ROOT_URL/session/sso_provider?sso=URL_ENCODED_PAYLOAD&sig=HEX_SIGNATURE + """ + + nonce = ''.join([str(random.randint(0, 9)) for i in range(99)]) + + url_data = {"nonce": nonce, "return_sso_url": CALLBACK_URL_AFTER_LOGIN_ON_DISCOURSE} + url_encoded = urllib.parse.urlencode(url_data) + payload = base64.b64encode(url_encoded.encode()).decode() + sig = hmac.new(DISCOURSE_SSO_SECRET.encode(), msg=payload.encode(), digestmod=hashlib.sha256).hexdigest() + data = {"sig": sig, "sso": payload} + url = f"{DISCOURSE_SSO_ENDPOINT}?{urllib.parse.urlencode(data)}" + + return nonce, url diff --git a/store/requirements.txt b/store/requirements.txt new file mode 100644 index 00000000..5aad892a --- /dev/null +++ b/store/requirements.txt @@ -0,0 +1 @@ +Flask==2.3.2 diff --git a/store/templates/app.html b/store/templates/app.html new file mode 100644 index 00000000..ece72937 --- /dev/null +++ b/store/templates/app.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% block main %} +
+ +

{{ app_id }}

+ +

{{ infos }}

+
+{% endblock %} diff --git a/store/templates/base.html b/store/templates/base.html new file mode 100644 index 00000000..5b2b69b3 --- /dev/null +++ b/store/templates/base.html @@ -0,0 +1,123 @@ + + + + + YunoHost app store + + + + + + + +
+
+ + Home + + + +
+ + +
+
+ + {% if not user %} + + Login using YunoHost's forum + + {% else %} + + + {% endif %} +
+ + +
+
+
+
+ + {% block main %} + {% endblock %} + +

TODO : add a proper footer
+ + + diff --git a/store/templates/catalog.html b/store/templates/catalog.html new file mode 100644 index 00000000..6f1782a9 --- /dev/null +++ b/store/templates/catalog.html @@ -0,0 +1,113 @@ +{% extends "base.html" %} +{% block main %} +{% set locale = 'en' %} +
+

+ Application Catalog +

+
+
+
+
+ + + + + + + +
+ +
+ +
+
+ + Sort by newest +
+
+ + Show only your bookmarks +
+
+
+ + + +{% endblock %} diff --git a/store/templates/index.html b/store/templates/index.html new file mode 100644 index 00000000..2691d726 --- /dev/null +++ b/store/templates/index.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% block main %} +{% set locale = 'en' %} + +
+ +

+ Application Store +

+
+ +
+ + {% for id, category in catalog['categories'].items() %} + + {% endfor %} +
+{% endblock %} diff --git a/store/templates/wishlist.html b/store/templates/wishlist.html new file mode 100644 index 00000000..a4651183 --- /dev/null +++ b/store/templates/wishlist.html @@ -0,0 +1,103 @@ +{% extends "base.html" %} +{% block main %} +
+

+ Application Wishlist +

+
+ +
+
+
+ + + + + + + +
+ + + + Add an app to the wishlist + +
+
+ + + + +
+ + + + + + + + + + + + + + {% for infos in wishlist %} + + + + + + + + + {% endfor %} + +
+ Name + + Description +
+ {{ infos['name'] }} + {{ infos['description'] }} + {% if infos['website'] %} + + + + {% endif %} + + {% if infos['upstream'] %} + + + + {% endif %} + + + + Vote + + + + 123 + +
+
+{% endblock %}