You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
4.8 KiB
157 lines
4.8 KiB
from pathlib import Path |
|
from flask import send_file, abort, Blueprint, render_template, session, redirect, url_for, flash |
|
import hmac, hashlib, time, urllib.parse, os |
|
|
|
OTB_PORTAL_SHARED_SECRET = os.getenv("OTB_PORTAL_SHARED_SECRET", "!2Eas678") |
|
OTB_CLOUD_URL = os.getenv("OTB_CLOUD_URL", "https://otb-cloud.outsidethebox.top") |
|
|
|
portal_services_bp = Blueprint("portal_services", __name__) |
|
|
|
def _portal_user_is_logged_in() -> bool: |
|
return bool( |
|
session.get("portal_user_id") |
|
or session.get("client_user_id") |
|
or session.get("portal_client_id") |
|
or session.get("client_id") |
|
or session.get("user_id") |
|
) |
|
|
|
@portal_services_bp.route("/portal/services") |
|
def portal_services_home(): |
|
if not _portal_user_is_logged_in(): |
|
flash("Please sign in to access services.", "warning") |
|
return redirect(url_for("portal_login")) |
|
|
|
client = { |
|
"contact_name": session.get("portal_contact_name"), |
|
"company_name": session.get("portal_company_name"), |
|
"email": session.get("portal_email"), |
|
} |
|
|
|
client_name = ( |
|
client.get("contact_name") |
|
or client.get("company_name") |
|
or client.get("email") |
|
or "Client" |
|
) |
|
|
|
services = [ |
|
{ |
|
"key": "follow_me", |
|
"name": "Follow-me Tracker", |
|
"summary": "Create and manage your GPS tracking network. Free for up to 2 users.", |
|
"status": "beta", |
|
"enabled": True, |
|
"href": "/portal/services/follow-me-launch", |
|
"button_text": "Open Follow-me", |
|
}, |
|
{ |
|
"key": "video_render", |
|
"name": "Video Rendering / Streaming", |
|
"summary": "Submit video rendering, conversion, and hosted streaming jobs.", |
|
"status": "coming_soon", |
|
"enabled": False, |
|
"href": "#", |
|
"button_text": "Coming Soon", |
|
}, |
|
{ |
|
"key": "otb_cloud", |
|
"name": "OTB Cloud Backup & Storage", |
|
"summary": "Secure backup and storage for documents, photos, videos, and device uploads.", |
|
"status": "beta", |
|
"enabled": True, |
|
"href": "/portal/services/otb-cloud-launch", |
|
"button_text": "Open OTB Cloud", |
|
}, |
|
{ |
|
"key": "miner_rentals", |
|
"name": "Miner Rentals", |
|
"summary": "Rent available OTB hashpower by time or package.", |
|
"status": "coming_soon", |
|
"enabled": False, |
|
"href": "#", |
|
"button_text": "Coming Soon", |
|
}, |
|
] |
|
|
|
return render_template( |
|
"portal/services_here.html", |
|
client=client, |
|
client_name=client_name, |
|
services=services, |
|
) |
|
|
|
|
|
def build_otb_cloud_handoff_url(uid, email): |
|
ts = str(int(time.time())) |
|
payload = f"{uid}|{email}|{ts}".encode("utf-8") |
|
sig = hmac.new( |
|
OTB_PORTAL_SHARED_SECRET.encode(), |
|
payload, |
|
hashlib.sha256 |
|
).hexdigest() |
|
|
|
query = urllib.parse.urlencode({ |
|
"uid": uid, |
|
"email": email, |
|
"ts": ts, |
|
"sig": sig, |
|
}) |
|
|
|
return f"{OTB_CLOUD_URL}/auth/handoff?{query}" |
|
|
|
|
|
@portal_services_bp.route("/portal/services/otb-cloud-launch") |
|
def portal_launch_otb_cloud(): |
|
if not _portal_user_is_logged_in(): |
|
flash("Please sign in to access services.", "warning") |
|
return redirect(url_for("portal_login")) |
|
|
|
uid = session.get("portal_client_id") |
|
email = session.get("portal_email") |
|
|
|
if not uid or not email: |
|
flash("Unable to launch OTB Cloud because the portal session is missing required account details.", "danger") |
|
return redirect(url_for("portal_services.portal_services_home")) |
|
|
|
url = build_otb_cloud_handoff_url(uid, email) |
|
return redirect(url) |
|
|
|
|
|
@portal_services_bp.route("/portal/downloads/apps/<app_slug>/latest") |
|
def portal_download_latest_app(app_slug): |
|
client = get_portal_client() |
|
if not client: |
|
return redirect("/portal") |
|
|
|
allowed_apps = { |
|
"otb-cloud": { |
|
"filename": "latest.apk", |
|
"download_name": "otb-cloud-latest.apk", |
|
}, |
|
"follow-me": { |
|
"filename": "latest.apk", |
|
"download_name": "follow-me-latest.apk", |
|
}, |
|
} |
|
|
|
if app_slug not in allowed_apps: |
|
abort(404) |
|
|
|
base_dir = Path(__file__).resolve().parents[2] / "downloads" / "apps" / app_slug |
|
file_path = (base_dir / allowed_apps[app_slug]["filename"]).resolve() |
|
|
|
try: |
|
if base_dir.resolve() not in file_path.parents: |
|
abort(403) |
|
if not file_path.exists() or not file_path.is_file(): |
|
abort(404) |
|
except Exception: |
|
abort(404) |
|
|
|
return send_file( |
|
str(file_path), |
|
as_attachment=True, |
|
download_name=allowed_apps[app_slug]["download_name"], |
|
mimetype="application/vnd.android.package-archive", |
|
)
|
|
|