gigafibre-fsm/scripts/migration/setup_portal_auth_bridge.py
louispaulb 101faa21f1 feat: inline editing, search, notifications + full repo cleanup
- InlineField component + useInlineEdit composable for Odoo-style dblclick editing
- Client search by name, account ID, and legacy_customer_id (or_filters)
- SMS/Email notification panel on ContactCard via n8n webhooks
- Ticket reply thread via Communication docs
- All migration scripts (51 files) now tracked
- Client portal and field tech app added to monorepo
- README rewritten with full feature list, migration summary, architecture
- CHANGELOG updated with all recent work
- ROADMAP updated with current completion status
- Removed hardcoded tokens from docs (use $ERP_SERVICE_TOKEN)
- .gitignore updated (docker/, .claude/, exports/, .quasar/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 07:34:41 -04:00

100 lines
3.3 KiB
Python

"""
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: '...' }")