diff --git a/app/controllers/Detail.php b/app/controllers/Detail.php
index 2441e0d59d8f60fafcc3b877bfea8bf6566252de..6799ca97ed749df26564eacf81de0bc0f3756c9a 100644
--- a/app/controllers/Detail.php
+++ b/app/controllers/Detail.php
@@ -66,7 +66,7 @@ class Detail extends Controller
         $entityBody = json_decode(file_get_contents('php://input'), true);
         //TODO MASUKKIN KARTU NASABAH ID
         $user = $model_user->readUserById($user_id);
-        $orderid = $soap->buyBook($_COOKIE['bookid'], $entityBody['total'], $user['no_kartu']);
+        $orderid = $soap->buyBook($_COOKIE['bookid'], $entityBody['total'], $user['no_kartu'], $entityBody['token']);
         if ($orderid != -1) {
             $model->createOrder($_COOKIE['bookid'], $user_id, $orderid);
         }
diff --git a/app/models/SoapHelper.php b/app/models/SoapHelper.php
index e391860a458b5d2b5ae7e8b14eda5752e78db206..bac9da126db9435c86ad7c6c599a2559406d11e8 100644
--- a/app/models/SoapHelper.php
+++ b/app/models/SoapHelper.php
@@ -49,8 +49,8 @@ class SoapHelper {
         return $output;
     }
 
