Add basic auth + draft templates

This commit is contained in:
Nicolò P 2024-10-28 10:34:32 +01:00
parent a9ef27ccb6
commit da27b36eb4
13 changed files with 277 additions and 55 deletions

View File

@ -24,8 +24,35 @@ function showPasswd(selector) {
});
}
document.addEventListener('DOMContentLoaded', () => {
if (location.pathname.includes('login')) {
showPasswd('#show-pw');
console.log('whatever');
if (location.pathname.includes('login')) {
showPasswd('#show-pw');
document.querySelector('#submit > button').addEventListener('click', function () {
this.parentElement.classList.add('is-loading');
});
const error = document.querySelector('.is-danger');
if (error) {
error.querySelector('.delete').addEventListener('click', () => {
error.classList.add('is-hidden');
});
}
});
}
if (! location.pathname.includes('login')) {
const vocabs = document.querySelector('#vocabs');
document.querySelector('#for-vocabs').addEventListener('click', function () {
vocabs.classList.toggle('is-hidden');
if (this.firstElementChild.classList.contains('fa-angle-right')) {
this.firstElementChild.classList.remove('fa-angle-right');
this.firstElementChild.classList.add('fa-angle-down');
} else {
this.firstElementChild.classList.remove('fa-angle-down');
this.firstElementChild.classList.add('fa-angle-right');
}
});
}

BIN
assets/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
assets/img/guide.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
assets/img/media.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -4,4 +4,17 @@
body {
font-family: 'nunitolight';
}
}
.arcoa-menu {
background-color: rgba(66,135,199,0.98);
max-height: 100vh;
overflow-y: auto;
}
#vocabs a {
color: #fff;
}
/* For dev only*/
.sf-toolbar-clearer {
display: none !important;
}

View File

@ -4,7 +4,12 @@ security:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
# used to reload user from session & other features (e.g. switch_user)
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
@ -15,7 +20,7 @@ security:
check_path: app_login
enable_csrf: true
lazy: true
provider: users_in_memory
provider: app_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
@ -27,8 +32,8 @@ security:
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/$, roles: ROLE_USER }
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
when@test:
security:

View File

@ -11,7 +11,7 @@ class HomeController extends AbstractController
#[Route('/', name: 'app_home')]
public function index(): Response
{
return $this->render('index.html.twig', [
return $this->render('home/index.html.twig', [
'controller_name' => 'HomeController',
]);
}

View File

@ -2,6 +2,7 @@
namespace App\Controller;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

View File

@ -4,57 +4,44 @@ namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
class User
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 30)]
private ?string $firstName = null;
#[ORM\Column(length: 40)]
private ?string $lastName = null;
#[ORM\Column(length: 30)]
#[ORM\Column(length: 180)]
private ?string $username = null;
#[ORM\Column(length: 200)]
/**
* @var list<string> The user roles
*/
#[ORM\Column]
private array $roles = [];
/**
* @var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 30, nullable: true)]
private ?string $firstname = null;
#[ORM\Column(length: 40, nullable: true)]
private ?string $lastname = null;
public function getId(): ?int
{
return $this->id;
}
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setFirstName(string $firstName): static
{
$this->firstName = $firstName;
return $this;
}
public function getLastName(): ?string
{
return $this->lastName;
}
public function setLastName(string $lastName): static
{
$this->lastName = $lastName;
return $this;
}
public function getUsername(): ?string
{
return $this->username;
@ -67,6 +54,43 @@ class User
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->username;
}
/**
* @see UserInterface
*
* @return list<string>
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
/**
* @param list<string> $roles
*/
public function setRoles(array $roles): static
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): ?string
{
return $this->password;
@ -78,4 +102,37 @@ class User
return $this;
}
/**
* @see UserInterface
*/
public function eraseCredentials(): void
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(?string $firstname): static
{
$this->firstname = $firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->lastname;
}
public function setLastname(?string $lastname): static
{
$this->lastname = $lastname;
return $this;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
/**
* @extends ServiceEntityRepository<User>
*/
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
}
$user->setPassword($newHashedPassword);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
// /**
// * @return User[] Returns an array of User objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('u.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?User
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
<link rel="shortcut icon" type="image/png" href="{{ asset('img/favicon.png') }}">
{% block stylesheets %}
{% endblock %}
@ -19,7 +19,7 @@
<p><strong><a href="/about">About</a></strong></p>
<p><strong><a href="/credits">Credits</a> </strong></p>
<p>
<strong>ArCOA Data Entry</strong> - Copyright &copy; 2021 - {{ 'now' | date('Y') }}
<strong>ArCOA</strong> - Copyright &copy; 2021 - {{ 'now' | date('Y') }}
<a href="https://ispc.cnr.it">CNR-ISPC</a> - <a href="http://www.studilefili.unimi.it/ecm/home">UniMi</a>
</p>
</div>

