Comment: | Improve login flow from NextCloud/ownCloud apps |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA3-256: |
b2887ad79513aa9510d0cd63e4c492a9 |
User & Date: | bohwaz on 2022-11-13 21:39:47 |
Other Links: | branch diff | manifest | tags |
2022-11-13
| ||
22:39 | Add HTTP_LOG_FILE constant check-in: d798cee6b5 user: bohwaz tags: dev | |
21:39 | Improve login flow from NextCloud/ownCloud apps check-in: b2887ad795 user: bohwaz tags: dev | |
17:58 | Fix router for root files check-in: bc10ac1dcb user: bohwaz tags: dev | |
Modified src/include/lib/Garradin/Files/Files.php from [afb6bfd1f0] to [c3c27c45ee].
︙ | ︙ | |||
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | self::$quota = true; } static public function disableQuota(): void { self::$quota = false; } /** * Returns an array of all file permissions for a given user */ static public function buildUserPermissions(Session $s): array { $is_admin = $s->canAccess($s::SECTION_CONFIG, $s::ACCESS_ADMIN); $is_web_admin = $is_admin || $s->canAccess($s::SECTION_WEB, $s::ACCESS_ADMIN); $p = []; if ($s->isLogged() && $id = $s::getUserId()) { // The user can always access his own profile files | > > > > > > > > > > > > > > > > > > > > > > > | | 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | self::$quota = true; } static public function disableQuota(): void { self::$quota = false; } static public function listContextsPermissions(Session $s): array { $perm = self::buildUserPermissions($s); $contexts = [ 'Fichiers de votre fiche de membre personnelle' => File::CONTEXT_USER . '/' . $s::getUserId() . '/', 'Documents de l\'association' => File::CONTEXT_DOCUMENTS, 'Fichiers des membres' => File::CONTEXT_USER . '//', 'Fichiers des écritures comptables' => File::CONTEXT_TRANSACTION . '//', 'Fichiers du site web (contenu des pages, images, etc.)' => File::CONTEXT_WEB . '//', 'Squelettes du site web' => File::CONTEXT_SKELETON . '/web/', 'Fichiers de la configuration (logo, etc.)' => File::CONTEXT_CONFIG, 'Squelettes des formulaires' => File::CONTEXT_SKELETON, ]; $out = []; foreach ($contexts as $name => $path) { $out[$name] = $perm[$path] ?? null; } return $out; } /** * Returns an array of all file permissions for a given user */ static public function buildUserPermissions(Session $s): array { $is_admin = $s->canAccess($s::SECTION_CONFIG, $s::ACCESS_ADMIN); $is_web_admin = $is_admin || $s->canAccess($s::SECTION_WEB, $s::ACCESS_ADMIN); $p = []; if ($s->isLogged() && $id = $s::getUserId()) { // The user can always access his own profile files $p[File::CONTEXT_USER . '/' . $s::getUserId() . '/'] = [ 'mkdir' => false, 'move' => false, 'create' => false, 'read' => true, 'write' => false, 'delete' => false, 'share' => false, |
︙ | ︙ | |||
86 87 88 89 90 91 92 | 'read' => $s->isLogged(), // All config files can be accessed by all logged-in users 'write' => $is_admin, 'delete' => false, 'share' => false, ]; // Web skeletons | | | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 'read' => $s->isLogged(), // All config files can be accessed by all logged-in users 'write' => $is_admin, 'delete' => false, 'share' => false, ]; // Web skeletons $p[File::CONTEXT_SKELETON . '/web/'] = [ 'mkdir' => $is_web_admin, 'move' => $is_web_admin, 'create' => $is_web_admin, 'read' => $s->isLogged(), 'write' => $is_web_admin, 'delete' => $is_web_admin, 'share' => false, |
︙ | ︙ |
Modified src/include/lib/Garradin/Files/WebDAV/NextCloud.php from [3312455c47] to [beaec70d4b].
︙ | ︙ | |||
9 10 11 12 13 14 15 | use const Garradin\{SECRET_KEY, ADMIN_URL, CACHE_ROOT, WWW_URL}; class NextCloud extends WebDAV_NextCloud { protected string $temporary_chunks_path; | | < > > > > | 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 | use const Garradin\{SECRET_KEY, ADMIN_URL, CACHE_ROOT, WWW_URL}; class NextCloud extends WebDAV_NextCloud { protected string $temporary_chunks_path; public function __construct() { $this->temporary_chunks_path = CACHE_ROOT . '/webdav.chunks'; $this->setRootURL(WWW_URL); } public function auth(?string $login, ?string $password): bool { $session = Session::getInstance(); if ($session->isLogged()) { return true; } if (!$login || !$password) { return false; } if ($session->checkAppCredentials($login, $password)) { return true; } if ($session->login($login, $password)) { return true; |
︙ | ︙ | |||
67 68 69 70 71 72 73 | { return Session::getInstance()->verifyAppToken($_POST['token']); } public function getLoginURL(?string $token): string { if ($token) { | | | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | { return Session::getInstance()->verifyAppToken($_POST['token']); } public function getLoginURL(?string $token): string { if ($token) { return sprintf('%slogin.php?app=%s', ADMIN_URL, $token); } else { return sprintf('%slogin.php?app=redirect', ADMIN_URL); } } public function getDirectDownloadSecret(string $uri, string $login): string { return hash_hmac('sha1', $uri, SECRET_KEY); } protected function cleanChunks(): void { // 36 hours $expire = time() - 36*3600; |
︙ | ︙ |
Modified src/include/lib/Garradin/Files/WebDAV/Server.php from [c6e87defd1] to [07a8dc31c3].
︙ | ︙ | |||
11 12 13 14 15 16 17 | class Server { static public function route(?string $uri = null): bool { $uri = '/' . ltrim($uri, '/'); $dav = new WebDAV; | > > | | | 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 | class Server { static public function route(?string $uri = null): bool { $uri = '/' . ltrim($uri, '/'); $dav = new WebDAV; $nc = new NextCloud($dav); $storage = new Storage($nc); $dav->setStorage($storage); header('Access-Control-Allow-Origin: *', true); $method = $_SERVER['REQUEST_METHOD'] ?? null; // Always say YES to OPTIONS if ($method == 'OPTIONS') { $dav->http_options(); return true; } if (WOPI_DISCOVERY_URL) { $wopi = new WOPI; $wopi->setServer($dav); if ($wopi->route($uri)) { return true; } } $nc->setServer($dav); if ($r = $nc->route($uri)) { // NextCloud route already replied something, stop here return true; } // If NextCloud layer didn't return anything |
︙ | ︙ |
Modified src/include/lib/Garradin/Files/WebDAV/Storage.php from [bc5fae8168] to [7cbcbef5e2].
︙ | ︙ | |||
18 19 20 21 22 23 24 | { /** * These file names will be ignored when doing a PUT * as they are garbage, coming from some OS */ const PUT_IGNORE_PATTERN = '!^~(?:lock\.|^\._)|^(?:\.DS_Store|Thumbs\.db|desktop\.ini)$!'; | | | > > | > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | { /** * These file names will be ignored when doing a PUT * as they are garbage, coming from some OS */ const PUT_IGNORE_PATTERN = '!^~(?:lock\.|^\._)|^(?:\.DS_Store|Thumbs\.db|desktop\.ini)$!'; protected array $cache = []; protected array $root = []; protected NextCloud $nextcloud; public function __construct(NextCloud $nextcloud) { $this->nextcloud = $nextcloud; $access = Files::listReadAccessContexts(Session::getInstance()); $this->cache[''] = (object) ['name' => '', 'type' => File::TYPE_DIRECTORY]; foreach ($access as $context => $name) { $this->cache[$context] = (object) ['name' => $context, 'type' => File::TYPE_DIRECTORY]; $this->root[] = $context; |
︙ | ︙ | |||
157 158 159 160 161 162 163 | // NextCloud stuff case NextCloud::PROP_NC_HAS_PREVIEW: case NextCloud::PROP_NC_IS_ENCRYPTED: return 'false'; case NextCloud::PROP_OC_SHARETYPES: return WebDAV::EMPTY_PROP_VALUE; case NextCloud::PROP_OC_DOWNLOADURL: | | | 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | // NextCloud stuff case NextCloud::PROP_NC_HAS_PREVIEW: case NextCloud::PROP_NC_IS_ENCRYPTED: return 'false'; case NextCloud::PROP_OC_SHARETYPES: return WebDAV::EMPTY_PROP_VALUE; case NextCloud::PROP_OC_DOWNLOADURL: return $this->nextcloud->getDirectURL($uri, 'null'); case Nextcloud::PROP_NC_RICH_WORKSPACE: return ''; case NextCloud::PROP_OC_ID: return NextCloud::getDirectID('', $uri); case NextCloud::PROP_OC_PERMISSIONS: $permissions = [ NextCloud::PERM_READ => $file->canRead(), |
︙ | ︙ |
Modified src/include/lib/Garradin/Users/Session.php from [38f0d3ac47] to [fa0c926a33].
︙ | ︙ | |||
153 154 155 156 157 158 159 160 161 162 163 164 165 166 | return $this->db->delete('users_sessions', $this->db->where('selector', $selector)); } protected function deleteAllRememberMeSelectors($user_id) { return $this->db->delete('users_sessions', $this->db->where('id_user', $user_id)); } /** * Create a temporary app token for an external service session (eg. NextCloud) */ public function generateAppToken(): string { $token = hash('sha256', random_bytes(16)); | > > > > > | 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | return $this->db->delete('users_sessions', $this->db->where('selector', $selector)); } protected function deleteAllRememberMeSelectors($user_id) { return $this->db->delete('users_sessions', $this->db->where('id_user', $user_id)); } public function getAppLoginToken(): ?string { return $_GET['app'] ?? null; } /** * Create a temporary app token for an external service session (eg. NextCloud) */ public function generateAppToken(): string { $token = hash('sha256', random_bytes(16)); |
︙ | ︙ | |||
200 201 202 203 204 205 206 | return true; } /** * Verify temporary app token and create a session, * this is similar to "remember me" sessions but without cookies */ | | | 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | return true; } /** * Verify temporary app token and create a session, * this is similar to "remember me" sessions but without cookies */ public function verifyAppToken(string $token): ?array { if (!ctype_alnum($token) || strlen($token) > 64) { return null; } $token = $this->getRememberMeSelector('tok_' . $token); |
︙ | ︙ | |||
226 227 228 229 230 231 232 | // Create a real session, not too long $selector = $this->createSelectorValues($token->user_id, $token->user_password, '+1 month'); $this->storeRememberMeSelector($selector->selector, $selector->hash, $selector->expiry, $token->user_id); $login = $selector->selector; $password = $selector->verifier; | | | 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | // Create a real session, not too long $selector = $this->createSelectorValues($token->user_id, $token->user_password, '+1 month'); $this->storeRememberMeSelector($selector->selector, $selector->hash, $selector->expiry, $token->user_id); $login = $selector->selector; $password = $selector->verifier; return compact('login', 'password'); } public function createAppCredentials(): \stdClass { if (!$this->isLogged()) { throw new \LogicException('User is not logged'); |
︙ | ︙ | |||
260 261 262 263 264 265 266 | } if (!$this->checkRememberMeSelector($selector, $password)) { $this->deleteRememberMeSelector($selector->selector); return null; } | | > > > > > > | | 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | } if (!$this->checkRememberMeSelector($selector, $password)) { $this->deleteRememberMeSelector($selector->selector); return null; } $this->_user = Users::get($selector->user_id); if (!$this->_user) { return null; } $this->user = $selector->user_id; return $this->_user; } public function isLogged(bool $disable_local_login = false) { $logged = parent::isLogged(); // Ajout de la gestion de LOCAL_LOGIN |
︙ | ︙ |
Modified src/include/lib/Garradin/Web/Router.php from [599059a936] to [4d6c8743e9].
︙ | ︙ | |||
23 24 25 26 27 28 29 30 31 32 33 34 35 36 | class Router { const DAV_ROUTES = [ 'dav', 'wopi', 'remote.php', 'index.php', 'ocs', ]; static public function route(): void { $uri = !empty($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; | > | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class Router { const DAV_ROUTES = [ 'dav', 'wopi', 'remote.php', 'index.php', 'status.php', 'ocs', ]; static public function route(): void { $uri = !empty($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; |
︙ | ︙ | |||
71 72 73 74 75 76 77 | return; } elseif ('form' == $first) { $uri = substr($uri, 5); UserForms::serve($uri); return; } | > | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | return; } elseif ('form' == $first) { $uri = substr($uri, 5); UserForms::serve($uri); return; } elseif ((in_array($uri, self::DAV_ROUTES) || in_array($first, self::DAV_ROUTES)) && WebDAV_Server::route($uri)) { return; } elseif (Files::getContext($uri) && (($file = Files::getFromURI($uri)) || ($file = Web::getAttachmentFromURI($uri)))) { $size = null; |
︙ | ︙ |
Modified src/templates/login.tpl from [89a2db753b] to [f6f5859e6b].
|
| < < < < < < | | | | | | | | | | | | | | | | | < | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < < | | | | | | | | | | | | < | 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | {include file="_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> {elseif isset($_GET['logout'])} <p class="block confirm"> Vous avez bien été déconnecté. </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}" data-focus="{if $_POST}2{else}1{/if}"> {if $app_token} <p class="alert block">Une application tiers demande à accéder aux fichiers de l'association.<br />Connectez-vous pour pouvoir confirmer l'accès.</p> {/if} <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} {if !$app_token} {input type="checkbox" name="permanent" value="1" label="Rester connecté⋅e" help="recommandé seulement sur ordinateur personnel"} {/if} </dl> </fieldset> {if $captcha} <fieldset> <legend>Vérification de sécurité</legend> <input type="hidden" name="c_hash" value="{$captcha.hash}" /> <dl> <dt><label for="f_c_answer">Merci de recopier en chiffres (par exemple <em>1234</em>) le nombre suivant :<b>(obligatoire)</b></label></dt> <dd><tt>{$captcha.spellout}</tt></dd> <dd>{input name="c_answer" type="text" maxlength=4 label=null required=true}</dd> <dd class="help">Cette vérification est demandée après plusieurs tentatives de connexion infructueuses.</dd> </dl> </fieldset> {/if} <p class="submit"> {csrf_field key="login"} {button type="submit" name="login" label="Se connecter" shape="right" class="main"} {if !$app_token} {linkbutton href="!password.php" label="Mot de passe perdu ?" shape="help"} {linkbutton href="!password.php?new" label="Première connexion ?" shape="user"} {/if} </p> </form> {literal} <script type="text/javascript" async="async"> if (window.navigator.userAgent.match(/MSIE|Trident\/|Edge\//)) { document.getElementById('old_browser').style.display = 'block'; } </script> {/literal} {include file="_foot.tpl"} |
Added src/templates/login_app.tpl version [317583c35e].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | {include file="_head.tpl" title="Accès par une application tiers" layout="public"} {if $app_token == 'ok'} <p class="block confirm">L'application a bien été autorisée.</p> <div class="progressing block"></div> <p class="help">Vous pourrez fermer cette fenêtre quand l'application aura terminé l'autorisation.</p> {else} <p class="alert block">Une application tiers demande à accéder aux fichiers de l'association.</p> <form method="post" action="{$self_url}"> <fieldset> <legend>Confirmer l'accès</legend> <h3 class="warning">Autoriser l'application à accéder aux fichiers ?</h3> <p class="help">L'application aura accès aux fichiers suivants :</p> <table class="list auto"> <thead> <tr> <td>Section</td> <td>Lecture</td> <td>Modification</td> <td>Suppression</td> </tr> </thead> <tbody> {foreach from=$permissions key="name" item="access"} <tr> <td>{$name}</td> <td class="check">{if $access.read}{icon shape="check"}{/if}</td> <td class="check">{if $access.write}{icon shape="check"}{/if}</td> <td class="check">{if $access.delete}{icon shape="check"}{/if}</td> </tr> {/foreach} </tbody> </table> <p class="actions"> {csrf_field key=$csrf_key} {button type="submit" label="Autoriser l'accès" shape="right" class="main" name="confirm"} </p> <p class="submit"> {button type="submit" label="Annuler" shape="left" name="cancel"} </p> </fieldset> </form> {/if} {include file="_foot.tpl"} |
Modified src/templates/login_otp.tpl from [fbb6ee7328] to [6f52c70b8a].
1 2 3 4 | {include file="_head.tpl" title="Connexion — double facteur"} {form_errors} | | | 1 2 3 4 5 6 7 8 9 10 11 12 | {include file="_head.tpl" title="Connexion — double facteur"} {form_errors} <form method="post" action="{$self_url}" data-focus="1"> <fieldset> <legend>Authentification à double facteur</legend> <dl> {input type="text" class="otp" minlength=6 maxlength=6 label="Code TOTP" name="code" help="Entrez ici le code donné par l'application d'authentification double facteur." required=true} </dl> </fieldset> |
︙ | ︙ |
Modified src/www/admin/login.php from [93267989eb] to [722866c99b].
1 2 3 4 5 6 7 8 9 | <?php namespace Garradin; use KD2\HTTP; use KD2\Security; use Garradin\Users\DynamicFields; use Garradin\Users\Session; | | | | | | | | | > > | > > > > | > | | | | | | | | | | | | | | | < | | < < < < > | < < | < < < | < | < < < < | | | | < | | 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | <?php namespace Garradin; use KD2\HTTP; use KD2\Security; use Garradin\Users\DynamicFields; use Garradin\Users\Session; use Garradin\UserException; 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'); header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); header('Content-Type: image/gif'); echo base64_decode("R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); exit; } $app_token = $session->getAppLoginToken(); $args = $app_token ? '?app=' . rawurlencode($app_token) : ''; $layout = $app_token ? 'public' : null; // L'utilisateur est déjà connecté if ($session->isLogged()) { if ($app_token) { Utils::redirect('!login_app.php' . $args); } else { Utils::redirect(ADMIN_URL); } } $id_field = DynamicFields::get(DynamicFields::getLoginField()); $id_field_name = $id_field->label; $lock = Log::isLocked(); $form->runIf('login', function () use ($id_field_name, $session, $lock, $args) { if ($lock == 1) { throw new UserException(sprintf("Vous avez dépassé la limite de tentatives de connexion.\nMerci d'attendre %d minutes avant de ré-essayer de vous connecter.", Log::LOCKOUT_DELAY/60)); } elseif ($lock == -1 && !Security::checkCaptcha(SECRET_KEY, f('c_hash'), f('c_answer'))) { throw new UserException('Le code de vérification entré n\'est pas correct.'); } $_POST['c_answer'] = null; 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é.'); } $ok = $session->login(f('id'), f('password'), (bool) f('permanent')); if (!$ok) { throw new UserException(sprintf("Connexion impossible.\nVérifiez votre identifiant (%s) et votre mot de passe.", $id_field_name)); } if ($session::REQUIRE_OTP == $ok) { Utils::redirect('!login_otp.php' . $args); } elseif ($args) { Utils::redirect('!login_app.php' . $args); } }, 'login', ADMIN_URL); $captcha = $lock == -1 ? Security::createCaptcha(SECRET_KEY, 'fr_FR') : null; $ssl_enabled = HTTP::getScheme() == 'https'; $changed = qg('changed') !== null; $tpl->assign(compact('id_field', 'ssl_enabled', 'changed', 'app_token', 'layout', 'captcha')); $tpl->display('login.tpl'); |
Added src/www/admin/login_app.php version [aeca79011c].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <?php namespace Garradin; use Garradin\Files\Files; use Garradin\Users\Session; require_once __DIR__ . '/_inc.php'; $session = Session::getInstance(); $app_token = $session->getAppLoginToken(); if (!$app_token) { die("No app token was supplied."); } $csrf_key = 'app_confirm_' . $app_token; $form->runIf('cancel', function () { Utils::redirect('!logout.php'); }); $form->runIf('confirm', function () use ($app_token, $session) { if ($app_token == 'redirect') { $data = $session->createAppCredentials(); } else { $session->validateAppToken($app_token); } if ($data->redirect ?? null) { http_response_code(303); header('Location: ' . $data->redirect); exit; } Utils::redirect('!login_app.php?app=ok'); }, $csrf_key); $permissions = Files::listContextsPermissions($session); $tpl->assign(compact('app_token', 'csrf_key', 'permissions')); $tpl->display('login_app.tpl'); |
Modified src/www/admin/login_otp.php from [bf071aca86] to [54b9408ded].
︙ | ︙ | |||
13 14 15 16 17 18 19 | if (!$session->isOTPRequired()) { Utils::redirect(ADMIN_URL); } $login = null; $csrf_key = 'login_otp'; | > > > > | > > > > | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | if (!$session->isOTPRequired()) { Utils::redirect(ADMIN_URL); } $login = null; $csrf_key = 'login_otp'; $app_token = $session->getAppLoginToken(); $args = $app_token ? '?app=' . rawurlencode($app_token) : ''; $layout = $app_token ? 'public' : null; $form->runIf('login', function () use ($session, $args) { if (!$session->loginOTP(f('code'))) { throw new UserException(sprintf('Code incorrect. Vérifiez que votre téléphone est à l\'heure (heure du serveur : %s).', date('d/m/Y H:i:s'))); } if ($args) { Utils::redirect('!login_app.php' . $args); } }, $csrf_key, '!'); $tpl->assign(compact('csrf_key', 'layout')); $tpl->display('login_otp.tpl'); |
Modified src/www/admin/static/styles/06-tables.css from [70535fc490] to [bb14c89e08].
︙ | ︙ | |||
70 71 72 73 74 75 76 | font-weight: bold; } table.list .confirm { color: darkgreen; } | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | font-weight: bold; } table.list .confirm { color: darkgreen; } table.list .num, table.list .check { text-align: center; } table.list .check { width: 1%; } |
︙ | ︙ |