finished user prfile and passwords
This commit is contained in:
parent
a488e489da
commit
560e96cf18
28
.eslintrc
28
.eslintrc
@ -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
1
.gitignore
vendored
@ -21,3 +21,4 @@ yarn-error.log
|
||||
/public/media/cache/
|
||||
###< liip/imagine-bundle ###
|
||||
/tools/php-cs-fixer/vendor/
|
||||
/public/uploads/
|
||||
|
4
TODO
4
TODO
@ -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
|
||||
|
||||
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
662
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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],
|
||||
];
|
||||
|
@ -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
|
@ -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'
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
4
config/packages/reset_password.yaml
Normal file
4
config/packages/reset_password.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
symfonycasts_reset_password:
|
||||
request_password_repository: App\Repository\ResetPasswordRequestRepository
|
||||
lifetime: 3600
|
||||
throttle_limit: 1
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -1,4 +0,0 @@
|
||||
api_platform:
|
||||
resource: .
|
||||
type: api_platform
|
||||
prefix: /api
|
@ -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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
@ -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 \'\'');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
73
migrations/Version20221028172857.php
Normal file
73
migrations/Version20221028172857.php
Normal 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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
33
migrations/Version20221030161847.php
Normal file
33
migrations/Version20221030161847.php
Normal 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');
|
||||
}
|
||||
}
|
16
src/Controller/BaseController.php
Normal file
16
src/Controller/BaseController.php
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
]);
|
||||
|
179
src/Controller/ResetPasswordController.php
Normal file
179
src/Controller/ResetPasswordController.php
Normal 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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
39
src/Entity/ResetPasswordRequest.php
Normal file
39
src/Entity/ResetPasswordRequest.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
54
src/Form/ChangePasswordFormType.php
Normal file
54
src/Form/ChangePasswordFormType.php
Normal 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: []);
|
||||
}
|
||||
}
|
45
src/Form/EditProfileFormType.php
Normal file
45
src/Form/EditProfileFormType.php
Normal 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'
|
||||
]);
|
||||
}
|
||||
}
|
49
src/Form/RegistrationFormType.php
Normal file
49
src/Form/RegistrationFormType.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
31
src/Form/ResetPasswordRequestFormType.php
Normal file
31
src/Form/ResetPasswordRequestFormType.php
Normal 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: []);
|
||||
}
|
||||
}
|
51
src/Repository/ResetPasswordRequestRepository.php
Normal file
51
src/Repository/ResetPasswordRequestRepository.php
Normal 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);
|
||||
}
|
||||
}
|
54
src/Security/EmailVerifier.php
Normal file
54
src/Security/EmailVerifier.php
Normal 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();
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
32
symfony.lock
32
symfony.lock
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
@ -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>
|
||||
|
||||
|
@ -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"> 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"> 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"> 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"> 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"> 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"> 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">
|
||||
|
@ -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>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends template_dir ~'base.html.twig' %}
|
||||
{% extends '@default/base.html.twig' %}
|
||||
|
||||
{% block title %}Privacy{% endblock %}
|
||||
|
||||
|
70
templates/themes/default/projects/index.html.twig
Normal file
70
templates/themes/default/projects/index.html.twig
Normal 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 %}
|
35
templates/themes/default/projects/show.html.twig
Normal file
35
templates/themes/default/projects/show.html.twig
Normal 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> <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 %}
|
22
templates/themes/default/security/forgot_password.html.twig
Normal file
22
templates/themes/default/security/forgot_password.html.twig
Normal 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 %}
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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 %}
|
60
templates/themes/default/security/register.html.twig
Normal file
60
templates/themes/default/security/register.html.twig
Normal 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 %}
|
||||
|
||||
|
@ -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 %}
|
||||
|
||||
|
12
templates/themes/default/security/reset_password.html.twig
Normal file
12
templates/themes/default/security/reset_password.html.twig
Normal 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 %}
|
@ -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>
|
@ -1,4 +1,4 @@
|
||||
{% extends '../themes/default/base.html.twig' %}
|
||||
{% extends '@default/base.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
Profile of {{ user.username }}
|
Loading…
Reference in New Issue
Block a user