mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Added an basic form to add Google Authenticator.
This commit is contained in:
parent
24672a30b9
commit
35b5640627
10 changed files with 277 additions and 3 deletions
|
@ -90,6 +90,8 @@ require('./jquery.tristate.js');
|
|||
|
||||
require('darkmode-js');
|
||||
|
||||
window.QRCode = require('qrcode');
|
||||
|
||||
require('../ts_src/ajax_ui');
|
||||
import {ajaxUI} from "../ts_src/ajax_ui";
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import {ajaxUI} from "./ajax_ui";
|
|||
import "bootbox";
|
||||
import "marked";
|
||||
import * as marked from "marked";
|
||||
import "qrcode";
|
||||
import {parse} from "marked";
|
||||
|
||||
/************************************
|
||||
|
@ -458,6 +459,16 @@ $(document).on("ajaxUI:start ajaxUI:reload attachment:create", function() {
|
|||
$('select.attachment_type_selector').change(updater).each(updater);
|
||||
});
|
||||
|
||||
$(document).on("ajaxUI:start ajaxUI:reload", function() {
|
||||
$('.qrcode').each(function() {
|
||||
let canvas = $(this);
|
||||
//@ts-ignore
|
||||
QRCode.toCanvas(canvas[0], canvas.data('content'), function(error) {
|
||||
if(error) console.error(error);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
//Need for proper body padding, with every navbar height
|
||||
$(window).resize(function () {
|
||||
let height : number = $('#navbar').height() + 10;
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
"marked": "^0.7.0",
|
||||
"patternfly-bootstrap-treeview": "^2.1.8",
|
||||
"pdfmake": "^0.1.53",
|
||||
"qrcode": "^1.4.4",
|
||||
"ts-loader": "^5.3.3",
|
||||
"typescript": "^3.3.4000"
|
||||
}
|
||||
|
|
|
@ -25,12 +25,14 @@ use App\Entity\Attachments\AttachmentType;
|
|||
use App\Entity\Attachments\UserAttachment;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Form\Permissions\PermissionsType;
|
||||
use App\Form\TFASettingsType;
|
||||
use App\Form\UserAdminForm;
|
||||
use App\Form\UserSettingsType;
|
||||
use App\Services\EntityExporter;
|
||||
use App\Services\EntityImporter;
|
||||
use App\Services\StructuralElementRecursionHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
|
||||
use Symfony\Component\Asset\Packages;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
|
@ -150,7 +152,7 @@ class UserController extends AdminPages\BaseAdminController
|
|||
/**
|
||||
* @Route("/settings", name="user_settings")
|
||||
*/
|
||||
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder)
|
||||
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator)
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
|
@ -224,6 +226,22 @@ class UserController extends AdminPages\BaseAdminController
|
|||
$this->addFlash('success', 'user.settings.pw_changed_flash');
|
||||
}
|
||||
|
||||
//Handle 2FA things
|
||||
$tfa_form = $this->createForm(TFASettingsType::class, $user);
|
||||
$tfa_form->handleRequest($request);
|
||||
if (!$user->getGoogleAuthenticatorSecret()) {
|
||||
$user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret());
|
||||
$tfa_form->setData($user);
|
||||
}
|
||||
|
||||
if ($tfa_form->isSubmitted() && $tfa_form->isValid()) {
|
||||
//Save 2FA settings (save secrets)
|
||||
$user->setGoogleAuthenticatorSecret($tfa_form->get('googleAuthenticatorSecret')->getData());
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.2fa.google.activated');
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
* Output both forms
|
||||
*****************************/
|
||||
|
@ -232,6 +250,13 @@ class UserController extends AdminPages\BaseAdminController
|
|||
'settings_form' => $form->createView(),
|
||||
'pw_form' => $pw_form->createView(),
|
||||
'page_need_reload' => $page_need_reload,
|
||||
|
||||
'tfa_form' => $tfa_form->createView(),
|
||||
'tfa_google' => [
|
||||
'qrContent' => $googleAuthenticator->getQRContent($user),
|
||||
'secret' => $user->getGoogleAuthenticatorSecret(),
|
||||
'username' => $user->getGoogleAuthenticatorUsername()
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
42
src/Form/TFASettingsType.php
Normal file
42
src/Form/TFASettingsType.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Validator\Constraints\ValidGoogleAuthCode;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ResetType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TFASettingsType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('google_confirmation', TextType::class, [
|
||||
'mapped' => false,
|
||||
'attr' => ['maxlength' => '6', 'minlength' => '6', 'pattern' => '\d*'],
|
||||
'constraints' => [new ValidGoogleAuthCode()]
|
||||
]);
|
||||
|
||||
$builder->add('googleAuthenticatorSecret', HiddenType::class,[
|
||||
'disabled' => false,
|
||||
]);
|
||||
|
||||
|
||||
$builder->add('submit', SubmitType::class);
|
||||
$builder->add('cancel', ResetType::class);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
]);
|
||||
}
|
||||
}
|
12
src/Validator/Constraints/ValidGoogleAuthCode.php
Normal file
12
src/Validator/Constraints/ValidGoogleAuthCode.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Validator\Constraints;
|
||||
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
class ValidGoogleAuthCode extends Constraint
|
||||
{
|
||||
|
||||
}
|
65
src/Validator/Constraints/ValidGoogleAuthCodeValidator.php
Normal file
65
src/Validator/Constraints/ValidGoogleAuthCodeValidator.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Validator\Constraints;
|
||||
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
|
||||
class ValidGoogleAuthCodeValidator extends ConstraintValidator
|
||||
{
|
||||
|
||||
protected $googleAuthenticator;
|
||||
|
||||
public function __construct(GoogleAuthenticator $googleAuthenticator)
|
||||
{
|
||||
$this->googleAuthenticator = $googleAuthenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!$constraint instanceof ValidGoogleAuthCode) {
|
||||
throw new UnexpectedTypeException($constraint, ValidGoogleAuthCode::class);
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_string($value)) {
|
||||
throw new UnexpectedValueException($value, 'string');
|
||||
}
|
||||
|
||||
if(!ctype_digit($value)) {
|
||||
$this->context->addViolation('validator.google_code.only_digits_allowed');
|
||||
}
|
||||
|
||||
//Number must have 6 digits
|
||||
if(strlen($value) !== 6) {
|
||||
$this->context->addViolation('validator.google_code.wrong_digit_count');
|
||||
}
|
||||
|
||||
//Try to retrieve the user we want to check
|
||||
if($this->context->getObject() instanceof FormInterface &&
|
||||
$this->context->getObject()->getParent() instanceof FormInterface
|
||||
&& $this->context->getObject()->getParent()->getData() instanceof User) {
|
||||
$user = $this->context->getObject()->getParent()->getData();
|
||||
|
||||
//Check if the given code is valid
|
||||
if(!$this->googleAuthenticator->checkCode($user, $value)) {
|
||||
$this->context->addViolation('validator.google_code.wrong_code');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
44
templates/Users/_2fa_settings.html.twig
Normal file
44
templates/Users/_2fa_settings.html.twig
Normal file
|
@ -0,0 +1,44 @@
|
|||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<i class="fa fa-shield-alt fa-fw" aria-hidden="true"></i>
|
||||
{% trans %}user.settings.2fa_settings{% endtrans %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ form_start(tfa_form) }}
|
||||
<ul class="nav nav-tabs" id="tfa-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="google-tab" data-toggle="tab" href="#tfa-google" role="tab"
|
||||
aria-controls="home" aria-selected="true">{% trans %}tfa.settings.google.tab{% endtrans %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="backup-tab" data-toggle="tab" href="#tfa-backup" role="tab"
|
||||
aria-controls="profile" aria-selected="false">{% trans %}tfa.settings.bakup.tab{% endtrans %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content mt-3 mb-3" id="tfa-tabs-content">
|
||||
<div class="tab-pane fade show active" id="tfa-google" role="tabpanel" aria-labelledby="google-tab">
|
||||
<div class="offset-3 row">
|
||||
<div class="col-3">
|
||||
<canvas class="qrcode" data-content="{{ tfa_google.qrContent }}"></canvas>
|
||||
</div>
|
||||
<div class="col-9 my-auto">
|
||||
<ol class="">
|
||||
<li>{% trans %}tfa_google.step.download{% endtrans %}</li>
|
||||
<li>{% trans %}tfa_google.step.scan{% endtrans %}</li>
|
||||
<li>{% trans %}tfa_google.step.input_code{% endtrans %}</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_row(tfa_form.google_confirmation) }}
|
||||
</div>
|
||||
<div class="tab-pane fade" id="tfa-backup" role="tabpanel" aria-labelledby="backup-tab">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_row(tfa_form.submit) }}
|
||||
{{ form_row(tfa_form.cancel) }}
|
||||
{{ form_end(tfa_form) }}
|
||||
</div>
|
||||
</div>
|
|
@ -47,6 +47,8 @@
|
|||
{% block content %}
|
||||
{{ parent() }}
|
||||
|
||||
{% include "Users/_2fa_settings.html.twig" %}
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<i class="fa fa-key fa-fw" aria-hidden="true"></i>
|
||||
|
|
74
yarn.lock
74
yarn.lock
|
@ -1490,12 +1490,30 @@ browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.8.2:
|
|||
electron-to-chromium "^1.3.322"
|
||||
node-releases "^1.1.42"
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
|
||||
|
||||
buffer-alloc@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-equal@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b"
|
||||
integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
|
||||
|
||||
buffer-from@^1.0.0, buffer-from@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
@ -1519,6 +1537,14 @@ buffer@^4.3.0:
|
|||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
buffer@^5.4.3:
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
|
||||
integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
builtin-status-codes@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||
|
@ -2431,6 +2457,11 @@ diffie-hellman@^5.0.0:
|
|||
miller-rabin "^4.0.0"
|
||||
randombytes "^2.0.0"
|
||||
|
||||
dijkstrajs@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b"
|
||||
integrity sha1-082BIh4+pAdCz83lVtTpnpjdxxs=
|
||||
|
||||
dir-glob@^2.0.0:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
|
||||
|
@ -3934,6 +3965,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
isarray@^2.0.1:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
|
@ -5060,6 +5096,11 @@ png-js@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.0.0.tgz#e5484f1e8156996e383aceebb3789fd75df1874d"
|
||||
integrity sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==
|
||||
|
||||
pngjs@^3.3.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
|
||||
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
|
||||
|
||||
popper.js@>=1.12.9, popper.js@^1.14.1, popper.js@^1.14.7:
|
||||
version "1.16.0"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.0.tgz#2e1816bcbbaa518ea6c2e15a466f4cb9c6e2fbb3"
|
||||
|
@ -5512,6 +5553,19 @@ q@^1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
||||
|
||||
qrcode@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83"
|
||||
integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==
|
||||
dependencies:
|
||||
buffer "^5.4.3"
|
||||
buffer-alloc "^1.2.0"
|
||||
buffer-from "^1.1.1"
|
||||
dijkstrajs "^1.0.1"
|
||||
isarray "^2.0.1"
|
||||
pngjs "^3.3.0"
|
||||
yargs "^13.2.4"
|
||||
|
||||
qs@6.7.0:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
|
@ -7086,7 +7140,7 @@ yargs-parser@^12.0.0:
|
|||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^13.1.0:
|
||||
yargs-parser@^13.1.0, yargs-parser@^13.1.1:
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
|
||||
integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
|
||||
|
@ -7128,3 +7182,19 @@ yargs@13.2.4:
|
|||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.0"
|
||||
|
||||
yargs@^13.2.4:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
|
||||
integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==
|
||||
dependencies:
|
||||
cliui "^5.0.0"
|
||||
find-up "^3.0.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^3.0.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue