finished user prfile and passwords

This commit is contained in:
tracer 2022-11-01 14:57:36 +01:00
parent a488e489da
commit 560e96cf18
61 changed files with 1581 additions and 852 deletions

View File

@ -2,9 +2,7 @@
"extends": [
// add more generic rulesets here, such as:
"eslint:recommended",
"airbnb-base",
"plugin:vue/recommended",
"plugin:vue/vue3-recommended"
"airbnb-base"
],
"parserOptions": {
"parser": "espree",
@ -13,35 +11,13 @@
},
"env": {
"es6": true,
"browser": true,
"vue/setup-compiler-macros": true
"browser": true
},
"rules": {
"semi": [
"error",
"never"
],
"vue/no-v-html": "off",
"vue/html-indent": [
"error",
"tab"
],
"vue/html-closing-bracket-spacing": [
"error",
{
"startTag": "never",
"endTag": "never",
"selfClosingTag": "never"
}
],
"vue/component-api-style": [
"error",
[
"script-setup",
"composition"
]
// "script-setup", "composition", "composition-vue2", or "options"
],
"indent": [
"error",
"tab"

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ yarn-error.log
/public/media/cache/
###< liip/imagine-bundle ###
/tools/php-cs-fixer/vendor/
/public/uploads/

4
TODO
View File

@ -1,7 +1,7 @@
accent color #d43934
remove vue
use bootsrap
merge RequestController/SecurityController
don't use html in easyadmin / quotes
use turbo
make unit tests

View File

@ -9,9 +9,9 @@ import 'bootswatch/dist/slate/bootstrap.min.css'
$(document).ready(function () {
console.log('ready')
$('#toggleSidebar').on('click', function () {
$('#toggleIcon').toggleClass('fa fa-lg fa-fw fa-caret-square-o-left')
$('#toggleIcon').toggleClass('fa fa-lg fa-fw fa-caret-square-o-right')
let toggleIcon = $('#toggleIcon')
toggleIcon.toggleClass('fa fa-lg fa-fw fa-caret-square-o-left')
toggleIcon.toggleClass('fa fa-lg fa-fw fa-caret-square-o-right')
$('#sidebar').toggleClass('active')
})
})

View File

@ -15,7 +15,7 @@
}
html, body{
html, body {
margin: 0;
padding: 0;
//background: linear-gradient(90deg, rgba(14,14,16,1) 0%, rgba(255,128,64,1) 35%, rgba(14,14,16,1) 100%);
@ -35,6 +35,7 @@ $jet-black: #0e0e10;
#sidebar {
min-width: 180px;
max-width: 180px;
height: 100%;
display: inline;
white-space: nowrap;
overflow: hidden;
@ -54,3 +55,44 @@ $jet-black: #0e0e10;
display: none;
}
#main_content {
overflow-y: auto;
height: calc(100vh - 160px)
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: 50px auto auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input {
border-radius: 0;
}
.form-signin input[id="registration_form_username"] {
margin-bottom: -1px;
border-radius: 0.375rem 0.375rem 0 0;
}
.form-signin input[id="registration_form_password_second"] {
margin-bottom: 10px;
border-radius: 0 0 0.375rem 0.375rem;
}

View File

@ -7,7 +7,6 @@
"php": ">=8.1.0",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^2.6",
"doctrine/annotations": "^1.0",
"doctrine/doctrine-bundle": "^2.7",
"doctrine/doctrine-migrations-bundle": "^3.2",
@ -42,6 +41,7 @@
"symfony/proxy-manager-bridge": "6.1.*",
"symfony/runtime": "6.1.*",
"symfony/security-bundle": "6.1.*",
"symfony/security-csrf": "6.1.*",
"symfony/serializer": "6.1.*",
"symfony/string": "6.1.*",
"symfony/translation": "6.1.*",
@ -51,8 +51,11 @@
"symfony/webapp-meta": "^1.0",
"symfony/webpack-encore-bundle": "^1.14",
"symfony/yaml": "6.1.*",
"symfonycasts/reset-password-bundle": "^1.16",
"symfonycasts/verify-email-bundle": "^1.12",
"twig/extra-bundle": "^3.4",
"twig/markdown-extra": "^3.4",
"twig/twig": "3.3.10"
"twig/twig": "3.4.*"
},
"config": {
"allow-plugins": {

662
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ return [
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
Knp\Bundle\TimeBundle\KnpTimeBundle::class => ['all' => true],
FOS\CKEditorBundle\FOSCKEditorBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
];

View File

@ -1,8 +0,0 @@
api_platform:
mapping:
paths: ['%kernel.project_dir%/src/Entity']
patch_formats:
json: ['application/merge-patch+json']
swagger:
versions: [3]
show_webby: false

View File

@ -1,5 +0,0 @@
# Read the documentation: https://symfony.com/doc/current/bundles/FOSCKEditorBundle/index.html
twig:
form_themes:
- '@FOSCKEditor/Form/ckeditor_widget.html.twig'

View File

@ -1,7 +1,7 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
csrf_protection: true
http_method_override: false
# Enables session support. Note that the session will ONLY be started if you read or write from it.

View File

@ -16,7 +16,7 @@ framework:
# sync: 'sync://'
routing:
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
#Symfony\Component\Mailer\Messenger\SendEmailMessage: async
Symfony\Component\Notifier\Message\ChatMessage: async
Symfony\Component\Notifier\Message\SmsMessage: async

View File

@ -0,0 +1,4 @@
symfonycasts_reset_password:
request_password_repository: App\Repository\ResetPasswordRequestRepository
lifetime: 3600
throttle_limit: 1

View File

@ -22,13 +22,11 @@ security:
main:
lazy: true
provider: app_user_provider
# custom_authenticator: App\Security\LoginFormAuthenticator
json_login:
check_path: app_login
username_path: username
password_path: password
custom_authenticator: App\Security\LoginFormAuthenticator
##form_login:
# login_path: app_login
# check_path: app_login
logout:
path: app_logout
switch_user: true

View File

@ -1,7 +1,10 @@
twig:
default_path: '%kernel.project_dir%/templates'
# default_path: '%kernel.project_dir%/templates'
paths:
'%kernel.project_dir%/templates/themes/default': default
'%kernel.project_dir%/templates/admin': admin
form_themes:
- '@FOSCKEditor/Form/ckeditor_widget.html.twig'
- bootstrap_5_layout.html.twig
when@test:
twig:

View File

@ -1,4 +0,0 @@
api_platform:
resource: .
type: api_platform
prefix: /api

View File

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220409135404 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE user ADD first_name VARCHAR(255) DEFAULT NULL, ADD last_name VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `user` DROP first_name, DROP last_name');
}
}

View File

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220409150258 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE quotes (id INT AUTO_INCREMENT NOT NULL, quote LONGTEXT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE quotes');
}
}

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220410123550 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE projects (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE user CHANGE first_name first_name VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE projects');
$this->addSql('ALTER TABLE `user` CHANGE first_name first_name VARCHAR(255) DEFAULT \'\'');
}
}

