feat SSO: Update login JS to POST /api/method/lms.lms.api.eduvia_proxy_login
This commit is contained in:
@@ -25,6 +25,8 @@ web_include_js = [
|
|||||||
"/assets/lms/js/eduvia_sso_login.js",
|
"/assets/lms/js/eduvia_sso_login.js",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
before_request = ["lms.integrations.eduvia_sso.try_sso_login"]
|
||||||
|
|
||||||
# include custom scss in every website theme (without file extension ".scss")
|
# include custom scss in every website theme (without file extension ".scss")
|
||||||
# website_theme_scss = "lms/public/scss/website"
|
# website_theme_scss = "lms/public/scss/website"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
if (location.pathname !== '/login') return;
|
// ajoute le bouton que sur la page de login - s'il n'y en a pas deja
|
||||||
|
if (!location.pathname.includes('/login')) return;
|
||||||
|
|
||||||
const target =
|
const target =
|
||||||
document.querySelector('.page-card .page-card-actions') ||
|
document.querySelector('.page-card .page-card-actions') ||
|
||||||
@@ -10,16 +11,86 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
const btn = document.createElement('a');
|
const btn = document.createElement('a');
|
||||||
btn.id = 'eduvia-login-btn';
|
btn.id = 'eduvia-login-btn';
|
||||||
btn.href = '/eduvia/sso/start?return=' + encodeURIComponent(location.href);
|
|
||||||
btn.className = 'btn btn-primary btn-block mt-3';
|
btn.className = 'btn btn-primary btn-block mt-3';
|
||||||
btn.innerText = 'Se connecter avec Eduvia';
|
btn.innerText = 'Se connecter avec Eduvia';
|
||||||
|
btn.href = '#';
|
||||||
|
|
||||||
if (target.tagName && target.tagName.toLowerCase() === 'form') {
|
const insertIntoForm = (container) => {
|
||||||
const div = document.createElement('div');
|
if (container && container.tagName && container.tagName.toLowerCase() === 'form') {
|
||||||
div.className = 'mt-3';
|
const wrap = document.createElement('div');
|
||||||
div.appendChild(btn);
|
wrap.className = 'mt-3';
|
||||||
target.appendChild(div);
|
wrap.appendChild(btn);
|
||||||
|
container.appendChild(wrap);
|
||||||
} else {
|
} else {
|
||||||
target.appendChild(btn);
|
target.appendChild(btn);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
insertIntoForm(target);
|
||||||
|
|
||||||
|
btn.addEventListener(
|
||||||
|
'click',
|
||||||
|
async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (btn.dataset.loading === '1') return;
|
||||||
|
|
||||||
|
// Recupere user/pass depuis le formulaire s'ils existent -sinon prompt()
|
||||||
|
const emailEl =
|
||||||
|
document.querySelector('#login_email') ||
|
||||||
|
document.querySelector('input[name="login_email"]') ||
|
||||||
|
document.querySelector('input[type="email"]');
|
||||||
|
|
||||||
|
const passEl =
|
||||||
|
document.querySelector('#login_password') ||
|
||||||
|
document.querySelector('input[name="login_password"]') ||
|
||||||
|
document.querySelector('input[type="password"]');
|
||||||
|
|
||||||
|
const user =
|
||||||
|
(emailEl && emailEl.value && emailEl.value.trim()) ||
|
||||||
|
(typeof prompt === 'function' ? prompt('Utilisateur Eduvia') : '');
|
||||||
|
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
const password =
|
||||||
|
(passEl && passEl.value) ||
|
||||||
|
(typeof prompt === 'function' ? prompt('Mot de passe Eduvia') : '');
|
||||||
|
|
||||||
|
if (password == null) return;
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.set('user', user);
|
||||||
|
form.set('password', password);
|
||||||
|
|
||||||
|
const originalText = btn.innerText;
|
||||||
|
btn.dataset.loading = '1';
|
||||||
|
btn.innerText = 'Connexion en cours...';
|
||||||
|
btn.classList.add('disabled');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/method/lms.lms.api.eduvia_proxy_login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: form,
|
||||||
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Frappe renvoie { message: {...} }
|
||||||
|
const payload = await res.json().catch(() => ({}));
|
||||||
|
const msg = payload && (payload.message || payload);
|
||||||
|
|
||||||
|
if (res.ok && msg && msg.success) {
|
||||||
|
// Connecte: redirige vers la home (ou recharge)
|
||||||
|
window.location.assign('/');
|
||||||
|
} else {
|
||||||
|
alert((msg && msg.error) || 'Login Eduvia échoué');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('Erreur réseau');
|
||||||
|
} finally {
|
||||||
|
btn.dataset.loading = '';
|
||||||
|
btn.innerText = originalText;
|
||||||
|
btn.classList.remove('disabled');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import time
|
|
||||||
import uuid
|
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
|
|
||||||
try:
|
|
||||||
import jwt # PyJWT
|
|
||||||
except Exception:
|
|
||||||
jwt = None
|
|
||||||
|
|
||||||
|
|
||||||
def _now_utc():
|
|
||||||
return datetime.now(timezone.utc)
|
|
||||||
|
|
||||||
|
|
||||||
def get_context(context):
|
|
||||||
conf = frappe.get_conf()
|
|
||||||
|
|
||||||
if not conf.get("eduvia_sso_mock_enabled"):
|
|
||||||
frappe.throw("Mock désactivé. Ajoutez eduvia_sso_mock_enabled: 1 dans site_config.json")
|
|
||||||
|
|
||||||
if jwt is None:
|
|
||||||
frappe.throw("PyJWT manquant. Installez-le: bench pip install PyJWT")
|
|
||||||
|
|
||||||
shared_secret = conf.get("eduvia_sso_shared_secret")
|
|
||||||
if not shared_secret:
|
|
||||||
frappe.throw("Ajoutez eduvia_sso_shared_secret dans site_config.json")
|
|
||||||
|
|
||||||
algorithm = conf.get("eduvia_sso_algorithm", "HS256")
|
|
||||||
cookie_name = conf.get("eduvia_sso_cookie_name", "eduvia_sso")
|
|
||||||
# Optionnel: mets "lms.localhost" en dev HTTP, ou ".eduvia.app" en prod HTTPS
|
|
||||||
cookie_domain = conf.get("eduvia_sso_cookie_domain")
|
|
||||||
cookie_path = conf.get("eduvia_sso_cookie_path", "/")
|
|
||||||
cookie_ttl_seconds = int(conf.get("eduvia_sso_cookie_ttl_seconds", 120))
|
|
||||||
issuer = conf.get("eduvia_sso_issuer", "eduvia")
|
|
||||||
audience = conf.get("eduvia_sso_audience", "lms-eduvia")
|
|
||||||
|
|
||||||
user_email = frappe.form_dict.get("email") or "demo.user@eduvia.local"
|
|
||||||
return_url = frappe.form_dict.get("return") or "/"
|
|
||||||
|
|
||||||
now = _now_utc()
|
|
||||||
expires = now + timedelta(seconds=cookie_ttl_seconds)
|
|
||||||
claims = {
|
|
||||||
"iss": issuer,
|
|
||||||
"aud": audience,
|
|
||||||
"sub": user_email,
|
|
||||||
"email": user_email,
|
|
||||||
"iat": int(now.timestamp()),
|
|
||||||
"exp": int(expires.timestamp()),
|
|
||||||
"jti": str(uuid.uuid4()),
|
|
||||||
}
|
|
||||||
|
|
||||||
token = jwt.encode(claims, shared_secret, algorithm=algorithm)
|
|
||||||
|
|
||||||
is_https = getattr(getattr(frappe, "request", None), "scheme", "") == "https"
|
|
||||||
default_secure = 1 if is_https else 0
|
|
||||||
secure_flag = bool(int(conf.get("eduvia_sso_cookie_secure", default_secure)))
|
|
||||||
|
|
||||||
# Set the cookie
|
|
||||||
cookies = frappe.local.response.setdefault("cookie", {})
|
|
||||||
cookie = {
|
|
||||||
"value": token,
|
|
||||||
"path": cookie_path,
|
|
||||||
"secure": False, # change to True for production
|
|
||||||
"httponly": True,
|
|
||||||
"samesite": "Lax",
|
|
||||||
"max_age": cookie_ttl_seconds,
|
|
||||||
"expires": int(time.time()) + cookie_ttl_seconds,
|
|
||||||
}
|
|
||||||
if cookie_domain:
|
|
||||||
cookie["domain"] = cookie_domain # otherwise host-only
|
|
||||||
|
|
||||||
cookies[cookie_name] = cookie
|
|
||||||
|
|
||||||
# Redirect to the original page
|
|
||||||
frappe.local.flags.redirect_location = return_url
|
|
||||||
raise frappe.Redirect
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import frappe
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
|
|
||||||
def get_context(context):
|
|
||||||
return_url = frappe.form_dict.get("return") or "/"
|
|
||||||
email = frappe.form_dict.get("email")
|
|
||||||
conf = frappe.get_conf()
|
|
||||||
|
|
||||||
if conf.get("eduvia_sso_mock_enabled"):
|
|
||||||
params = {"return": return_url}
|
|
||||||
if email:
|
|
||||||
params["email"] = email
|
|
||||||
target = f"{frappe.utils.get_url('/eduvia/sso/mock')}?{urlencode(params)}"
|
|
||||||
else:
|
|
||||||
eduvia_start = conf.get("eduvia_sso_start_url") or "https://app.eduvia.app/sso/start"
|
|
||||||
target = f"{eduvia_start}?{urlencode({'return': return_url})}"
|
|
||||||
|
|
||||||
frappe.local.flags.redirect_location = target
|
|
||||||
raise frappe.Redirect
|
|
||||||
Reference in New Issue
Block a user