2043 lines
217 KiB
HTML
2043 lines
217 KiB
HTML
|
<!DOCTYPE html><html lang="fr">
|
|||
|
<head><meta charset="utf-8">
|
|||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><title>PHP Authentification RobThree/TwoFactorAuth - YannStatic</title>
|
|||
|
|
|||
|
<meta name="description" content="Bibliothèque PHP 2FA">
|
|||
|
<link rel="canonical" href="https://static.rnmkcy.eu/2021/03/17/PHP_Authentification_2FA(RobThree).html"><link rel="alternate" type="application/rss+xml" title="YannStatic" href="/feed.xml">
|
|||
|
|
|||
|
<!-- - include head/favicon.html - -->
|
|||
|
<link rel="shortcut icon" type="image/png" href="/assets/favicon/favicon.png"><link rel="stylesheet" href="/assets/css/main.css"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" ><!-- start custom head snippets --><link rel="stylesheet" href="/assets/css/expand.css">
|
|||
|
<!-- end custom head snippets --><script>(function() {
|
|||
|
window.isArray = function(val) {
|
|||
|
return Object.prototype.toString.call(val) === '[object Array]';
|
|||
|
};
|
|||
|
window.isString = function(val) {
|
|||
|
return typeof val === 'string';
|
|||
|
};
|
|||
|
|
|||
|
window.hasEvent = function(event) {
|
|||
|
return 'on'.concat(event) in window.document;
|
|||
|
};
|
|||
|
|
|||
|
window.isOverallScroller = function(node) {
|
|||
|
return node === document.documentElement || node === document.body || node === window;
|
|||
|
};
|
|||
|
|
|||
|
window.isFormElement = function(node) {
|
|||
|
var tagName = node.tagName;
|
|||
|
return tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
|
|||
|
};
|
|||
|
|
|||
|
window.pageLoad = (function () {
|
|||
|
var loaded = false, cbs = [];
|
|||
|
window.addEventListener('load', function () {
|
|||
|
var i;
|
|||
|
loaded = true;
|
|||
|
if (cbs.length > 0) {
|
|||
|
for (i = 0; i < cbs.length; i++) {
|
|||
|
cbs[i]();
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
return {
|
|||
|
then: function(cb) {
|
|||
|
cb && (loaded ? cb() : (cbs.push(cb)));
|
|||
|
}
|
|||
|
};
|
|||
|
})();
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
window.throttle = function(func, wait) {
|
|||
|
var args, result, thisArg, timeoutId, lastCalled = 0;
|
|||
|
|
|||
|
function trailingCall() {
|
|||
|
lastCalled = new Date;
|
|||
|
timeoutId = null;
|
|||
|
result = func.apply(thisArg, args);
|
|||
|
}
|
|||
|
return function() {
|
|||
|
var now = new Date,
|
|||
|
remaining = wait - (now - lastCalled);
|
|||
|
|
|||
|
args = arguments;
|
|||
|
thisArg = this;
|
|||
|
|
|||
|
if (remaining <= 0) {
|
|||
|
clearTimeout(timeoutId);
|
|||
|
timeoutId = null;
|
|||
|
lastCalled = now;
|
|||
|
result = func.apply(thisArg, args);
|
|||
|
} else if (!timeoutId) {
|
|||
|
timeoutId = setTimeout(trailingCall, remaining);
|
|||
|
}
|
|||
|
return result;
|
|||
|
};
|
|||
|
};
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var Set = (function() {
|
|||
|
var add = function(item) {
|
|||
|
var i, data = this._data;
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
if (data[i] === item) {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
this.size ++;
|
|||
|
data.push(item);
|
|||
|
return data;
|
|||
|
};
|
|||
|
|
|||
|
var Set = function(data) {
|
|||
|
this.size = 0;
|
|||
|
this._data = [];
|
|||
|
var i;
|
|||
|
if (data.length > 0) {
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
add.call(this, data[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
Set.prototype.add = add;
|
|||
|
Set.prototype.get = function(index) { return this._data[index]; };
|
|||
|
Set.prototype.has = function(item) {
|
|||
|
var i, data = this._data;
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
if (this.get(i) === item) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
};
|
|||
|
Set.prototype.is = function(map) {
|
|||
|
if (map._data.length !== this._data.length) { return false; }
|
|||
|
var i, j, flag, tData = this._data, mData = map._data;
|
|||
|
for (i = 0; i < tData.length; i++) {
|
|||
|
for (flag = false, j = 0; j < mData.length; j++) {
|
|||
|
if (tData[i] === mData[j]) {
|
|||
|
flag = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!flag) { return false; }
|
|||
|
}
|
|||
|
return true;
|
|||
|
};
|
|||
|
Set.prototype.values = function() {
|
|||
|
return this._data;
|
|||
|
};
|
|||
|
return Set;
|
|||
|
})();
|
|||
|
|
|||
|
window.Lazyload = (function(doc) {
|
|||
|
var queue = {js: [], css: []}, sources = {js: {}, css: {}}, context = this;
|
|||
|
var createNode = function(name, attrs) {
|
|||
|
var node = doc.createElement(name), attr;
|
|||
|
for (attr in attrs) {
|
|||
|
if (attrs.hasOwnProperty(attr)) {
|
|||
|
node.setAttribute(attr, attrs[attr]);
|
|||
|
}
|
|||
|
}
|
|||
|
return node;
|
|||
|
};
|
|||
|
var end = function(type, url) {
|
|||
|
var s, q, qi, cbs, i, j, cur, val, flag;
|
|||
|
if (type === 'js' || type ==='css') {
|
|||
|
s = sources[type], q = queue[type];
|
|||
|
s[url] = true;
|
|||
|
for (i = 0; i < q.length; i++) {
|
|||
|
cur = q[i];
|
|||
|
if (cur.urls.has(url)) {
|
|||
|
qi = cur, val = qi.urls.values();
|
|||
|
qi && (cbs = qi.callbacks);
|
|||
|
for (flag = true, j = 0; j < val.length; j++) {
|
|||
|
cur = val[j];
|
|||
|
if (!s[cur]) {
|
|||
|
flag = false;
|
|||
|
}
|
|||
|
}
|
|||
|
if (flag && cbs && cbs.length > 0) {
|
|||
|
for (j = 0; j < cbs.length; j++) {
|
|||
|
cbs[j].call(context);
|
|||
|
}
|
|||
|
qi.load = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
var load = function(type, urls, callback) {
|
|||
|
var s, q, qi, node, i, cur,
|
|||
|
_urls = typeof urls === 'string' ? new Set([urls]) : new Set(urls), val, url;
|
|||
|
if (type === 'js' || type ==='css') {
|
|||
|
s = sources[type], q = queue[type];
|
|||
|
for (i = 0; i < q.length; i++) {
|
|||
|
cur = q[i];
|
|||
|
if (_urls.is(cur.urls)) {
|
|||
|
qi = cur;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
val = _urls.values();
|
|||
|
if (qi) {
|
|||
|
callback && (qi.load || qi.callbacks.push(callback));
|
|||
|
callback && (qi.load && callback());
|
|||
|
} else {
|
|||
|
q.push({
|
|||
|
urls: _urls,
|
|||
|
callbacks: callback ? [callback] : [],
|
|||
|
load: false
|
|||
|
});
|
|||
|
for (i = 0; i < val.length; i++) {
|
|||
|
node = null, url = val[i];
|
|||
|
if (s[url] === undefined) {
|
|||
|
(type === 'js' ) && (node = createNode('script', { src: url }));
|
|||
|
(type === 'css') && (node = createNode('link', { rel: 'stylesheet', href: url }));
|
|||
|
if (node) {
|
|||
|
node.onload = (function(type, url) {
|
|||
|
return function() {
|
|||
|
end(type, url);
|
|||
|
};
|
|||
|
})(type, url);
|
|||
|
(doc.head || doc.body).appendChild(node);
|
|||
|
s[url] = false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
return {
|
|||
|
js: function(url, callback) {
|
|||
|
load('js', url, callback);
|
|||
|
},
|
|||
|
css: function(url, callback) {
|
|||
|
load('css', url, callback);
|
|||
|
}
|
|||
|
};
|
|||
|
})(this.document);
|
|||
|
})();
|
|||
|
</script><script>
|
|||
|
(function() {
|
|||
|
var TEXT_VARIABLES = {
|
|||
|
version: '2.2.6',
|
|||
|
sources: {
|
|||
|
font_awesome: 'https://use.fontawesome.com/releases/v5.0.13/css/all.css',
|
|||
|
jquery: '/assets/js/jquery.min.js',
|
|||
|
leancloud_js_sdk: '//cdn.jsdelivr.net/npm/leancloud-storage@3.13.2/dist/av-min.js',
|
|||
|
chart: 'https://cdn.bootcss.com/Chart.js/2.7.2/Chart.bundle.min.js',
|
|||
|
gitalk: {
|
|||
|
js: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.js',
|
|||
|
css: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.css'
|
|||
|
},
|
|||
|
valine: 'https://unpkg.com/valine/dist/Valine.min.js'
|
|||
|
},
|
|||
|
site: {
|
|||
|
toc: {
|
|||
|
selectors: 'h1,h2,h3'
|
|||
|
}
|
|||
|
},
|
|||
|
paths: {
|
|||
|
search_js: '/assets/search.js'
|
|||
|
}
|
|||
|
};
|
|||
|
window.TEXT_VARIABLES = TEXT_VARIABLES;
|
|||
|
})();
|
|||
|
</script>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div class="root" data-is-touch="false">
|
|||
|
<div class="layout--page js-page-root"><!----><div class="page__main js-page-main page__viewport hide-footer has-aside has-aside cell cell--auto">
|
|||
|
|
|||
|
<div class="page__main-inner"><div class="page__header d-print-none"><header class="header"><div class="main">
|
|||
|
<div class="header__title">
|
|||
|
<div class="header__brand"><svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="478.9473684210526" viewBox="0, 0, 400,478.9473684210526"><g id="svgg"><path id="path0" d="M308.400 56.805 C 306.970 56.966,303.280 57.385,300.200 57.738 C 290.906 58.803,278.299 59.676,269.200 59.887 L 260.600 60.085 259.400 61.171 C 258.010 62.428,256.198 63.600,255.645 63.600 C 255.070 63.600,252.887 65.897,252.598 66.806 C 252.460 67.243,252.206 67.600,252.034 67.600 C 251.397 67.600,247.206 71.509,247.202 72.107 C 247.201 72.275,246.390 73.190,245.400 74.138 C 243.961 75.517,243.598 76.137,243.592 77.231 C 243.579 79.293,241.785 83.966,240.470 85.364 C 239.176 86.740,238.522 88.365,237.991 91.521 C 237.631 93.665,236.114 97.200,235.554 97.200 C 234.938 97.200,232.737 102.354,232.450 104.472 C 232.158 106.625,230.879 109.226,229.535 110.400 C 228.933 110.926,228.171 113.162,226.434 119.500 C 226.178 120.435,225.795 121.200,225.584 121.200 C 225.373 121.200,225.200 121.476,225.200 121.813 C 225.200 122.149,224.885 122.541,224.500 122.683 C 223.606 123.013,223.214 123.593,223.204 124.600 C 223.183 126.555,220.763 132.911,219.410 134.562 C 218.443 135.742,217.876 136.956,217.599 138.440 C 217.041 141.424,215.177 146.434,214.532 146.681 C 214.240 146.794,214.000 147.055,214.000 147.261 C 214.000 147.467,213.550 148.086,213.000 148.636 C 212.450 149.186,212.000 149.893,212.000 150.208 C 212.000 151.386,208.441 154.450,207.597 153.998 C 206.319 153.315,204.913 150.379,204.633 147.811 C 204.365 145.357,202.848 142.147,201.759 141.729 C 200.967 141.425,199.200 137.451,199.200 135.974 C 199.200 134.629,198.435 133.224,196.660 131.311 C 195.363 129.913,194.572 128.123,193.870 125.000 C 193.623 123.900,193.236 122.793,193.010 122.540 C 190.863 120.133,190.147 118.880,188.978 115.481 C 188.100 112.928,187.151 111.003,186.254 109.955 C 185.358 108.908,184.518 107.204,183.847 105.073 C 183.280 103.273,182.497 101.329,182.108 100.753 C 181.719 100.177,180.904 98.997,180.298 98.131 C 179.693 97.265,178.939 95.576,178.624 94.378 C 178.041 92.159,177.125 90.326,175.023 87.168 C 174.375 86.196,173.619 84.539,173.342 83.486 C 172.800 81.429,171.529 79.567,170.131 78.785 C 169.654 78.517,168.697 77.511,168.006 76.549 C 167.316 75.587,166.594 74.800,166.402 74.800 C 166.210 74.800,164.869 73.633,163.421 72.206 C 160.103 68.936,161.107 69.109,146.550 69.301 C 133.437 69.474,128.581 70.162,126.618 72.124 C 126.248 72.495,125.462 72.904,124.872 73.033 C 124.282 73.163,123.088 73.536,122.219 73.863 C 121.349 74.191,119.028 74.638,117.061 74.858 C 113.514 75.254,109.970 76.350,108.782 77.419 C 107.652 78.436,100.146 80.400,97.388 80.400 C 95.775 80.400,93.167 81.360,91.200 82.679 C 90.430 83.195,89.113 83.804,88.274 84.031 C 85.875 84.681,78.799 90.910,74.400 96.243 L 73.400 97.456 73.455 106.028 C 73.526 117.055,74.527 121.238,77.820 124.263 C 78.919 125.273,80.400 127.902,80.400 128.842 C 80.400 129.202,81.075 130.256,81.900 131.186 C 83.563 133.059,85.497 136.346,86.039 138.216 C 86.233 138.886,87.203 140.207,88.196 141.153 C 89.188 142.098,90.000 143.104,90.000 143.388 C 90.000 144.337,92.129 148.594,92.869 149.123 C 93.271 149.410,93.600 149.831,93.600 150.059 C 93.600 150.286,93.932 150.771,94.337 151.136 C 94.743 151.501,95.598 153.004,96.237 154.475 C 96.877 155.947,97.760 157.351,98.200 157.596 C 98.640 157.841,99.900 159.943,101.000 162.267 C 102.207 164.817,103.327 166.644,103.825 166.876 C 104.278 167.087,105.065 168.101,105.573 169.130 C 107.658 173.348,108.097 174.093,110.006 176.647 C 111.103 178.114,112.000 179.725,112.000 180.227 C 112.000 181.048,113.425 183.163,114.678 184.200 C 115.295 184.711,117.396 188.733,117.720 190.022 C 117.855 190.562,118.603 191.633,119.381 192.402 C 120.160 193.171,121.496 195.258,122.351 197.039 C 123.206 198.820,124.167 200.378,124.487 200.501 C 124.807 200.624,125.953 202.496,127.034 204.662 C 128.114 206.828,129.676 209.299,130.505 210.153 C 131.333 211.007,132.124 212.177,132.262 212.753 C 132.618 214.239,134.291 217.048,136.288 219.5
|
|||
|
" href="/">YannStatic</a></div><!--<button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button>--><!-- <li><button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button></li> -->
|
|||
|
<!-- Champ de recherche -->
|
|||
|
<div id="searchbox" class="search search--dark" style="visibility: visible">
|
|||
|
<div class="main">
|
|||
|
<div class="search__header"></div>
|
|||
|
<div class="search-bar">
|
|||
|
<div class="search-box js-search-box">
|
|||
|
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
|||
|
<input id="search-input" type="text" />
|
|||
|
<!-- <div class="search-box__icon-clear js-icon-clear">
|
|||
|
<a><i class="fas fa-times"></i></a>
|
|||
|
</div> -->
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<!-- Script pointing to search-script.js -->
|
|||
|
<script>/*!
|
|||
|
* Simple-Jekyll-Search
|
|||
|
* Copyright 2015-2020, Christian Fei
|
|||
|
* Licensed under the MIT License.
|
|||
|
*/
|
|||
|
|
|||
|
(function(){
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Templater_7 = {
|
|||
|
compile: compile,
|
|||
|
setOptions: setOptions
|
|||
|
}
|
|||
|
|
|||
|
const options = {}
|
|||
|
options.pattern = /\{(.*?)\}/g
|
|||
|
options.template = ''
|
|||
|
options.middleware = function () {}
|
|||
|
|
|||
|
function setOptions (_options) {
|
|||
|
options.pattern = _options.pattern || options.pattern
|
|||
|
options.template = _options.template || options.template
|
|||
|
if (typeof _options.middleware === 'function') {
|
|||
|
options.middleware = _options.middleware
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compile (data) {
|
|||
|
return options.template.replace(options.pattern, function (match, prop) {
|
|||
|
const value = options.middleware(prop, data[prop], options.template)
|
|||
|
if (typeof value !== 'undefined') {
|
|||
|
return value
|
|||
|
}
|
|||
|
return data[prop] || match
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
function fuzzysearch (needle, haystack) {
|
|||
|
var tlen = haystack.length;
|
|||
|
var qlen = needle.length;
|
|||
|
if (qlen > tlen) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (qlen === tlen) {
|
|||
|
return needle === haystack;
|
|||
|
}
|
|||
|
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|||
|
var nch = needle.charCodeAt(i);
|
|||
|
while (j < tlen) {
|
|||
|
if (haystack.charCodeAt(j++) === nch) {
|
|||
|
continue outer;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var _$fuzzysearch_1 = fuzzysearch;
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
|
|||
|
|
|||
|
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
|
|||
|
|
|||
|
function FuzzySearchStrategy () {
|
|||
|
this.matches = function (string, crit) {
|
|||
|
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
|
|||
|
|
|||
|
function LiteralSearchStrategy () {
|
|||
|
this.matches = function (str, crit) {
|
|||
|
if (!str) return false
|
|||
|
|
|||
|
str = str.trim().toLowerCase()
|
|||
|
crit = crit.trim().toLowerCase()
|
|||
|
|
|||
|
return crit.split(' ').filter(function (word) {
|
|||
|
return str.indexOf(word) >= 0
|
|||
|
}).length === crit.split(' ').length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Repository_4 = {
|
|||
|
put: put,
|
|||
|
clear: clear,
|
|||
|
search: search,
|
|||
|
setOptions: __setOptions_4
|
|||
|
}
|
|||
|
|
|||
|
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
|
|||
|
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
|
|||
|
|
|||
|
function NoSort () {
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
const data = []
|
|||
|
let opt = {}
|
|||
|
|
|||
|
opt.fuzzy = false
|
|||
|
opt.limit = 10
|
|||
|
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = NoSort
|
|||
|
opt.exclude = []
|
|||
|
|
|||
|
function put (data) {
|
|||
|
if (isObject(data)) {
|
|||
|
return addObject(data)
|
|||
|
}
|
|||
|
if (isArray(data)) {
|
|||
|
return addArray(data)
|
|||
|
}
|
|||
|
return undefined
|
|||
|
}
|
|||
|
function clear () {
|
|||
|
data.length = 0
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function isObject (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
|
|||
|
}
|
|||
|
|
|||
|
function isArray (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function addObject (_data) {
|
|||
|
data.push(_data)
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function addArray (_data) {
|
|||
|
const added = []
|
|||
|
clear()
|
|||
|
for (let i = 0, len = _data.length; i < len; i++) {
|
|||
|
if (isObject(_data[i])) {
|
|||
|
added.push(addObject(_data[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
return added
|
|||
|
}
|
|||
|
|
|||
|
function search (crit) {
|
|||
|
if (!crit) {
|
|||
|
return []
|
|||
|
}
|
|||
|
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
|
|||
|
}
|
|||
|
|
|||
|
function __setOptions_4 (_opt) {
|
|||
|
opt = _opt || {}
|
|||
|
|
|||
|
opt.fuzzy = _opt.fuzzy || false
|
|||
|
opt.limit = _opt.limit || 10
|
|||
|
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = _opt.sort || NoSort
|
|||
|
opt.exclude = _opt.exclude || []
|
|||
|
}
|
|||
|
|
|||
|
function findMatches (data, crit, strategy, opt) {
|
|||
|
const matches = []
|
|||
|
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
|
|||
|
const match = findMatchesInObject(data[i], crit, strategy, opt)
|
|||
|
if (match) {
|
|||
|
matches.push(match)
|
|||
|
}
|
|||
|
}
|
|||
|
return matches
|
|||
|
}
|
|||
|
|
|||
|
function findMatchesInObject (obj, crit, strategy, opt) {
|
|||
|
for (const key in obj) {
|
|||
|
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
|
|||
|
return obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isExcluded (term, excludedTerms) {
|
|||
|
for (let i = 0, len = excludedTerms.length; i < len; i++) {
|
|||
|
const excludedTerm = excludedTerms[i]
|
|||
|
if (new RegExp(excludedTerm).test(term)) {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
/* globals ActiveXObject:false */
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$JSONLoader_2 = {
|
|||
|
load: load
|
|||
|
}
|
|||
|
|
|||
|
function load (location, callback) {
|
|||
|
const xhr = getXHR()
|
|||
|
xhr.open('GET', location, true)
|
|||
|
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
|
|||
|
xhr.send()
|
|||
|
}
|
|||
|
|
|||
|
function createStateChangeListener (xhr, callback) {
|
|||
|
return function () {
|
|||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|||
|
try {
|
|||
|
callback(null, JSON.parse(xhr.responseText))
|
|||
|
} catch (err) {
|
|||
|
callback(err, null)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getXHR () {
|
|||
|
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$OptionsValidator_3 = function OptionsValidator (params) {
|
|||
|
if (!validateParams(params)) {
|
|||
|
throw new Error('-- OptionsValidator: required options missing')
|
|||
|
}
|
|||
|
|
|||
|
if (!(this instanceof OptionsValidator)) {
|
|||
|
return new OptionsValidator(params)
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = params.required
|
|||
|
|
|||
|
this.getRequiredOptions = function () {
|
|||
|
return requiredOptions
|
|||
|
}
|
|||
|
|
|||
|
this.validate = function (parameters) {
|
|||
|
const errors = []
|
|||
|
requiredOptions.forEach(function (requiredOptionName) {
|
|||
|
if (typeof parameters[requiredOptionName] === 'undefined') {
|
|||
|
errors.push(requiredOptionName)
|
|||
|
}
|
|||
|
})
|
|||
|
return errors
|
|||
|
}
|
|||
|
|
|||
|
function validateParams (params) {
|
|||
|
if (!params) {
|
|||
|
return false
|
|||
|
}
|
|||
|
return typeof params.required !== 'undefined' && params.required instanceof Array
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$utils_9 = {
|
|||
|
merge: merge,
|
|||
|
isJSON: isJSON
|
|||
|
}
|
|||
|
|
|||
|
function merge (defaultParams, mergeParams) {
|
|||
|
const mergedOptions = {}
|
|||
|
for (const option in defaultParams) {
|
|||
|
mergedOptions[option] = defaultParams[option]
|
|||
|
if (typeof mergeParams[option] !== 'undefined') {
|
|||
|
mergedOptions[option] = mergeParams[option]
|
|||
|
}
|
|||
|
}
|
|||
|
return mergedOptions
|
|||
|
}
|
|||
|
|
|||
|
function isJSON (json) {
|
|||
|
try {
|
|||
|
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
|
|||
|
return true
|
|||
|
}
|
|||
|
return false
|
|||
|
} catch (err) {
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var _$src_8 = {};
|
|||
|
(function (window) {
|
|||
|
'use strict'
|
|||
|
|
|||
|
let options = {
|
|||
|
searchInput: null,
|
|||
|
resultsContainer: null,
|
|||
|
json: [],
|
|||
|
success: Function.prototype,
|
|||
|
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
|
|||
|
templateMiddleware: Function.prototype,
|
|||
|
sortMiddleware: function () {
|
|||
|
return 0
|
|||
|
},
|
|||
|
noResultsText: 'No results found',
|
|||
|
limit: 10,
|
|||
|
fuzzy: false,
|
|||
|
debounceTime: null,
|
|||
|
exclude: []
|
|||
|
}
|
|||
|
|
|||
|
let debounceTimerHandle
|
|||
|
const debounce = function (func, delayMillis) {
|
|||
|
if (delayMillis) {
|
|||
|
clearTimeout(debounceTimerHandle)
|
|||
|
debounceTimerHandle = setTimeout(func, delayMillis)
|
|||
|
} else {
|
|||
|
func.call()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
|
|||
|
|
|||
|
/* removed: const _$Templater_7 = require('./Templater') */;
|
|||
|
/* removed: const _$Repository_4 = require('./Repository') */;
|
|||
|
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
|
|||
|
const optionsValidator = _$OptionsValidator_3({
|
|||
|
required: requiredOptions
|
|||
|
})
|
|||
|
/* removed: const _$utils_9 = require('./utils') */;
|
|||
|
|
|||
|
window.SimpleJekyllSearch = function (_options) {
|
|||
|
const errors = optionsValidator.validate(_options)
|
|||
|
if (errors.length > 0) {
|
|||
|
throwError('You must specify the following required options: ' + requiredOptions)
|
|||
|
}
|
|||
|
|
|||
|
options = _$utils_9.merge(options, _options)
|
|||
|
|
|||
|
_$Templater_7.setOptions({
|
|||
|
template: options.searchResultTemplate,
|
|||
|
middleware: options.templateMiddleware
|
|||
|
})
|
|||
|
|
|||
|
_$Repository_4.setOptions({
|
|||
|
fuzzy: options.fuzzy,
|
|||
|
limit: options.limit,
|
|||
|
sort: options.sortMiddleware,
|
|||
|
exclude: options.exclude
|
|||
|
})
|
|||
|
|
|||
|
if (_$utils_9.isJSON(options.json)) {
|
|||
|
initWithJSON(options.json)
|
|||
|
} else {
|
|||
|
initWithURL(options.json)
|
|||
|
}
|
|||
|
|
|||
|
const rv = {
|
|||
|
search: search
|
|||
|
}
|
|||
|
|
|||
|
typeof options.success === 'function' && options.success.call(rv)
|
|||
|
return rv
|
|||
|
}
|
|||
|
|
|||
|
function initWithJSON (json) {
|
|||
|
_$Repository_4.put(json)
|
|||
|
registerInput()
|
|||
|
}
|
|||
|
|
|||
|
function initWithURL (url) {
|
|||
|
_$JSONLoader_2.load(url, function (err, json) {
|
|||
|
if (err) {
|
|||
|
throwError('failed to get JSON (' + url + ')')
|
|||
|
}
|
|||
|
initWithJSON(json)
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function emptyResultsContainer () {
|
|||
|
options.resultsContainer.innerHTML = ''
|
|||
|
}
|
|||
|
|
|||
|
function appendToResultsContainer (text) {
|
|||
|
options.resultsContainer.innerHTML += text
|
|||
|
}
|
|||
|
|
|||
|
function registerInput () {
|
|||
|
options.searchInput.addEventListener('input', function (e) {
|
|||
|
if (isWhitelistedKey(e.which)) {
|
|||
|
emptyResultsContainer()
|
|||
|
debounce(function () { search(e.target.value) }, options.debounceTime)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function search (query) {
|
|||
|
if (isValidQuery(query)) {
|
|||
|
emptyResultsContainer()
|
|||
|
render(_$Repository_4.search(query), query)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function render (results, query) {
|
|||
|
const len = results.length
|
|||
|
if (len === 0) {
|
|||
|
return appendToResultsContainer(options.noResultsText)
|
|||
|
}
|
|||
|
for (let i = 0; i < len; i++) {
|
|||
|
results[i].query = query
|
|||
|
appendToResultsContainer(_$Templater_7.compile(results[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isValidQuery (query) {
|
|||
|
return query && query.length > 0
|
|||
|
}
|
|||
|
|
|||
|
function isWhitelistedKey (key) {
|
|||
|
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
|
|||
|
}
|
|||
|
|
|||
|
function throwError (message) {
|
|||
|
throw new Error('SimpleJekyllSearch --- ' + message)
|
|||
|
}
|
|||
|
})(window)
|
|||
|
|
|||
|
}());
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Configuration -->
|
|||
|
<script>
|
|||
|
SimpleJekyllSearch({
|
|||
|
searchInput: document.getElementById('search-input'),
|
|||
|
resultsContainer: document.getElementById('results-container'),
|
|||
|
json: '/search.json',
|
|||
|
//searchResultTemplate: '<li><a href="https://static.rnmkcy.eu{url}">{date} {title}</a></li>'
|
|||
|
searchResultTemplate: '<li><a href="{url}">{date} {title}</a></li>'
|
|||
|
})
|
|||
|
</script>
|
|||
|
<!-- Fin déclaration champ de recherche --></div><nav class="navigation">
|
|||
|
<ul><li class="navigation__item"><a href="/archive.html">Etiquettes</a></li><li class="navigation__item"><a href="/htmldoc.html">Documents</a></li><li class="navigation__item"><a href="/liens_ttrss.html">Liens</a></li><li class="navigation__item"><a href="/aide-jekyll-text-theme.html">Aide</a></li></ul>
|
|||
|
</nav></div>
|
|||
|
</header>
|
|||
|
|
|||
|
</div><div class="page__content"><div class ="main"><div class="grid grid--reverse">
|
|||
|
<div class="col-main cell cell--auto"><!-- start custom main top snippet --><div id="results-container" class="search-result js-search-result"></div><!-- end custom main top snippet -->
|
|||
|
<article itemscope itemtype="http://schema.org/Article"><div class="article__header"><header><h1 style="color:Tomato;">PHP Authentification RobThree/TwoFactorAuth</h1></header></div><meta itemprop="headline" content="PHP Authentification RobThree/TwoFactorAuth"><div class="article__info clearfix"><ul class="left-col menu"><li>
|
|||
|
<a class="button button--secondary button--pill button--sm"
|
|||
|
href="/archive.html?tag=virtuel">virtuel</a>
|
|||
|
</li><li>
|
|||
|
<a class="button button--secondary button--pill button--sm"
|
|||
|
href="/archive.html?tag=authentification">authentification</a>
|
|||
|
</li></ul><ul class="right-col menu"><li>
|
|||
|
<i class="far fa-calendar-alt"></i> <span title="Création" style="color:#FF00FF">17 mars 2021</span>
|
|||
|
|
|||
|
<span title="Modification" style="color:#00FF7F">30 mars 2021</span></li></ul></div><meta itemprop="datePublished" content="2021-03-30T00:00:00+02:00">
|
|||
|
<meta itemprop="keywords" content="virtuel,authentification"><div class="js-article-content">
|
|||
|
<div class="layout--article"><!-- start custom article top snippet -->
|
|||
|
<style>
|
|||
|
#myBtn {
|
|||
|
display: none;
|
|||
|
position: fixed;
|
|||
|
bottom: 10px;
|
|||
|
right: 10px;
|
|||
|
z-index: 99;
|
|||
|
font-size: 12px;
|
|||
|
font-weight: bold;
|
|||
|
border: none;
|
|||
|
outline: none;
|
|||
|
background-color: white;
|
|||
|
color: black;
|
|||
|
cursor: pointer;
|
|||
|
padding: 5px;
|
|||
|
border-radius: 4px;
|
|||
|
}
|
|||
|
|
|||
|
#myBtn:hover {
|
|||
|
background-color: #555;
|
|||
|
}
|
|||
|
</style>
|
|||
|
|
|||
|
<button onclick="topFunction()" id="myBtn" title="Haut de page">⇧</button>
|
|||
|
|
|||
|
<script>
|
|||
|
//Get the button
|
|||
|
var mybutton = document.getElementById("myBtn");
|
|||
|
|
|||
|
// When the user scrolls down 20px from the top of the document, show the button
|
|||
|
window.onscroll = function() {scrollFunction()};
|
|||
|
|
|||
|
function scrollFunction() {
|
|||
|
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
|
|||
|
mybutton.style.display = "block";
|
|||
|
} else {
|
|||
|
mybutton.style.display = "none";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// When the user clicks on the button, scroll to the top of the document
|
|||
|
function topFunction() {
|
|||
|
document.body.scrollTop = 0;
|
|||
|
document.documentElement.scrollTop = 0;
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
|
|||
|
<!-- end custom article top snippet -->
|
|||
|
<div class="article__content" itemprop="articleBody"><details>
|
|||
|
<summary><b>Afficher/cacher Sommaire</b></summary>
|
|||
|
<!-- affichage sommaire -->
|
|||
|
<div class="toc-aside js-toc-root"></div>
|
|||
|
</details><h2 id="bibliothèque-php-2fa">Bibliothèque PHP 2FA</h2>
|
|||
|
|
|||
|
<p><em>Bibliothèque PHP pour l’authentification à deux facteurs</em></p>
|
|||
|
|
|||
|
<h3 id="prérequis">Prérequis</h3>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>Unzip : <code class="language-plaintext highlighter-rouge">sudo apt install unzip</code></li>
|
|||
|
<li>Testé sur PHP 5.6 jusqu’à 8.0.</li>
|
|||
|
<li><a href="http://php.net/manual/en/book.curl.php">cURL</a> en utilisant le <code class="language-plaintext highlighter-rouge">QRServerProvider</code> (par défaut), <code class="language-plaintext highlighter-rouge">ImageChartsQRCodeProvider</code> ou <code class="language-plaintext highlighter-rouge">QRicketProvider</code> mais vous pouvez aussi fournir votre propre fournisseur de QR-code.</li>
|
|||
|
<li><a href="http://php.net/manual/en/function.random-bytes.php">random_bytes()</a>, <a href="http://php.net/manual/en/book.mcrypt.php">MCrypt</a>, <a href="http://php.net/manual/en/book.openssl.php">OpenSSL</a> ou <a href="http://php.net/manual/en/book.hash.php">Hash</a> selon le RNG intégré que vous utilisez (TwoFactorAuth essaiera d’autodétecter et d’utiliser le meilleur disponible) ; cependant : n’hésitez pas à fournir votre propre (CS)RNG.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>En option, vous pouvez avoir besoin de :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li><a href="https://github.com/endroid/qr-code">endroid/qr-code</a> si vous utilisez <code class="language-plaintext highlighter-rouge">EndroidQrCodeProvider</code> ou <code class="language-plaintext highlighter-rouge">EndroidQrCodeWithLogoProvider</code>.</li>
|
|||
|
<li><a href="https://github.com/Bacon/BaconQrCode">bacon/bacon-qr-code</a> si vous utilisez <code class="language-plaintext highlighter-rouge">BaconQrCodeProvider</code>.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p><a href="/2019/12/25/php-composer.html">Installer PHP composer</a></p>
|
|||
|
|
|||
|
<p>Bash d’installation</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano composer.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
|
|||
|
|
|||
|
EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
|
|||
|
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
|||
|
ACTUAL_SIGNATURE="$(php -r "echo hash_file('SHA384', 'composer-setup.php');")"
|
|||
|
|
|||
|
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
|
|||
|
then
|
|||
|
>&2 echo 'ERROR: Invalid installer signature'
|
|||
|
rm composer-setup.php
|
|||
|
exit 1
|
|||
|
fi
|
|||
|
|
|||
|
php composer-setup.php --quiet
|
|||
|
RESULT=$?
|
|||
|
rm composer-setup.php
|
|||
|
exit $RESULT
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><a href="https://github.com/RobThree/TwoFactorAuth">PHP library for Two Factor Authentication</a><br />
|
|||
|
Installation, exécutez la commande suivante :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>php composer.phar require robthree/twofactorauth
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/php2fa01.png" alt="" /></p>
|
|||
|
|
|||
|
<h3 id="usage">Usage</h3>
|
|||
|
|
|||
|
<p>Voici quelques extraits de code qui devraient vous aider à démarrer…</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Créez une instance TwoFactorAuth</span>
|
|||
|
<span class="nv">$tfa</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RobThree\Auth\TwoFactorAuth</span><span class="p">(</span><span class="s1">'My Company'</span><span class="p">)</span> <span class="p">;</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Le constructeur de la classe TwoFactorAuth accepte 7 arguments (tous facultatifs) :</p>
|
|||
|
|
|||
|
<table>
|
|||
|
<thead>
|
|||
|
<tr>
|
|||
|
<th>Argument</th>
|
|||
|
<th>Valeur par défaut</th>
|
|||
|
<th>Utilisation</th>
|
|||
|
</tr>
|
|||
|
</thead>
|
|||
|
<tbody>
|
|||
|
<tr>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">$issuer</code></td>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">null</code></td>
|
|||
|
<td>Sera affiché dans l’application comme nom de l’émetteur.</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">$digits</code></td>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">6</code></td>
|
|||
|
<td>Le nombre de chiffres des codes résultants.</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">$period</code></td>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">30</code></td>
|
|||
|
<td>Le nombre de secondes pendant lesquelles le code sera valide.</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">$algorithm</code></td>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">sha1</code></td>
|
|||
|
<td>L’algorithme utilisé (un parmi <code class="language-plaintext highlighter-rouge">sha1</code>, <code class="language-plaintext highlighter-rouge">sha256</code>, <code class="language-plaintext highlighter-rouge">sha512</code>, <code class="language-plaintext highlighter-rouge">md5</code>)</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">$qrcodeprovider</code></td>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">null</code></td>
|
|||
|
<td>Fournisseur de code QR (plus d’informations à ce sujet plus tard)</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">$rngprovider</code></td>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">null</code></td>
|
|||
|
<td>Fournisseur de générateur de nombres aléatoires (plus d’informations à ce sujet plus tard)</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">$timeprovider</code></td>
|
|||
|
<td><code class="language-plaintext highlighter-rouge">null</code></td>
|
|||
|
<td>Fournisseur de l’heure (plus sur ce sujet plus tard)</td>
|
|||
|
</tr>
|
|||
|
</tbody>
|
|||
|
</table>
|
|||
|
|
|||
|
<p>Ces arguments sont tous ‘<code class="language-plaintext highlighter-rouge">write once</code>’ ; la classe utilisera ces valeurs, pour toute sa durée de vie, lors de la génération / du calcul des codes. Le nombre de chiffres, la période et l’algorithme sont tous définis en fonction des valeurs utilisées (et supportées) par l’application Authenticator de Google. Vous pouvez spécifier 8 chiffres, une période de 45 secondes et l’algorithme sha256, mais l’application d’authentification (qu’il s’agisse de l’application de Google, d’Authy ou d’une autre application) peut ou non prendre en charge ces valeurs. Votre expérience peut varier ; soyez prudent si vous ne contrôlez pas l’application utilisée par votre public.</p>
|
|||
|
|
|||
|
<h4 id="étape-1--créer-une-clé-partagée-secrète">Étape 1 : Créer une clé partagée secrète</h4>
|
|||
|
|
|||
|
<p>Lorsqu’un utilisateur souhaite configurer l’authentification à deux facteurs (ou, plus correctement, l’authentification à plusieurs facteurs), vous devez créer un secret. Ce sera votre <strong>secret partagé</strong>. Ce secret devra être saisi par l’utilisateur dans son application. Cela peut être fait manuellement, auquel cas vous affichez simplement le secret et demandez à l’utilisateur de le saisir dans l’application :</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$secret</span> <span class="o">=</span> <span class="nv">$tfa</span><span class="o">-></span><span class="nf">createSecret</span><span class="p">()</span> <span class="p">;</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>La méthode <code class="language-plaintext highlighter-rouge">createSecret()</code> accepte deux arguments : <code class="language-plaintext highlighter-rouge">$bits</code> (par défaut : <code class="language-plaintext highlighter-rouge">80</code>) et <code class="language-plaintext highlighter-rouge">$requirecryptosecure</code> (par défaut : <code class="language-plaintext highlighter-rouge">true</code>). Le premier est le nombre de bits générés pour le secret partagé. Assurez-vous que cet argument est un multiple de 8 et, encore une fois, gardez à l’esprit que toutes les combinaisons ne sont pas forcément supportées par toutes les applications. L’authentificateur de Google semble satisfait avec 80 et 160, la valeur par défaut est 80 car c’est ce que la plupart des sites (que je connais) utilisent actuellement ; cependant une valeur de 160 ou plus est recommandée (voir <a href="https://tools.ietf.org/html/rfc4226#section-4">RFC 4226 - Algorithm Requirements</a>). Ce dernier est utilisé pour s’assurer que le secret est cryptographiquement sécurisé ; si vous ne vous souciez pas beaucoup des secrets cryptographiquement sécurisés, vous pouvez spécifier <code class="language-plaintext highlighter-rouge">false</code> et utiliser un fournisseur de RNG <strong>non</strong>-cryptographiquement sécurisé.</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Afficher le secret partagé
|
|||
|
<span class="nt"><p></span>Veuillez entrer le code suivant dans votre application : '<span class="cp"><?php</span> <span class="k">echo</span> <span class="nv">$secret</span> <span class="p">;</span> <span class="cp">?></span>'<span class="nt"></p></span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Une autre façon, plus conviviale, de faire entrer le secret partagé dans l’application consiste à générer un <a href="http://en.wikipedia.org/wiki/QR_code">QR-code</a> qui peut être scanné par l’application.<br />
|
|||
|
Pour générer ces codes QR, vous pouvez utiliser l’une des classes intégrées <code class="language-plaintext highlighter-rouge">QRProvider</code> :</p>
|
|||
|
|
|||
|
<ol>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">QRServerProvider</code> (par défaut)</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">ImageChartsQRCodeProvider</code>.</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">QRicketProvider</code> (par défaut)</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">EndroidQrCodeProvider</code> (nécessite l’installation de <code class="language-plaintext highlighter-rouge">endroid/qr-code</code>)</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">EndroidQrCodeWithLogoProvider</code> (identique, mais supportant les images intégrées)</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">BaconQrCodeProvider</code> (nécessite l’installation de <code class="language-plaintext highlighter-rouge">bacon/bacon-qr-code</code>)</li>
|
|||
|
</ol>
|
|||
|
|
|||
|
<p>…ou implémentez votre propre fournisseur. Pour implémenter votre propre fournisseur, tout ce que vous avez à faire est d’implémenter l’interface <code class="language-plaintext highlighter-rouge">IQRCodeProvider</code>.</p>
|
|||
|
|
|||
|
<p>Vous pouvez utiliser les fournisseurs intégrés mentionnés précédemment pour servir d’exemple ou lire le chapitre suivant de ce fichier. Les classes intégrées utilisent toutes une troisième partie (par exemple externe) (Image-charts, QRServer et QRicket) pour le travail difficile de génération de QR-codes (note : chacun de ces services pourrait à un moment donné ne pas être disponible ou imposer des limitations au nombre de codes générés par jour, heure, etc.)<br />
|
|||
|
Cependant, vous pouvez facilement utiliser un projet comme <a href="http://phpqrcode.sourceforge.net/">PHP QR Code</a> (ou l’un des <a href="https://packagist.org/search/?q=qr">nombreux autres</a>) pour générer vos QR-codes sans dépendre de sources externes. Plus tard, nous allons <a href="#qr-code-providers">démontrer</a> comment faire cela.</p>
|
|||
|
|
|||
|
<p>Les fournisseurs intégrés ont tous des “ réglages “ spécifiques que vous pouvez “ appliquer “. Certains prennent en charge différentes couleurs, d’autres vous permettent de spécifier le format d’image souhaité, etc. Ce qu’ils ont tous en commun, c’est qu’ils renvoient un code QR sous la forme d’un blob binaire qui, à son tour, sera transformé en <a href="http://en.wikipedia.org/wiki/Data_URI_scheme">URI de données</a> par la classe <code class="language-plaintext highlighter-rouge">TwoFactorAuth</code>. Cela vous permet d’afficher facilement l’image sans nécessiter d’allers-retours supplémentaires entre le navigateur et le serveur et vice-versa.</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Afficher le code QR à l'utilisateur
|
|||
|
<span class="nt"><p></span>Scannez l'image suivante avec votre application :<span class="nt"></p></span>
|
|||
|
<span class="nt"><p><img</span> <span class="na">src=</span><span class="s">"</span><span class="cp"><?php</span> <span class="k">echo</span> <span class="nv">$tfa</span><span class="o">-></span><span class="nf">getQRCodeImageAsDataUri</span><span class="p">(</span><span class="s1">'Bob Ross'</span><span class="p">,</span> <span class="nv">$secret</span><span class="p">)</span> <span class="p">;</span> <span class="cp">?></span><span class="s">"</span><span class="nt">></p></span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Lors de la sortie d’un QR-code, vous pouvez choisir un <code class="language-plaintext highlighter-rouge">$label</code> pour l’utilisateur (qui, lors de la saisie manuelle d’un secret partagé, devra être choisi par l’utilisateur). Ce label peut être une chaîne vide ou <code class="language-plaintext highlighter-rouge">null</code>. On peut aussi spécifier une <code class="language-plaintext highlighter-rouge">$taille</code> (en pixels, largeur == hauteur) pour laquelle nous utilisons une valeur par défaut de <code class="language-plaintext highlighter-rouge">200</code>.</p>
|
|||
|
|
|||
|
<h4 id="étape-2--vérification-du-secret-partagé">Étape 2 : Vérification du secret partagé</h4>
|
|||
|
|
|||
|
<p>Lorsque le secret partagé est ajouté à l’application, l’application sera prête à commencer à générer des codes qui “expirent” chaque “<code class="language-plaintext highlighter-rouge">$period</code>” nombre de secondes. Pour s’assurer que le secret a été saisi ou scanné correctement, vous devez le vérifier en demandant à l’utilisateur de saisir un code généré. Pour vérifier si le code généré est valide, appelez la méthode <code class="language-plaintext highlighter-rouge">verifyCode()</code> :</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Vérifier le code</span>
|
|||
|
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$tfa</span><span class="o">-></span><span class="nf">verifyCode</span><span class="p">(</span><span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">'secret'</span><span class="p">],</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'verification'</span><span class="p">])</span> <span class="p">;</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Si vous effectuez des validations supplémentaires avec vos valeurs <code class="language-plaintext highlighter-rouge">$_POST</code>, assurez-vous que le code est toujours soumis en tant que chaîne de caractères - même si c’est un code numérique, le convertir en entier n’est pas fiable. De plus, vous pouvez avoir besoin de stocker <code class="language-plaintext highlighter-rouge">$secret</code> dans une <code class="language-plaintext highlighter-rouge">$_SESSION</code> ou un autre stockage persistant entre les requêtes. <code class="language-plaintext highlighter-rouge">verifyCode()</code> retournera soit <code class="language-plaintext highlighter-rouge">true</code> (le code est valide) soit <code class="language-plaintext highlighter-rouge">false</code> (le code n’est pas valide ; aucun point pour vous !).</p>
|
|||
|
|
|||
|
<p>La fonction <code class="language-plaintext highlighter-rouge">verifyCode()</code> accepte, en plus de <code class="language-plaintext highlighter-rouge">$secret</code> et <code class="language-plaintext highlighter-rouge">$code</code>, <u>trois arguments</u> supplémentaires.</p>
|
|||
|
|
|||
|
<ol>
|
|||
|
<li><u>Le premier</u> étant <code class="language-plaintext highlighter-rouge">$discrepancy</code>. Comme les codes TOTP sont basés sur le temps(“tranches”), il est très important que le serveur (mais aussi le client) ait une date/heure correcte. Mais comme les deux peuvent différer un peu, nous accordons généralement une certaine marge de manœuvre. Parce que les codes générés sont valides pour une période spécifique (vous vous souvenez de l’argument <code class="language-plaintext highlighter-rouge">$period</code> dans le constructeur de <code class="language-plaintext highlighter-rouge">TwoFactorAuth</code> ?) nous vérifions généralement la période directement avant et la période directement après l’heure actuelle lors de la validation des codes. Ainsi, lorsque l’heure courante est <code class="language-plaintext highlighter-rouge">14:34:21</code>, ce qui donne une ‘tranche de temps courante’ de <code class="language-plaintext highlighter-rouge">14:34:00</code> à <code class="language-plaintext highlighter-rouge">14:34:30</code>, nous calculons/vérifions également les codes pour <code class="language-plaintext highlighter-rouge">14:33:30</code> à <code class="language-plaintext highlighter-rouge">14:34:00</code> et pour <code class="language-plaintext highlighter-rouge">14:34:30</code> à <code class="language-plaintext highlighter-rouge">14:35:00</code>. Cela nous donne une ‘fenêtre’ de <code class="language-plaintext highlighter-rouge">14:33:30</code> à <code class="language-plaintext highlighter-rouge">14:35:00</code>. L’argument <code class="language-plaintext highlighter-rouge">$discrepancy</code> spécifie le nombre de périodes (ou : tranches de temps) que nous vérifions dans les deux directions de l’heure actuelle. La valeur par défaut de <code class="language-plaintext highlighter-rouge">$discrepancy</code> est de <code class="language-plaintext highlighter-rouge">1</code>, ce qui donne (max.) 3 vérifications de périodes : -1, courant et +1 période. Un <code class="language-plaintext highlighter-rouge">$discrepancy</code> de <code class="language-plaintext highlighter-rouge">4</code> donne une fenêtre plus large (ou : une plus grande différence de temps entre le client et le serveur) de -4, -3, -2, -1, current, +1, +2, +3 et +4 périodes.</li>
|
|||
|
<li><u>Le second</u>, <code class="language-plaintext highlighter-rouge">$time</code>, vous permet de vérifier un code pour un moment précis dans le temps. Cet argument n’a pas de réelle utilité pratique mais peut être pratique pour unittesting etc. La valeur par défaut, <code class="language-plaintext highlighter-rouge">null</code>, signifie : utiliser le temps actuel.</li>
|
|||
|
<li><u>Le troisième</u>, <code class="language-plaintext highlighter-rouge">$timeslice</code>, est un argument de sortie ; la valeur retournée dans <code class="language-plaintext highlighter-rouge">$timeslice</code> est la valeur du timeslice qui correspond au code (s’il existe). Cette valeur sera 0 si le code ne correspond pas et non nulle si le code correspond. Cette valeur peut être stockée avec l’utilisateur et peut être utilisée pour empêcher les attaques par rejeu. Tout ce que vous avez à faire est de vous assurer, lors d’une connexion réussie, que <code class="language-plaintext highlighter-rouge">$timeslice</code> est plus grand que le timeslice précédemment stocké.</li>
|
|||
|
</ol>
|
|||
|
|
|||
|
<h3 id="étape-3--stocker-secret-avec-lutilisateur-et-cest-fini-">Étape 3 : Stocker <code class="language-plaintext highlighter-rouge">$secret</code> avec l’utilisateur et c’est fini !</h3>
|
|||
|
|
|||
|
<p>Ok, donc maintenant le code a été vérifié et trouvé correct. Maintenant nous pouvons stocker le <code class="language-plaintext highlighter-rouge">$secret</code> avec notre utilisateur dans notre base de données (ou ailleurs) et à chaque fois que l’utilisateur commence une nouvelle session nous lui demandons un code généré par l’application d’authentification de son choix. Tout ce que nous avons à faire est d’appeler <code class="language-plaintext highlighter-rouge">verifyCode()</code> à nouveau avec le secret partagé et le code entré et nous savons si l’utilisateur est légitime ou non.</p>
|
|||
|
|
|||
|
<p>Aussi simple que 1-2-3.</p>
|
|||
|
|
|||
|
<p>Tout ce dont nous avons besoin, c’est de 3 méthodes et d’un constructeur :</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span>
|
|||
|
<span class="nv">$issuer</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span>
|
|||
|
<span class="nv">$digits</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span>
|
|||
|
<span class="nv">$period</span> <span class="o">=</span> <span class="mi">30</span><span class="p">,</span>
|
|||
|
<span class="nv">$algorithm</span> <span class="o">=</span> <span class="s1">'sha1'</span><span class="p">,</span>
|
|||
|
<span class="kt">RobThree</span><span class="nc">\Auth\Providers\Qr\IQRCodeProvider</span> <span class="nv">$qrcodeprovider</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span>
|
|||
|
<span class="kt">RobThree</span><span class="nc">\Auth\Providers\Rng\IRNGProvider</span> <span class="nv">$rngprovider</span> <span class="o">=</span> <span class="kc">null</span>
|
|||
|
<span class="p">)</span> <span class="p">;</span>
|
|||
|
<span class="k">public</span> <span class="k">function</span> <span class="n">createSecret</span><span class="p">(</span><span class="nv">$bits</span> <span class="o">=</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">$requirecryptosecure</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="p">:</span> <span class="kt">string</span> <span class="p">;</span>
|
|||
|
<span class="k">public</span> <span class="k">function</span> <span class="n">getQRCodeImageAsDataUri</span><span class="p">(</span><span class="nv">$label</span><span class="p">,</span> <span class="nv">$secret</span><span class="p">,</span> <span class="nv">$size</span> <span class="o">=</span> <span class="mi">200</span><span class="p">)</span> <span class="p">:</span> <span class="kt">string</span> <span class="p">;</span>
|
|||
|
<span class="k">public</span> <span class="k">function</span> <span class="n">verifyCode</span><span class="p">(</span><span class="nv">$secret</span><span class="p">,</span> <span class="nv">$code</span><span class="p">,</span> <span class="nv">$discrepancy</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">$time</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">:</span> <span class="kt">bool</span> <span class="p">;</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="fournisseurs-de-codes-qr">Fournisseurs de codes QR</h3>
|
|||
|
|
|||
|
<p>Comme mentionné précédemment, cette bibliothèque est fournie avec cinq fournisseurs de codes QR “intégrés”. Ce chapitre abordera un peu le sujet mais la plupart d’entre eux devraient être auto-explicatifs. La classe <code class="language-plaintext highlighter-rouge">TwoFactorAuth</code> accepte un argument <code class="language-plaintext highlighter-rouge">$qrcodeprovider</code> qui vous permet de spécifier un fournisseur de QR-code intégré ou personnalisé. Les cinq fournisseurs intégrés font une simple requête HTTP pour récupérer une image en utilisant cURL et implémentent l’interface <a href="lib/Providers/Qr/IQRCodeProvider.php"><code class="language-plaintext highlighter-rouge">IQRCodeProvider</code></a> qui est tout ce que vous devez implémenter pour écrire votre propre fournisseur de code QR.</p>
|
|||
|
|
|||
|
<p>Le fournisseur par défaut est le <a href="lib/Providers/Qr/QRServerProvider.php"><code class="language-plaintext highlighter-rouge">QRServerProvider</code></a> qui utilise l’API <a href="http://goqr.me/api/doc/create-qr-code/">goqr.me</a> pour rendre les codes QR. Ensuite, nous avons le <a href="lib/Providers/Qr/ImageChartsQRCodeProvider.php"><code class="language-plaintext highlighter-rouge">ImageChartsQRCodeProvider</code></a> qui utilise le <a href="https://image-charts.com">remplacement de Google Image Charts par image-charts.com</a> pour afficher les codes QR et le <a href="lib/Providers/Qr/QRicketProvider.php"><code class="language-plaintext highlighter-rouge">QRicketProvider</code></a> qui utilise l’<a href="http://qrickit.com/qrickit_apps/qrickit_api.php">API QRickit</a>. Ces trois fournisseurs héritent tous d’une classe de base commune (abstraite) appelée <a href="lib/Providers/Qr/BaseHTTPQRCodeProvider.php"><code class="language-plaintext highlighter-rouge">BaseHTTPQRCodeProvider</code></a> car ils partagent tous les trois la même fonctionnalité : récupérer une image d’un tiers via HTTP. Enfin, nous avons <a href="lib/Providers/Qr/EndroidQrCodeProvider.php"><code class="language-plaintext highlighter-rouge">EndroidQrCodeProvider</code></a>, <a href="lib/Providers/Qr/EndroidQrCodeWithLogoProvider. php"><code class="language-plaintext highlighter-rouge">EndroidQrCodeWithLogoProvider</code></a> et <a href="lib/Providers/Qr/BaconQrCodeProvider.php"><code class="language-plaintext highlighter-rouge">BaconQrCodeProvider</code></a> qui nécessitent l’installation d’une dépendance optionnelle pour être utilisées (voir la section Exigences ci-dessus), mais qui génèrent les codes QR localement. Les cinq classes ont des constructeurs qui vous permettent de modifier certains paramètres et la plupart des arguments, sinon tous, devraient parler d’eux-mêmes. Si vous n’êtes pas sûr des valeurs prises en charge, cliquez sur les liens dans ce paragraphe pour obtenir de la documentation sur les API utilisées par ces classes.</p>
|
|||
|
|
|||
|
<p>Si vous n’aimez pas une des classes intégrées parce que vous ne voulez pas dépendre de ressources externes par exemple ou parce que vous êtes paranoïaque à l’idée d’envoyer le secret TOTP à ces tiers (ce qui leur est inutile puisqu’ils manquent <em>au moins un</em> autre facteur dans le [processus MFA] (http://en.wikipedia.org/wiki/Multi-factor_authentication)), n’hésitez pas à implémenter votre propre classe. L’interface <code class="language-plaintext highlighter-rouge">IQRCodeProvider</code> ne pourrait pas être plus simple. Tout ce que vous avez à faire est d’implémenter 2 méthodes :</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">getMimeType</span><span class="p">();</span>
|
|||
|
<span class="nf">getQRCodeImage</span><span class="p">(</span><span class="nv">$qrtext</span><span class="p">,</span> <span class="nv">$size</span><span class="p">);</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>La méthode <code class="language-plaintext highlighter-rouge">getMimeType()</code> doit retourner le <a href="http://en.wikipedia.org/wiki/Internet_media_type">MIME type</a> de l’image qui est retournée par notre implémentation de <code class="language-plaintext highlighter-rouge">getQRCodeImage()</code>. Dans cet exemple, il s’agit simplement de <code class="language-plaintext highlighter-rouge">image/png</code>. Deux arguments sont passés à la méthode <code class="language-plaintext highlighter-rouge">getQRCodeImage()</code> : <code class="language-plaintext highlighter-rouge">$qrtext</code> et <code class="language-plaintext highlighter-rouge">$size</code>. Ce dernier, <code class="language-plaintext highlighter-rouge">$size</code>, est simplement la largeur/hauteur en pixels de l’image souhaitée par l’appelant. Le premier, <code class="language-plaintext highlighter-rouge">$qrtext</code>, est le texte qui doit être encodé dans le code QR. Un exemple d’un tel texte serait :</p>
|
|||
|
|
|||
|
<p><code class="language-plaintext highlighter-rouge">otpauth://totp/LABEL:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=ISSUER</code></p>
|
|||
|
|
|||
|
<p>Il suffit de renvoyer le QR-code sous forme de données d’image binaires et le tour est joué. Toutes les parties du <code class="language-plaintext highlighter-rouge">$qrtext</code> ont été échappées pour vous (mais notez : vous <em>pourrez</em> avoir besoin d’échapper le <code class="language-plaintext highlighter-rouge">$qrtext</code> entier une fois de plus quand vous passerez les données à un autre serveur comme argument GET).</p>
|
|||
|
|
|||
|
<p>Voyons si nous pouvons utiliser <a href="http://phpqrcode.sourceforge.net/">PHP QR Code</a> pour implémenter notre propre fournisseur d’accès, personnalisé, sans tiers autorisé. Nous commençons par télécharger le <a href="https://github.com/t0k4rt/phpqrcode/blob/master/phpqrcode.php">fichier (unique) requis</a> et le placer dans le répertoire où se trouve <code class="language-plaintext highlighter-rouge">TwoFactorAuth.php</code>. Maintenant, implémentons le fournisseur : créez un autre fichier nommé <code class="language-plaintext highlighter-rouge">myprovider.php</code> dans le répertoire <code class="language-plaintext highlighter-rouge">Providers\Qr</code> et collez-y ce contenu :</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
|
|||
|
<span class="k">require_once</span> <span class="s1">'../../phpqrcode.php'</span><span class="p">;</span> <span class="c1">// Yeah, we're gonna need that</span>
|
|||
|
|
|||
|
<span class="kn">namespace</span> <span class="nn">RobThree\Auth\Providers\Qr</span><span class="p">;</span>
|
|||
|
|
|||
|
<span class="kd">class</span> <span class="nc">MyProvider</span> <span class="kd">implements</span> <span class="nc">IQRCodeProvider</span> <span class="p">{</span>
|
|||
|
<span class="k">public</span> <span class="k">function</span> <span class="n">getMimeType</span><span class="p">()</span> <span class="p">{</span>
|
|||
|
<span class="k">return</span> <span class="s1">'image/png'</span><span class="p">;</span> <span class="c1">// This provider only returns PNG's</span>
|
|||
|
<span class="p">}</span>
|
|||
|
|
|||
|
<span class="k">public</span> <span class="k">function</span> <span class="n">getQRCodeImage</span><span class="p">(</span><span class="nv">$qrtext</span><span class="p">,</span> <span class="nv">$size</span><span class="p">)</span> <span class="p">{</span>
|
|||
|
<span class="nb">ob_start</span><span class="p">();</span> <span class="c1">// 'Catch' QRCode's output</span>
|
|||
|
<span class="nc">QRCode</span><span class="o">::</span><span class="nf">png</span><span class="p">(</span><span class="nv">$qrtext</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="no">QR_ECLEVEL_L</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span> <span class="c1">// We ignore $size and set it to 3</span>
|
|||
|
<span class="c1">// since phpqrcode doesn't support</span>
|
|||
|
<span class="c1">// a size in pixels...</span>
|
|||
|
<span class="nv">$result</span> <span class="o">=</span> <span class="nb">ob_get_contents</span><span class="p">();</span> <span class="c1">// 'Catch' QRCode's output</span>
|
|||
|
<span class="nb">ob_end_clean</span><span class="p">();</span> <span class="c1">// Cleanup</span>
|
|||
|
<span class="k">return</span> <span class="nv">$result</span><span class="p">;</span> <span class="c1">// Return image</span>
|
|||
|
<span class="p">}</span>
|
|||
|
<span class="p">}</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>C’est fini. Nous avons terminé ! Nous avons implémenté notre propre fournisseur (avec l’aide de PHP QR Code). Plus de dépendances externes, plus de latences inutiles. Maintenant, nous allons <em>utiliser</em> notre fournisseur :</p>
|
|||
|
|
|||
|
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
|
|||
|
<span class="nv">$mp</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RobThree\Auth\Providers\Qr\MyProvider</span><span class="p">()</span> <span class="p">;</span>
|
|||
|
<span class="nv">$tfa</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RobThree\Auth\TwoFactorAuth</span><span class="p">(</span><span class="s1">'My Company'</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="s1">'sha1'</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">)</span> <span class="p">;</span>
|
|||
|
<span class="nv">$secret</span> <span class="o">=</span> <span class="nv">$tfa</span><span class="o">-></span><span class="nf">createSecret</span><span class="p">()</span> <span class="p">;</span>
|
|||
|
<span class="cp">?></span>
|
|||
|
<span class="nt"><p><img</span> <span class="na">src=</span><span class="s">"</span><span class="cp"><?php</span> <span class="k">echo</span> <span class="nv">$tfa</span><span class="o">-></span><span class="nf">getQRCodeImageAsDataUri</span><span class="p">(</span><span class="s1">'Bob Ross'</span><span class="p">,</span> <span class="nv">$secret</span><span class="p">)</span> <span class="p">;</span> <span class="cp">?></span><span class="s">"</span><span class="nt">></p></span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Voilà. On ne pouvait pas faire plus simple.</p>
|
|||
|
|
|||
|
<h3 id="fournisseurs-de-rng">Fournisseurs de RNG</h3>
|
|||
|
|
|||
|
<p>Cette bibliothèque est également fournie avec trois fournisseurs RNG “intégrés” (<a href="https://en.wikipedia.org/wiki/Random_number_generation">Random Number Generator</a>). Le fournisseur RNG génère un certain nombre d’octets aléatoires et renvoie ces octets sous forme de chaîne. Ces valeurs sont ensuite utilisées pour créer le secret. Par défaut (aucun fournisseur RNG spécifié), TwoFactorAuth essaiera de déterminer le meilleur fournisseur RNG disponible à utiliser. Par défaut, il essaiera d’utiliser le <a href="lib/Providers/Rng/CSRNGProvider.php"><code class="language-plaintext highlighter-rouge">CSRNGProvider</code></a> pour PHP7+ ou le <a href="lib/Providers/Rng/MCryptRNGProvider. php"><code class="language-plaintext highlighter-rouge">MCryptRNGProvider</code></a> ; si ce dernier n’est pas disponible/pris en charge pour une raison quelconque, il essaiera d’utiliser le <a href="lib/Providers/Rng/OpenSSLRNGProvider.php"><code class="language-plaintext highlighter-rouge">OpenSSLRNGProvider</code></a> et si celui-ci n’est pas non plus disponible/pris en charge, il essaiera d’utiliser le dernier fournisseur de RNG : <a href="lib/Providers/Rng/HashRNGProvider.php"><code class="language-plaintext highlighter-rouge">HashRNGProvider</code></a>. Chacun de ces fournisseurs utilise sa propre méthode pour générer une séquence aléatoire d’octets. Les trois premiers (<code class="language-plaintext highlighter-rouge">CSRNGProvider</code>, <code class="language-plaintext highlighter-rouge">OpenSSLRNGProvider</code> et <code class="language-plaintext highlighter-rouge">MCryptRNGProvider</code>) renvoient une séquence <a href="https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator">cryptographiquement sûre</a> d’octets aléatoires tandis que le <code class="language-plaintext highlighter-rouge">HashRNGProvider</code> renvoie une séquence <strong>non-cryptographiquement sûre</strong>.</p>
|
|||
|
|
|||
|
<p>Vous pouvez facilement implémenter votre propre <code class="language-plaintext highlighter-rouge">RNGProvider</code> en implémentant simplement l’interface <code class="language-plaintext highlighter-rouge">IRNGProvider</code>. Chacun des fournisseurs de RNG “intégrés” possède des arguments de construction qui vous permettent de modifier certains des paramètres à utiliser lors de la création des octets aléatoires, comme la source à utiliser (<code class="language-plaintext highlighter-rouge">MCryptRNGProvider</code>) ou l’algorithme de hachage (<code class="language-plaintext highlighter-rouge">HashRNGProvider</code>). Je vous encourage à jeter un coup d’oeil à certains des fournisseurs de RNG intégrés](lib/Providers/Rng) pour plus de détails et à l’interface <a href="lib/Providers/Rng/IRNGProvider.php"><code class="language-plaintext highlighter-rouge">IRNGProvider</code></a>.</p>
|
|||
|
|
|||
|
<h3 id="fournisseurs-de-temps">Fournisseurs de temps</h3>
|
|||
|
|
|||
|
<p>Un autre ensemble de fournisseurs dans cette bibliothèque sont les fournisseurs de temps ; cette bibliothèque fournit trois fournisseurs ‘intégrés’.</p>
|
|||
|
|
|||
|
<ol>
|
|||
|
<li>Le fournisseur de temps par défaut est le <a href="lib/Providers/Time/LocalMachineTimeProvider.php"><code class="language-plaintext highlighter-rouge">LocalMachineTimeProvider</code></a> ; ce fournisseur retourne simplement la sortie de <code class="language-plaintext highlighter-rouge">Time()</code> et est <em>hautement recommandé</em> comme fournisseur par défaut.</li>
|
|||
|
<li>Le <a href="lib/Providers/Time/HttpTimeProvider.php"><code class="language-plaintext highlighter-rouge">HttpTimeProvider</code></a> exécute une requête <code class="language-plaintext highlighter-rouge">HEAD</code> contre un serveur web donné (par défaut : google.com) et essaie d’extraire l’en-tête <code class="language-plaintext highlighter-rouge">Date:</code>-HTTP et retourne sa date. D’autres url/domaines peuvent être utilisés en spécifiant l’url dans le constructeur.</li>
|
|||
|
<li>Le dernier fournisseur de temps est le <a href="lib/Providers/Time/NTPTimeProvider.php"><code class="language-plaintext highlighter-rouge">NTPTimeProvider</code></a> qui fait une requête NTP à un serveur NTP spécifié.</li>
|
|||
|
</ol>
|
|||
|
|
|||
|
<p>Vous pouvez facilement implémenter votre propre <code class="language-plaintext highlighter-rouge">TimeProvider</code> en implémentant simplement l’interface <code class="language-plaintext highlighter-rouge">ITimeProvider</code>.</p>
|
|||
|
|
|||
|
<p>Pour ce qui est de la raison pour laquelle ces fournisseurs de temps sont implémentés, ils permettent à la bibliothèque TwoFactorAuth de s’assurer que l’heure de l’hôte est correcte (ou plutôt : avec une marge). Vous pouvez utiliser la méthode <code class="language-plaintext highlighter-rouge">ensureCorrectTime()</code> pour vous assurer que l’heure de l’hôte est correcte. Par défaut, cette méthode va comparer l’heure de l’hôte (retournée par l’appel de <code class="language-plaintext highlighter-rouge">time()</code> sur le <code class="language-plaintext highlighter-rouge">LocalMachineTimeProvider</code>) avec les <code class="language-plaintext highlighter-rouge">NTPTimeProvider</code> et <code class="language-plaintext highlighter-rouge">HttpTimeProvider</code> par défaut. Vous pouvez passer un tableau de <code class="language-plaintext highlighter-rouge">ITimeProvider</code>s pour changer cela et spécifier la <code class="language-plaintext highlighter-rouge">l'indulgence</code> (deuxième argument) autorisée (par défaut : 5 secondes). La méthode sera lancée si le fournisseur de temps de TwoFactorAuth (qui peut être n’importe quel <code class="language-plaintext highlighter-rouge">ITimeProvider</code>, voir constructeur) diffère de plus de la quantité donnée de secondes de n’importe lequel des <code class="language-plaintext highlighter-rouge">ITimeProviders</code> donnés. Nous vous conseillons d’appeler cette méthode avec parcimonie lorsque vous vous appuyez sur des tiers (ce que font le <code class="language-plaintext highlighter-rouge">HttpTimeProvider</code> et le <code class="language-plaintext highlighter-rouge">NTPTimeProvider</code>) ou, si vous avez besoin de vous assurer que l’heure est correcte sur une base (très) régulière, d’implémenter un <code class="language-plaintext highlighter-rouge">ITimeProvider</code> qui soit plus efficace que les <code class="language-plaintext highlighter-rouge">intégrés' (comme utiliser un signal GPS). La méthode </code>ensureCorrectTime()` est surtout utilisée pour s’assurer que le serveur est configuré correctement.</p>
|
|||
|
|
|||
|
<h3 id="intégrations">Intégrations</h3>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>CakePHP 3](https://github.com/andrej-griniuk/cakephp-two-factor-auth)</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h3 id="licence">Licence</h3>
|
|||
|
|
|||
|
<p>Sous licence MIT. Voir <a href="https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/LICENSE">LICENSE</a> pour plus de détails.</p>
|
|||
|
|
|||
|
<p>Logo / icône](http://www.iconmay.com/Simple/Travel_and_Tourism_Part_2/luggage_lock_safety_baggage_keys_cylinder_lock_hotel_travel_tourism_luggage_lock_icon_465) sous CC0 1.0 Universal (CC0 1.0) Dédicace du domaine public (<a href="http://riii.nl/tm7ap">Page archivée</a>)</p>
|
|||
|
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2021-03-17T00:00:00+01:00"><!-- start custom article footer snippet -->
|
|||
|
|
|||
|
<!-- end custom article footer snippet -->
|
|||
|
<!--
|
|||
|
<div align="right"><a type="application/rss+xml" href="/feed.xml" title="S'abonner"><i class="fa fa-rss fa-2x"></i></a>
|
|||
|
|
|||
|
 </div>
|
|||
|
-->
|
|||
|
</footer>
|
|||
|
<div class="article__section-navigator clearfix"><div class="previous"><span>PRÉCÉDENT</span><a href="/2021/03/08/PC1-ArchLinux-XFCE-ASUS_H110M_A-nvme_1To.html">PC1 Ordinateur Bureau ArchLinux xfce - NVME 1To</a></div><div class="next"><span>SUIVANT</span><a href="/2021/03/24/A20-Olinuxino-Domoticz-logiciel-de-gestion-et-de-controle-domotique.html">A20-Olinuxino - Domoticz logiciel de gestion et de contrôle domotique</a></div></div></div>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
$(function() {
|
|||
|
var $this ,$scroll;
|
|||
|
var $articleContent = $('.js-article-content');
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
var scroll = hasSidebar ? '.js-page-main' : 'html, body';
|
|||
|
$scroll = $(scroll);
|
|||
|
|
|||
|
$articleContent.find('.highlight').each(function() {
|
|||
|
$this = $(this);
|
|||
|
$this.attr('data-lang', $this.find('code').attr('data-lang'));
|
|||
|
});
|
|||
|
$articleContent.find('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').each(function() {
|
|||
|
$this = $(this);
|
|||
|
$this.append($('<a class="anchor d-print-none" aria-hidden="true"></a>').html('<i class="fas fa-anchor"></i>'));
|
|||
|
});
|
|||
|
$articleContent.on('click', '.anchor', function() {
|
|||
|
$scroll.scrollToAnchor('#' + $(this).parent().attr('id'), 400);
|
|||
|
});
|
|||
|
});
|
|||
|
});
|
|||
|
})();
|
|||
|
</script>
|
|||
|
|
|||
|
</div><section class="page__comments d-print-none"></section></article><!-- start custom main bottom snippet -->
|
|||
|
<!-- end custom main bottom snippet -->
|
|||
|
</div>
|
|||
|
</div></div></div></div>
|
|||
|
</div><script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $body = $('body'), $window = $(window);
|
|||
|
var $pageRoot = $('.js-page-root'), $pageMain = $('.js-page-main');
|
|||
|
var activeCount = 0;
|
|||
|
function modal(options) {
|
|||
|
var $root = this, visible, onChange, hideWhenWindowScroll = false;
|
|||
|
var scrollTop;
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
visible = _options.initialVisible === undefined ? false : show;
|
|||
|
onChange = _options.onChange;
|
|||
|
hideWhenWindowScroll = _options.hideWhenWindowScroll;
|
|||
|
}
|
|||
|
function init() {
|
|||
|
setState(visible);
|
|||
|
}
|
|||
|
function setState(isShow) {
|
|||
|
if (isShow === visible) {
|
|||
|
return;
|
|||
|
}
|
|||
|
visible = isShow;
|
|||
|
if (visible) {
|
|||
|
activeCount++;
|
|||
|
scrollTop = $(window).scrollTop() || $pageMain.scrollTop();
|
|||
|
$root.addClass('modal--show');
|
|||
|
$pageMain.scrollTop(scrollTop);
|
|||
|
activeCount === 1 && ($pageRoot.addClass('show-modal'), $body.addClass('of-hidden'));
|
|||
|
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.on('scroll', hide);
|
|||
|
$window.on('keyup', handleKeyup);
|
|||
|
} else {
|
|||
|
activeCount > 0 && activeCount--;
|
|||
|
$root.removeClass('modal--show');
|
|||
|
$window.scrollTop(scrollTop);
|
|||
|
activeCount === 0 && ($pageRoot.removeClass('show-modal'), $body.removeClass('of-hidden'));
|
|||
|
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.off('scroll', hide);
|
|||
|
$window.off('keyup', handleKeyup);
|
|||
|
}
|
|||
|
onChange && onChange(visible);
|
|||
|
}
|
|||
|
function show() {
|
|||
|
setState(true);
|
|||
|
}
|
|||
|
function hide() {
|
|||
|
setState(false);
|
|||
|
}
|
|||
|
function handleKeyup(e) {
|
|||
|
// Char Code: 27 ESC
|
|||
|
if (e.which === 27) {
|
|||
|
hide();
|
|||
|
}
|
|||
|
}
|
|||
|
setOptions(options);
|
|||
|
init();
|
|||
|
return {
|
|||
|
show: show,
|
|||
|
hide: hide,
|
|||
|
$el: $root
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.modal = modal;
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><div class="modal modal--overflow page__search-modal d-print-none js-page-search-modal"><script>
|
|||
|
(function () {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
// search panel
|
|||
|
var search = (window.search || (window.search = {}));
|
|||
|
var useDefaultSearchBox = window.useDefaultSearchBox === undefined ?
|
|||
|
true : window.useDefaultSearchBox ;
|
|||
|
|
|||
|
var $searchModal = $('.js-page-search-modal');
|
|||
|
var $searchToggle = $('.js-search-toggle');
|
|||
|
var searchModal = $searchModal.modal({ onChange: handleModalChange, hideWhenWindowScroll: true });
|
|||
|
var modalVisible = false;
|
|||
|
search.searchModal = searchModal;
|
|||
|
|
|||
|
var $searchBox = null;
|
|||
|
var $searchInput = null;
|
|||
|
var $searchClear = null;
|
|||
|
|
|||
|
function getModalVisible() {
|
|||
|
return modalVisible;
|
|||
|
}
|
|||
|
search.getModalVisible = getModalVisible;
|
|||
|
|
|||
|
function handleModalChange(visible) {
|
|||
|
modalVisible = visible;
|
|||
|
if (visible) {
|
|||
|
search.onShow && search.onShow();
|
|||
|
useDefaultSearchBox && $searchInput[0] && $searchInput[0].focus();
|
|||
|
} else {
|
|||
|
search.onShow && search.onHide();
|
|||
|
useDefaultSearchBox && $searchInput[0] && $searchInput[0].blur();
|
|||
|
setTimeout(function() {
|
|||
|
useDefaultSearchBox && ($searchInput.val(''), $searchBox.removeClass('not-empty'));
|
|||
|
search.clear && search.clear();
|
|||
|
window.pageAsideAffix && window.pageAsideAffix.refresh();
|
|||
|
}, 400);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$searchToggle.on('click', function() {
|
|||
|
modalVisible ? searchModal.hide() : searchModal.show();
|
|||
|
});
|
|||
|
// Char Code: 83 S, 191 /
|
|||
|
$(window).on('keyup', function(e) {
|
|||
|
if (!modalVisible && !window.isFormElement(e.target || e.srcElement) && (e.which === 83 || e.which === 191)) {
|
|||
|
modalVisible || searchModal.show();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
if (useDefaultSearchBox) {
|
|||
|
$searchBox = $('.js-search-box');
|
|||
|
$searchInput = $searchBox.children('input');
|
|||
|
$searchClear = $searchBox.children('.js-icon-clear');
|
|||
|
search.getSearchInput = function() {
|
|||
|
return $searchInput.get(0);
|
|||
|
};
|
|||
|
search.getVal = function() {
|
|||
|
return $searchInput.val();
|
|||
|
};
|
|||
|
search.setVal = function(val) {
|
|||
|
$searchInput.val(val);
|
|||
|
};
|
|||
|
|
|||
|
$searchInput.on('focus', function() {
|
|||
|
$(this).addClass('focus');
|
|||
|
});
|
|||
|
$searchInput.on('blur', function() {
|
|||
|
$(this).removeClass('focus');
|
|||
|
});
|
|||
|
$searchInput.on('input', window.throttle(function() {
|
|||
|
var val = $(this).val();
|
|||
|
if (val === '' || typeof val !== 'string') {
|
|||
|
search.clear && search.clear();
|
|||
|
} else {
|
|||
|
$searchBox.addClass('not-empty');
|
|||
|
search.onInputNotEmpty && search.onInputNotEmpty(val);
|
|||
|
}
|
|||
|
}, 400));
|
|||
|
$searchClear.on('click', function() {
|
|||
|
$searchInput.val(''); $searchBox.removeClass('not-empty');
|
|||
|
search.clear && search.clear();
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><div class="search search--dark">
|
|||
|
<div class="main">
|
|||
|
<div class="search__header">Recherche</div>
|
|||
|
<div class="search-bar">
|
|||
|
<div class="search-box js-search-box">
|
|||
|
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
|||
|
<input id="search-input" type="text" />
|
|||
|
<div class="search-box__icon-clear js-icon-clear">
|
|||
|
<a><i class="fas fa-times"></i></a>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<button class="button button--theme-dark button--pill search__cancel js-search-toggle">
|
|||
|
Annuler</button>
|
|||
|
</div>
|
|||
|
<div id="results-container" class="search-result js-search-result"></div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<!-- Script pointing to search-script.js -->
|
|||
|
<script>/*!
|
|||
|
* Simple-Jekyll-Search
|
|||
|
* Copyright 2015-2020, Christian Fei
|
|||
|
* Licensed under the MIT License.
|
|||
|
*/
|
|||
|
|
|||
|
(function(){
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Templater_7 = {
|
|||
|
compile: compile,
|
|||
|
setOptions: setOptions
|
|||
|
}
|
|||
|
|
|||
|
const options = {}
|
|||
|
options.pattern = /\{(.*?)\}/g
|
|||
|
options.template = ''
|
|||
|
options.middleware = function () {}
|
|||
|
|
|||
|
function setOptions (_options) {
|
|||
|
options.pattern = _options.pattern || options.pattern
|
|||
|
options.template = _options.template || options.template
|
|||
|
if (typeof _options.middleware === 'function') {
|
|||
|
options.middleware = _options.middleware
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compile (data) {
|
|||
|
return options.template.replace(options.pattern, function (match, prop) {
|
|||
|
const value = options.middleware(prop, data[prop], options.template)
|
|||
|
if (typeof value !== 'undefined') {
|
|||
|
return value
|
|||
|
}
|
|||
|
return data[prop] || match
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
function fuzzysearch (needle, haystack) {
|
|||
|
var tlen = haystack.length;
|
|||
|
var qlen = needle.length;
|
|||
|
if (qlen > tlen) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (qlen === tlen) {
|
|||
|
return needle === haystack;
|
|||
|
}
|
|||
|
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|||
|
var nch = needle.charCodeAt(i);
|
|||
|
while (j < tlen) {
|
|||
|
if (haystack.charCodeAt(j++) === nch) {
|
|||
|
continue outer;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var _$fuzzysearch_1 = fuzzysearch;
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
|
|||
|
|
|||
|
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
|
|||
|
|
|||
|
function FuzzySearchStrategy () {
|
|||
|
this.matches = function (string, crit) {
|
|||
|
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
|
|||
|
|
|||
|
function LiteralSearchStrategy () {
|
|||
|
this.matches = function (str, crit) {
|
|||
|
if (!str) return false
|
|||
|
|
|||
|
str = str.trim().toLowerCase()
|
|||
|
crit = crit.trim().toLowerCase()
|
|||
|
|
|||
|
return crit.split(' ').filter(function (word) {
|
|||
|
return str.indexOf(word) >= 0
|
|||
|
}).length === crit.split(' ').length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Repository_4 = {
|
|||
|
put: put,
|
|||
|
clear: clear,
|
|||
|
search: search,
|
|||
|
setOptions: __setOptions_4
|
|||
|
}
|
|||
|
|
|||
|
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
|
|||
|
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
|
|||
|
|
|||
|
function NoSort () {
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
const data = []
|
|||
|
let opt = {}
|
|||
|
|
|||
|
opt.fuzzy = false
|
|||
|
opt.limit = 10
|
|||
|
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = NoSort
|
|||
|
opt.exclude = []
|
|||
|
|
|||
|
function put (data) {
|
|||
|
if (isObject(data)) {
|
|||
|
return addObject(data)
|
|||
|
}
|
|||
|
if (isArray(data)) {
|
|||
|
return addArray(data)
|
|||
|
}
|
|||
|
return undefined
|
|||
|
}
|
|||
|
function clear () {
|
|||
|
data.length = 0
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function isObject (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
|
|||
|
}
|
|||
|
|
|||
|
function isArray (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function addObject (_data) {
|
|||
|
data.push(_data)
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function addArray (_data) {
|
|||
|
const added = []
|
|||
|
clear()
|
|||
|
for (let i = 0, len = _data.length; i < len; i++) {
|
|||
|
if (isObject(_data[i])) {
|
|||
|
added.push(addObject(_data[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
return added
|
|||
|
}
|
|||
|
|
|||
|
function search (crit) {
|
|||
|
if (!crit) {
|
|||
|
return []
|
|||
|
}
|
|||
|
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
|
|||
|
}
|
|||
|
|
|||
|
function __setOptions_4 (_opt) {
|
|||
|
opt = _opt || {}
|
|||
|
|
|||
|
opt.fuzzy = _opt.fuzzy || false
|
|||
|
opt.limit = _opt.limit || 10
|
|||
|
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = _opt.sort || NoSort
|
|||
|
opt.exclude = _opt.exclude || []
|
|||
|
}
|
|||
|
|
|||
|
function findMatches (data, crit, strategy, opt) {
|
|||
|
const matches = []
|
|||
|
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
|
|||
|
const match = findMatchesInObject(data[i], crit, strategy, opt)
|
|||
|
if (match) {
|
|||
|
matches.push(match)
|
|||
|
}
|
|||
|
}
|
|||
|
return matches
|
|||
|
}
|
|||
|
|
|||
|
function findMatchesInObject (obj, crit, strategy, opt) {
|
|||
|
for (const key in obj) {
|
|||
|
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
|
|||
|
return obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isExcluded (term, excludedTerms) {
|
|||
|
for (let i = 0, len = excludedTerms.length; i < len; i++) {
|
|||
|
const excludedTerm = excludedTerms[i]
|
|||
|
if (new RegExp(excludedTerm).test(term)) {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
/* globals ActiveXObject:false */
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$JSONLoader_2 = {
|
|||
|
load: load
|
|||
|
}
|
|||
|
|
|||
|
function load (location, callback) {
|
|||
|
const xhr = getXHR()
|
|||
|
xhr.open('GET', location, true)
|
|||
|
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
|
|||
|
xhr.send()
|
|||
|
}
|
|||
|
|
|||
|
function createStateChangeListener (xhr, callback) {
|
|||
|
return function () {
|
|||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|||
|
try {
|
|||
|
callback(null, JSON.parse(xhr.responseText))
|
|||
|
} catch (err) {
|
|||
|
callback(err, null)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getXHR () {
|
|||
|
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$OptionsValidator_3 = function OptionsValidator (params) {
|
|||
|
if (!validateParams(params)) {
|
|||
|
throw new Error('-- OptionsValidator: required options missing')
|
|||
|
}
|
|||
|
|
|||
|
if (!(this instanceof OptionsValidator)) {
|
|||
|
return new OptionsValidator(params)
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = params.required
|
|||
|
|
|||
|
this.getRequiredOptions = function () {
|
|||
|
return requiredOptions
|
|||
|
}
|
|||
|
|
|||
|
this.validate = function (parameters) {
|
|||
|
const errors = []
|
|||
|
requiredOptions.forEach(function (requiredOptionName) {
|
|||
|
if (typeof parameters[requiredOptionName] === 'undefined') {
|
|||
|
errors.push(requiredOptionName)
|
|||
|
}
|
|||
|
})
|
|||
|
return errors
|
|||
|
}
|
|||
|
|
|||
|
function validateParams (params) {
|
|||
|
if (!params) {
|
|||
|
return false
|
|||
|
}
|
|||
|
return typeof params.required !== 'undefined' && params.required instanceof Array
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$utils_9 = {
|
|||
|
merge: merge,
|
|||
|
isJSON: isJSON
|
|||
|
}
|
|||
|
|
|||
|
function merge (defaultParams, mergeParams) {
|
|||
|
const mergedOptions = {}
|
|||
|
for (const option in defaultParams) {
|
|||
|
mergedOptions[option] = defaultParams[option]
|
|||
|
if (typeof mergeParams[option] !== 'undefined') {
|
|||
|
mergedOptions[option] = mergeParams[option]
|
|||
|
}
|
|||
|
}
|
|||
|
return mergedOptions
|
|||
|
}
|
|||
|
|
|||
|
function isJSON (json) {
|
|||
|
try {
|
|||
|
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
|
|||
|
return true
|
|||
|
}
|
|||
|
return false
|
|||
|
} catch (err) {
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var _$src_8 = {};
|
|||
|
(function (window) {
|
|||
|
'use strict'
|
|||
|
|
|||
|
let options = {
|
|||
|
searchInput: null,
|
|||
|
resultsContainer: null,
|
|||
|
json: [],
|
|||
|
success: Function.prototype,
|
|||
|
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
|
|||
|
templateMiddleware: Function.prototype,
|
|||
|
sortMiddleware: function () {
|
|||
|
return 0
|
|||
|
},
|
|||
|
noResultsText: 'No results found',
|
|||
|
limit: 10,
|
|||
|
fuzzy: false,
|
|||
|
debounceTime: null,
|
|||
|
exclude: []
|
|||
|
}
|
|||
|
|
|||
|
let debounceTimerHandle
|
|||
|
const debounce = function (func, delayMillis) {
|
|||
|
if (delayMillis) {
|
|||
|
clearTimeout(debounceTimerHandle)
|
|||
|
debounceTimerHandle = setTimeout(func, delayMillis)
|
|||
|
} else {
|
|||
|
func.call()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
|
|||
|
|
|||
|
/* removed: const _$Templater_7 = require('./Templater') */;
|
|||
|
/* removed: const _$Repository_4 = require('./Repository') */;
|
|||
|
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
|
|||
|
const optionsValidator = _$OptionsValidator_3({
|
|||
|
required: requiredOptions
|
|||
|
})
|
|||
|
/* removed: const _$utils_9 = require('./utils') */;
|
|||
|
|
|||
|
window.SimpleJekyllSearch = function (_options) {
|
|||
|
const errors = optionsValidator.validate(_options)
|
|||
|
if (errors.length > 0) {
|
|||
|
throwError('You must specify the following required options: ' + requiredOptions)
|
|||
|
}
|
|||
|
|
|||
|
options = _$utils_9.merge(options, _options)
|
|||
|
|
|||
|
_$Templater_7.setOptions({
|
|||
|
template: options.searchResultTemplate,
|
|||
|
middleware: options.templateMiddleware
|
|||
|
})
|
|||
|
|
|||
|
_$Repository_4.setOptions({
|
|||
|
fuzzy: options.fuzzy,
|
|||
|
limit: options.limit,
|
|||
|
sort: options.sortMiddleware,
|
|||
|
exclude: options.exclude
|
|||
|
})
|
|||
|
|
|||
|
if (_$utils_9.isJSON(options.json)) {
|
|||
|
initWithJSON(options.json)
|
|||
|
} else {
|
|||
|
initWithURL(options.json)
|
|||
|
}
|
|||
|
|
|||
|
const rv = {
|
|||
|
search: search
|
|||
|
}
|
|||
|
|
|||
|
typeof options.success === 'function' && options.success.call(rv)
|
|||
|
return rv
|
|||
|
}
|
|||
|
|
|||
|
function initWithJSON (json) {
|
|||
|
_$Repository_4.put(json)
|
|||
|
registerInput()
|
|||
|
}
|
|||
|
|
|||
|
function initWithURL (url) {
|
|||
|
_$JSONLoader_2.load(url, function (err, json) {
|
|||
|
if (err) {
|
|||
|
throwError('failed to get JSON (' + url + ')')
|
|||
|
}
|
|||
|
initWithJSON(json)
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function emptyResultsContainer () {
|
|||
|
options.resultsContainer.innerHTML = ''
|
|||
|
}
|
|||
|
|
|||
|
function appendToResultsContainer (text) {
|
|||
|
options.resultsContainer.innerHTML += text
|
|||
|
}
|
|||
|
|
|||
|
function registerInput () {
|
|||
|
options.searchInput.addEventListener('input', function (e) {
|
|||
|
if (isWhitelistedKey(e.which)) {
|
|||
|
emptyResultsContainer()
|
|||
|
debounce(function () { search(e.target.value) }, options.debounceTime)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function search (query) {
|
|||
|
if (isValidQuery(query)) {
|
|||
|
emptyResultsContainer()
|
|||
|
render(_$Repository_4.search(query), query)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function render (results, query) {
|
|||
|
const len = results.length
|
|||
|
if (len === 0) {
|
|||
|
return appendToResultsContainer(options.noResultsText)
|
|||
|
}
|
|||
|
for (let i = 0; i < len; i++) {
|
|||
|
results[i].query = query
|
|||
|
appendToResultsContainer(_$Templater_7.compile(results[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isValidQuery (query) {
|
|||
|
return query && query.length > 0
|
|||
|
}
|
|||
|
|
|||
|
function isWhitelistedKey (key) {
|
|||
|
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
|
|||
|
}
|
|||
|
|
|||
|
function throwError (message) {
|
|||
|
throw new Error('SimpleJekyllSearch --- ' + message)
|
|||
|
}
|
|||
|
})(window)
|
|||
|
|
|||
|
}());
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Configuration -->
|
|||
|
<script>
|
|||
|
SimpleJekyllSearch({
|
|||
|
searchInput: document.getElementById('search-input'),
|
|||
|
resultsContainer: document.getElementById('results-container'),
|
|||
|
noResultsText: '<p>Aucun résultat!</p>',
|
|||
|
json: '/search.json',
|
|||
|
searchResultTemplate: '<li><a href="{url}">{date} {title}</a> (Création {create})</li>'
|
|||
|
})
|
|||
|
</script>
|
|||
|
|
|||
|
</div></div>
|
|||
|
|
|||
|
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function scrollToAnchor(anchor, duration, callback) {
|
|||
|
var $root = this;
|
|||
|
$root.animate({ scrollTop: $(anchor).position().top }, duration, function() {
|
|||
|
window.history.replaceState(null, '', window.location.href.split('#')[0] + anchor);
|
|||
|
callback && callback();
|
|||
|
});
|
|||
|
}
|
|||
|
$.fn.scrollToAnchor = scrollToAnchor;
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function affix(options) {
|
|||
|
var $root = this, $window = $(window), $scrollTarget, $scroll,
|
|||
|
offsetBottom = 0, scrollTarget = window, scroll = window.document, disabled = false, isOverallScroller = true,
|
|||
|
rootTop, rootLeft, rootHeight, scrollBottom, rootBottomTop,
|
|||
|
hasInit = false, curState;
|
|||
|
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
_options.offsetBottom && (offsetBottom = _options.offsetBottom);
|
|||
|
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
|||
|
_options.scroll && (scroll = _options.scroll);
|
|||
|
_options.disabled !== undefined && (disabled = _options.disabled);
|
|||
|
$scrollTarget = $(scrollTarget);
|
|||
|
isOverallScroller = window.isOverallScroller($scrollTarget[0]);
|
|||
|
$scroll = $(scroll);
|
|||
|
}
|
|||
|
function preCalc() {
|
|||
|
top();
|
|||
|
rootHeight = $root.outerHeight();
|
|||
|
rootTop = $root.offset().top + (isOverallScroller ? 0 : $scrollTarget.scrollTop());
|
|||
|
rootLeft = $root.offset().left;
|
|||
|
}
|
|||
|
function calc(needPreCalc) {
|
|||
|
needPreCalc && preCalc();
|
|||
|
scrollBottom = $scroll.outerHeight() - offsetBottom - rootHeight;
|
|||
|
rootBottomTop = scrollBottom - rootTop;
|
|||
|
}
|
|||
|
function top() {
|
|||
|
if (curState !== 'top') {
|
|||
|
$root.removeClass('fixed').css({
|
|||
|
left: 0,
|
|||
|
top: 0
|
|||
|
});
|
|||
|
curState = 'top';
|
|||
|
}
|
|||
|
}
|
|||
|
function fixed() {
|
|||
|
if (curState !== 'fixed') {
|
|||
|
$root.addClass('fixed').css({
|
|||
|
left: rootLeft + 'px',
|
|||
|
top: 0
|
|||
|
});
|
|||
|
curState = 'fixed';
|
|||
|
}
|
|||
|
}
|
|||
|
function bottom() {
|
|||
|
if (curState !== 'bottom') {
|
|||
|
$root.removeClass('fixed').css({
|
|||
|
left: 0,
|
|||
|
top: rootBottomTop + 'px'
|
|||
|
});
|
|||
|
curState = 'bottom';
|
|||
|
}
|
|||
|
}
|
|||
|
function setState() {
|
|||
|
var scrollTop = $scrollTarget.scrollTop();
|
|||
|
if (scrollTop >= rootTop && scrollTop <= scrollBottom) {
|
|||
|
fixed();
|
|||
|
} else if (scrollTop < rootTop) {
|
|||
|
top();
|
|||
|
} else {
|
|||
|
bottom();
|
|||
|
}
|
|||
|
}
|
|||
|
function init() {
|
|||
|
if(!hasInit) {
|
|||
|
var interval, timeout;
|
|||
|
calc(true); setState();
|
|||
|
// run calc every 100 millisecond
|
|||
|
interval = setInterval(function() {
|
|||
|
calc();
|
|||
|
}, 100);
|
|||
|
timeout = setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
}, 45000);
|
|||
|
window.pageLoad.then(function() {
|
|||
|
setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
clearTimeout(timeout);
|
|||
|
}, 3000);
|
|||
|
});
|
|||
|
$scrollTarget.on('scroll', function() {
|
|||
|
disabled || setState();
|
|||
|
});
|
|||
|
$window.on('resize', function() {
|
|||
|
disabled || (calc(true), setState());
|
|||
|
});
|
|||
|
hasInit = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
setOptions(options);
|
|||
|
if (!disabled) {
|
|||
|
init();
|
|||
|
}
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
init();
|
|||
|
}, 200));
|
|||
|
return {
|
|||
|
setOptions: setOptions,
|
|||
|
refresh: function() {
|
|||
|
calc(true, { animation: false }); setState();
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.affix = affix;
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function toc(options) {
|
|||
|
var $root = this, $window = $(window), $scrollTarget, $scroller, $tocUl = $('<ul class="toc toc--ellipsis"></ul>'), $tocLi, $headings, $activeLast, $activeCur,
|
|||
|
selectors = 'h1,h2,h3', container = 'body', scrollTarget = window, scroller = 'html, body', disabled = false,
|
|||
|
headingsPos, scrolling = false, hasRendered = false, hasInit = false;
|
|||
|
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
_options.selectors && (selectors = _options.selectors);
|
|||
|
_options.container && (container = _options.container);
|
|||
|
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
|||
|
_options.scroller && (scroller = _options.scroller);
|
|||
|
_options.disabled !== undefined && (disabled = _options.disabled);
|
|||
|
$headings = $(container).find(selectors).filter('[id]');
|
|||
|
$scrollTarget = $(scrollTarget);
|
|||
|
$scroller = $(scroller);
|
|||
|
}
|
|||
|
function calc() {
|
|||
|
headingsPos = [];
|
|||
|
$headings.each(function() {
|
|||
|
headingsPos.push(Math.floor($(this).position().top));
|
|||
|
});
|
|||
|
}
|
|||
|
function setState(element, disabled) {
|
|||
|
var scrollTop = $scrollTarget.scrollTop(), i;
|
|||
|
if (disabled || !headingsPos || headingsPos.length < 1) { return; }
|
|||
|
if (element) {
|
|||
|
$activeCur = element;
|
|||
|
} else {
|
|||
|
for (i = 0; i < headingsPos.length; i++) {
|
|||
|
if (scrollTop >= headingsPos[i]) {
|
|||
|
$activeCur = $tocLi.eq(i);
|
|||
|
} else {
|
|||
|
$activeCur || ($activeCur = $tocLi.eq(i));
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
$activeLast && $activeLast.removeClass('active');
|
|||
|
($activeLast = $activeCur).addClass('active');
|
|||
|
}
|
|||
|
function render() {
|
|||
|
if(!hasRendered) {
|
|||
|
$root.append($tocUl);
|
|||
|
$headings.each(function() {
|
|||
|
var $this = $(this);
|
|||
|
$tocUl.append($('<li></li>').addClass('toc-' + $this.prop('tagName').toLowerCase())
|
|||
|
.append($('<a></a>').text($this.text()).attr('href', '#' + $this.prop('id'))));
|
|||
|
});
|
|||
|
$tocLi = $tocUl.children('li');
|
|||
|
$tocUl.on('click', 'a', function(e) {
|
|||
|
e.preventDefault();
|
|||
|
var $this = $(this);
|
|||
|
scrolling = true;
|
|||
|
setState($this.parent());
|
|||
|
$scroller.scrollToAnchor($this.attr('href'), 400, function() {
|
|||
|
scrolling = false;
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
hasRendered = true;
|
|||
|
}
|
|||
|
function init() {
|
|||
|
var interval, timeout;
|
|||
|
if(!hasInit) {
|
|||
|
render(); calc(); setState(null, scrolling);
|
|||
|
// run calc every 100 millisecond
|
|||
|
interval = setInterval(function() {
|
|||
|
calc();
|
|||
|
}, 100);
|
|||
|
timeout = setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
}, 45000);
|
|||
|
window.pageLoad.then(function() {
|
|||
|
setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
clearTimeout(timeout);
|
|||
|
}, 3000);
|
|||
|
});
|
|||
|
$scrollTarget.on('scroll', function() {
|
|||
|
disabled || setState(null, scrolling);
|
|||
|
});
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
if (!disabled) {
|
|||
|
render(); calc(); setState(null, scrolling);
|
|||
|
}
|
|||
|
}, 100));
|
|||
|
}
|
|||
|
hasInit = true;
|
|||
|
}
|
|||
|
|
|||
|
setOptions(options);
|
|||
|
if (!disabled) {
|
|||
|
init();
|
|||
|
}
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
init();
|
|||
|
}, 200));
|
|||
|
return {
|
|||
|
setOptions: setOptions
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.toc = toc;
|
|||
|
});
|
|||
|
})();
|
|||
|
/*(function () {
|
|||
|
|
|||
|
})();*/
|
|||
|
</script><script>
|
|||
|
/* toc must before affix, since affix need to konw toc' height. */(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
var TOC_SELECTOR = window.TEXT_VARIABLES.site.toc.selectors;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $window = $(window);
|
|||
|
var $articleContent = $('.js-article-content');
|
|||
|
var $tocRoot = $('.js-toc-root'), $col2 = $('.js-col-aside');
|
|||
|
var toc;
|
|||
|
var tocDisabled = false;
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
var hasToc = $articleContent.find(TOC_SELECTOR).length > 0;
|
|||
|
|
|||
|
function disabled() {
|
|||
|
return $col2.css('display') === 'none' || !hasToc;
|
|||
|
}
|
|||
|
|
|||
|
tocDisabled = disabled();
|
|||
|
|
|||
|
toc = $tocRoot.toc({
|
|||
|
selectors: TOC_SELECTOR,
|
|||
|
container: $articleContent,
|
|||
|
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroller: hasSidebar ? '.js-page-main' : null,
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
tocDisabled = disabled();
|
|||
|
toc && toc.setOptions({
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
}, 100));
|
|||
|
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $window = $(window), $pageFooter = $('.js-page-footer');
|
|||
|
var $pageAside = $('.js-page-aside');
|
|||
|
var affix;
|
|||
|
var tocDisabled = false;
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
|
|||
|
affix = $pageAside.affix({
|
|||
|
offsetBottom: $pageFooter.outerHeight(),
|
|||
|
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroller: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroll: hasSidebar ? $('.js-page-main').children() : null,
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
affix && affix.setOptions({
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
}, 100));
|
|||
|
|
|||
|
window.pageAsideAffix = affix;
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><!---->
|
|||
|
|
|||
|
</div>
|
|||
|
<script>(function () {
|
|||
|
var $root = document.getElementsByClassName('root')[0];
|
|||
|
if (window.hasEvent('touchstart')) {
|
|||
|
$root.dataset.isTouch = true;
|
|||
|
document.addEventListener('touchstart', function(){}, false);
|
|||
|
}
|
|||
|
})();
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
|