View File

@ -0,0 +1,63 @@
{% extends 'base.html.twig' %}
{% block title %}Home | ArCOA{% endblock %}
{% block body %}
<div class="columns mb-0">
<div class="column is-one-fifth arcoa-menu mb-0">
<aside class="menu">
<p class="menu-label has-text-white mt-3 pt-5 pl-5 is-size-6">
Vocabularies
<span class="icon is-clickable pl-4" id="for-vocabs">
<i class="fa fa-angle-right"></i>
</span>
</p>
<ul class="pl-6 is-hidden has-text-white" id="vocabs">
<li><a>Access rights</a></li>
<li><a>Civilization</a></li>
<li><a>Copyright</a></li>
<li><a>Document format</a></li>
<li><a>Document type</a></li>
<li><a>Functional context</a></li>
<li><a>Language</a></li>
<li><a>License</a></li>
<li><a>Medium</a></li>
<li><a>Object class</a></li>
<li><a>Object type</a></li>
<li><a>Period</a></li>
<li><a>Script</a></li>
<li><a>Text type</a></li>
</ul>
</aside>
</div>
<div class="column mt-6 mb-6">
<div class="has-text-centered">
<img width="200px" src="{{ asset('img/Logo-ArCOA-def.png') }}" />
</div>
<h1 class="is-size-1 mt-6 mb-6 has-text-centered">ArCOA Digital Archive</h1>
<h2 class="is-size-3 mb-3 has-text-centered">Archivi e Collezioni dell'Oriente Antico</h2>
<h2 class="is-size-3 mb-6 has-text-centered">Archives and Collections of the Ancient Near East</h2>
<div class="columns" style="max-width: 35vw; margin: 0 auto;">
<div class="column mt-6 mb-5">
<div class="card content p-4 is-clickable">
<h3 class="is-size-5 has-text-centered">Resources</h3>
<figure class="figure has-text-centered">
<img src="{{ asset('img/media.png') }}" width="120px" />
</figure>
</div>
</div>
<div class="column mt-6 mb-5">
<div class="card content p-4 is-clickable">
<h3 class="is-size-5 has-text-centered">Guide</h3>
<figure class="figure has-text-centered">
<img src="{{ asset('img/guide.png') }}" width="120px"/>
</figure>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -4,7 +4,7 @@
{% block body %}
<div class="container mt-6">
<div class="container mt-6 mb-6">
<div class="has-text-centered">
<img width="200px" src="{{ asset('img/Logo-ArCOA-def.png') }}" />
</div>
@ -13,14 +13,10 @@
<div class="card" style="max-width: 40vw; margin: 0 auto;">
{% if error %}
<article class="message is-danger">
<div class="message-header">
<p>Authentication error</p>
<button class="delete" aria-label="delete"></button>
</div>
<div class="message-body">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
<div class="message-body">Wrong user name and/or password. Please retry</div>
</article>
{% endif %}
<form class="card-content p-5" action="{{ path('app_login') }}" id="login" method="post">
<form class="card-content pl-5 pr-5 pt-6" action="{{ path('app_login') }}" id="login" method="post">
<div class="field">
<h2 class="label is-size-3 has-text-centered">Sign in</h2>
</div>
@ -41,8 +37,8 @@
</div>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<div class="field mt-5">
<p class="control">
<button class="button is-link is-fullwidth">
<p class="control" id="submit">
<button class="button is-link is-fullwidth" type="submit">
Sign in
</button>
</p>