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//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", )