Skip to content

Commit

Permalink
Add csrf protection to api rotation, and some cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
Seldaek committed Aug 19, 2024
1 parent 4efe72b commit c8dac60
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 28 deletions.
2 changes: 1 addition & 1 deletion js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import 'bootstrap';
e.preventDefault();
});
});
$('.btn-rotate-api-token').on('click', function (e) {
$('.btn-rotate-api-token').click(function (e) {
if (!window.confirm('Are you sure? This will revoke your current API token and generate a new one.')) {
e.preventDefault();
}
Expand Down
15 changes: 9 additions & 6 deletions src/Controller/ProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class ProfileController extends Controller
{
#[Route(path: '/profile/', name: 'my_profile')]
public function myProfile(Request $req, FavoriteManager $favMgr, DownloadManager $dlMgr, #[CurrentUser] User $user): Response
public function myProfile(Request $req, FavoriteManager $favMgr, DownloadManager $dlMgr, #[CurrentUser] User $user, CsrfTokenManagerInterface $csrfTokenManager): Response
{
$packages = $this->getUserPackages($req, $user);
$lastGithubSync = $this->doctrine->getRepository(Job::class)->getLastGitHubSyncJob($user->getId());
Expand All @@ -46,6 +47,7 @@ public function myProfile(Request $req, FavoriteManager $favMgr, DownloadManager
if (!count($packages)) {
$data['deleteForm'] = $this->createFormBuilder([])->getForm()->createView();
}
$data['rotateApiCsrfToken'] = $csrfTokenManager->getToken('rotate_api');

return $this->render(
'user/my_profile.html.twig',
Expand Down Expand Up @@ -155,15 +157,16 @@ public function editAction(Request $request, UserNotifier $userNotifier): Respon
}

#[Route(path: '/profile/token/rotate', name: 'rotate_token', methods: ['POST'])]
public function tokenRotateAction(Request $request, UserNotifier $userNotifier): Response
public function tokenRotateAction(Request $request, #[CurrentUser] User $user, UserNotifier $userNotifier): Response
{
$user = $this->getUser();
if (!$user instanceof User) {
throw $this->createAccessDeniedException('This user does not have access to this section.');
if (!$this->isCsrfTokenValid('rotate_api', (string) $request->request->get('token'))) {
$this->addFlash('error', 'Invalid csrf token, try again.');
return $this->redirectToRoute('my_profile');
}
$user->initializeApiToken();

$user->initializeApiToken();
$userNotifier->notifyChange($user->getEmail(), 'Your API token has been rotated');
$this->addFlash('success', 'Your API token has been rotated');

$this->getEM()->persist($user);
$this->getEM()->flush();
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ public function isNotifiableForFailures(): bool

public function getGravatarUrl(): string
{
return 'https://www.gravatar.com/avatar/'.md5(strtolower($this->getEmail())).'?d=identicon';
return 'https://www.gravatar.com/avatar/'.hash('md5', strtolower($this->getEmail())).'?d=identicon';
}

public function setTotpSecret(string|null $secret): void
Expand Down
2 changes: 1 addition & 1 deletion src/Twig/PackagistExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public function prettifySourceReference(string $sourceReference): string

public function generateGravatarHash(string $email): string
{
return md5(strtolower($email));
return hash('md5', strtolower($email));
}

/**
Expand Down
8 changes: 6 additions & 2 deletions templates/user/edit.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
{{ form_errors(form.failureNotifications) }}
<label>
{{ form_widget(form.failureNotifications) }}
{{ 'profile.notify_on_failure'|trans }}
Notify me of package update failures
</label>
</div>

Expand All @@ -51,7 +51,11 @@
<a href="{{ app.user.githubId ? '#' : path('connect_github_start') }}" class="btn btn-block btn-github btn-lg {{ app.user.githubId ? 'disabled' : 'btn-primary' }}">
<span class="icon-github"></span>
{% set ghUser = app.user.githubUsername %}
{{ (app.user.githubId ? 'profile.accounts_connected' : 'profile.connect_accounts')|trans({"%user%": ghUser|default('unknown')}) }}
{% if app.user.githubId %}
Account connected to {{ ghUser|default('unknown') }}
{% else %}
Connect accounts
{% endif %}
</a>

{% if app.user.githubId %}
Expand Down
11 changes: 6 additions & 5 deletions templates/user/my_profile.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
{% block user_content %}
<section class="col-md-9">
{%- if app.user.apiToken %}
<h3 class="font-normal profile-title">{{ 'profile.your_api_token'|trans }}</h3>
<h3 class="font-normal profile-title">Your API Token</h3>

<div class="input-group api-token-group clearfix">
<input id="api-token" type="text" class="form-control" value="" data-api-token="{{ app.user.apiToken }}" readonly="readonly">
<span class="input-group-btn">
<button class="btn btn-success btn-show-api-token" type="button">{{ 'profile.show_api_token'|trans }}</button>
<button class="btn btn-success btn-show-api-token" type="button">Show API Token</button>
</span>
</div>

<p>{{ 'profile.api_token_explain'|trans({ '%path_about%':path('about') })|raw }}</p>
<p>{{ 'profile.api_token_rotate'|trans }}</p>
<p>You can use your API token to interact with the Packagist API, see details in <a href="{{ path('about') }}#how-to-update-packages">the docs</a>.</p>
<p>You can rotate your API token and generate a new one at any time.</p>
<form class="rotate action" action="{{ path('rotate_token') }}" method="POST">
<input class="btn btn-inverse btn-rotate-api-token" type="submit" value="{{ 'profile.api_token_rotate_submit'|trans }}" />
<input class="btn btn-inverse btn-rotate-api-token" type="submit" value="Rotate API Token" />
<input type="hidden" name="token" value="{{ rotateApiCsrfToken }}" />
</form>

<hr>
Expand Down
12 changes: 0 additions & 12 deletions translations/messages.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,6 @@ edit:
title: "Edit %name%"
submit: Update

profile:
your_api_token: Your API Token
show_api_token: Show API Token
accounts_connected: Account connected to %user%
connect_accounts: Connect accounts
notify_on_failure: Notify me of package update failures
api_token_explain: |
You can use your API token to interact with the Packagist API, see details in <a href="%path_about%#how-to-update-packages">the docs</a>.
api_token_rotate: |
You can rotate your API token and generate a new one at any time.
api_token_rotate_submit: Rotate API Token

stats:
title: Install Statistics
subtitle: install statistics
Expand Down

0 comments on commit c8dac60

Please sign in to comment.