Overview
Comment:Refactor password change form, customize for first password
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA3-256: ff4431861d9c76a44b4c56e58f6b89545c88fef31510c615ccc6e3229fff5ac2
User & Date: bohwaz on 2022-08-13 12:04:31
Other Links: branch diff | manifest | tags
Context
2022-08-13
20:22
Refactor user security details page, login and password recovery check-in: e4a64ff99c user: bohwaz tags: dev
12:04
Refactor password change form, customize for first password check-in: ff4431861d user: bohwaz tags: dev
10:20
Merge with trunk check-in: cb79a7bfc6 user: bohwaz tags: dev
Changes

Modified src/include/data/1.2.0_schema.sql from [9e6714862a] to [18fc632152].

106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

CREATE TABLE IF NOT EXISTS users_sessions
-- Permanent sessions for logged-in users
(
    selector TEXT NOT NULL,
    hash TEXT NOT NULL,
    id_user INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
    expire INT NOT NULL,

    PRIMARY KEY (selector, id_user)
);

CREATE TABLE IF NOT EXISTS logs
(
    id INTEGER NOT NULL PRIMARY KEY,







|







106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

CREATE TABLE IF NOT EXISTS users_sessions
-- Permanent sessions for logged-in users
(
    selector TEXT NOT NULL,
    hash TEXT NOT NULL,
    id_user INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
    expiry INT NOT NULL,

    PRIMARY KEY (selector, id_user)
);

CREATE TABLE IF NOT EXISTS logs
(
    id INTEGER NOT NULL PRIMARY KEY,

Modified src/include/lib/Garradin/Template.php from [afb4864343] to [158689978f].

85
86
87
88
89
90
91


92
93
94
95
96
97
98
99
100
101
		$session = null;

		if (!defined('Garradin\INSTALL_PROCESS')) {
			$session = Session::getInstance();
			$this->assign('config', Config::getInstance());
		}



		$this->assign('session', $session);
		$this->assign('is_logged', $session ? $session->isLogged() : null);
		$this->assign('logged_user', $session ? $session->getUser() : null);
		$this->assign('session', $session);
		$this->assign('dialog', isset($_GET['_dialog']));

		$this->assign('password_pattern', sprintf('.{%d,}', Session::MINIMUM_PASSWORD_LENGTH));
		$this->assign('password_length', Session::MINIMUM_PASSWORD_LENGTH);

		$this->register_compile_function('continue', function (Smartyer $s, $pos, $block, $name, $raw_args) {







>
>

|
|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
		$session = null;

		if (!defined('Garradin\INSTALL_PROCESS')) {
			$session = Session::getInstance();
			$this->assign('config', Config::getInstance());
		}

		$is_logged = $session ? $session->isLogged() : null;

		$this->assign('session', $session);
		$this->assign('is_logged', $is_logged);
		$this->assign('logged_user', $is_logged ? $session->getUser() : null);
		$this->assign('session', $session);
		$this->assign('dialog', isset($_GET['_dialog']));

		$this->assign('password_pattern', sprintf('.{%d,}', Session::MINIMUM_PASSWORD_LENGTH));
		$this->assign('password_length', Session::MINIMUM_PASSWORD_LENGTH);

		$this->register_compile_function('continue', function (Smartyer $s, $pos, $block, $name, $raw_args) {
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
			{
				return sprintf('use %s;', $raw_args);
			}
		});

		$this->register_function('form_errors', [$this, 'formErrors']);
		$this->register_function('show_error', [$this, 'showError']);
		$this->register_function('form_field', [$this, 'formField']);
		$this->register_function('input', [$this, 'formInput']);
		$this->register_function('password_change', [$this, 'passwordChangeInput']);

		$this->register_function('custom_colors', [$this, 'customColors']);
		$this->register_function('plugin_url', ['Garradin\Utils', 'plugin_url']);
		$this->register_function('diff', [$this, 'diff']);
		$this->register_function('display_permissions', [$this, 'displayPermissions']);







<







112
113
114
115
116
117
118

119
120
121
122
123
124
125
			{
				return sprintf('use %s;', $raw_args);
			}
		});

		$this->register_function('form_errors', [$this, 'formErrors']);
		$this->register_function('show_error', [$this, 'showError']);

		$this->register_function('input', [$this, 'formInput']);
		$this->register_function('password_change', [$this, 'passwordChangeInput']);

		$this->register_function('custom_colors', [$this, 'customColors']);
		$this->register_function('plugin_url', ['Garradin\Utils', 'plugin_url']);
		$this->register_function('diff', [$this, 'diff']);
		$this->register_function('display_permissions', [$this, 'displayPermissions']);
400
401
402
403
404
405
406



407
408
409
410
411
412
413
			if ($v = \DateTime::createFromFormat('!Y-m-d H:i:s', $current_value)) {
				$current_value = $v->format('H:i');
			}
			elseif ($v = \DateTime::createFromFormat('!Y-m-d H:i', $current_value)) {
				$current_value = $v->format('H:i');
			}
		}





		$attributes['id'] = 'f_' . str_replace(['[', ']'], '', $name);
		$attributes['name'] = $name;

		if (!isset($attributes['autocomplete']) && ($type == 'money' || $type == 'password')) {
			$attributes['autocomplete'] = 'off';







>
>
>







401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
			if ($v = \DateTime::createFromFormat('!Y-m-d H:i:s', $current_value)) {
				$current_value = $v->format('H:i');
			}
			elseif ($v = \DateTime::createFromFormat('!Y-m-d H:i', $current_value)) {
				$current_value = $v->format('H:i');
			}
		}
		elseif ($type == 'password') {
			$current_value = null;
		}


		$attributes['id'] = 'f_' . str_replace(['[', ']'], '', $name);
		$attributes['name'] = $name;

		if (!isset($attributes['autocomplete']) && ($type == 'money' || $type == 'password')) {
			$attributes['autocomplete'] = 'off';
555
556
557
558
559
560
561



562
563
564
565
566
567
568
			$value = isset($attributes['value']) ? '' : sprintf(' value="%s"', $this->escape($current_value));
			$input = sprintf('<input type="%s" %s %s />', $type, $attributes_string, $value);
		}

		if ($type == 'file') {
			$input .= sprintf('<input type="hidden" name="MAX_FILE_SIZE" value="%d" id="f_maxsize" />', Utils::return_bytes(Utils::getMaxUploadSize()));
		}




		$input .= $suffix;

		// No label? then we only want the input without the widget
		if (empty($label)) {
			if (!array_key_exists('label', $params) && ($type == 'radio' || $type == 'checkbox')) {
				$input .= sprintf('<label for="%s"></label>', $attributes['id']);







>
>
>







559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
			$value = isset($attributes['value']) ? '' : sprintf(' value="%s"', $this->escape($current_value));
			$input = sprintf('<input type="%s" %s %s />', $type, $attributes_string, $value);
		}

		if ($type == 'file') {
			$input .= sprintf('<input type="hidden" name="MAX_FILE_SIZE" value="%d" id="f_maxsize" />', Utils::return_bytes(Utils::getMaxUploadSize()));
		}
		elseif (!empty($copy)) {
			$input .= sprintf('<input type="button" onclick="var a = $(\'#f_%s\'); a.focus(); a.select(); document.execCommand(\'copy\'); this.value = \'Copié !\'; this.focus(); return false;" onblur="this.value = \'Copier\';" value="Copier" title="Copier dans le presse-papier" />', $params['name']);
		}

		$input .= $suffix;

		// No label? then we only want the input without the widget
		if (empty($label)) {
			if (!array_key_exists('label', $params) && ($type == 'radio' || $type == 'checkbox')) {
				$input .= sprintf('<label for="%s"></label>', $attributes['id']);
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
			if (isset($help)) {
				$out .= sprintf(' <em class="help">(%s)</em>', $this->escape($help));
			}

			$out .= '</dd>';
		}
		else {
			if (!empty($copy)) {
				$input .= sprintf('<input type="button" onclick="var a = $(\'#f_%s\'); a.focus(); a.select(); document.execCommand(\'copy\'); this.value = \'Copié !\'; this.focus(); return false;" onblur="this.value = \'Copier\';" value="Copier" />', $params['name']);
			}

			$out = sprintf('<dt>%s%s</dt><dd>%s</dd>', $label, $required_label, $input);

			if ($type == 'file' && empty($params['no_size_limit'])) {
				$out .= sprintf('<dd class="help"><small>Taille maximale : %s</small></dd>', Utils::format_bytes(Utils::getMaxUploadSize()));
			}

			if (isset($help)) {
				$out .= sprintf('<dd class="help">%s</dd>', $this->escape($help));
			}
		}

		return $out;
	}

	/**
	 * @deprecated
	 */
	protected function formField(array $params, $escape = true)
	{
		if (!isset($params['name']))
		{
			throw new \BadFunctionCallException('name argument is mandatory');
		}

		$name = $params['name'];

		if (isset($_POST[$name]))
			$value = $_POST[$name];
		elseif (isset($params['data']) && is_array($params['data']) && array_key_exists($name, $params['data']))
		{
			$value = $params['data'][$name];
		}
		elseif (isset($params['data']) && is_object($params['data']) && property_exists($params['data'], $name))
		{
			$value = $params['data']->$name;
		}
		elseif (isset($params['default']))
			$value = $params['default'];
		else
			$value = '';

		if (is_array($value))
		{
			return $value;
		}

		if (isset($params['checked']))
		{
			if ($value == $params['checked'])
				return ' checked="checked" ';

			return '';
		}
		elseif (isset($params['selected']))
		{
			if ($value == $params['selected'])
				return ' selected="selected" ';

			return '';
		}

		return $escape ? htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8') : $value;
	}

	protected function formatPhoneNumber($n)
	{
		if (empty($n)) {
			return '';
		}

		$country = Config::getInstance()->get('country');







<
<
<
<














<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







586
587
588
589
590
591
592




593
594
595
596
597
598
599
600
601
602
603
604
605
606


















































607
608
609
610
611
612
613
			if (isset($help)) {
				$out .= sprintf(' <em class="help">(%s)</em>', $this->escape($help));
			}

			$out .= '</dd>';
		}
		else {




			$out = sprintf('<dt>%s%s</dt><dd>%s</dd>', $label, $required_label, $input);

			if ($type == 'file' && empty($params['no_size_limit'])) {
				$out .= sprintf('<dd class="help"><small>Taille maximale : %s</small></dd>', Utils::format_bytes(Utils::getMaxUploadSize()));
			}

			if (isset($help)) {
				$out .= sprintf('<dd class="help">%s</dd>', $this->escape($help));
			}
		}

		return $out;
	}



















































	protected function formatPhoneNumber($n)
	{
		if (empty($n)) {
			return '';
		}

		$country = Config::getInstance()->get('country');

Modified src/include/lib/Garradin/Users/Session.php from [c4e089fae6] to [9aa42938e1].

247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262


263


264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296

		$qrcode = new QRCode($out['url']);
		$out['qrcode'] = 'data:image/svg+xml;base64,' . base64_encode($qrcode->toSVG());

		return $out;
	}

	public function recoverPasswordSend(int $id): int
	{
		$user = $this->fetchUserForPasswordRecovery($id);

		if (!$user) {
			return 1;
		}

		if ($user->perm_connect == self::ACCESS_NONE) {


			return 2;


		}

		$query = $this->makePasswordRecoveryQuery($user);

		$message = "Bonjour,\n\nVous avez oublié votre mot de passe ? Pas de panique !\n\n";
		$message.= "Il vous suffit de cliquer sur le lien ci-dessous pour modifier votre mot de passe.\n\n";
		$message.= ADMIN_URL . 'password.php?c=' . $query;
		$message.= "\n\nSi vous n'avez pas demandé à recevoir ce message, ignorez-le, votre mot de passe restera inchangé.";

		if ($user->clef_pgp) {
			$content = Security::encryptWithPublicKey($user->pgp_key, $message);
		}

		Emails::queue(Emails::CONTEXT_SYSTEM, [$user->email => null], null, 'Mot de passe perdu ?', $message);
		return 0;
	}

	protected function fetchUserForPasswordRecovery(int $id): ?\stdClass
	{
		$db = DB::getInstance();

		$id_field = DynamicFields::getLoginField();
		$email_field = DynamicFields::getFirstEmailField();

		// Fetch user, must have an email
		$sql = sprintf('SELECT u.id, u.%s AS email, u.password, u.pgp_key, u.perm_connect
			FROM users u
			INNER JOIN users_categories c ON c.id = u.id_category
			WHERE u.%s = ? COLLATE NOCASE
				AND u.%1$s IS NOT NULL
			LIMIT 1;',
			$db->quoteIdentifier($email_field),
			$db->quoteIdentifier($id_field));







|




|



>
>
|
>
>









|




<










|







247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299

		$qrcode = new QRCode($out['url']);
		$out['qrcode'] = 'data:image/svg+xml;base64,' . base64_encode($qrcode->toSVG());

		return $out;
	}

	public function recoverPasswordSend(int $id): void
	{
		$user = $this->fetchUserForPasswordRecovery($id);

		if (!$user) {
			throw new UserException('Aucun membre trouvé avec cette adresse e-mail, ou le membre trouvé n\'a pas le droit de se connecter.');
		}

		if ($user->perm_connect == self::ACCESS_NONE) {
			throw new UserException('Ce membre n\'a pas le droit de se connecter.');
		}

		if (!trim($user->email)) {
			throw new UserException('Ce membre n\'a pas d\'adresse e-mail renseignée dans son profil.');
		}

		$query = $this->makePasswordRecoveryQuery($user);

		$message = "Bonjour,\n\nVous avez oublié votre mot de passe ? Pas de panique !\n\n";
		$message.= "Il vous suffit de cliquer sur le lien ci-dessous pour modifier votre mot de passe.\n\n";
		$message.= ADMIN_URL . 'password.php?c=' . $query;
		$message.= "\n\nSi vous n'avez pas demandé à recevoir ce message, ignorez-le, votre mot de passe restera inchangé.";

		if ($user->pgp_key) {
			$content = Security::encryptWithPublicKey($user->pgp_key, $message);
		}

		Emails::queue(Emails::CONTEXT_SYSTEM, [$user->email => null], null, 'Mot de passe perdu ?', $message);

	}

	protected function fetchUserForPasswordRecovery(int $id): ?\stdClass
	{
		$db = DB::getInstance();

		$id_field = DynamicFields::getLoginField();
		$email_field = DynamicFields::getFirstEmailField();

		// Fetch user, must have an email
		$sql = sprintf('SELECT u.id, u.%s AS email, u.password, u.pgp_key, c.perm_connect
			FROM users u
			INNER JOIN users_categories c ON c.id = u.id_category
			WHERE u.%s = ? COLLATE NOCASE
				AND u.%1$s IS NOT NULL
			LIMIT 1;',
			$db->quoteIdentifier($email_field),
			$db->quoteIdentifier($id_field));
306
307
308
309
310
311
312


313
314
315
316
317
318
319
		$hash = hash_hmac('sha256', $user->email . $user->id . $user->password . $expire, SECRET_KEY, true);
		$hash = substr(Security::base64_encode_url_safe($hash), 0, 16);
		return $hash;
	}

	protected function makePasswordRecoveryQuery(\stdClass $user): string
	{


		$id = base_convert($user->id, 10, 36);
		$expire = base_convert($expire, 10, 36);
		return sprintf('%s.%s.%s', $id, $expire, $hash);
	}

	/**
	 * Check that the supplied query is valid, if so, return the user information







>
>







309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
		$hash = hash_hmac('sha256', $user->email . $user->id . $user->password . $expire, SECRET_KEY, true);
		$hash = substr(Security::base64_encode_url_safe($hash), 0, 16);
		return $hash;
	}

	protected function makePasswordRecoveryQuery(\stdClass $user): string
	{
		$expire = ceil((time() - strtotime('2017-01-01')) / 3600) + 1;
		$hash = $this->makePasswordRecoveryHash($user, $expire);
		$id = base_convert($user->id, 10, 36);
		$expire = base_convert($expire, 10, 36);
		return sprintf('%s.%s.%s', $id, $expire, $hash);
	}

	/**
	 * Check that the supplied query is valid, if so, return the user information
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
		}

		return $user;
	}

	public function recoverPasswordChange(string $query, string $password, string $password_confirm)
	{
		$user = $this->checkRecoveryPasswordQuery($code);

		if (null === $user) {
			throw new UserException('Le code permettant de changer le mot de passe a expiré. Merci de bien vouloir recommencer la procédure.');
		}

		$password = trim($password);
		$password_confirm = trim($password_confirm);







|







357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
		}

		return $user;
	}

	public function recoverPasswordChange(string $query, string $password, string $password_confirm)
	{
		$user = $this->checkRecoveryPasswordQuery($query);

		if (null === $user) {
			throw new UserException('Le code permettant de changer le mot de passe a expiré. Merci de bien vouloir recommencer la procédure.');
		}

		$password = trim($password);
		$password_confirm = trim($password_confirm);
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
		$message = "Bonjour,\n\nLe mot de passe de votre compte a bien été modifié.\n\n";
		$message.= "Votre adresse email : ".$user->email."\n";
		$message.= "La demande émanait de l'adresse IP : ".Utils::getIP()."\n\n";
		$message.= "Si vous n'avez pas demandé à changer votre mot de passe, merci de nous le signaler.";

		DB::getInstance()->update('users', ['password' => $password], 'id = :id', ['id' => (int)$user->id]);

		return Emails::queue(Emails::CONTEXT_SYSTEM, [$membre->email => null], null, 'Mot de passe changé', $message);
	}

	public function user(): ?User
	{
		return $this->getUser();
	}








|







381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
		$message = "Bonjour,\n\nLe mot de passe de votre compte a bien été modifié.\n\n";
		$message.= "Votre adresse email : ".$user->email."\n";
		$message.= "La demande émanait de l'adresse IP : ".Utils::getIP()."\n\n";
		$message.= "Si vous n'avez pas demandé à changer votre mot de passe, merci de nous le signaler.";

		DB::getInstance()->update('users', ['password' => $password], 'id = :id', ['id' => (int)$user->id]);

		return Emails::queue(Emails::CONTEXT_SYSTEM, [$user->email => null], null, 'Mot de passe changé', $message);
	}

	public function user(): ?User
	{
		return $this->getUser();
	}

Modified src/templates/login.tpl from [52da43c89e] to [ddc91ba971].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{include file="admin/_head.tpl" title="Connexion"}

{form_errors}

{if $changed}
    <p class="block confirm">
        Votre mot de passe a bien été modifié.<br />
        Vous pouvez maintenant l'utiliser pour vous reconnecter.
    </p>
{/if}

<p class="block error" style="display: none;" id="old_browser">
    Le navigateur que vous utilisez n'est pas supporté. Des fonctionnalités peuvent ne pas fonctionner.<br />
    Merci d'utiliser un navigateur web moderne comme <a href="https://www.getfirefox.com/" target="_blank">Firefox</a> ou <a href="https://vivaldi.com/fr/" target="_blank">Vivaldi</a>.
</p>

<form method="post" action="{$self_url}">

    <fieldset>
        <legend>Connexion</legend>
        <dl>
            <dt><label for="f_id">{$id_field_name}</label></dt>
            <dd><input type="text" name="_id" id="f_id" value="{form_field name=_id}" /></dd>
            <dt><label for="f_passe">Mot de passe</label></dt>
            <dd><input type="password" name="password" id="f_passe" value="" autocomplete="current-password" />
                {if $ssl_enabled}
                    <b class="icn confirm" title="Connexion chiffrée">&#x1f512;</b>
                    <span class="confirm">Connexion sécurisée</span>
                {else}
                    <b class="icn error" title="Connexion non chiffrée">&#x1f513;</b>
                    <span class="alert">Connexion non-sécurisée</span>
                {/if}
            </dd>



            {input type="checkbox" name="permanent" value="1" label="Rester connecté⋅e" help="recommandé seulement sur ordinateur personnel"}
        </dl>
    </fieldset>

    <p class="submit">
        {csrf_field key="login"}
        {button type="submit" name="login" label="Se connecter" shape="right" class="main"}
    </p>

    <p class="help">
        <a href="{$admin_url}password.php">Première connexion ou mot de passe perdu ?</a>
    </p>

</form>

{literal}
<script type="text/javascript">
if (window.navigator.userAgent.match(/MSIE|Trident\/|Edge\//)) {
    document.getElementById('old_browser').style.display = 'block';
}

g.enhancePasswordField($('#f_passe'));
</script>
{/literal}

{include file="admin/_foot.tpl"}





|
|
|
|



|
|




|
|
<
<
<
<
<
|
<
|
|
<
|
|
|
>
>
>
|
|
|

|
|
|
<
|
<
|
|






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20





21

22
23

24
25
26
27
28
29
30
31
32
33
34
35
36

37

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
{include file="admin/_head.tpl" title="Connexion"}

{form_errors}

{if $changed}
	<p class="block confirm">
		Votre mot de passe a bien été modifié.<br />
		Vous pouvez maintenant l'utiliser pour vous reconnecter.
	</p>
{/if}

<p class="block error" style="display: none;" id="old_browser">
	Le navigateur que vous utilisez n'est pas supporté. Des fonctionnalités peuvent ne pas fonctionner.<br />
	Merci d'utiliser un navigateur web moderne comme <a href="https://www.getfirefox.com/" target="_blank">Firefox</a> ou <a href="https://vivaldi.com/fr/" target="_blank">Vivaldi</a>.
</p>

<form method="post" action="{$self_url}">

	<fieldset>
		<legend>





			{if $ssl_enabled}

				<span class="confirm">{icon shape="lock"} Connexion sécurisée</span>
			{else}

				<span class="alert">{icon shape="unlock"} Connexion non-sécurisée</span>
			{/if}
		</legend>
		<dl>
			{input type=$id_field.type label=$id_field.label required=true name="id"}
			{input type="password" name="password" label="Mot de passe" required=true}
			{input type="checkbox" name="permanent" value="1" label="Rester connecté⋅e" help="recommandé seulement sur ordinateur personnel"}
		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="login"}
		{button type="submit" name="login" label="Se connecter" shape="right" class="main"}

		{linkbutton href="!password.php" label="Mot de passe perdu ?" shape="help"}

		{linkbutton href="!password.php?new" label="Première connexion ?" shape="user"}
	</p>

</form>

{literal}
<script type="text/javascript">
if (window.navigator.userAgent.match(/MSIE|Trident\/|Edge\//)) {
	document.getElementById('old_browser').style.display = 'block';
}

g.enhancePasswordField($('#f_passe'));
</script>
{/literal}

{include file="admin/_foot.tpl"}

Modified src/templates/password.tpl from [6ce594bca0] to [c6d2d188c0].

1
2
3
4
5

6

7

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22



23

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{include file="admin/_head.tpl" title="Mot de passe oublié ou première connexion ?"}


{if $sent}
    <p class="block confirm">

        Un e-mail vous a été envoyé, cliquez sur le lien dans cet e-mail

        pour modifier votre mot de passe.

    </p>
    <p class="block alert">
        Si le message n'apparaît pas dans les prochaines minutes, vérifiez le dossier Spam ou Indésirables.
    </p>

{else}

    {form_errors}

    <form method="post" action="{$self_url_no_qs}">

        <fieldset>
            <legend>Recevoir un e-mail avec un nouveau mot de passe</legend>
            <p class="help">
                Inscrivez ici votre identifiant.



                Nous vous enverrons un e-mail avec un lien vous permettant de créer ou changer le mot de passe.

            </p>
            <dl>
                <dt><label for="f_id">{$champ.title}</label></dt>
                <dd><input type="text" name="id" id="f_id" value="{form_field name=id}" /></dd>
            </dl>
        </fieldset>

        <p class="submit">
            {csrf_field key=$csrf_key}
            {button type="submit" name="recover" label="Envoyer" shape="right" class="main"}
        </p>

    </form>
{/if}

{include file="admin/_foot.tpl"}
|

<

|
>
|
>
|
>
|
|
|
|



|

|

|
|
|
|
>
>
>
|
>
|
|
<
|
|
|

|
|
|
|

|



1
2

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

32
33
34
35
36
37
38
39
40
41
42
43
44
{include file="admin/_head.tpl" title=$title}


{if $sent}
	<p class="block confirm">
		{if $new}
			Un e-mail vous a été envoyé, cliquez sur le lien dans cet e-mail pour choisir votre mot de passe.
		{else}
			Un e-mail vous a été envoyé, cliquez sur le lien dans cet e-mail pour modifier votre mot de passe.
		{/if}
	</p>
	<p class="help">
		Si le message n'apparaît pas dans les prochaines minutes, vérifiez le dossier Spam ou Indésirables.
	</p>

{else}

	{form_errors}

	<form method="post" action="{$self_url}">

		<fieldset>
			<legend>{if $new}Envoyer un e-mail pour choisir son mot de passe{else}Envoyer un e-mail pour modifier son mot de passe{/if}</legend>
			<p class="help">
				Inscrivez ici votre identifiant.<br/>
				{if $new}
					Vous recevrez un e-mail à l'adresse renseignée dans votre fiche membre, avec un lien vous permettant de créer votre mot de passe.
				{else}
					Nous vous enverrons un e-mail à l'adresse renseignée dans votre fiche membre, avec un lien vous permettant de modifier votre mot de passe.
				{/if}
			</p>
			<dl>

				{input type=$id_field.type label=$id_field.label required=true name="id"}
			</dl>
		</fieldset>

		<p class="submit">
			{csrf_field key=$csrf_key}
			{button type="submit" name="recover" label="Envoyer" shape="right" class="main"}
		</p>

	</form>
{/if}

{include file="admin/_foot.tpl"}

Modified src/templates/password_change.tpl from [4df344e2c8] to [73a04e08fa].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
		<legend>Choisir un nouveau mot de passe</legend>
		<dl>
			{include file="users/_password_form.tpl" required=true}
		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="changePassword"}
		{button type="submit" name="change" label="Modifier mon mot de passe" shape="right" class="main"}
	</p>


</form>


{include file="admin/_foot.tpl"}







|








9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
		<legend>Choisir un nouveau mot de passe</legend>
		<dl>
			{include file="users/_password_form.tpl" required=true}
		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="change" label="Modifier mon mot de passe" shape="right" class="main"}
	</p>


</form>


{include file="admin/_foot.tpl"}

Modified src/templates/users/_password_form.tpl from [2f1eb246b7] to [17aea97add].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
?>

<dd class="help">
	Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
</dd>
<dd class="help">
	Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
	<input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$suggestion}" autocomplete="off" />
</dd>

{input type="password" name="password" required=$required label="Mot de passe" help="Minimum %d caractères"|args:$password_length autocomplete="off" minlength=$password_length}

{input type="password" name="password_confirmed" required=$required label="Encore le mot de pase (vérification)" help="Minimum %d caractères"|args:$password_length autocomplete="off" minlength=$password_length}

<script type="text/javascript" async="async">
{literal}
g.script('scripts/password.js', () => {
	initPasswordField('pw_suggest', 'f_password', 'f_password_confirmed');
});
{/literal}
</script>







|









|



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
?>

<dd class="help">
	Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
</dd>
<dd class="help">
	Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
	{input type="text" readonly=true title="Cliquer pour utiliser cette suggestion comme mot de passe" default=$suggestion autocomplete="off" copy=true name="suggest"}
</dd>

{input type="password" name="password" required=$required label="Mot de passe" help="Minimum %d caractères"|args:$password_length autocomplete="off" minlength=$password_length}

{input type="password" name="password_confirmed" required=$required label="Encore le mot de pase (vérification)" help="Minimum %d caractères"|args:$password_length autocomplete="off" minlength=$password_length}

<script type="text/javascript" async="async">
{literal}
g.script('scripts/password.js', () => {
	initPasswordField('f_suggest', 'f_password', 'f_password_confirmed');
});
{/literal}
</script>

Modified src/www/admin/login.php from [3f8f31fcb9] to [82663e1336].

1
2
3
4
5



6
7
8


9
10
11
12
13
14
15
<?php
namespace Garradin;

use KD2\HTTP;




const LOGIN_PROCESS = true;

require_once __DIR__ . '/_inc.php';



// Relance session_start et renvoie une image de 1px transparente
if (qg('keepSessionAlive') !== null)
{
    $session->keepAlive();

    header('Cache-Control: no-cache, must-revalidate');





>
>
>



>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
namespace Garradin;

use KD2\HTTP;

use Garradin\Users\DynamicFields;
use Garradin\Users\Session;

const LOGIN_PROCESS = true;

require_once __DIR__ . '/_inc.php';

$session = Session::getInstance();

// Relance session_start et renvoie une image de 1px transparente
if (qg('keepSessionAlive') !== null)
{
    $session->keepAlive();

    header('Cache-Control: no-cache, must-revalidate');
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

48
49
50
51
52
    Utils::redirect(ADMIN_URL . '');
}

$id_field = DynamicFields::get(DynamicFields::getLoginField());
$id_field_name = $id_field->label;

$form->runIf('login', function () use ($id_field_name, $session) {
    if (!trim((string) f('_id'))) {
        throw new UserException(sprintf('L\'identifiant (%s) n\'a pas été renseigné.', $id_field_name));
    }

    if (!trim((string) f('password'))) {
        throw new UserException('Le mot de passe n\'a pas été renseigné.');
    }

    if (!$session->login(f('_id'), f('password'), (bool) f('permanent'))) {
        throw new UserException(sprintf("Connexion impossible.\nVérifiez votre identifiant (%s) et votre mot de passe.", $id_field_name));
    }
}, 'login', ADMIN_URL);

$tpl->assign('ssl_enabled', HTTP::getScheme() == 'https' ? false : true);


$tpl->assign(compact('id_field_name'));
$tpl->assign('changed', qg('changed') !== null);

$tpl->display('login.tpl');







|







|




|
>

|
<


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

56
57
    Utils::redirect(ADMIN_URL . '');
}

$id_field = DynamicFields::get(DynamicFields::getLoginField());
$id_field_name = $id_field->label;

$form->runIf('login', function () use ($id_field_name, $session) {
    if (!trim((string) f('id'))) {
        throw new UserException(sprintf('L\'identifiant (%s) n\'a pas été renseigné.', $id_field_name));
    }

    if (!trim((string) f('password'))) {
        throw new UserException('Le mot de passe n\'a pas été renseigné.');
    }

    if (!$session->login(f('id'), f('password'), (bool) f('permanent'))) {
        throw new UserException(sprintf("Connexion impossible.\nVérifiez votre identifiant (%s) et votre mot de passe.", $id_field_name));
    }
}, 'login', ADMIN_URL);

$ssl_enabled = HTTP::getScheme() == 'https';
$changed = qg('changed') !== null;

$tpl->assign(compact('id_field', 'ssl_enabled', 'changed'));


$tpl->display('login.tpl');

Modified src/www/admin/password.php from [cecdb57f0d] to [0bb839b213].

1
2
3
4



5
6
7
8
9
10
11
12
13
14
15

16
17
18
19
20
21
22
23
24
25
26
27

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

43
44
45
46
<?php

namespace Garradin;




const LOGIN_PROCESS = true;

require_once __DIR__ . '/_inc.php';

$session = Session::getInstance();

$form->runIf(qg('c'), function () use ($session, $form) {
	if (!$session->recoverPasswordCheck(qg('c'))) {
		throw new UserException('Le lien que vous avez suivi est invalide ou a expiré.');
	}


	$csrf_key = 'password_change_' . qg('c');

	$form->runIf('change', function () use ($session) {
		$session->recoverPasswordChange(qg('c'), f('password'), f('password_confirmed'));
	}, $csrf_key, '!login.php?changed');

	$tpl->assign(compact('csrf_key'));
	$tpl->display('password_change.tpl');
	exit;
});

$csrf_key = 'recover_password';


$form->runIf('recover', function () use ($session) {
	$error = $session->recoverPasswordSend((int) f('id'));

	if ($error === 1) {
		throw new UserException('Aucun membre trouvé avec cette adresse e-mail, ou le membre trouvé n\'a pas le droit de se connecter.');
	}
	elseif ($error === 2) {
		throw new UserException('Ce membre n\'a pas le droit de se connecter.');
	}
}, $csrf_key, '!password.php?sent');

$sent = !$form->hasErrors() && null !== qg('sent');

$id_field = DynamicFields::get(DynamicFields::getLoginField());


$tpl->assign(compact('id_field', 'sent', 'csrf_key'));

$tpl->display('password.tpl');




>
>
>






|
|



>
|











>


|
<
<
<
<
<
<
<
|




>

|


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35







36
37
38
39
40
41
42
43
44
45
<?php

namespace Garradin;

use Garradin\Users\DynamicFields;
use Garradin\Users\Session;

const LOGIN_PROCESS = true;

require_once __DIR__ . '/_inc.php';

$session = Session::getInstance();

$form->runIf(qg('c') !== null, function () use ($session, $form, $tpl) {
	if (!$session->checkRecoveryPasswordQuery(qg('c'))) {
		throw new UserException('Le lien que vous avez suivi est invalide ou a expiré.');
	}


	$csrf_key = 'password_change_' . md5(qg('c'));

	$form->runIf('change', function () use ($session) {
		$session->recoverPasswordChange(qg('c'), f('password'), f('password_confirmed'));
	}, $csrf_key, '!login.php?changed');

	$tpl->assign(compact('csrf_key'));
	$tpl->display('password_change.tpl');
	exit;
});

$csrf_key = 'recover_password';
$new = qg('new') !== null;

$form->runIf('recover', function () use ($session) {
	$session->recoverPasswordSend((int) f('id'));







}, $csrf_key, '!password.php?sent' . ($new ? '&new' : ''));

$sent = !$form->hasErrors() && null !== qg('sent');

$id_field = DynamicFields::get(DynamicFields::getLoginField());
$title = $new ? 'Première connexion ?' : 'Mot de passe perdu ?';

$tpl->assign(compact('id_field', 'sent', 'csrf_key', 'title', 'new'));

$tpl->display('password.tpl');