-    public function buyBook($id, $quantity, $no_rek) {
-        $data = $this->conn->buyBook($id, $quantity, $no_rek);
+    public function buyBook($id, $quantity, $no_rek, $token) {
+        $data = $this->conn->buyBook($id, $quantity, $no_rek, $token);
         $array = json_decode(json_encode($data), True);
         return $array;
     }
diff --git a/app/models/Token.php b/app/models/Token.php
index 5828e971c9381943ff8c3b36d274884983fbf89e..0bbed2b6d60999544a7d6228980c3cc29118fc9b 100644
--- a/app/models/Token.php
+++ b/app/models/Token.php
@@ -24,6 +24,8 @@ class Token extends Model
         $ip_address = $_SERVER['REMOTE_ADDR'];
         $result = $this->conn->query($sql)->fetch_assoc();
         if ($result['id'] && $result['browser'] == $browser && $result['ip_address'] == $ip_address && time() < (int)$result['time']) {
+            $sql = "UPDATE access_token SET time = " . (time() + (int)1200) . " WHERE token = '" . $token . "';" ;
+            $this->conn->query($sql);
             return $result['id'];
         } else {
             return null;
diff --git a/app/views/detail.php b/app/views/detail.php
index 6ca7e33b22fead9478d58c8054c58dc11511151b..ca4457cacd07340cc0fd77da5c87c2513c9de8a9 100644
--- a/app/views/detail.php
+++ b/app/views/detail.php
@@ -52,6 +52,11 @@
                     <?php } ?>
                 </select>
             </div>
+                <label for="token" class="inp">
+                  <input type="text" id="token" placeholder="&nbsp;">
+                  <span class="label">Token</span>
+                  <span class="border"></span>
+                </label>
             <button onclick="order()" <?php if ($data['book']['price'] == -1) {echo "disabled";}?>>Order</button>
         </section>
         <section id="reviews">
@@ -98,7 +103,7 @@
         <div id="dialog-msg">
             <img id="check-notif" src="/public/images/check.png">
             <div id="msg-detail">
-                <p>Pemesanan Berhasil!</p>
+                <p id="text-msg-detail">Pemesanan Berhasil!</p>
                 <p>Nomor Transaksi : <span id="transaction-number">3<span></p>
             </div>
             <img id="close-notif" onclick="close_notif()" src="/public/images/close.png">
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..707632dcae8d2de97e4d6883184911888438f4f6
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,5 @@
+{
+    "require": {
+        "sonata-project/google-authenticator": "^2.2"
+    }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000000000000000000000000000000000000..f36ec219428a511a46ef23ef81b8d453f0d0bcde
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,75 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "e2df69fcfa963bd7ffe8e358d7e93d70",
+    "packages": [
+        {
+            "name": "sonata-project/google-authenticator",
+            "version": "2.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sonata-project/GoogleAuthenticator.git",
+                "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sonata-project/GoogleAuthenticator/zipball/feda53899b26af24e3db2fe7a3e5f053ca483762",
+                "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Google\\Authenticator\\": "src/",
+                    "Sonata\\GoogleAuthenticator\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Christian Stocker",
+                    "email": "me@chregu.tv"
+                },
+                {
+                    "name": "Andre DeMarre",
+                    "homepage": "http://www.devnetwork.net/viewtopic.php?f=50&t=94989"
+                },
+                {
+                    "name": "Thomas Rabaix",
+                    "email": "thomas.rabaix@gmail.com"
+                }
+            ],
+            "description": "Library to integrate Google Authenticator into a PHP project",
+            "homepage": "https://github.com/sonata-project/GoogleAuthenticator",
+            "keywords": [
+                "google authenticator"
+            ],
+            "time": "2018-07-18T22:08:02+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
diff --git a/example.php b/example.php
new file mode 100644
index 0000000000000000000000000000000000000000..50366d2fa9c470a34eb2474568393a4642a0ad78
--- /dev/null
+++ b/example.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the Sonata Project package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+include_once __DIR__.'/../src/FixedBitNotation.php';
+include_once __DIR__.'/../src/GoogleAuthenticator.php';
+include_once __DIR__.'/../src/GoogleQrUrl.php';
+
+$secret = 'XVQ2UIGO75XRUKJO';
+$code = '846474';
+
+$g = new \Sonata\GoogleAuthenticator\GoogleAuthenticator();
+
+echo 'Current Code is: ';
+echo $g->getCode($secret);
+
+echo "\n";
+
+echo "Check if $code is valid: ";
+
+if ($g->checkCode($secret, $code)) {
+    echo "YES \n";
+} else {
+    echo "NO \n";
+}
+
+$secret = $g->generateSecret();
+echo "Get a new Secret: $secret \n";
+echo "The QR Code for this secret (to scan with the Google Authenticator App: \n";
+
+echo \Sonata\GoogleAuthenticator\GoogleQrUrl::generate('chregu', $secret, 'GoogleAuthenticatorExample');
+echo "\n";
diff --git a/index.php b/index.php
index afa4a1aba47c70e2c90aea4438d7feb3d3673b83..996aa32a611deba4c23295a3f6db6b5f0bf47503 100644
--- a/index.php
+++ b/index.php
@@ -1,5 +1,6 @@
 <?php
 
 ini_set('display_errors', 0);
+require_once 'vendor/autoload.php';
 require_once 'app/init.php';
 
diff --git a/public/css/main.css b/public/css/main.css
index 7bfd34b9d22487c2d17c8ba9e1c19c5793f86d2e..c9ce394755783299421cdd3feb46ed4635dd4d19 100644
--- a/public/css/main.css
+++ b/public/css/main.css
@@ -1034,4 +1034,74 @@ body {
     height: 200px !important;
     -webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px);
     transform: translate(-100px, -100px) scale(1) translate(100px, 100px);
+}
+
+.qr-code{
+    display: block;
+    text-align: center; 
+    width: 100%;
+    height: 100%;
+    margin: auto;
+    border-radius: 3px;
+}
+
+* .inp {
+    position: relative;
+    margin: auto;
+    width: 100%;
+    max-width: 280px;
+}
+* .inp .label {
+    position: absolute;
+    top: 16px;
+    left: 0;
+    font-size: 16px;
+    color: #9098a9;
+    font-weight: 500;
+    transform-origin: 0 0;
+    transition: all 0.2s ease;
+}
+* .inp .border {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    height: 2px;
+    width: 100%;
+    background: #07f;
+    transform: scaleX(0);
+    transform-origin: 0 0;
+    transition: all 0.15s ease;
+}
+* .inp input {
+    -webkit-appearance: none;
+    width: 100%;
+    border: 0;
+    font-family: inherit;
+    padding: 12px 0;
+    height: 48px;
+    font-size: 16px;
+    font-weight: 500;
+    border-bottom: 2px solid #c8ccd4;
+    background: none;
+    border-radius: 0;
+    color: #223254;
+    transition: all 0.15s ease;
+}
+* .inp input:hover {
+    background: rgba(34,50,84,0.03);
+}
+* .inp input:not(:placeholder-shown) + span {
+    color: #5a667f;
+    transform: translateY(-26px) scale(0.75);
+}
+* .inp input:focus {
+    background: none;
+    outline: none;
+}
+* .inp input:focus + span {
+    color: #07f;
+    transform: translateY(-26px) scale(0.75);
+}
+* .inp input:focus + span + .border {
+    transform: scaleX(1);
 }
\ No newline at end of file
diff --git a/public/images/profile/32 b/public/images/profile/32
new file mode 100644
index 0000000000000000000000000000000000000000..27cac80db760b8d73f20e7d0401ee5f3b1e5e530
Binary files /dev/null and b/public/images/profile/32 differ
diff --git a/public/js/detail.js b/public/js/detail.js
index f3d3a3cd777d45704ea1ef3b49b310fc7eaee87f..c0ddcce64d299c92f8c1ce866ed8c01267770417 100644
--- a/public/js/detail.js
+++ b/public/js/detail.js
@@ -1,5 +1,7 @@
 function order() {
     var total_order_element = document.getElementById("total-order");
+    var token = document.getElementById("token").value;
+    console.log("token: "+token);
     var sum_order = total_order_element.options[total_order_element.selectedIndex].value;
     var xhttp = new XMLHttpRequest();
     xhttp.open("POST", "/detail/order");
@@ -7,11 +9,21 @@ function order() {
     xhttp.onreadystatechange = function() {
         console.log(this.responseText);
         if (this.readyState == 4 && this.status == 200 && this.responseText != -1) {
+            document.getElementById("text-msg-detail").innerHTML = "Pemesanan Berhasil!";
             document.getElementById("transaction-number").innerHTML = this.responseText;
             document.getElementById("notif").setAttribute("style", "display: flex");
+            document.getElementById("check-notif").src = "/public/images/check.png";
+        } else {
+            document.getElementById("text-msg-detail").innerHTML = "Pemesanan Gagal!";
+            document.getElementById("transaction-number").innerHTML = -1;
+            document.getElementById("notif").setAttribute("style", "display: flex");
+            document.getElementById("check-notif").src = "/public/images/close.png";
+
         }
     }
-    xhttp.send(JSON.stringify({total : sum_order}));
+    xhttp.send(JSON.stringify({
+        total : sum_order,
+        token : token}));
 }
 
 function close_notif() {
diff --git a/public/js/profile.js b/public/js/profile.js
new file mode 100644
index 0000000000000000000000000000000000000000..d26153eb0e2a39618d3132309d02ee6a54f6d6b8
--- /dev/null
+++ b/public/js/profile.js
@@ -0,0 +1,11 @@
+// console.log("No kartu: " + document.getElementById("no-kartu").value);
+var xhttp = new XMLHttpRequest();
+xhttp.onreadystatechange = function() {
+    if (this.readyState == 4 && this.status == 200) {
+        var response = JSON.parse(this.responseText);
+        document.getElementById("qr-img").src = response.values
+    }
+}
+xhttp.open("POST", "http://localhost:3000/qrcode", true);
+xhttp.setRequestHeader("Content-type", "application/json");
+xhttp.send(JSON.stringify({ "no_kartu": document.getElementById("no-kartu").value }));
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 0000000000000000000000000000000000000000..46ffb4ce11adee005348f0eb20d6189078f6ac8f
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInitae14d3cd7eb3d58ce5dbc9a906ade70c::getLoader();
diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php
new file mode 100644
index 0000000000000000000000000000000000000000..fce8549f0781bafdc7da2301b84d048286757445
--- /dev/null
+++ b/vendor/composer/ClassLoader.php
@@ -0,0 +1,445 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    http://www.php-fig.org/psr/psr-0/
+ * @see    http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    // PSR-4
+    private $prefixLengthsPsr4 = array();
+    private $prefixDirsPsr4 = array();
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    private $prefixesPsr0 = array();
+    private $fallbackDirsPsr0 = array();
+
+    private $useIncludePath = false;
+    private $classMap = array();
+    private $classMapAuthoritative = false;
+    private $missingClasses = array();
+    private $apcuPrefix;
+
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
+        }
+
+        return array();
+    }
+
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array $classMap Class to filename map
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string       $prefix  The prefix
+     * @param array|string $paths   The PSR-0 root directories
+     * @param bool         $prepend Whether to prepend the directories
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string       $prefix  The prefix/namespace, with trailing '\\'
+     * @param array|string $paths   The PSR-4 base directories
+     * @param bool         $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string       $prefix The prefix
+     * @param array|string $paths  The PSR-0 base directories
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string       $prefix The prefix/namespace, with trailing '\\'
+     * @param array|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return bool|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            includeFile($file);
+
+            return true;
+        }
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath . '\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        if (file_exists($file = $dir . $pathEnd)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+    include $file;
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f27399a042d95c4708af3a8c74d35d338763cf8f
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a91153b0d8ea10bc693176a81d8a9eb96883a76
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000000000000000000000000000000000000..b7fc0125dbca56fd7565ad62097672a59473e64e
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
new file mode 100644
index 0000000000000000000000000000000000000000..c57cc2d8fb15c2c170b30ca1e7176b7b427ac591
--- /dev/null
+++ b/vendor/composer/autoload_psr4.php
@@ -0,0 +1,11 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Sonata\\GoogleAuthenticator\\' => array($vendorDir . '/sonata-project/google-authenticator/src'),
+    'Google\\Authenticator\\' => array($vendorDir . '/sonata-project/google-authenticator/src'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 0000000000000000000000000000000000000000..6cd5c1f9cc557f31c8346d95f0dfe9cea48f8d7a
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,52 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInitae14d3cd7eb3d58ce5dbc9a906ade70c
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        spl_autoload_register(array('ComposerAutoloaderInitae14d3cd7eb3d58ce5dbc9a906ade70c', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+        spl_autoload_unregister(array('ComposerAutoloaderInitae14d3cd7eb3d58ce5dbc9a906ade70c', 'loadClassLoader'));
+
+        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+        if ($useStaticLoader) {
+            require_once __DIR__ . '/autoload_static.php';
+
+            call_user_func(\Composer\Autoload\ComposerStaticInitae14d3cd7eb3d58ce5dbc9a906ade70c::getInitializer($loader));
+        } else {
+            $map = require __DIR__ . '/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                $loader->set($namespace, $path);
+            }
+
+            $map = require __DIR__ . '/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                $loader->setPsr4($namespace, $path);
+            }
+
+            $classMap = require __DIR__ . '/autoload_classmap.php';
+            if ($classMap) {
+                $loader->addClassMap($classMap);
+            }
+        }
+
+        $loader->register(true);
+
+        return $loader;
+    }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 0000000000000000000000000000000000000000..20899b7474c2f76f372ce0ab6b481a0fb5ceb782
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,39 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInitae14d3cd7eb3d58ce5dbc9a906ade70c
+{
+    public static $prefixLengthsPsr4 = array (
+        'S' => 
+        array (
+            'Sonata\\GoogleAuthenticator\\' => 27,
+        ),
+        'G' => 
+        array (
+            'Google\\Authenticator\\' => 21,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Sonata\\GoogleAuthenticator\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sonata-project/google-authenticator/src',
+        ),
+        'Google\\Authenticator\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sonata-project/google-authenticator/src',
+        ),
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInitae14d3cd7eb3d58ce5dbc9a906ade70c::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitae14d3cd7eb3d58ce5dbc9a906ade70c::$prefixDirsPsr4;
+
+        }, null, ClassLoader::class);
+    }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 0000000000000000000000000000000000000000..b6b2711fd134f8028c4db588065cf44c51ab00ff
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,61 @@
+[
+    {
+        "name": "sonata-project/google-authenticator",
+        "version": "2.2.0",
+        "version_normalized": "2.2.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/sonata-project/GoogleAuthenticator.git",
+            "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/sonata-project/GoogleAuthenticator/zipball/feda53899b26af24e3db2fe7a3e5f053ca483762",
+            "reference": "feda53899b26af24e3db2fe7a3e5f053ca483762",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^7.1"
+        },
+        "require-dev": {
+            "symfony/phpunit-bridge": "^4.0"
+        },
+        "time": "2018-07-18T22:08:02+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "2.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Google\\Authenticator\\": "src/",
+                "Sonata\\GoogleAuthenticator\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Christian Stocker",
+                "email": "me@chregu.tv"
+            },
+            {
+                "name": "Andre DeMarre",
+                "homepage": "http://www.devnetwork.net/viewtopic.php?f=50&t=94989"
+            },
+            {
+                "name": "Thomas Rabaix",
+                "email": "thomas.rabaix@gmail.com"
+            }
+        ],
+        "description": "Library to integrate Google Authenticator into a PHP project",
+        "homepage": "https://github.com/sonata-project/GoogleAuthenticator",
+        "keywords": [
+            "google authenticator"
+        ]
+    }
+]
diff --git a/vendor/sonata-project/google-authenticator/LICENSE b/vendor/sonata-project/google-authenticator/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..0135e0b6aebb4addef7246658ac1af1e56ba9b53
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2010 Thomas Rabaix
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/sonata-project/google-authenticator/Makefile b/vendor/sonata-project/google-authenticator/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4ae0c63d810bcc5615b7da192ff0145a5a7e72c8
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/Makefile
@@ -0,0 +1,60 @@
+# DO NOT EDIT THIS FILE!
+#
+# It's auto-generated by sonata-project/dev-kit package.
+
+all:
+	@echo "Please choose a task."
+.PHONY: all
+
+lint: lint-composer lint-yaml lint-composer lint-xml lint-php
+.PHONY: lint
+
+lint-composer:
+	composer validate
+.PHONY: lint-composer
+
+lint-yaml:
+	yaml-lint --ignore-non-yaml-files --quiet --exclude vendor .
+
+.PHONY: lint-yaml
+
+lint-xml:
+	find . \( -name '*.xml' -or -name '*.xliff' \) \
+		-not -path './vendor/*' \
+		-not -path './src/Resources/public/vendor/*' \
+		| while read xmlFile; \
+	do \
+		XMLLINT_INDENT='    ' xmllint --encode UTF-8 --format "$$xmlFile"|diff - "$$xmlFile"; \
+		if [ $$? -ne 0 ] ;then exit 1; fi; \
+	done
+
+.PHONY: lint-xml
+
+lint-php:
+	php-cs-fixer fix --ansi --verbose --diff --dry-run
+.PHONY: lint-php
+
+cs-fix: cs-fix-php cs-fix-xml
+.PHONY: cs-fix
+
+cs-fix-php:
+	php-cs-fixer fix --verbose
+.PHONY: cs-fix-php
+
+cs-fix-xml:
+	find . \( -name '*.xml' -or -name '*.xliff' \) \
+		-not -path './vendor/*' \
+		-not -path './src/Resources/public/vendor/*' \
+		| while read xmlFile; \
+	do \
+		XMLLINT_INDENT='    ' xmllint --encode UTF-8 --format "$$xmlFile" --output "$$xmlFile"; \
+	done
+.PHONY: cs-fix-xml
+
+test:
+	phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml
+.PHONY: test
+
+docs:
+	cd docs && sphinx-build -W -b html -d _build/doctrees . _build/html
+.PHONY: docs
diff --git a/vendor/sonata-project/google-authenticator/composer.json b/vendor/sonata-project/google-authenticator/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..4fbb160acfb53ae77238e3a9b848ea7c7ae8277c
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/composer.json
@@ -0,0 +1,50 @@
+{
+    "name": "sonata-project/google-authenticator",
+    "type": "library",
+    "description": "Library to integrate Google Authenticator into a PHP project",
+    "keywords": [
+        "google authenticator"
+    ],
+    "homepage": "https://github.com/sonata-project/GoogleAuthenticator",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Thomas Rabaix",
+            "email": "thomas.rabaix@gmail.com"
+        },
+        {
+            "name": "Christian Stocker",
+            "email": "me@chregu.tv"
+        },
+        {
+            "name": "Andre DeMarre",
+            "homepage": "http://www.devnetwork.net/viewtopic.php?f=50&t=94989"
+        }
+    ],
+    "require": {
+        "php": "^7.1"
+    },
+    "require-dev": {
+        "symfony/phpunit-bridge": "^4.0"
+    },
+    "config": {
+        "sort-packages": true
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "2.x-dev"
+        }
+    },
+    "autoload": {
+        "psr-4": {
+            "Google\\Authenticator\\": "src/",
+            "Sonata\\GoogleAuthenticator\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Google\\Authenticator\\Tests\\": "tests/",
+            "Sonata\\GoogleAuthenticator\\Tests\\": "tests/"
+        }
+    }
+}
diff --git a/vendor/sonata-project/google-authenticator/phpunit.xml.dist b/vendor/sonata-project/google-authenticator/phpunit.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..492b036db0eb7cfd428de82bdf6e1303b56eefec
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/phpunit.xml.dist
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+DO NOT EDIT THIS FILE!
+
+It's auto-generated by sonata-project/dev-kit package.
+-->
+
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="false"
+         bootstrap="tests/bootstrap.php"
+>
+    <testsuites>
+        <testsuite name="Sonata Google Authenticator Test Suite">
+            <directory suffix="Test.php">./tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <listeners>
+       <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
+    </listeners>
+
+    <filter>
+        <whitelist>
+            <directory suffix=".php">./src/</directory>
+        </whitelist>
+    </filter>
+
+    <php>
+        <ini name="precision" value="8"/>
+    </php>
+
+</phpunit>
diff --git a/vendor/sonata-project/google-authenticator/sample/example.php b/vendor/sonata-project/google-authenticator/sample/example.php
new file mode 100644
index 0000000000000000000000000000000000000000..50366d2fa9c470a34eb2474568393a4642a0ad78
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/example.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the Sonata Project package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+include_once __DIR__.'/../src/FixedBitNotation.php';
+include_once __DIR__.'/../src/GoogleAuthenticator.php';
+include_once __DIR__.'/../src/GoogleQrUrl.php';
+
+$secret = 'XVQ2UIGO75XRUKJO';
+$code = '846474';
+
+$g = new \Sonata\GoogleAuthenticator\GoogleAuthenticator();
+
+echo 'Current Code is: ';
+echo $g->getCode($secret);
+
+echo "\n";
+
+echo "Check if $code is valid: ";
+
+if ($g->checkCode($secret, $code)) {
+    echo "YES \n";
+} else {
+    echo "NO \n";
+}
+
+$secret = $g->generateSecret();
+echo "Get a new Secret: $secret \n";
+echo "The QR Code for this secret (to scan with the Google Authenticator App: \n";
+
+echo \Sonata\GoogleAuthenticator\GoogleQrUrl::generate('chregu', $secret, 'GoogleAuthenticatorExample');
+echo "\n";
diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/ask-for-otp.php b/vendor/sonata-project/google-authenticator/sample/tmpl/ask-for-otp.php
new file mode 100644
index 0000000000000000000000000000000000000000..f3e06d477e22f323426f97ae46c67be8b7a2efba
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/tmpl/ask-for-otp.php
@@ -0,0 +1,23 @@
+
+<h1>please otp</h1>
+<p>
+<form method="post" action="./">
+<?php if ($debug) {
+    ?>
+    <br/>
+    (Set $debug in index.php to false, if you don't want to have the OTP prefilled (for real life application, for example ;))<br/>
+<?php
+}
+?>
+
+otp: <input name="otp"
+value="<?php
+if ($debug) {
+    $g = new GoogleAuthenticator();
+    echo $g->getCode($user->getSecret());
+}
+?>"/><br/>
+<input type="checkbox" name="remember" id="remember" /><label for="remember"> Remember verification for this computer for 1 day.</label> <br/>
+<input type="submit"/>
+
+</form>
diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/loggedin.php b/vendor/sonata-project/google-authenticator/sample/tmpl/loggedin.php
new file mode 100644
index 0000000000000000000000000000000000000000..2a19032ca4f1527b6e49c0128c4103799746e547
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/tmpl/loggedin.php
@@ -0,0 +1,19 @@
+
+<p>
+Hello <?php echo $user->getUsername(); ?>
+</p>
+<?php
+if (!isset($_GET['showqr'])) {
+    ?>
+
+<p>
+<a href="?showqr=1">Show QR Code</a>
+</p>
+
+<?php
+}
+?>
+
+<p>
+<a href="?logout=1">Logout</a>
+</p>
diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/login-error.php b/vendor/sonata-project/google-authenticator/sample/tmpl/login-error.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d23fd33c7bd09a636c365d3c56e91fa70f08c59
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/tmpl/login-error.php
@@ -0,0 +1,6 @@
+<p>
+Wrong username or password or token.
+</p>
+<p>
+<a href="./">try again</a>
+</p>
diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/login.php b/vendor/sonata-project/google-authenticator/sample/tmpl/login.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd81623009b4b8620b6d3c8e21111c93df2c182c
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/tmpl/login.php
@@ -0,0 +1,8 @@
+
+<h1>please login</h1>
+<p>
+<form method="post" action="./">
+username: <input name="username"/><br/>
+password: <input name="password" type="password"/><br/>
+<input type="submit"/>
+</form>
diff --git a/vendor/sonata-project/google-authenticator/sample/tmpl/show-qr.php b/vendor/sonata-project/google-authenticator/sample/tmpl/show-qr.php
new file mode 100644
index 0000000000000000000000000000000000000000..774a298deed8458831ed231cb0680441223a4d0a
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/tmpl/show-qr.php
@@ -0,0 +1,11 @@
+<h1>Please scan this </h1>
+
+<p> with <a href="http://www.google.com/support/a/bin/answer.py?hl=en&answer=1037451">the Google Authenticator App</a></p>
+
+<p>
+<?php
+$link = \Sonata\GoogleAuthenticator\GoogleQrUrl::generate($user->getUsername(), $secret, 'GoogleAuthenticatorExample');
+?>
+
+<a  href="<?php echo $link; ?>"><img style="border: 0; padding:10px" src="<?php echo $link; ?>"/></a>
+</p>
diff --git a/vendor/sonata-project/google-authenticator/sample/users.dat b/vendor/sonata-project/google-authenticator/sample/users.dat
new file mode 100644
index 0000000000000000000000000000000000000000..fdcc130c43b31f2d7b337b977705a164826b9688
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/users.dat
@@ -0,0 +1 @@
+{"chregu":{"password":"foobar"}}
\ No newline at end of file
diff --git a/vendor/sonata-project/google-authenticator/sample/web/Users.php b/vendor/sonata-project/google-authenticator/sample/web/Users.php
new file mode 100644
index 0000000000000000000000000000000000000000..fb169def9245eae78279bf2c6a7357c6fc5e71a8
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/web/Users.php
@@ -0,0 +1,155 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the Sonata Project package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Users
+{
+    public function __construct(string $file = '../users.dat')
+    {
+        $this->userFile = $file;
+
+        $this->users = json_decode(file_get_contents($file), true);
+    }
+
+    public function hasSession()
+    {
+        session_start();
+        if (isset($_SESSION['username'])) {
+            return $_SESSION['username'];
+        }
+
+        return false;
+    }
+
+    public function storeData(User $user): void
+    {
+        $this->users[$user->getUsername()] = $user->getData();
+        file_put_contents($this->userFile, json_encode($this->users));
+    }
+
+    public function loadUser($name)
+    {
+        if (isset($this->users[$name])) {
+            return new User($name, $this->users[$name]);
+        }
+
+        return false;
+    }
+}
+
+class User
+{
+    public function __construct($user, $data)
+    {
+        $this->data = $data;
+        $this->user = $user;
+    }
+
+    public function auth($pass)
+    {
+        if ($this->data['password'] === $pass) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public function startSession(): void
+    {
+        $_SESSION['username'] = $this->user;
+    }
+
+    public function doLogin(): void
+    {
+        session_regenerate_id();
+        $_SESSION['loggedin'] = true;
+        $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];
+    }
+
+    public function doOTP(): void
+    {
+        $_SESSION['OTP'] = true;
+    }
+
+    public function isOTP()
+    {
+        if (isset($_SESSION['OTP']) && true == $_SESSION['OTP']) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public function isLoggedIn()
+    {
+        if (isset($_SESSION['loggedin']) && true == $_SESSION['loggedin'] &&
+            isset($_SESSION['ua']) && $_SESSION['ua'] == $_SERVER['HTTP_USER_AGENT']
+        ) {
+            return $_SESSION['username'];
+        }
+
+        return false;
+    }
+
+    public function getUsername()
+    {
+        return $this->user;
+    }
+
+    public function getSecret()
+    {
+        if (isset($this->data['secret'])) {
+            return $this->data['secret'];
+        }
+
+        return false;
+    }
+
+    public function generateSecret()
+    {
+        $g = new \Sonata\GoogleAuthenticator\GoogleAuthenticator();
+        $secret = $g->generateSecret();
+        $this->data['secret'] = $secret;
+
+        return $secret;
+    }
+
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    public function setOTPCookie(): void
+    {
+        $time = floor(time() / (3600 * 24)); // get day number
+        //about using the user agent: It's easy to fake it, but it increases the barrier for stealing and reusing cookies nevertheless
+        // and it doesn't do any harm (except that it's invalid after a browser upgrade, but that may be even intented)
+        $cookie = $time.':'.hash_hmac('sha1', $this->getUsername().':'.$time.':'.$_SERVER['HTTP_USER_AGENT'], $this->getSecret());
+        setcookie('otp', $cookie, time() + (30 * 24 * 3600), null, null, null, true);
+    }
+
+    public function hasValidOTPCookie()
+    {
+        // 0 = tomorrow it is invalid
+        $daysUntilInvalid = 0;
+        $time = (string) floor((time() / (3600 * 24))); // get day number
+        if (isset($_COOKIE['otp'])) {
+            list($otpday, $hash) = explode(':', $_COOKIE['otp']);
+
+            if ($otpday >= $time - $daysUntilInvalid && $hash == hash_hmac('sha1', $this->getUsername().':'.$otpday.':'.$_SERVER['HTTP_USER_AGENT'], $this->getSecret())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/vendor/sonata-project/google-authenticator/sample/web/index.php b/vendor/sonata-project/google-authenticator/sample/web/index.php
new file mode 100644
index 0000000000000000000000000000000000000000..c039674c53f53691340e7c3a6f4bb1c236d94335
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/sample/web/index.php
@@ -0,0 +1,119 @@
+<?php declare(strict_types=1);
+ob_start(); //i'm too lazy to check when is sent what ;)
+//set session cookie to be read only via http and not by JavaScript
+ini_set('session.cookie_httponly', '1');
+
+include_once __DIR__.'/../../src/GoogleAuthenticator.php';
+include_once __DIR__.'/../../src/GoogleQrUrl.php';
+include_once __DIR__.'/../../src/FixedBitNotation.php';
+include_once 'Users.php';
+
+?>
+<!DOCTYPE HTML>
+<html>
+<head>
+    <title>Google Authenticator in PHP demo</title>
+</head>
+<body>
+<?php
+
+//set this to false, if you don't want the token prefilled
+$debug = true;
+
+$users = new Users();
+//check if the user has a session, if not, show the login screen
+if ($username = $users->hasSession()) {
+    //load the user data from the json storage.
+    $user = $users->loadUser($username);
+    //if he clicked logout, destroy the session and redirect to the startscreen.
+    if (isset($_GET['logout'])) {
+        session_destroy();
+        header('Location: ./');
+    }
+    // check if the user is logged in.
+    if ($user->isLoggedIn()) {
+        include __DIR__.'/../tmpl/loggedin.php';
+        //show the QR code if whished so
+        if (isset($_GET['showqr'])) {
+            $secret = $user->getSecret();
+            include __DIR__.'/../tmpl/show-qr.php';
+        }
+    }
+    //if the user is in the OTP phase and submit the OTP.
+    else {
+        if ($user->isOTP() && isset($_POST['otp'])) {
+            $g = new \Google\Authenticator\GoogleAuthenticator();
+            // check if the submitted token is the right one and log in
+            if ($g->checkCode($user->getSecret(), $_POST['otp'])) {
+                // do log-in the user
+                $user->doLogin();
+                //if the user clicked the "remember the token" checkbox, set the cookie
+                if (isset($_POST['remember']) && $_POST['remember']) {
+                    $user->setOTPCookie();
+                }
+                include __DIR__.'/../tmpl/loggedin.php';
+            }
+            //if the OTP is wrong, destroy the session and tell the user to try again
+            else {
+                session_destroy();
+                include __DIR__.'/../tmpl/login-error.php';
+            }
+        }
+        // if the user is neither logged in nor in the OTP phase, show the login form
+        else {
+            session_destroy();
+            include __DIR__.'/../tmpl/login.php';
+        }
+    }
+    die();
+}
+    //if the username is set in _POST, then we assume the user filled in the login form.
+
+    if (isset($_POST['username'])) {
+        // check if we can load the user (ie. the user exists in our db)
+        $user = $users->loadUser($_POST['username']);
+        if ($user) {
+            //try to authenticate the password and start the session if it's correct.
+            if ($user->auth($_POST['password'])) {
+                $user->startSession();
+                //check if the user has a valid OTP cookie, so we don't have to
+                // ask for the current token and can directly log in
+                if ($user->hasValidOTPCookie()) {
+                    include __DIR__.'/../tmpl/loggedin.php';
+                    $user->doLogin();
+                }
+                // try to get the users' secret from the db,
+                //  if he doesn't have one, generate one, store it and show it.
+                else {
+                    if (!$user->getSecret()) {
+                        include __DIR__.'/../tmpl/loggedin.php';
+
+                        $secret = $user->generateSecret();
+                        $users->storeData($user);
+                        $user->doLogin();
+                        include __DIR__.'/../tmpl/show-qr.php';
+                    }
+                    // if the user neither has a valid OTP cookie nor it's the first login
+                    //  ask for the OTP
+                    else {
+                        $user->doOTP();
+                        include __DIR__.'/../tmpl/ask-for-otp.php';
+                    }
+                }
+
+                die();
+            }
+        }
+        // if we're here, something went wrong, destroy the session and show a login error
+        session_destroy();
+
+        include __DIR__.'/../tmpl/login-error.php';
+        die();
+    }
+
+// if neither a session nor tried to submit the login credentials -> login screen
+include __DIR__.'/../tmpl/login.php';
+
+?>
+</body>
+</html>
diff --git a/vendor/sonata-project/google-authenticator/src/FixedBitNotation.php b/vendor/sonata-project/google-authenticator/src/FixedBitNotation.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f5ff558b05e8a721dc4589796154bcd2d65d574
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/src/FixedBitNotation.php
@@ -0,0 +1,296 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the Sonata Project package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\GoogleAuthenticator;
+
+/**
+ * FixedBitNotation.
+ *
+ * The FixedBitNotation class is for binary to text conversion. It
+ * can handle many encoding schemes, formally defined or not, that
+ * use a fixed number of bits to encode each character.
+ *
+ * @author Andre DeMarre
+ */
+final class FixedBitNotation
+{
+    /**
+     * @var string
+     */
+    private $chars;
+
+    /**
+     * @var int
+     */
+    private $bitsPerCharacter;
+
+    /**
+     * @var int
+     */
+    private $radix;
+
+    /**
+     * @var bool
+     */
+    private $rightPadFinalBits;
+
+    /**
+     * @var bool
+     */
+    private $padFinalGroup;
+
+    /**
+     * @var string
+     */
+    private $padCharacter;
+
+    /**
+     * @var string[]
+     */
+    private $charmap;
+
+    /**
+     * @param int    $bitsPerCharacter  Bits to use for each encoded character
+     * @param string $chars             Base character alphabet
+     * @param bool   $rightPadFinalBits How to encode last character
+     * @param bool   $padFinalGroup     Add padding to end of encoded output
+     * @param string $padCharacter      Character to use for padding
+     */
+    public function __construct(int $bitsPerCharacter, string $chars = null, bool $rightPadFinalBits = false, bool $padFinalGroup = false, string $padCharacter = '=')
+    {
+        // Ensure validity of $chars
+        if (!is_string($chars) || ($charLength = strlen($chars)) < 2) {
+            $chars =
+            '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,';
+            $charLength = 64;
+        }
+
+        // Ensure validity of $bitsPerCharacter
+        if ($bitsPerCharacter < 1) {
+            // $bitsPerCharacter must be at least 1
+            $bitsPerCharacter = 1;
+            $radix = 2;
+        } elseif ($charLength < 1 << $bitsPerCharacter) {
+            // Character length of $chars is too small for $bitsPerCharacter
+            // Set $bitsPerCharacter to greatest acceptable value
+            $bitsPerCharacter = 1;
+            $radix = 2;
+
+            while ($charLength >= ($radix <<= 1) && $bitsPerCharacter < 8) {
+                ++$bitsPerCharacter;
+            }
+
+            $radix >>= 1;
+        } elseif ($bitsPerCharacter > 8) {
+            // $bitsPerCharacter must not be greater than 8
+            $bitsPerCharacter = 8;
+            $radix = 256;
+        } else {
+            $radix = 1 << $bitsPerCharacter;
+        }
+
+        $this->chars = $chars;
+        $this->bitsPerCharacter = $bitsPerCharacter;
+        $this->radix = $radix;
+        $this->rightPadFinalBits = $rightPadFinalBits;
+        $this->padFinalGroup = $padFinalGroup;
+        $this->padCharacter = $padCharacter[0];
+    }
+
+    /**
+     * Encode a string.
+     *
+     * @param string $rawString Binary data to encode
+     *
+     * @return string
+     */
+    public function encode($rawString): string
+    {
+        // Unpack string into an array of bytes
+        $bytes = unpack('C*', $rawString);
+        $byteCount = count($bytes);
+
+        $encodedString = '';
+        $byte = array_shift($bytes);
+        $bitsRead = 0;
+
+        $chars = $this->chars;
+        $bitsPerCharacter = $this->bitsPerCharacter;
+        $rightPadFinalBits = $this->rightPadFinalBits;
+        $padFinalGroup = $this->padFinalGroup;
+        $padCharacter = $this->padCharacter;
+
+        // Generate encoded output;
+        // each loop produces one encoded character
+        for ($c = 0; $c < $byteCount * 8 / $bitsPerCharacter; ++$c) {
+            // Get the bits needed for this encoded character
+            if ($bitsRead + $bitsPerCharacter > 8) {
+                // Not enough bits remain in this byte for the current
+                // character
+                // Save the remaining bits before getting the next byte
+                $oldBitCount = 8 - $bitsRead;
+                $oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount);
+                $newBitCount = $bitsPerCharacter - $oldBitCount;
+
+                if (!$bytes) {
+                    // Last bits; match final character and exit loop
+                    if ($rightPadFinalBits) {
+                        $oldBits <<= $newBitCount;
+                    }
+                    $encodedString .= $chars[$oldBits];
+
+                    if ($padFinalGroup) {
+                        // Array of the lowest common multiples of
+                        // $bitsPerCharacter and 8, divided by 8
+                        $lcmMap = [1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1];
+                        $bytesPerGroup = $lcmMap[$bitsPerCharacter];
+                        $pads = $bytesPerGroup * 8 / $bitsPerCharacter
+                        - ceil((strlen($rawString) % $bytesPerGroup)
+                        * 8 / $bitsPerCharacter);
+                        $encodedString .= str_repeat($padCharacter[0], $pads);
+                    }
+
+                    break;
+                }
+
+                // Get next byte
+                $byte = array_shift($bytes);
+                $bitsRead = 0;
+            } else {
+                $oldBitCount = 0;
+                $newBitCount = $bitsPerCharacter;
+            }
+
+            // Read only the needed bits from this byte
+            $bits = $byte >> 8 - ($bitsRead + $newBitCount);
+            $bits ^= $bits >> $newBitCount << $newBitCount;
+            $bitsRead += $newBitCount;
+
+            if ($oldBitCount) {
+                // Bits come from seperate bytes, add $oldBits to $bits
+                $bits = ($oldBits << $newBitCount) | $bits;
+            }
+
+            $encodedString .= $chars[$bits];
+        }
+
+        return $encodedString;
+    }
+
+    /**
+     * Decode a string.
+     *
+     * @param string $encodedString Data to decode
+     * @param bool   $caseSensitive
+     * @param bool   $strict        Returns null if $encodedString contains
+     *                              an undecodable character
+     *
+     * @return string
+     */
+    public function decode($encodedString, $caseSensitive = true, $strict = false): string
+    {
+        if (!$encodedString || !is_string($encodedString)) {
+            // Empty string, nothing to decode
+            return '';
+        }
+
+        $chars = $this->chars;
+        $bitsPerCharacter = $this->bitsPerCharacter;
+        $radix = $this->radix;
+        $rightPadFinalBits = $this->rightPadFinalBits;
+        $padCharacter = $this->padCharacter;
+
+        // Get index of encoded characters
+        if ($this->charmap) {
+            $charmap = $this->charmap;
+        } else {
+            $charmap = [];
+
+            for ($i = 0; $i < $radix; ++$i) {
+                $charmap[$chars[$i]] = $i;
+            }
+
+            $this->charmap = $charmap;
+        }
+
+        // The last encoded character is $encodedString[$lastNotatedIndex]
+        $lastNotatedIndex = strlen($encodedString) - 1;
+
+        // Remove trailing padding characters
+        while ($encodedString[$lastNotatedIndex] == $padCharacter[0]) {
+            $encodedString = substr($encodedString, 0, $lastNotatedIndex);
+            --$lastNotatedIndex;
+        }
+
+        $rawString = '';
+        $byte = 0;
+        $bitsWritten = 0;
+
+        // Convert each encoded character to a series of unencoded bits
+        for ($c = 0; $c <= $lastNotatedIndex; ++$c) {
+            if (!isset($charmap[$encodedString[$c]]) && !$caseSensitive) {
+                // Encoded character was not found; try other case
+                if (isset($charmap[$cUpper = strtoupper($encodedString[$c])])) {
+                    $charmap[$encodedString[$c]] = $charmap[$cUpper];
+                } elseif (isset($charmap[$cLower = strtolower($encodedString[$c])])) {
+                    $charmap[$encodedString[$c]] = $charmap[$cLower];
+                }
+            }
+
+            if (isset($charmap[$encodedString[$c]])) {
+                $bitsNeeded = 8 - $bitsWritten;
+                $unusedBitCount = $bitsPerCharacter - $bitsNeeded;
+
+                // Get the new bits ready
+                if ($bitsNeeded > $bitsPerCharacter) {
+                    // New bits aren't enough to complete a byte; shift them
+                    // left into position
+                    $newBits = $charmap[$encodedString[$c]] << $bitsNeeded
+                    - $bitsPerCharacter;
+                    $bitsWritten += $bitsPerCharacter;
+                } elseif ($c != $lastNotatedIndex || $rightPadFinalBits) {
+                    // Zero or more too many bits to complete a byte;
+                    // shift right
+                    $newBits = $charmap[$encodedString[$c]] >> $unusedBitCount;
+                    $bitsWritten = 8; //$bitsWritten += $bitsNeeded;
+                } else {
+                    // Final bits don't need to be shifted
+                    $newBits = $charmap[$encodedString[$c]];
+                    $bitsWritten = 8;
+                }
+
+                $byte |= $newBits;
+
+                if (8 == $bitsWritten || $c == $lastNotatedIndex) {
+                    // Byte is ready to be written
+                    $rawString .= pack('C', $byte);
+
+                    if ($c != $lastNotatedIndex) {
+                        // Start the next byte
+                        $bitsWritten = $unusedBitCount;
+                        $byte = ($charmap[$encodedString[$c]]
+                        ^ ($newBits << $unusedBitCount)) << 8 - $bitsWritten;
+                    }
+                }
+            } elseif ($strict) {
+                // Unable to decode character; abort
+                return null;
+            }
+        }
+
+        return $rawString;
+    }
+}
+
+// NEXT_MAJOR: Remove class alias
+class_alias('Sonata\GoogleAuthenticator\FixedBitNotation', 'Google\Authenticator\FixedBitNotation', false);
diff --git a/vendor/sonata-project/google-authenticator/src/GoogleAuthenticator.php b/vendor/sonata-project/google-authenticator/src/GoogleAuthenticator.php
new file mode 100644
index 0000000000000000000000000000000000000000..145739c54bc369c59d8863649968cb5c980b3230
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/src/GoogleAuthenticator.php
@@ -0,0 +1,173 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the Sonata Project package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\GoogleAuthenticator;
+
+/**
+ * @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format
+ */
+final class GoogleAuthenticator implements GoogleAuthenticatorInterface
+{
+    /**
+     * @var int
+     */
+    private $passCodeLength;
+
+    /**
+     * @var int
+     */
+    private $secretLength;
+
+    /**
+     * @var int
+     */
+    private $pinModulo;
+
+    /**
+     * @var \DateTimeInterface
+     */
+    private $now;
+
+    /**
+     * @var int
+     */
+    private $codePeriod = 30;
+
+    /**
+     * @param int                     $passCodeLength
+     * @param int                     $secretLength
+     * @param \DateTimeInterface|null $now
+     */
+    public function __construct(int $passCodeLength = 6, int $secretLength = 10, \DateTimeInterface $now = null)
+    {
+        $this->passCodeLength = $passCodeLength;
+        $this->secretLength = $secretLength;
+        $this->pinModulo = 10 ** $passCodeLength;
+        $this->now = $now ?? new \DateTimeImmutable();
+    }
+
+    /**
+     * @param string $secret
+     * @param string $code
+     */
+    public function checkCode($secret, $code): bool
+    {
+        /**
+         * The result of each comparison is accumulated here instead of using a guard clause
+         * (https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html). This is to implement
+         * constant time comparison to make side-channel attacks harder. See
+         * https://cryptocoding.net/index.php/Coding_rules#Compare_secret_strings_in_constant_time for details.
+         * Each comparison uses hash_equals() instead of an operator to implement constant time equality comparison
+         * for each code.
+         */
+        $result = 0;
+
+        // current period
+        $result += hash_equals($this->getCode($secret, $this->now), $code);
+
+        // previous period, happens if the user was slow to enter or it just crossed over
+        $dateTime = new \DateTimeImmutable('@'.($this->now->getTimestamp() - $this->codePeriod));
+        $result += hash_equals($this->getCode($secret, $dateTime), $code);
+
+        // next period, happens if the user is not completely synced and possibly a few seconds ahead
+        $dateTime = new \DateTimeImmutable('@'.($this->now->getTimestamp() + $this->codePeriod));
+        $result += hash_equals($this->getCode($secret, $dateTime), $code);
+
+        return $result > 0;
+    }
+
+    /**
+     * NEXT_MAJOR: add the interface typehint to $time and remove deprecation.
+     *
+     * @param string                                   $secret
+     * @param float|string|int|null|\DateTimeInterface $time
+     */
+    public function getCode($secret, /* \DateTimeInterface */$time = null): string
+    {
+        if (null === $time) {
+            $time = $this->now;
+        }
+
+        if ($time instanceof \DateTimeInterface) {
+            $timeForCode = floor($time->getTimestamp() / $this->codePeriod);
+        } else {
+            @trigger_error(
+                'Passing anything other than null or a DateTimeInterface to $time is deprecated as of 2.0 '.
+                'and will not be possible as of 3.0.',
+                E_USER_DEPRECATED
+            );
+            $timeForCode = $time;
+        }
+
+        $base32 = new FixedBitNotation(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', true, true);
+        $secret = $base32->decode($secret);
+
+        $timeForCode = str_pad(pack('N', $timeForCode), 8, chr(0), STR_PAD_LEFT);
+
+        $hash = hash_hmac('sha1', $timeForCode, $secret, true);
+        $offset = ord(substr($hash, -1));
+        $offset &= 0xF;
+
+        $truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
+
+        return str_pad((string) ($truncatedHash % $this->pinModulo), $this->passCodeLength, '0', STR_PAD_LEFT);
+    }
+
+    /**
+     * NEXT_MAJOR: Remove this method.
+     *
+     * @param string $user
+     * @param string $hostname
+     * @param string $secret
+     *
+     * @deprecated deprecated as of 2.1 and will be removed in 3.0. Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.
+     */
+    public function getUrl($user, $hostname, $secret): string
+    {
+        @trigger_error(sprintf(
+            'Using %s() is deprecated as of 2.1 and will be removed in 3.0. '.
+            'Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.',
+            __METHOD__
+        ), E_USER_DEPRECATED);
+
+        $issuer = func_get_args()[3] ?? null;
+        $accountName = sprintf('%s@%s', $user, $hostname);
+
+        // manually concat the issuer to avoid a change in URL
+        $url = GoogleQrUrl::generate($accountName, $secret);
+
+        if ($issuer) {
+            $url .= '%26issuer%3D'.$issuer;
+        }
+
+        return $url;
+    }
+
+    public function generateSecret(): string
+    {
+        return (new FixedBitNotation(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', true, true))
+            ->encode(random_bytes($this->secretLength));
+    }
+
+    /**
+     * @param string $bytes
+     * @param int    $start
+     */
+    private function hashToInt(string $bytes, int $start): int
+    {
+        return unpack('N', substr(substr($bytes, $start), 0, 4))[1];
+    }
+}
+
+// NEXT_MAJOR: Remove class alias
+class_alias('Sonata\GoogleAuthenticator\GoogleAuthenticator', 'Google\Authenticator\GoogleAuthenticator', false);
diff --git a/vendor/sonata-project/google-authenticator/src/GoogleAuthenticatorInterface.php b/vendor/sonata-project/google-authenticator/src/GoogleAuthenticatorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..70219f2090524c8549410973634cc5d7c06fe05a
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/src/GoogleAuthenticatorInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the Sonata Project package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\GoogleAuthenticator;
+
+interface GoogleAuthenticatorInterface
+{
+    /**
+     * @param string $secret
+     * @param string $code
+     */
+    public function checkCode($secret, $code): bool;
+
+    /**
+     * NEXT_MAJOR: add the interface typehint to $time and remove deprecation.
+     *
+     * @param string                                   $secret
+     * @param float|string|int|null|\DateTimeInterface $time
+     */
+    public function getCode($secret, /* \DateTimeInterface */$time = null): string;
+
+    /**
+     * NEXT_MAJOR: Remove this method.
+     *
+     * @param string $user
+     * @param string $hostname
+     * @param string $secret
+     *
+     * @deprecated deprecated as of 2.1 and will be removed in 3.0. Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.
+     */
+    public function getUrl($user, $hostname, $secret): string;
+
+    public function generateSecret(): string;
+}
diff --git a/vendor/sonata-project/google-authenticator/src/GoogleQrUrl.php b/vendor/sonata-project/google-authenticator/src/GoogleQrUrl.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e9a537a4aa63f764233da690e2dd40e14851ef9
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/src/GoogleQrUrl.php
@@ -0,0 +1,94 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the Sonata Project package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\GoogleAuthenticator;
+
+/**
+ * Responsible for QR image url generation.
+ *
+ * @see https://developers.google.com/chart/infographics/docs/qr_codes
+ * @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format
+ *
+ * @author Iltar van der Berg <kjarli@gmail.com>
+ */
+final class GoogleQrUrl
+{
+    /**
+     * Private by design.
+     */
+    private function __construct()
+    {
+    }
+
+    /**
+     * Generates a URL that is used to show a QR code.
+     *
+     * Account names may not contain a double colon (:). Valid account name
+     * examples:
+     *  - "John.Doe@gmail.com"
+     *  - "John Doe"
+     *  - "John_Doe_976"
+     *
+     * The Issuer may not contain a double colon (:). The issuer is recommended
+     * to pass along. If used, it will also be appended before the accountName.
+     *
+     * The previous examples with the issuer "Acme inc" would result in label:
+     *  - "Acme inc:John.Doe@gmail.com"
+     *  - "Acme inc:John Doe"
+     *  - "Acme inc:John_Doe_976"
+     *
+     * The contents of the label, issuer and secret will be encoded to generate
+     * a valid URL.
+     *
+     * @param string      $accountName The account name to show and identify
+     * @param string      $secret      The secret is the generated secret unique to that user
+     * @param string|null $issuer      Where you log in to
+     * @param int         $size        Image size in pixels, 200 will make it 200x200
+     *
+     * @return string
+     */
+    public static function generate(string $accountName, string $secret, string $issuer = null, int $size = 200): string
+    {
+        if ('' === $accountName || false !== strpos($accountName, ':')) {
+            throw RuntimeException::InvalidAccountName($accountName);
+        }
+
+        if ('' === $secret) {
+            throw RuntimeException::InvalidSecret();
+        }
+
+        $label = $accountName;
+        $otpauthString = 'otpauth://totp/%s?secret=%s';
+
+        if (null !== $issuer) {
+            if ('' === $issuer || false !== strpos($issuer, ':')) {
+                throw RuntimeException::InvalidIssuer($issuer);
+            }
+
+            // use both the issuer parameter and label prefix as recommended by Google for BC reasons
+            $label = $issuer.':'.$label;
+            $otpauthString .= '&issuer=%s';
+        }
+
+        $otpauthString = rawurlencode(sprintf($otpauthString, $label, $secret, $issuer));
+
+        return sprintf(
+            'https://chart.googleapis.com/chart?chs=%1$dx%1$d&chld=M|0&cht=qr&chl=%2$s',
+            $size,
+            $otpauthString
+        );
+    }
+}
+
+// NEXT_MAJOR: Remove class alias
+class_alias('Sonata\GoogleAuthenticator\GoogleQrUrl', 'Google\Authenticator\GoogleQrUrl', false);
diff --git a/vendor/sonata-project/google-authenticator/src/RuntimeException.php b/vendor/sonata-project/google-authenticator/src/RuntimeException.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d3cf5f63f2d3137dd8e9bc7095e8410ff49414d
--- /dev/null
+++ b/vendor/sonata-project/google-authenticator/src/RuntimeException.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the Sonata Project package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\GoogleAuthenticator;
+
+/**
+ * Contains runtime exception templates.
+ *
+ * @author Iltar van der Berg <kjarli@gmail.com>
+ */
+final class RuntimeException extends \RuntimeException
+{
+    public static function InvalidAccountName(string $accountName): self
+    {
+        return new self(sprintf(
+            'The account name may not contain a double colon (:) and may not be an empty string. Given "%s".',
+            $accountName
+        ));
+    }
+
+    public static function InvalidIssuer(string $issuer): self
+    {
+        return new self(sprintf(
+            'The issuer name may not contain a double colon (:) and may not be an empty string. Given "%s".',
+            $issuer
+        ));
+    }
+
+    public static function InvalidSecret(): self
+    {
+        return new self('The secret name may not be an empty string.');
+    }
+}
+
+// NEXT_MAJOR: Remove class alias
+class_alias('Sonata\GoogleAuthenticator\RuntimeException', 'Google\Authenticator\RuntimeException', false);