add admin panel #20

Merged
csimonis merged 12 commits from feature/admin-panel into main 2025-02-09 14:59:31 +00:00
7 changed files with 71 additions and 22 deletions
Showing only changes of commit 40186c6149 - Show all commits

5
.env
View File

@ -16,4 +16,9 @@ DATABASE_URL="postgresql://${DB_USER:-db}:${DB_PW:-db}@${DB_HOST:-db}:${DB_PORT:
### STRIPE
STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY}
STRIPE_SECRET_KEY=${STRIPE_PUBLIC_KEY}
###
### ADMIN PANEL
USER_PASSWORD=${USER_PASSWORD}
ADMIN_PASSWORD=${ADMIN_PASSWORD}
###

View File

@ -1,4 +1,6 @@
###> symfony/framework-bundle ###
APP_SECRET=5a866a6ab3ce4ef99240ba643868b123
###< symfony/framework-bundle ###
USER_PASSWORD=\$2y\$13\$z/XlUykvakLzDR8TeFrQk.jmGuOKOcULlMY/m17aWmkY4f4NrIaam
ADMIN_PASSWORD=\$2y\$13\$z/XlUykvakLzDR8TeFrQk.jmGuOKOcULlMY/m17aWmkY4f4NrIaam

View File

@ -7,8 +7,8 @@ security:
users_in_memory:
memory:
users:
user: { password: '123', roles: ['ROLE_ADMIN'] }
admin: { password: '123', roles: ['ROLE_SUPER_ADMIN'] }
user: { password: '%env(USER_PASSWORD)%', roles: ['ROLE_ADMIN'] }
admin: { password: '%env(ADMIN_PASSWORD)%', roles: ['ROLE_SUPER_ADMIN'] }
firewalls:
dev:
@ -20,7 +20,10 @@ security:
custom_authenticator: App\Security\AdminPanelAuthenticator
form_login:
login_path: /admin/login
check_path: /admin/login
logout:
path: /admin/logout
target: /admin/login
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
@ -29,6 +32,10 @@ security:
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
role_hierarchy:
ROLE_SUPER_ADMIN: ROLE_ADMIN
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }

View File

@ -7,12 +7,16 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
#[Route(path: '/admin/login', name: 'admin_login', methods: Request::METHOD_GET)]
public function login(): Response
#[Route(path: '/admin/login', name: 'admin_login', methods: [Request::METHOD_GET, Request::METHOD_POST])]
public function login(AuthenticationUtils $authenticationUtils): Response
{
return $this->render('admin/login.html.twig');
return $this->render('admin/login.html.twig', [
'last_username' => $authenticationUtils->getLastUsername(),
'error' => $authenticationUtils->getLastAuthenticationError(),
]);
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\DataObjects;
use Symfony\Component\HttpFoundation\Request;
class LoginData
{
public function __construct(
public ?string $username = '',
public ?string $password = ''
) {
}
public static function fromRequest(Request $request): self
{
return new self(
$request->get('_username'),
$request->get('_password'),
);
}
}

View File

@ -2,29 +2,41 @@
namespace App\Security;
use App\DataObjects\LoginData;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
class AdminPanelAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
return str_starts_with($request->getRequestUri(), '/admin');
return str_starts_with($request->getRequestUri(), '/admin') && $request->isMethod(Request::METHOD_POST);
}
public function authenticate(Request $request): Passport
{
throw new CustomUserMessageAuthenticationException();
$data = LoginData::fromRequest($request);
if ($request->isMethod(Request::METHOD_POST) && (!$data->password || !$data->username)) {
dd($data);
throw new CustomUserMessageAuthenticationException();
}
return new Passport(new UserBadge($data->username), new PasswordCredentials($data->password), [new RememberMeBadge()]);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
return new RedirectResponse('/admin');
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response

View File

@ -4,10 +4,14 @@
<div class="min-h-screen flex items-center justify-center bg-[#0a0a0a] px-4 sm:px-6 lg:px-8">
<div class="w-full max-w-[90%] sm:max-w-md">
<div class="text-center mb-8">
<h2 class="text-xl sm:text-2xl font-medium text-gray-200">Administration</h2>
<h2 class="text-xl sm:text-2xl font-medium text-gray-200">Abiball Admin Panel</h2>
</div>
<form method="post" class="space-y-4 sm:space-y-6">
{% if error %}
{{ error.message }}
{% endif %}
<form method="post" class="space-y-4 sm:space-y-6" action="{{ path('admin_login') }}">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}" />
<div>
@ -21,7 +25,7 @@
name="_username"
required
class="block w-full pl-9 sm:pl-10 py-2 sm:py-2.5 text-sm sm:text-base bg-[#2a2a2a] border border-[#333333] text-gray-200 rounded-md focus:ring-2 focus:ring-orange-500/20 focus:border-orange-500 transition-colors"
placeholder="admin@example.com" />
placeholder="username" />
</div>
</div>
@ -40,15 +44,7 @@
</div>
</div>
<div class="flex items-center">
<input type="checkbox"
id="remember_me"
name="_remember_me"
class="h-3.5 w-3.5 sm:h-4 sm:w-4 bg-[#2a2a2a] border-[#333333] rounded text-orange-500 focus:ring-orange-500/20" />
<label for="remember_me" class="ml-2 block text-xs sm:text-sm text-gray-300">
Angemeldet bleiben
</label>
</div>
<hr>
<div>
<button type="submit"