Files
pta/app/Views/pages/role.php
2026-08-25 15:12:11 +02:00

204 lines
9.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Écran de connexion — SEULE vue à ne pas passer par layouts/header.php.
*
* Elle s'affiche avant qu'une session existe, donc avant tout contrôle d'accès,
* et ne peut pas afficher la barre de navigation qui en dépend. Elle porte donc
* son propre <head> : c'est le TROISIÈME endroit où incrémenter le `?v=` des
* assets, avec layouts/header.php et layouts/footer.php.
*
* Depuis la V7.11, ce n'est plus un sélecteur de rôle mais une AUTHENTIFICATION :
* l'utilisateur présente le jeton que la DSI lui a remis, et c'est son compte qui
* détermine son rôle et son périmètre.
*
* La V7.12 ajoute un second facteur pour les comptes Service Enfance et DSI.
* D'où les quatre états de cet écran, portés par $etape :
* « jeton » — saisie du jeton ;
* « definition » — premier accès d'un compte privilégié : choisir son mot de passe ;
* « verification » — accès suivants : le saisir ;
* « changement » — session ouverte, le titulaire remplace son mot de passe.
* Un seul formulaire est affiché à la fois : les étapes sont exclusives.
*/
/** @var string $csrfToken */
/** @var string $error */
/** @var string $notice */
/** @var string $etape */
/** @var string $titulaire */
/** @var int $longueurMinimale */
/** @var array|null $currentProfile */
/** @var string $homeUrl */
/** @var string $cspNonce */
$titres = [
'jeton' => 'Connexion',
'definition' => 'Choisissez votre mot de passe',
'verification' => 'Mot de passe',
'changement' => 'Changer de mot de passe',
];
$titre = $titres[$etape] ?? 'Connexion';
?>
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars($titre) ?> — PTA</title>
<link rel="stylesheet" href="assets/style.css?v=7.29">
</head>
<body class="role-page-body">
<main class="role-page-shell">
<section class="role-intro">
<p class="eyebrow">PTA</p>
<h1><?= htmlspecialchars($titre) ?></h1>
<?php if ($etape === 'jeton'): ?>
<p>Saisissez le jeton d’accès qui vous a été remis. Il détermine votre rôle, les pages visibles et les actions autorisées.</p>
<?php elseif ($etape === 'definition'): ?>
<p>
Votre compte <strong><?= htmlspecialchars($titulaire) ?></strong> engage l’application :
il demande un second secret, connu de vous seul. Choisissez-le maintenant, il vous sera demandé à chaque connexion.
</p>
<?php elseif ($etape === 'verification'): ?>
<p>Jeton reconnu. Saisissez le mot de passe de <strong><?= htmlspecialchars($titulaire) ?></strong> pour ouvrir la session.</p>
<?php else: ?>
<p>Votre mot de passe actuel vous est demandé : sans lui, un poste laissé ouvert suffirait à confisquer votre compte.</p>
<?php endif; ?>
<?php if ($currentProfile && $etape === 'jeton'): ?>
<div class="role-current-profile">
Session ouverte : <strong><?= htmlspecialchars((string) $currentProfile['role_label']) ?></strong>
— <?= htmlspecialchars((string) $currentProfile['label']) ?>
<a class="button button-primary role-resume-link" href="<?= htmlspecialchars($homeUrl) ?>">Reprendre</a>
</div>
<?php endif; ?>
<?php if ($notice !== ''): ?>
<div class="message message-success"><?= htmlspecialchars($notice) ?></div>
<?php endif; ?>
<?php if ($error !== ''): ?>
<div class="message message-error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
</section>
<?php if ($etape === 'jeton'): ?>
<form method="post" class="role-selection-form role-token-form" id="role-token-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<label class="field">
<span>Jeton d’accès</span>
<?php /* autocomplete="off" : un jeton n'a pas à être retenu par le navigateur d'un poste partagé. */ ?>
<input
type="text"
name="token"
id="role-token-input"
class="role-token-input"
placeholder="PTA-XXXXXX-XXXXXX-XXXXXX-XXXXXX"
autocomplete="off"
autocapitalize="characters"
spellcheck="false"
required
autofocus>
</label>
<div class="role-form-actions">
<button type="submit" class="button button-primary">Continuer</button>
</div>
</form>
<?php elseif ($etape === 'definition' || $etape === 'verification'): ?>
<form method="post" class="role-selection-form role-token-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<input type="hidden" name="etape" value="mot_de_passe">
<label class="field">
<span><?= $etape === 'definition' ? 'Nouveau mot de passe' : 'Mot de passe' ?></span>
<input
type="password"
name="mot_de_passe"
autocomplete="<?= $etape === 'definition' ? 'new-password' : 'current-password' ?>"
minlength="<?= (int) $longueurMinimale ?>"
required
autofocus>
</label>
<?php if ($etape === 'definition'): ?>
<label class="field">
<span>Confirmation</span>
<input type="password" name="mot_de_passe_confirmation" autocomplete="new-password" minlength="<?= (int) $longueurMinimale ?>" required>
</label>
<p class="hint">
Au moins <?= (int) $longueurMinimale ?> caractères. Aucune règle de composition :
une phrase dont vous vous souvenez protège mieux qu’un mot court hérissé de symboles.
Il ne sera affiché nulle part — la DSI elle-même ne peut que l’effacer, jamais le lire.
</p>
<?php endif; ?>
<div class="role-form-actions">
<button type="submit" class="button button-primary"><?= $etape === 'definition' ? 'Enregistrer et se connecter' : 'Se connecter' ?></button>
</div>
<p class="hint role-step-back">
<a href="role.php?action=reset&amp;csrf_token=<?= htmlspecialchars(urlencode($csrfToken), ENT_QUOTES) ?>">Recommencer avec un autre jeton</a>
</p>
</form>
<?php else: ?>
<form method="post" class="role-selection-form role-token-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<input type="hidden" name="etape" value="changement">
<label class="field">
<span>Mot de passe actuel</span>
<input type="password" name="mot_de_passe_actuel" autocomplete="current-password" required autofocus>
</label>
<label class="field">
<span>Nouveau mot de passe</span>
<input type="password" name="mot_de_passe" autocomplete="new-password" minlength="<?= (int) $longueurMinimale ?>" required>
</label>
<label class="field">
<span>Confirmation</span>
<input type="password" name="mot_de_passe_confirmation" autocomplete="new-password" minlength="<?= (int) $longueurMinimale ?>" required>
</label>
<p class="hint">Au moins <?= (int) $longueurMinimale ?> caractères.</p>
<div class="role-form-actions">
<button type="submit" class="button button-primary">Changer le mot de passe</button>
</div>
<p class="hint role-step-back">
<a href="<?= htmlspecialchars($homeUrl) ?>">Revenir à l’application</a>
</p>
</form>
<?php endif; ?>
<aside class="role-security-note">
<?php if ($etape === 'changement'): ?>
<strong>Un mot de passe oublié ne se retrouve pas :</strong> la DSI l’efface, et vous en choisissez un nouveau à votre connexion suivante, avec votre jeton.
<?php else: ?>
<strong>Jeton perdu ou compromis ?</strong> Contactez la DSI : elle en génère un nouveau, ce qui révoque immédiatement l’ancien.
Un jeton vaut pour une personne — il ne se prête pas, puisque toutes les actions sont journalisées à son nom.
Les comptes Service Enfance et DSI y ajoutent un mot de passe, que vous seul connaissez.
<?php endif; ?>
</aside>
</main>
<script nonce="<?= htmlspecialchars($cspNonce, ENT_QUOTES) ?>">
(() => {
const input = document.querySelector('#role-token-input');
if (!input) return;
/**
* Met en forme la saisie au fil de la frappe : majuscules et suppression des
* espaces. Les jetons sont recopiés depuis un courriel ou un papier, et un
* copier-coller ramène presque toujours une espace ou un retour à la ligne.
* Le serveur normalise de toute façon (CompteModel::normaliser) ; ceci n'est
* qu'un confort de saisie — et cela ne concerne QUE le jeton, jamais le mot
* de passe, dont chaque caractère compte tel qu'il est frappé.
*/
input.addEventListener('input', () => {
const position = input.selectionStart;
const nettoye = input.value.toUpperCase().replace(/\s+/g, '');
if (nettoye !== input.value) {
input.value = nettoye;
input.setSelectionRange(position, position);
}
});
})();
</script>
</body>
</html>