View File

@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220410131552 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE projects_user (projects_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_B38D6A811EDE0F55 (projects_id), INDEX IDX_B38D6A81A76ED395 (user_id), PRIMARY KEY(projects_id, user_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE projects_user ADD CONSTRAINT FK_B38D6A811EDE0F55 FOREIGN KEY (projects_id) REFERENCES projects (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE projects_user ADD CONSTRAINT FK_B38D6A81A76ED395 FOREIGN KEY (user_id) REFERENCES `user` (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE projects ADD teaser_image VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE projects_user');
$this->addSql('ALTER TABLE projects DROP teaser_image');
}
}

View File

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220412144008 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE user ADD avatar VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `user` DROP avatar');
}
}

View File

@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220423085724 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE pages (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, name VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', modified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_2074E5757E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE pages ADD CONSTRAINT FK_2074E5757E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE pages');
}
}

View File

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220425082917 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE user ADD created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', ADD modified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `user` DROP created_at, DROP modified_at');
}
}

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20221028172857 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE pages (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, name VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', modified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', slug VARCHAR(255) NOT NULL, INDEX IDX_2074E5757E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE projects (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', teaser_image VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE projects_user (projects_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_B38D6A811EDE0F55 (projects_id), INDEX IDX_B38D6A81A76ED395 (user_id), PRIMARY KEY(projects_id, user_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE quotes (id INT AUTO_INCREMENT NOT NULL, quote LONGTEXT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL, INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE pages ADD CONSTRAINT FK_2074E5757E3C61F9 FOREIGN KEY (owner_id) REFERENCES user (id)');
$this->addSql('ALTER TABLE projects_user ADD CONSTRAINT FK_B38D6A811EDE0F55 FOREIGN KEY (projects_id) REFERENCES projects (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE projects_user ADD CONSTRAINT FK_B38D6A81A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE reset_password_request DROP FOREIGN KEY FK_7CE748AA76ED395');
$this->addSql('ALTER TABLE blog DROP FOREIGN KEY FK_C0155143DD7B2EBC');
$this->addSql('ALTER TABLE blog DROP FOREIGN KEY FK_C0155143F675F31B');
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CDAE07E97');
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CDD7B2EBC');
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CF675F31B');
$this->addSql('ALTER TABLE blog_section DROP FOREIGN KEY FK_C185C76CD823E37A');
$this->addSql('ALTER TABLE blog_section DROP FOREIGN KEY FK_C185C76CDAE07E97');
$this->addSql('DROP TABLE reset_password_request');
$this->addSql('DROP TABLE blog');
$this->addSql('DROP TABLE comment');
$this->addSql('DROP TABLE blog_section');
$this->addSql('DROP TABLE section');
$this->addSql('ALTER TABLE user ADD first_name VARCHAR(255) DEFAULT NULL, ADD last_name VARCHAR(255) DEFAULT NULL, ADD modified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', DROP last_login_at, CHANGE created_at created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE reset_password_request (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, selector VARCHAR(20) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, hashed_token VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, requested_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', expires_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_7CE748AA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
$this->addSql('CREATE TABLE blog (id INT AUTO_INCREMENT NOT NULL, author_id INT NOT NULL, edited_by_id INT DEFAULT NULL, title VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, teaser LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, teaser_image VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, content LONGTEXT CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, created_at DATETIME NOT NULL, edited_at DATETIME DEFAULT NULL, edit_reason VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, slug VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, INDEX IDX_C0155143DD7B2EBC (edited_by_id), UNIQUE INDEX UNIQ_C0155143989D9B62 (slug), INDEX IDX_C0155143F675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
$this->addSql('CREATE TABLE comment (id INT AUTO_INCREMENT NOT NULL, blog_id INT NOT NULL, author_id INT NOT NULL, edited_by_id INT DEFAULT NULL, title VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, content LONGTEXT CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, created_at DATETIME NOT NULL, edited_at DATETIME DEFAULT NULL, edit_reason VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, INDEX IDX_9474526CDD7B2EBC (edited_by_id), INDEX IDX_9474526CDAE07E97 (blog_id), INDEX IDX_9474526CF675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
$this->addSql('CREATE TABLE blog_section (blog_id INT NOT NULL, section_id INT NOT NULL, INDEX IDX_C185C76CDAE07E97 (blog_id), INDEX IDX_C185C76CD823E37A (section_id), PRIMARY KEY(blog_id, section_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
$this->addSql('CREATE TABLE section (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, description VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, teaser_image VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
$this->addSql('ALTER TABLE reset_password_request ADD CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)');
$this->addSql('ALTER TABLE blog ADD CONSTRAINT FK_C0155143DD7B2EBC FOREIGN KEY (edited_by_id) REFERENCES user (id)');
$this->addSql('ALTER TABLE blog ADD CONSTRAINT FK_C0155143F675F31B FOREIGN KEY (author_id) REFERENCES user (id)');
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CDAE07E97 FOREIGN KEY (blog_id) REFERENCES blog (id)');
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CDD7B2EBC FOREIGN KEY (edited_by_id) REFERENCES user (id)');
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CF675F31B FOREIGN KEY (author_id) REFERENCES user (id)');
$this->addSql('ALTER TABLE blog_section ADD CONSTRAINT FK_C185C76CD823E37A FOREIGN KEY (section_id) REFERENCES section (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE blog_section ADD CONSTRAINT FK_C185C76CDAE07E97 FOREIGN KEY (blog_id) REFERENCES blog (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE pages DROP FOREIGN KEY FK_2074E5757E3C61F9');
$this->addSql('ALTER TABLE projects_user DROP FOREIGN KEY FK_B38D6A811EDE0F55');
$this->addSql('ALTER TABLE projects_user DROP FOREIGN KEY FK_B38D6A81A76ED395');
$this->addSql('DROP TABLE pages');
$this->addSql('DROP TABLE projects');
$this->addSql('DROP TABLE projects_user');
$this->addSql('DROP TABLE quotes');
$this->addSql('DROP TABLE messenger_messages');
$this->addSql('ALTER TABLE user ADD last_login_at DATETIME DEFAULT NULL, DROP first_name, DROP last_name, DROP modified_at, CHANGE created_at created_at DATETIME NOT NULL');
}
}

View File

@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220424100610 extends AbstractMigration
final class Version20221030105205 extends AbstractMigration
{
public function getDescription(): string
{
@ -20,12 +20,12 @@ final class Version20220424100610 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE pages ADD slug VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE user ADD agreed_terms_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE pages DROP slug');
$this->addSql('ALTER TABLE user DROP agreed_terms_at');
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20221030161847 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE reset_password_request (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, selector VARCHAR(20) NOT NULL, hashed_token VARCHAR(100) NOT NULL, requested_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', expires_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_7CE748AA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE reset_password_request ADD CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE reset_password_request DROP FOREIGN KEY FK_7CE748AA76ED395');
$this->addSql('DROP TABLE reset_password_request');
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\User;
abstract class BaseController extends AbstractController
{
protected function getUser(): User
{
return parent::getUser();
}
}

View File

@ -10,27 +10,30 @@ use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
/**
*
*/
class FrontendController extends AbstractController
{
const TEMPLATE_DIR = 'themes/default/';
#[Route(path: '/', name: 'app_main')]
public function quote(QuotesRepository $quotesRepository): Response
{
$user = $this->getUser();
$quote = $quotesRepository->findOneRandom();
return $this->render(view: '@default/base.html.twig', parameters: [
'user' => $user,
'quote' => json_encode(value: $quote->getQuote())
]);
}
/**
* @throws Exception
*/
#[Route(path: '/', name: 'app_main')]
public function quote(SerializerInterface $serializer, QuotesRepository $quotesRepository): Response
#[Route(path: '/logout', name: 'app_logout')]
public function logout(): never
{
$quote = $quotesRepository->findOneRandom();
return $this->render(view: self::TEMPLATE_DIR . 'base.html.twig', parameters: [
'template_dir' => self::TEMPLATE_DIR,
'user' => $serializer->serialize(data: $this->getUser(), format: 'jsonld'),
'quote' => json_encode(value: $quote->getQuote())
]);
throw new Exception(message: 'Logout should never be reached.');
}
}

View File

@ -10,8 +10,6 @@ use Symfony\Component\Routing\Annotation\Route;
class PagesController extends AbstractController
{
const TEMPLATE_DIR = 'themes/default/';
#[Route(path: '/pages/{name}', name: 'pages_display')]
public function display(PagesRepository $pagesRepository, string $name): Response
{
@ -25,7 +23,7 @@ class PagesController extends AbstractController
$page->setContent(content: 'The requested page was not found.');
}
return $this->render(view: 'pages/display.html.twig', parameters: [
return $this->render(view: '@default/pages/display.html.twig', parameters: [
'page' => $page,
]);
}
@ -33,18 +31,12 @@ class PagesController extends AbstractController
#[Route(path: '/imprint', name: 'app_imprint')]
public function imprint(): Response
{
return $this->render(view: self::TEMPLATE_DIR . 'pages/imprint.html.twig', parameters: [
'template_dir' => self::TEMPLATE_DIR,
'controller_name' => 'PagesController',
]);
return $this->render(view: '@default/pages/imprint.html.twig');
}
#[Route(path: '/privacy', name: 'app_privacy')]
public function privacy(): Response
{
return $this->render(view: self::TEMPLATE_DIR . 'pages/privacy.html.twig', parameters: [
'template_dir' => self::TEMPLATE_DIR,
'controller_name' => 'PagesController',
]);
return $this->render(view: '@default/pages/privacy.html.twig');
}
}

View File

@ -13,7 +13,7 @@ class ProjectsController extends AbstractController
public function index(ProjectsRepository $projectsRepository, string $name = ''): Response
{
if ($name == '') {
return $this->render(view: 'projects/index.html.twig', parameters: [
return $this->render(view: '@default/projects/index.html.twig', parameters: [
'projects' => $projectsRepository->findAll(),
]);
} else {
@ -22,7 +22,7 @@ class ProjectsController extends AbstractController
// $parsedReadMe = $markdownParser->transformMarkdown(text: $readMe);
return $this->render(view: 'projects/show.html.twig', parameters: [
return $this->render(view: '@default/projects/show.html.twig', parameters: [
'project' => $project,
'readme' => $readMe,
]);

View File

@ -0,0 +1,179 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\ChangePasswordFormType;
use App\Form\ResetPasswordRequestFormType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
class ResetPasswordController extends AbstractController
{
use ResetPasswordControllerTrait;
public function __construct(
private readonly ResetPasswordHelperInterface $resetPasswordHelper,
private readonly EntityManagerInterface $entityManager
)
{
// empty body
}
/**
* Display & process form to request a password reset.
*/
#[Route(path: '/security/forgot/password', name: 'security_forgot_password')]
public function request(Request $request, MailerInterface $mailer): Response
{
$form = $this->createForm(type: ResetPasswordRequestFormType::class);
$form->handleRequest(request: $request);
if ($form->isSubmitted() && $form->isValid()) {
return $this->processSendingPasswordResetEmail(
formData: $form->get(name: 'account')->getData(),
mailer: $mailer
);
}
return $this->render(view: '@default/security/forgot_password.html.twig', parameters: [
'requestForm' => $form->createView(),
]);
}
/**
* Confirmation page after a user has requested a password reset.
*/
#[Route(path: '/security/recovery/mail/sent', name: 'security_recovery_mail_sent')]
public function checkEmail(): Response
{
// Generate a fake token if the user does not exist or someone hit this page directly.
// This prevents exposing whether a user was found with the given email address or username or not
if (null === ($resetToken = $this->getTokenObjectFromSession())) {
$resetToken = $this->resetPasswordHelper->generateFakeResetToken();
}
return $this->render(view: '@default/security/recovery_mail_sent.html.twig', parameters: [
'resetToken' => $resetToken,
]);
}
/**
* Validates and process the reset URL that the user clicked in their email.
*/
#[Route(path: '/security/recovery/reset/{token}', name: 'security_recovery_reset')]
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, string $token = null): Response
{
if ($token) {
// We store the token in session and remove it from the URL, to avoid the URL being
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
$this->storeTokenInSession(token: $token);
return $this->redirectToRoute(route: 'security_recovery_reset');
}
$token = $this->getTokenFromSession();
if (null === $token) {
throw $this->createNotFoundException(message: 'No reset password token found in the URL or in the session.');
}
try {
$user = $this->resetPasswordHelper->validateTokenAndFetchUser(fullToken: $token);
} catch (ResetPasswordExceptionInterface $e) {
$this->addFlash(type: 'reset_password_error', message: sprintf(
'%s - %s',
$translator->trans(id: ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, parameters: [], domain: 'ResetPasswordBundle'),
$translator->trans(id: $e->getReason(), parameters: [], domain: 'ResetPasswordBundle')
));
return $this->redirectToRoute(route: 'app_forgot_password_request');
}
// The token is valid; allow the user to change their password.
$form = $this->createForm(type: ChangePasswordFormType::class);
$form->handleRequest(request: $request);
if ($form->isSubmitted() && $form->isValid()) {
// A password reset token should be used only once, remove it.
$this->resetPasswordHelper->removeResetRequest(fullToken: $token);
// Encode(hash) the plain password, and set it.
$encodedPassword = $passwordHasher->hashPassword(
user: $user,
plainPassword: $form->get(name: 'plainPassword')->getData()
);
$user->setPassword($encodedPassword);
$this->entityManager->flush();
// The session is cleaned up after the password has been changed.
$this->cleanSessionAfterReset();
$this->addFlash(type: 'success', message: 'Your password has been changed.');
return $this->redirectToRoute(route: 'app_main');
}
return $this->render(view: '@default/security/reset_password.html.twig', parameters: [
'resetForm' => $form->createView(),
]);
}
private function processSendingPasswordResetEmail(string $formData, MailerInterface $mailer): RedirectResponse
{
$user = $this->entityManager->getRepository(entityName: User::class)->findOneBy(criteria: [
'email' => $formData,
]);
if (!$user) {
$user = $this->entityManager->getRepository(entityName: User::class)->findOneBy(criteria: [
'username' => $formData,
]);
}
// Do not reveal whether a user account was found or not.
// if (!$user) {
// return $this->redirectToRoute(route: 'app_check_email');
// }
try {
$resetToken = $this->resetPasswordHelper->generateResetToken(user: $user);
} catch (ResetPasswordExceptionInterface $e) {
$this->addFlash(type: 'reset_password_error', message: sprintf(
'%s - %s',
ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE,
$e->getReason()
));
return $this->redirectToRoute(route: 'security_forgot_password');
}
$email = (new TemplatedEmail())
->from(new Address(address: 'tracer@24unix.net', name: '24unix.net'))
->to($user->getEmail())
->subject(subject: 'Your password reset request')
->htmlTemplate(template: '@default/security/mail/recovery.html.twig')
->context(context: [
'resetToken' => $resetToken,
]);
$mailer->send(message: $email);
// Store the token object in session for retrieval in check-email route.
$this->setTokenObjectInSession(token: $resetToken);
return $this->redirectToRoute(route: 'security_recovery_mail_sent');
}
}

View File

@ -2,46 +2,117 @@
namespace App\Controller;
use ApiPlatform\Core\Api\IriConverterInterface;
use App\Entity\User;
use App\Form\LoginFormType;
use App\Form\RegistrationFormType;
use App\Repository\UserRepository;
use App\Security\EmailVerifier;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mime\Address;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Contracts\Translation\TranslatorInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
/**
*
*/
class SecurityController extends AbstractController
{
#[Route(path: '/login', name: 'app_login')] // *** method post
public function login(AuthenticationUtils $authenticationUtils, IriConverterInterface $iriConverter): Response
{
/** @var User $user */
$user = $this->getUser() ?? null;
return new Response(content: null, status: 204, headers: [
'Location' => $iriConverter->getIriFromItem(item: $user)
]);
public function __construct(private readonly EmailVerifier $emailVerifier)
{
// empty body
}
/*
return $this->render(view: 'security/login.html.twig', parameters: [
'error' => $authenticationUtils->getLastAuthenticationError(),
'last_username' => $authenticationUtils->getLastUsername(),
#[Route(path: '/login', name: 'app_login')]
public function index(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
if ($error = $authenticationUtils->getLastAuthenticationError()) {
$this->addFlash(type: 'error', message: $error->getMessageKey());
}
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(view: '@default/security/login.html.twig', parameters: [
'last_username' => $lastUsername,
'error' => '',
]);
*
}
/**
* @return mixed
*/
#[Route(path: '/logout', name: 'app_logout')]
public function logout(): never
#[Route(path: '/register', name: 'app_register')]
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
{
throw new Exception(message: 'Logout should never be reached.');
$form = $this->createForm(type: RegistrationFormType::class);
$form->handleRequest(request: $request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->getData();
// hash the plain password
$user->setPassword(
password: $userPasswordHasher->hashPassword(
user: $user,
plainPassword: $form->get(name: 'password')->getData()
)
);
if ($form->get(name: 'agreeTerms')->getData()) {
$user->agreeTerms();
} // no else, we already confirmed in the form itself
$entityManager->persist(entity: $user);
$entityManager->flush();
// generate a signed url and email it to the user
$this->emailVerifier->sendEmailConfirmation(verifyEmailRouteName: 'app_verify_email', user: $user,
email: (new TemplatedEmail())
->from(new Address(address: 'info@24unix.net', name: '24unix.net'))
->to($user->getEmail())
->subject(subject: 'Please Confirm your Email')
->htmlTemplate(template: '@default/security/mail/registration.html.twig')
->context(context: [
'username' => $user->getUsername()
])
);
return $this->render(view: '@default/security/registration_finished.html.twig');
}
return $this->render(view: '@default/security/register.html.twig', parameters: [
'registrationForm' => $form->createView(),
]);
}
#[Route(path: '/verify/email', name: 'app_verify_email')]
public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response
{
$id = $request->get(key: 'id');
if ($id === null) {
return $this->redirectToRoute(route: 'app_register');
}
$user = $userRepository->find($id);
if ($user === null) {
return $this->redirectToRoute(route: 'app_register');
}
// validate email confirmation link, sets User::isVerified=true and persists
try {
$this->emailVerifier->handleEmailConfirmation(request: $request, user: $user);
} catch (VerifyEmailExceptionInterface $exception) {
$this->addFlash(type: 'verify_email_error', message: $translator->trans(id: $exception->getReason(), parameters: [], domain: 'VerifyEmailBundle'));
return $this->redirectToRoute(route: 'app_main');
}
$this->addFlash(type: 'success', message: 'Your email address has been verified.');
return $this->redirectToRoute(route: 'app_main');
}
}

View File

@ -3,9 +3,13 @@
namespace App\Controller;
use App\Entity\User;
use App\Form\EditProfileFormType;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
@ -13,31 +17,56 @@ use Symfony\Component\Security\Core\Exception\UserNotFoundException;
/**
* Class UserController.
*/
class UserController extends AbstractController
class UserController extends BaseController
{
#[Route(path: '/profile/edit/{username}', name: 'app_profile_edit')]
public function editProfile(UserRepository $userRepository, string $username = ''): Response
public function editProfile(Request $request, UserRepository $userRepository, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager, string $username = ''): Response
{
/* var User $user */
if ($username === '') {
if ($this->isGranted(attribute: 'ROLE_USER')) {
$user = $this->getUser();
} else {
throw new AccessDeniedException(message: 'You need to be logged in.');
}
} else {
if ($username !== '') {
if ($this->isGranted(attribute: 'ROLE_ADMIN')) {
$user = $userRepository->findOneBy([
'username' => $username,
]);
} else {
throw new AccessDeniedException(message: 'Only admins are allowed to edit Profiles.');
}
} else {
$user = $this->getUser();
}
$form = $this->createForm(type: EditProfileFormType::class, data: $user);
$form->handleRequest(request: $request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->getData();
// if there's a new password, use it
if ($form->get(name: 'newPassword')->getData())
$user->setPassword(
password: $userPasswordHasher->hashPassword(
user: $user,
plainPassword: $form->get(name: 'newPassword')->getData()
)
);
$entityManager->persist(entity: $user);
$entityManager->flush();
return $this->redirectToRoute(route: 'app_main');
};
$user = $form->getData();
// hash the plain password
return $this->renderForm(view: '@default/user/edit_profile.html.twig', parameters: [
'user' => $user,
'userForm' => $form
]);
if (isset($user)) {
return $this->render(view: 'user/edit_profile.html.twig', parameters: [
'user' => $user,
]);
} else {
throw new UserNotFoundException();
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Entity;
use App\Repository\ResetPasswordRequestRepository;
use Doctrine\ORM\Mapping as ORM;
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestTrait;
#[ORM\Entity(repositoryClass: ResetPasswordRequestRepository::class)]
class ResetPasswordRequest implements ResetPasswordRequestInterface
{
use ResetPasswordRequestTrait;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?User $user = null;
public function __construct(User $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken)
{
$this->user = $user;
$this->initialize(expiresAt: $expiresAt, selector: $selector, hashedToken: $hashedToken);
}
public function getId(): ?int
{
return $this->id;
}
public function getUser(): object
{
return $this->user;
}
}

View File

@ -2,48 +2,40 @@
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use App\Repository\UserRepository;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Stringable;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* => ["security" => "is_granted('ROLE_ADMIN') or object.owner == user"]
attributes : ['security' => 'is_granted("ROLE_USER")']
*/
#[ORM\Entity(repositoryClass: UserRepository::class), ORM\HasLifecycleCallbacks]
#[ApiResource(
collectionOperations: ['get', 'post'],
itemOperations : ['get'],
)]
#[ApiFilter(filterClass: SearchFilter::class, properties: ['username' => 'exact'])]
#[UniqueEntity(fields: ['username', 'email'], message: 'There is already an account with this username or email.')]
class User implements UserInterface, PasswordAuthenticatedUserInterface, \Stringable
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'string', length: 180, unique: true)]
#[ORM\Column(type: 'string', length: 180, unique: true, nullable: false)]
private string $username;
#[ORM\Column(type: 'json')]
private array $roles = [];
#[ORM\Column(type: 'string')]
#[ORM\Column(type: 'string', nullable: false)]
private string $password;
private string $plainPassword;
#[ORM\Column(type: 'string', length: 255)]
#[ORM\Column(type: 'string', length: 255, nullable: false)]
#[Assert\Email(
message: 'The eMail {{ value }} is not a valid eMail.'
)]
private string $email;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
@ -67,15 +59,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, \String
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private $modifiedAt;
#[ORM\Column(type: 'boolean')]
private $isVerified = false;
#[ORM\Column]
private ?DateTimeImmutable $agreedTermsAt = null;
public function __construct()
{
$this->projects = new ArrayCollection();
$this->pages = new ArrayCollection();
}
/**
* @return string|null
*/
public function __toString(): string
{
return $this->username;
@ -86,11 +81,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, \String
return $this->id;
}
public function getPlainPassword(): string
{
return $this->plainPassword;
}
public function getUsername(): ?string
{
return $this->username;
@ -293,9 +283,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, \String
* @return string
*/
public function getAvatarUri()
{
return 'avatar';
}
{
return 'avatar';
}
#[ORM\PrePersist]
public function onPrePersist(): void
@ -308,4 +298,28 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, \String
{
$this->modifiedAt = new DateTimeImmutable(datetime: 'now');
}
public function isVerified(): bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): self
{
$this->isVerified = $isVerified;
return $this;
}
public function getAgreedTermsAt(): ?DateTimeImmutable
{
return $this->agreedTermsAt;
}
public function agreeTerms(): self
{
$this->agreedTermsAt = new DateTimeImmutable();
return $this;
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class ChangePasswordFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add(child: 'plainPassword', type: RepeatedType::class, options: [
'type' => PasswordType::class,
'options' => [
'attr' => [
'autocomplete' => 'new-password',
],
],
'first_options' => [
'constraints' => [
new NotBlank(options: [
'message' => 'Please enter a password',
]),
new Length(exactly: [
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
'label' => 'New password',
],
'second_options' => [
'label' => 'Repeat Password',
],
'invalid_message' => 'The password fields must match.',
// Instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(defaults: []);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
class EditProfileFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add(child: 'username')
->add(child: 'firstName')
->add(child: 'lastName')
->add(child: 'email', type: EmailType::class)
->add(child: 'newPassword', type: RepeatedType::class, options: [
'type' => PasswordType::class,
'mapped' => false,
'invalid_message' => 'The password fields must match.',
'options' => ['attr' => ['class' => 'password-field', 'autocomplete' => 'off']],
'required' => false,
'first_options' => ['label' => 'Password'],
'second_options' => ['label' => 'Repeat Password (only needed if you want to update the password'],
'constraints' => [new Length(exactly: ['min' => 6])]
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(defaults: [
'data_class' => User::class,
'csrf_protection' => true,
'csrf_field_name' => '_csrf_token',
'csrf_token_id' => 'authenticate'
]);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add(child: 'username')
->add(child: 'email', type: EmailType::class)
->add(child: 'password', type: RepeatedType::class, options: [
'type' => PasswordType::class,
'invalid_message' => 'The password fields must match.',
'options' => ['attr' => ['class' => 'password-field']],
'required' => true,
'first_options' => ['label' => 'Password'],
'second_options' => ['label' => 'Repeat Password'],
'constraints' => [new Length(exactly: ['min' => 6])]
])
->add(child: 'agreeTerms', type: CheckboxType::class, options: [
'mapped' => false,
'constraints' => [
new IsTrue(options: [
'message' => 'You should agree to our terms.',
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(defaults: [
'data_class' => User::class,
]);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
class ResetPasswordRequestFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add(child: 'account', type: null, options: [
'mapped' => false,
'constraints' => [
new NotBlank(options: [
'message' => 'Please enter your email or username',
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(defaults: []);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Repository;
use App\Entity\ResetPasswordRequest;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
use SymfonyCasts\Bundle\ResetPassword\Persistence\Repository\ResetPasswordRequestRepositoryTrait;
use SymfonyCasts\Bundle\ResetPassword\Persistence\ResetPasswordRequestRepositoryInterface;
/**
* @extends ServiceEntityRepository<ResetPasswordRequest>
*
* @method ResetPasswordRequest|null find($id, $lockMode = null, $lockVersion = null)
* @method ResetPasswordRequest|null findOneBy(array $criteria, array $orderBy = null)
* @method ResetPasswordRequest[] findAll()
* @method ResetPasswordRequest[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ResetPasswordRequestRepository extends ServiceEntityRepository implements ResetPasswordRequestRepositoryInterface
{
use ResetPasswordRequestRepositoryTrait;
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ResetPasswordRequest::class);
}
public function save(ResetPasswordRequest $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(ResetPasswordRequest $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function createResetPasswordRequest(object $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken): ResetPasswordRequestInterface
{
return new ResetPasswordRequest($user, $expiresAt, $selector, $hashedToken);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;
class EmailVerifier
{
public function __construct(
private readonly VerifyEmailHelperInterface $verifyEmailHelper,
private readonly MailerInterface $mailer,
private readonly EntityManagerInterface $entityManager
) {
}
public function sendEmailConfirmation(string $verifyEmailRouteName, User /* UserInterface */ $user, TemplatedEmail $email): void
{
$signatureComponents = $this->verifyEmailHelper->generateSignature(
routeName: $verifyEmailRouteName,
userId: $user->getId(),
userEmail: $user->getEmail(),
extraParams: ['id' => $user->getId()]
);
$context = $email->getContext();
$context['signedUrl'] = $signatureComponents->getSignedUrl();
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
$email->context(context: $context);
$this->mailer->send(message: $email);
}
/**
* @throws VerifyEmailExceptionInterface
*/
public function handleEmailConfirmation(Request $request, User /*UserInterface*/ $user): void
{
$this->verifyEmailHelper->validateEmailConfirmation(signedUrl: $request->getUri(), userId: $user->getId(), userEmail: $user->getEmail());
$user->setIsVerified(isVerified: true);
$this->entityManager->persist(entity: $user);
$this->entityManager->flush();
}
}

View File

@ -3,12 +3,17 @@
namespace App\Security;
use App\Entity\User;
use App\Form\LoginFormType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
@ -16,7 +21,9 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
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\CustomCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
/**
@ -38,15 +45,13 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
$csrfToken = $request->request->get(key: '_csrf_token');
$request->getSession()->set(name: Security::LAST_USERNAME, value: $username);
dd("here");
return new Passport(
userBadge: new UserBadge(userIdentifier: $username, userLoader: function ($userIdentifier) {
$user = $this->userRepository->findOneBy(['username' => $userIdentifier]);
userBadge: new UserBadge(userIdentifier: $username, userLoader: function ($username) {
$user = $this->userRepository->findOneBy(['username' => $username]);
if (!$user) {
$user = $this->userRepository->findOneBy(['email' => $userIdentifier]);
$user = $this->userRepository->findOneBy(['email' => $username]);
}
if (!$user) {
@ -56,10 +61,7 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
return $user;
}),
// remove me later for PasswordCredentials()
credentials: new CustomCredentials(customCredentialsChecker: fn($credentials, User $user) => $credentials === 'test', credentials : $password),
// new PasswordCredentials($password),
credentials: new PasswordCredentials(password: $password),
badges: [
new CsrfTokenBadge(csrfTokenId: 'authenticate', csrfToken: $csrfToken),
new RememberMeBadge(),
@ -82,4 +84,5 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
return $this->router->generate(name: 'app_login');
}
}

View File

@ -1,18 +1,4 @@
{
"api-platform/core": {
"version": "2.7",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.5",
"ref": "05b57782a78c21a664a42055dc11cf1954ca36bb"
},
"files": [
"config/packages/api_platform.yaml",
"config/routes/api_platform.yaml",
"src/Entity/.gitignore"
]
},
"doctrine/annotations": {
"version": "1.13",
"recipe": {
@ -303,5 +289,23 @@
"package.json",
"webpack.config.js"
]
},
"symfonycasts/reset-password-bundle": {
"version": "1.16",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "97c1627c0384534997ae1047b93be517ca16de43"
},
"files": [
"config/packages/reset_password.yaml"
]
},
"symfonycasts/verify-email-bundle": {
"version": "v1.12.0"
},
"twig/extra-bundle": {
"version": "v3.4.0"
}
}

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<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></svg>">
{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>

View File

@ -21,7 +21,7 @@
{% if is_granted('ROLE_USER') %}
<li class="nav-item dropdown me-auto">
<button type="button" id="navbar-dropdown" data-bs-target="#dropdown-menu" data-bs-toggle="dropdown"
class="btn btn-primary dropdown-toggle ml-auto mb-2 button-login">
class="btn btn-primary dropdown-toggle button-login">
{{ app.user.username }}
</button>

View File

@ -38,36 +38,36 @@
{% include '@default/_header.html.twig' %}
<!-- sidebar -->
<nav id="sidebar" class="m-3">
<ul class="list-group ml1" id="main-menu">
<li class="list-group-item list-group-item-action">
<a href="{{ path('app_projects') }}" class="text-decoration-none">
<i class="fa fa-lg fa-fw fa-file-code-o" aria-hidden="true" title="Projects"></i>
<span class="menuText">&nbsp;Projects</span>
</a>
</li>
<li class="list-group-item list-group-item-action">
<a href="//git.24unix.net" class="text-decoration-none" target="_blank">
<i class="fa fa-lg fa-fw fa-gitea" aria-hidden="true" title="Gitea"></i>
<span class="menuText">&nbsp;Gitea</span>
</a>
<span class="menuText"><i class="fa fa-external-link" aria-hidden="true"></i></span>
</li>
<li class="list-group-item list-group-item-action">
<a href="//cloud.24unix.net" class="text-decoration-none" target="_blank">
<i class="fa fa-lg fa-fw fa-nextcloud" aria-hidden="true" title="NextCloud"></i>
<span class="menuText">&nbsp;NextCloud</span>
</a>
<span class="menuText"><i class="fa fa-external-link" aria-hidden="true"></i></span>
</li>
<li class="list-group-item list-group-item-action">
<a href="#" id="toggleSidebar">
<i id="toggleIcon" class="fa fa-lg fa-caret-square-o-left"></i>
</a>
</li>
<!-- <a href="//pastebin.24unix.net">pastebin.24unix.net</a>-->
</ul>
</nav>
<nav id="sidebar" class="m-3">
<ul class="list-group ml1" id="main-menu">
<li class="list-group-item list-group-item-action">
<a href="{{ path('app_projects') }}" class="text-decoration-none">
<i class="fa fa-lg fa-fw fa-file-code-o" aria-hidden="true" title="Projects"></i>
<span class="menuText">&nbsp;Projects</span>
</a>
</li>
<li class="list-group-item list-group-item-action">
<a href="//git.24unix.net" class="text-decoration-none" target="_blank">
<i class="fa fa-lg fa-fw fa-gitea" aria-hidden="true" title="Gitea"></i>
<span class="menuText">&nbsp;Gitea</span>
</a>
<span class="menuText"><i class="fa fa-external-link" aria-hidden="true"></i></span>
</li>
<li class="list-group-item list-group-item-action">
<a href="//cloud.24unix.net" class="text-decoration-none" target="_blank">
<i class="fa fa-lg fa-fw fa-nextcloud" aria-hidden="true" title="NextCloud"></i>
<span class="menuText">&nbsp;NextCloud</span>
</a>
<span class="menuText"><i class="fa fa-external-link" aria-hidden="true"></i></span>
</li>
<li class="list-group-item list-group-item-action">
<a href="#" id="toggleSidebar">
<i id="toggleIcon" class="fa fa-lg fa-caret-square-o-left"></i>
</a>
</li>
<!-- <a href="//pastebin.24unix.net">pastebin.24unix.net</a>-->
</ul>
</nav>
<div class="container-fluid" id="content">
{% for message in app.flashes('success') %}
@ -82,7 +82,7 @@
</div>
{% endfor %}
<div class="col">
<div class="col m-3" id="main_content">
{% block body %}
<h1 class="q-title-show">Quote of the Moment</h1>
<div class="q-display p-3">

View File

@ -1,13 +1,16 @@
{% extends template_dir ~ 'base.html.twig' %}
{% extends '@default/base.html.twig' %}
{% block title %}Imprint{% endblock %}
{% block body %}
<div class="imprint-wrapper">
<p>This page is only available in German, as the server is located in Germany.</p>
<h2>This page is only available in German, as the server is located in Germany.</h2>
<p>Inhaltlich Verantwortlicher gemäß § 55 Abs. 2RStV: Michael Espey, Große Kirchreihe 27, 25377 Kollmar</p>
<p>Inhaltlich Verantwortlicher gemäß § 55 Abs. 2RStV:<br><br>
Michael Espey<br>
Große Kirchreihe 27<br>
25377 Kollmar</p>
<p>eMail: webmaster (AT) 24unix.net</p>

View File

@ -1,4 +1,4 @@
{% extends template_dir ~'base.html.twig' %}
{% extends '@default/base.html.twig' %}
{% block title %}Privacy{% endblock %}

View File

@ -0,0 +1,70 @@
{# templates/projects/index.html.twig #}
{% extends '@default/base.html.twig' %}
{% block title %} Projects {% endblock %}
{% block body %}
<div class="container-fluid box">
<div class="row">
<h2>This is an overview of my current public (and open source) projects.</h2>
<!-- projects List -->
<div class="col-sm-12">
{% for project in projects %}
<div class="project-container bg-dark my-4">
<div class="row">
<div class="col-sm-3">
<a href="{{ path('app_projects', { name: project.name }) }}">
{% if project.teaserImage %}
<img
class="blog-img"
src="/uploads/projects/{{ project.teaserImage }}"
alt="Teaser">
{% else %}
<img
class="blog-img"
src="{{ asset('build/images/24unix/24_logo_bg_96x96.png') }}"
alt="Teaser">
{% endif %}
</a>
<br>
<div>
{% for developer in project.developer %}
<a class="align-left blog-details"
href="{{ path('app_profile', { 'username':developer.username }) }}">
<img class="article-author-img rounded-circle"
src="{{ asset('build/images/tracer_schmolle.png') }}"
alt="profile"></a>
<a href="{{ path('app_profile', { 'username':developer.username }) }}">{{ developer.username }}</a>
{% endfor %}
</div>
</div>
<div class="col-sm-8 mt-2">
<a href="{{ path('app_projects', { name: project.name }) }}">
<div class="article-title d-inline-block pl-3 align-middle">
<h2>{{ project.name }}</h2>
</div>
</a>
<br>
<div class="blog-teaser mb-2 pb-2 text-xl-start">
{{ project.description }}
<br>
<br>
started: {{ project.createdAt | ago }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% if is_granted('ROLE_ADMIN') %}
<div class="text-xl-start">
<a href="{{ path('app_main') }}"><i class="fa fa-plus-circle"></i></a>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,35 @@
{# templates/blog/blog_show.html.twig #}
{% extends '@default/base.html.twig' %}
{% block title %}Projects{% endblock %}
{% block body %}
<div class="row">
<div class="col-sm-12">
{% if is_granted('ROLE_ADMIN') %}
<div class="d-flex justify-content-end">
<a href="{{ path('app_main', { id : project.id }) }}"><i class="fa fa-3x fa-fw fa-edit"></i></a>
<a href="{{ path('app_main', { id : project.id }) }}"><i class="fa fa-3x fa-fw fa-trash"></i></a>
</div>
{% endif %}
<div class="show-article-container p-3 mt-4">
<div class="show-article-title-container d-inline-block pl-3 align-middle">
<h2>{{ project.name }}</h2>
</div>
<div>
Source: <a href="{{ project.url }}" target="_blank">{{ project.url }}</a>&nbsp;<i
class="fa fa-external-link" aria-hidden="true"></i>
</div>
<div class="row">
<div class="col-sm-12">
<div class="article-text">
{{ readme | markdown_to_html }}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends '@default/base.html.twig' %}
{% block title %}Reset your password{% endblock %}
{% block body %}
{% for flash_error in app.flashes('reset_password_error') %}
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
{% endfor %}
<h1>Reset your password</h1>
{{ form_start(requestForm) }}
{{ form_row(requestForm.account) }}
<div>
<small>
Enter your email address or your username and we will send you a
link to reset your password.
</small>
</div>
<button class="btn btn-primary float-end">Send password reset email</button>
{{ form_end(requestForm) }}
{% endblock %}

View File

@ -1,23 +1,27 @@
{% extends '../themes/default/base.html.twig' %}
{% extends '@default/base.html.twig' %}
{% block title %}Log In!{% endblock %}
{% block body %}
<div class="container box">
<div class="container rounded">
<div class="row">
<div class="login-form bg-dark mt-4 p-4">
<form method="post" class="row g-3">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
{% if error %}
{% if error %}
<div class="alert-dark alert-danger">
{{ error.messageKey|trans(error.messageData, 'security') }}
</div>
{% endif %}
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<div class="col-12">
<label for="inputEmail">Username or eMail</label>
<input type="text" name="username" id="inputUsername" class="form-control" value="{{ last_username }}" required autofocus>
<label for="inputUsername">Username or eMail</label>
<input type="text" name="username" id="inputUsername"
class="form-control" {# value="{{ last_username }}" #} required autofocus>
</div>
<div class="col-12">
<label for="inputPassword">Password</label>
@ -28,13 +32,9 @@
<input type="checkbox" name="_remember_me" class="form-check-input">Remember me
</label>
</div>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<div class="col-12">
<button class="btn btn-lg btn-primary float-end" type="submit">
Sign in
</button>
</div>
<button class="btn btn-lg btn-primary float-end" type="submit">
Sign in
</button>
</form>
</div>
</div>

View File

@ -0,0 +1,9 @@
<h1>Hi!</h1>
<p>To reset your password, please visit the following link</p>
<a href="{{ url('security_recovery_reset', {token: resetToken.token}) }}">Reset password</a>
<p>This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.</p>
<p>Cheers!</p>

View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en" style="margin:0;padding:0;">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="format-detection" content="telephone=no"/>
<title></title>
<style type="text/css">
@media screen and (max-width: 599px) {
div {
max-width: 100% !important;
}
}
body {
background-color: #1e1f1e;
margin:25px;
padding:0;
}
</style>
</head>
<body>
<div style="border-collapse:collapse;border-spacing:0;">
<h1>Hi {{ username }}, please confirm your email!</h1>
<p>
Please confirm your email address by clicking the following link: <br><br>
<a href="{{ signedUrl|raw }}">Confirm my Email</a>.
<br>
This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
</p>
<p>
Kind regards,
24unix.net
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,11 @@
{% extends '@default/base.html.twig' %}
{% block title %}Password Reset Email Sent{% endblock %}
{% block body %}
<p>
If an account matching your email exists, then an email was just sent that contains a link that you can use to reset your password.
This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.
</p>
<p>If you don't receive an email please check your spam folder or <a href="{{ path('security_forgot_password') }}">try again</a>.</p>
{% endblock %}

View File

@ -0,0 +1,60 @@
{% extends '@default/base.html.twig' %}
{% form_theme registrationForm _self %}
{% block title %}Register{% endblock %}
{% block form_row %}
{%- set widget_attr = {} -%}
{%- if help is not empty -%}
{%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
{%- endif -%}
{{- form_label(form, null, {
label_attr: { class: 'sr-only' }
}) -}}
{{- form_errors(form) -}}
{{- form_widget(form, widget_attr) -}}
{{- form_help(form) -}}
{% endblock %}
{% block body %}
{% for flash_error in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
{% endfor %}
<div class="container rounded">
<div class="row">
<div class="login-form bg-dark mt-4 p-4">
<h1 class="h3 mb-3 font-weight-normal">Register</h1>
{{ form_start(registrationForm, {
attr: { class: 'form-signin' }
}) }}
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
{{ form_row(registrationForm.username, {
attr: { placeholder: 'User name' }
}) }}
{{ form_row(registrationForm.email, {
attr: { placeholder: 'eMail' }
}) }}
{{ form_row(registrationForm.password.first, {
attr: { placeholder: 'Password'}
}) }}
{{ form_row(registrationForm.password.second, {
attr: { placeholder: 'Confirm password'}
}) }}
{{ form_row(registrationForm.agreeTerms) }}
<button type="submit" class="btn btn-primary float-end">Register</button>
{{ form_end(registrationForm) }}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,19 @@
{% extends '@default/base.html.twig' %}
{% block title %}Registration finished{% endblock %}
{% block body %}
<div class="container rounded">
<div class="row">
<div class="login-form bg-dark mt-4 p-4">
<h1 class="h3 mb-3 font-weight-normal">Registration finished</h1>
<p>You'll receive an email soon to confirm your identity.</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends '@default/base.html.twig' %}
{% block title %}Reset your password{% endblock %}
{% block body %}
<h1>Reset your password</h1>
{{ form_start(resetForm) }}
{{ form_row(resetForm.plainPassword) }}
<button class="btn btn-primary">Reset password</button>
{{ form_end(resetForm) }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends '../themes/default/base.html.twig' %}
{% extends '@default/base.html.twig' %}
{% block title %}
Profile of {{ user.username }}
@ -13,37 +13,29 @@
<img class="rounded-circle mt-5"
width="150px"
src=" {{ asset('build/images/tracer_schmolle150x150.png') }}" alt="profile image">
<span class="font-weight-bold">{{ user.username }}</span>
<span class="text-white-50"><i class="fa fa-lg fa-envelope me-1"></i>{{ user.email }}</span>
</div>
</div>
<div class="col-md-5 border-right">
{{ form_start(userForm) }}
<div class="p-3 py-5">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="text-right">User Profile</h4>
</div>
<div class="row mt-2">
<div class="col-md-6">
<label class="labels" for="first-name">First Name</label>
<input type="text" id="first-name" class="form-control" placeholder="First Name" value="{{ user.firstName }}">
</div>
<div class="col-md-6">
<label class="labels" for="last-name">Last Name</label>
<input type="text" id="last-name" class="form-control" value="{{ user.lastName }}" placeholder="Last Name">
</div>
{{ form_row(userForm.username) }}
{{ form_row(userForm.firstName) }}
{{ form_row(userForm.lastName) }}
{{ form_row(userForm.email) }}
{{ form_row(userForm.newPassword.first) }}
{{ form_row(userForm.newPassword.second) }}
{{ form_rest(userForm) }}
</div>
<div class="col-md-6">
<label class="labels" for="username">Username</label>
<input type="text" class="form-control" id="username" placeholder="eMail address" value="{{ user.email }}">
<div class="mb-5 text-center float-end">
<button class="btn btn-primary profile-button" type="submit">Save Profile</button>
</div>
<!--
<div class="mt-5 text-center">
<button class="btn btn-primary profile-button" type="button">Save Profile</button>
</div>
-->
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
{% extends '../themes/default/base.html.twig' %}
{% extends '@default/base.html.twig' %}
{% block title %}
Profile of {{ user.username }}