Skip to content

Commit

Permalink
Save the date when a webauthn key was used last time for 2 factor aut…
Browse files Browse the repository at this point in the history
…hentication and show it in user settings
  • Loading branch information
jbtronics committed Apr 28, 2024
1 parent b886c0a commit db72dac
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/Entity/UserSystem/WebauthnKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampable

#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
protected ?\DateTimeInterface $last_time_used = null;

public function getName(): string
{
return $this->name;
Expand Down
106 changes: 106 additions & 0 deletions src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

declare(strict_types=1);


namespace App\Security\TwoFactor;

use App\Entity\UserSystem\WebauthnKey;
use Doctrine\ORM\EntityManagerInterface;
use Jbtronics\TFAWebauthn\Services\UserPublicKeyCredentialSourceRepository;
use Jbtronics\TFAWebauthn\Services\WebauthnProvider;
use Scheb\TwoFactorBundle\Security\TwoFactor\AuthenticationContextInterface;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorFormRendererInterface;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;

/**
* This class decorates the Webauthn TwoFactorProvider and adds additional logic which allows us to set a last used date
* on the used webauthn key, which can be viewed in the user settings.
*/
#[AsDecorator('jbtronics_webauthn_tfa.two_factor_provider')]
class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface
{

public function __construct(
#[AutowireDecorated]
private TwoFactorProviderInterface $decorated,
private EntityManagerInterface $entityManager,
#[Autowire(service: 'jbtronics_webauthn_tfa.user_public_key_source_repo')]
private UserPublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository,
#[Autowire(service: 'jbtronics_webauthn_tfa.webauthn_provider')]
private WebauthnProvider $webauthnProvider,
)
{
}

public function beginAuthentication(AuthenticationContextInterface $context): bool
{
return $this->decorated->beginAuthentication($context);
}

public function prepareAuthentication(object $user): void
{
$this->decorated->prepareAuthentication($user);
}

public function validateAuthenticationCode(object $user, string $authenticationCode): bool
{
//Try to extract the used webauthn key from the code
$webauthnKey = $this->getWebauthnKeyFromCode($authenticationCode);

//Perform the actual validation like normal
$tmp = $this->decorated->validateAuthenticationCode($user, $authenticationCode);

//Update the last used date of the webauthn key, if the validation was successful
if($tmp && $webauthnKey !== null) {
$webauthnKey->updateLastTimeUsed();
$this->entityManager->flush();
}

return $tmp;
}

public function getFormRenderer(): TwoFactorFormRendererInterface
{
return $this->decorated->getFormRenderer();
}

private function getWebauthnKeyFromCode(string $authenticationCode): ?WebauthnKey
{
$publicKeyCredentialLoader = $this->webauthnProvider->getPublicKeyCredentialLoader();

//Try to load the public key credential from the code
$publicKeyCredential = $publicKeyCredentialLoader->load($authenticationCode);

//Find the credential source for the given credential id
$publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($publicKeyCredential->rawId);

//If the credential source is not an instance of WebauthnKey, return null
if(!($publicKeyCredentialSource instanceof WebauthnKey)) {
return null;
}

return $publicKeyCredentialSource;
}
}
10 changes: 9 additions & 1 deletion templates/helper.twig
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,12 @@
{% endfor %}
</tbody>
</table>
{% endmacro parameters_table %}
{% endmacro parameters_table %}

{% macro format_date_nullable(datetime) %}
{% if datetime is null %}
<i>{% trans %}datetime.never{% endtrans %}</i>
{% else %}
{{ datetime|format_datetime }}
{% endif %}
{% endmacro %}
5 changes: 5 additions & 0 deletions templates/users/_2fa_settings.html.twig
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{# @var user \App\Entity\UserSystem\User #}

{% import "helper.twig" as helper %}

<div class="card mt-4">
<div class="card-header">
<i class="fa fa-shield-alt fa-fw" aria-hidden="true"></i>
Expand Down Expand Up @@ -124,6 +126,7 @@
<th>#</th>
<th>{% trans %}tfa_u2f.keys.name{% endtrans %}</th>
<th>{% trans %}tfa_u2f.keys.added_date{% endtrans %}</th>
<th>{% trans %}api_tokens.last_time_used{% endtrans %}</th>
<th></th>
</tr>
</thead>
Expand All @@ -133,6 +136,7 @@
<td>{{ loop.index }} <b>(U2F)</b></td>
<td>{{ key.name }}</td>
<td>{{ key.addedDate | format_datetime }}</td>
<td></td> {# For legacy keys no last time use date is saved #}
<td><button type="submit" class="btn btn-danger btn-sm" name="key_id" value="{{ key.id }}"><i class="fas fa-trash-alt fa-fw"></i> {% trans %}tfa_u2f.key_delete{% endtrans %}</button></td>
</tr>
{% endfor %}
Expand All @@ -141,6 +145,7 @@
<td>{{ loop.index }} <b>(WebAuthn)</b></td>
<td>{{ key.name }}</td>
<td>{{ key.addedDate | format_datetime }}</td>
<td>{{ helper.format_date_nullable(key.lastTimeUsed) }}</td>
<td><button type="submit" class="btn btn-danger btn-sm" name="webauthn_key_id" value="{{ key.id }}"><i class="fas fa-trash-alt fa-fw"></i> {% trans %}tfa_u2f.key_delete{% endtrans %}</button></td>
</tr>
{% endfor %}
Expand Down
14 changes: 4 additions & 10 deletions templates/users/_api_tokens.html.twig
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
{# @var user \App\Entity\UserSystem\User #}

{% macro format_date(datetime) %}
{% if datetime is null %}
<i>{% trans %}datetime.never{% endtrans %}</i>
{% else %}
{{ datetime|format_datetime }}
{% endif %}
{% endmacro %}
{% import "helper.twig" as helper %}

<div class="card mt-4">
<div class="card-header">
Expand Down Expand Up @@ -48,15 +42,15 @@
<small class="text-muted">{% trans%}api_token.ends_with{% endtrans%} ...<i>{{ api_token.lastTokenChars }}</i></small></td>
<td>{{ api_token.level.translationKey|trans }}</td>
<td>
{{ _self.format_date(api_token.validUntil) }}
{{ helper.format_date_nullable(api_token.validUntil) }}
{% if api_token.valid %}
<span class="badge bg-success badge-success">{% trans %}api_token.valid{% endtrans %}</span>
{% else %}
<span class="badge bg-warning badge-warning">{% trans %}api_token.expired{% endtrans %}</span>
{% endif %}
</td>
<td>{{ _self.format_date(api_token.addedDate) }}</td>
<td>{{ _self.format_date(api_token.lastTimeUsed) }}</td>
<td>{{ helper.format_date_nullable(api_token.addedDate) }}</td>
<td>{{ helper.format_date_nullable(api_token.lastTimeUsed) }}</td>
<td>
<button type="submit" class="btn btn-danger btn-sm" name="token_id"
value="{{ api_token.id }}" {% if not is_granted('@api.manage_tokens') %}disabled="disabled"{% endif %}>
Expand Down

0 comments on commit db72dac

Please sign in to comment.