Implement invitation handling for account switching in mobile interface. Introduce signature validation for invite links to prevent spoofing and enhance security. Update logout redirection to support both reset and invite links. Increment version code to 4.72.803 to reflect these changes.

This commit is contained in:
2026-07-13 16:55:47 -04:00
parent b948f401d1
commit 8191970086
4 changed files with 133 additions and 10 deletions

View File

@ -8,6 +8,7 @@ require_once('php/inc_fx_messages.php');
require_once('php/inc_fx_compte.php'); require_once('php/inc_fx_compte.php');
require_once('php/inc_fx_promoteur.php'); require_once('php/inc_fx_promoteur.php');
require_once('php/inc_fx_inscriptions_gestion.php'); require_once('php/inc_fx_inscriptions_gestion.php');
require_once('php/inc_fx_eve_acces_promoteur.php');
require_once('php/inc_v2_assets.php'); require_once('php/inc_v2_assets.php');
global $objDatabase, $vDomaine, $vPage, $vtexte_page; global $objDatabase, $vDomaine, $vPage, $vtexte_page;
@ -61,6 +62,96 @@ if (!isset($_SESSION['com_id'])) {
exit; exit;
} }
// MSIN-4401 — Lien d'invite destiné à un autre compte : forcer déconnexion.
$intInviteCom = intval($_GET['invite_com'] ?? 0);
$strInviteSig = (string)($_GET['invite_sig'] ?? '');
$intEveForInvite = 0;
if (!empty($_GET['promoteur_eve_id'])) {
$intEveForInvite = fxInscrGestionParseEveId($_GET['promoteur_eve_id']);
}
if ($intInviteCom > 0 && $intEveForInvite > 0
&& function_exists('fxEveAccesPromoteurInviteSigValid')
&& fxEveAccesPromoteurInviteSigValid($intInviteCom, $intEveForInvite, $strInviteSig)
&& intval($_SESSION['com_id']) !== $intInviteCom) {
global $objDatabase;
$arrInviteCompte = $objDatabase->fxGetRow(
'SELECT com_id, com_prenom, com_nom, com_courriel, com_login FROM inscriptions_comptes WHERE com_id = '
. $intInviteCom . ' AND com_actif = 1 LIMIT 1'
);
$strSessionLabel = trim(
(string)($_SESSION['com_info']['com_prenom'] ?? $_SESSION['com_prenom'] ?? '')
. ' '
. (string)($_SESSION['com_info']['com_nom'] ?? '')
);
if ($strSessionLabel === '') {
$strSessionLabel = (string)($_SESSION['com_info']['com_courriel'] ?? ('#' . intval($_SESSION['com_id'])));
}
$strTargetLabel = $arrInviteCompte
? trim(($arrInviteCompte['com_prenom'] ?? '') . ' ' . ($arrInviteCompte['com_nom'] ?? ''))
: '';
if ($strTargetLabel === '' && $arrInviteCompte) {
$strTargetLabel = (string)($arrInviteCompte['com_courriel'] ?? $arrInviteCompte['com_login'] ?? ('#' . $intInviteCom));
}
if ($strTargetLabel === '') {
$strTargetLabel = '#' . $intInviteCom;
}
$strTargetMail = $arrInviteCompte ? (string)($arrInviteCompte['com_courriel'] ?? '') : '';
$strReturnPath = (string)(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/mobile');
$strQs = (string)(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY) ?: '');
if ($strQs !== '') {
$strReturnPath .= '?' . $strQs;
}
$strLogoutUrl = $vDomaine . '/logout.php?lang=' . urlencode($strLangue)
. '&return=' . rawurlencode($strReturnPath);
$strStayUrl = $vDomaine . ($strLangue === 'en' ? '/account' : '/compte');
$strMetaTitle = ($strLangue === 'fr') ? 'Inscriptions mobile' : 'Mobile registrations';
$strMetaDescription = '';
$strMetaKeywords = '';
$blnBoutonRetour = false;
$footer_script = false;
require_once('inc_header.php');
?>
<div id="page">
<div id="main">
<div class="container bg-white p-3 p-xl-4">
<div class="alert alert-warning" role="alert">
<?php if ($strLangue === 'en') { ?>
<p class="mb-2">You are currently signed in as
<strong><?php echo htmlspecialchars($strSessionLabel, ENT_QUOTES, 'UTF-8'); ?></strong>.</p>
<p class="mb-3">This invitation is for
<strong><?php echo htmlspecialchars($strTargetLabel, ENT_QUOTES, 'UTF-8'); ?></strong><?php
echo $strTargetMail !== '' ? ' (' . htmlspecialchars($strTargetMail, ENT_QUOTES, 'UTF-8') . ')' : '';
?>.
Sign out first, then sign in with that account.</p>
<?php } else { ?>
<p class="mb-2">Vous êtes actuellement connecté en tant que
<strong><?php echo htmlspecialchars($strSessionLabel, ENT_QUOTES, 'UTF-8'); ?></strong>.</p>
<p class="mb-3">Cette invitation concerne le compte
<strong><?php echo htmlspecialchars($strTargetLabel, ENT_QUOTES, 'UTF-8'); ?></strong><?php
echo $strTargetMail !== '' ? ' (' . htmlspecialchars($strTargetMail, ENT_QUOTES, 'UTF-8') . ')' : '';
?>.
Déconnectez-vous d&rsquo;abord, puis reconnectez-vous avec ce compte.</p>
<?php } ?>
<a class="btn btn-primary rounded-0 mr-2" href="<?php echo htmlspecialchars($strLogoutUrl, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo ($strLangue === 'en') ? 'Sign out and continue' : 'Se déconnecter et continuer'; ?>
</a>
<a class="btn btn-outline-secondary rounded-0" href="<?php echo htmlspecialchars($strStayUrl, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo ($strLangue === 'en') ? 'Stay on my current account' : 'Rester sur mon compte actuel'; ?>
</a>
</div>
</div>
</div>
</div>
<?php
require_once('inc_footer.php');
exit;
}
unset($_SESSION['affiche_pas_connexion']); unset($_SESSION['affiche_pas_connexion']);
$intEveId = 0; $intEveId = 0;

View File

@ -1351,14 +1351,18 @@ function fxLogout($strLangue = 'fr')
exit; exit;
} }
// MSIN-4401 — retour vers lien reset après déconnexion (chemin relatif sûr uniquement). // MSIN-4401 — retour sécurisé après déconnexion (reset OU lien invite /mobile).
if (!empty($_GET['return'])) { if (!empty($_GET['return'])) {
$strReturn = (string)$_GET['return']; $strReturn = (string)$_GET['return'];
if ($strReturn !== '' && $strReturn[0] === '/' $blnReturnOk = ($strReturn !== '' && $strReturn[0] === '/'
&& preg_match('#^/(compte|account)/reset(\?|$)#i', $strReturn)
&& strpos($strReturn, '//') === false && strpos($strReturn, '//') === false
&& strpos($strReturn, "\n") === false && strpos($strReturn, "\n") === false
&& strpos($strReturn, "\r") === false) { && strpos($strReturn, "\r") === false
&& (
preg_match('#^/(compte|account)/reset(\?|$)#i', $strReturn)
|| preg_match('#^/mobile(/en)?(\?|$)#i', $strReturn)
));
if ($blnReturnOk) {
header('Location: ' . $vDomaine . $strReturn); header('Location: ' . $vDomaine . $strReturn);
exit; exit;
} }

View File

@ -232,6 +232,30 @@ function fxEveAccesPromoteurCreateCompteInvite($strCourriel, $strPrenom, $strNom
); );
} }
/**
* MSIN-4401 — Signature du destinataire invite (évite spoof invite_com).
*/
function fxEveAccesPromoteurInviteSig($intComId, $intEveId)
{
$strSecret = defined('QR_SECRET_KEY') ? (string)QR_SECRET_KEY : 'ms1-invite';
return substr(hash_hmac('sha256', intval($intComId) . '|' . intval($intEveId), $strSecret), 0, 20);
}
function fxEveAccesPromoteurInviteSigValid($intComId, $intEveId, $strSig)
{
$strExpect = fxEveAccesPromoteurInviteSig($intComId, $intEveId);
return is_string($strSig) && $strSig !== '' && hash_equals($strExpect, $strSig);
}
/** Ajoute invite_com + invite_sig à une URL /mobile (ou absolue). */
function fxEveAccesPromoteurAppendInviteParams($strUrl, $intComId, $intEveId)
{
$strSep = (strpos($strUrl, '?') !== false) ? '&' : '?';
return $strUrl . $strSep
. 'invite_com=' . intval($intComId)
. '&invite_sig=' . rawurlencode(fxEveAccesPromoteurInviteSig($intComId, $intEveId));
}
function fxEveAccesPromoteurSendInviteMail($arrCompte, $arrEve, $arrRole, $strLangue, $blnNewAccount, $strResetToken = '') function fxEveAccesPromoteurSendInviteMail($arrCompte, $arrEve, $arrRole, $strLangue, $blnNewAccount, $strResetToken = '')
{ {
global $vDomaine, $vEmail, $vClient; global $vDomaine, $vEmail, $vClient;
@ -251,13 +275,15 @@ function fxEveAccesPromoteurSendInviteMail($arrCompte, $arrEve, $arrRole, $strLa
$strPrenom = fxUnescape($arrCompte['com_prenom'] ?? ''); $strPrenom = fxUnescape($arrCompte['com_prenom'] ?? '');
$strLogin = $arrCompte['com_login'] ?? $arrCompte['com_courriel']; $strLogin = $arrCompte['com_login'] ?? $arrCompte['com_courriel'];
$intEveId = intval($arrEve['eve_id'] ?? 0); $intEveId = intval($arrEve['eve_id'] ?? 0);
$intComId = intval($arrCompte['com_id'] ?? 0);
$strEveParam = urlencode(base64_encode((string)$intEveId)); $strEveParam = urlencode(base64_encode((string)$intEveId));
$strComptePath = ($strLangue === 'en') ? '/account' : '/compte'; $strComptePath = ($strLangue === 'en') ? '/account' : '/compte';
// MSIN-4401 — Lien terrain : même outil inscriptions, entrée /mobile (pas le hub). // MSIN-4401 — Lien terrain : /mobile + identité destinataire (invite_com/sig).
$strMobileUrl = $vDomaine . '/mobile?promoteur_eve_id=' . $strEveParam; $strMobileUrl = $vDomaine . '/mobile?promoteur_eve_id=' . $strEveParam;
if ($strLangue === 'en') { if ($strLangue === 'en') {
$strMobileUrl = $vDomaine . '/mobile/en?promoteur_eve_id=' . $strEveParam; $strMobileUrl = $vDomaine . '/mobile/en?promoteur_eve_id=' . $strEveParam;
} }
$strMobileUrl = fxEveAccesPromoteurAppendInviteParams($strMobileUrl, $intComId, $intEveId);
if ($blnNewAccount && $strResetToken !== '') { if ($blnNewAccount && $strResetToken !== '') {
if ($strLangue === 'fr') { if ($strLangue === 'fr') {
@ -292,16 +318,18 @@ function fxEveAccesPromoteurSendInviteMail($arrCompte, $arrEve, $arrRole, $strLa
$message = 'Bonjour ' . $strPrenom . ',<br><br>' $message = 'Bonjour ' . $strPrenom . ',<br><br>'
. 'Un accès vous a été accordé pour <strong>' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '</strong>' . 'Un accès vous a été accordé pour <strong>' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '</strong>'
. ' (kit : ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').<br><br>' . ' (kit : ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').<br><br>'
. 'Connectez-vous avec votre compte (<strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong>), ' . 'Ouvrez le lien ci-dessous et connectez-vous avec le compte '
. 'puis ouvrez le lien ci-dessous.'; . '<strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong> '
. '(si un autre compte est déjà ouvert, vous serez invité à vous déconnecter).';
$attachment_label = 'Ouvrir l\'accès (mobile)'; $attachment_label = 'Ouvrir l\'accès (mobile)';
} else { } else {
$subject = 'New access — ' . $strEveNom; $subject = 'New access — ' . $strEveNom;
$message = 'Hello ' . $strPrenom . ',<br><br>' $message = 'Hello ' . $strPrenom . ',<br><br>'
. 'You were granted access to <strong>' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '</strong>' . 'You were granted access to <strong>' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '</strong>'
. ' (kit: ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').<br><br>' . ' (kit: ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').<br><br>'
. 'Sign in with your account (<strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong>), ' . 'Open the link below and sign in as '
. 'then open the link below.'; . '<strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong> '
. '(if another account is already open, you will be asked to sign out).';
$attachment_label = 'Open access (mobile)'; $attachment_label = 'Open access (mobile)';
} }
$attachment_url = $strMobileUrl; $attachment_url = $strMobileUrl;

View File

@ -7,7 +7,7 @@
* Constantes * * Constantes *
* *
**************/ **************/
define('_VERSION_CODE', '4.72.802'); define('_VERSION_CODE', '4.72.803');
define('_DATE_CODE', '2026-07-13'); define('_DATE_CODE', '2026-07-13');
//MSIN-4290 //MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');