""" Install the portal auth bridge as a Server Script in ERPNext. This creates a whitelisted API endpoint: /api/method/portal_login Flow: 1. User POSTs email + password 2. If user has legacy_password_md5: → md5(password) matches? → update to pbkdf2, clear legacy hash, create session → no match? → error 3. If no legacy hash: standard frappe auth Run inside erpnext-backend-1: /home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/setup_portal_auth_bridge.py """ import os, sys os.chdir("/home/frappe/frappe-bench/sites") import frappe frappe.init(site="erp.gigafibre.ca", sites_path=".") frappe.connect() print("Connected:", frappe.local.site) SCRIPT_NAME = "Portal Login Bridge" script_code = ''' import hashlib import frappe from frappe.utils.password import update_password, check_password email = frappe.form_dict.get("email", "").strip().lower() password = frappe.form_dict.get("password", "") if not email or not password: frappe.throw("Email et mot de passe requis", frappe.AuthenticationError) if not frappe.db.exists("User", email): frappe.throw("Identifiants invalides", frappe.AuthenticationError) user = frappe.get_doc("User", email) if not user.enabled: frappe.throw("Compte désactivé", frappe.AuthenticationError) legacy_hash = (user.get("legacy_password_md5") or "").strip() authenticated = False if legacy_hash: input_md5 = hashlib.md5(password.encode("utf-8")).hexdigest() if input_md5 == legacy_hash: update_password(email, password, logout_all_sessions=False) frappe.db.set_value("User", email, "legacy_password_md5", "", update_modified=False) frappe.db.commit() frappe.logger().info(f"Portal auth bridge: migrated password for {email}") authenticated = True else: try: check_password(email, password) frappe.db.set_value("User", email, "legacy_password_md5", "", update_modified=False) frappe.db.commit() authenticated = True except frappe.AuthenticationError: frappe.throw("Mot de passe incorrect", frappe.AuthenticationError) else: try: check_password(email, password) authenticated = True except frappe.AuthenticationError: frappe.throw("Mot de passe incorrect", frappe.AuthenticationError) if authenticated: frappe.local.login_manager.login_as(email) frappe.response["message"] = "OK" frappe.response["user"] = email frappe.response["full_name"] = user.full_name ''' # Create or update the Server Script if frappe.db.exists("Server Script", SCRIPT_NAME): doc = frappe.get_doc("Server Script", SCRIPT_NAME) doc.script = script_code doc.save(ignore_permissions=True) print(f" Updated Server Script: {SCRIPT_NAME}") else: doc = frappe.get_doc({ "doctype": "Server Script", "name": SCRIPT_NAME, "__newname": SCRIPT_NAME, "script_type": "API", "api_method": "portal_login", "allow_guest": 1, "script": script_code, }) doc.insert(ignore_permissions=True) print(f" Created Server Script: {SCRIPT_NAME}") frappe.db.commit() print(" Done! Endpoint available at: POST /api/method/portal_login") print(" Params: email, password") print(" Returns: { message: 'OK', user: '...', full_name: '...' }")