Compare commits

..

No commits in common. "fix_symfony6_preparations" and "master" have entirely different histories.

10 changed files with 251 additions and 188 deletions

View file

@ -22,7 +22,7 @@
"composer/package-versions-deprecated": "1.11.99.5", "composer/package-versions-deprecated": "1.11.99.5",
"doctrine/annotations": "^1.0", "doctrine/annotations": "^1.0",
"doctrine/doctrine-bundle": "^2", "doctrine/doctrine-bundle": "^2",
"doctrine/doctrine-migrations-bundle": "^3", "doctrine/doctrine-migrations-bundle": "^2",
"doctrine/orm": "^2", "doctrine/orm": "^2",
"excelwebzone/recaptcha-bundle": "^1.5", "excelwebzone/recaptcha-bundle": "^1.5",
"pagerfanta/doctrine-orm-adapter": "^3.6", "pagerfanta/doctrine-orm-adapter": "^3.6",

173
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d075bf85a61c962f3316ab9d71fa4650", "content-hash": "bfa726a8284bf45a7ccbbb6600e02128",
"packages": [ "packages": [
{ {
"name": "babdev/pagerfanta-bundle", "name": "babdev/pagerfanta-bundle",
@ -724,38 +724,35 @@
}, },
{ {
"name": "doctrine/dbal", "name": "doctrine/dbal",
"version": "3.3.7", "version": "2.13.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/dbal.git", "url": "https://github.com/doctrine/dbal.git",
"reference": "9f79d4650430b582f4598fe0954ef4d52fbc0a8a" "reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/9f79d4650430b582f4598fe0954ef4d52fbc0a8a", "url": "https://api.github.com/repos/doctrine/dbal/zipball/c480849ca3ad6706a39c970cdfe6888fa8a058b8",
"reference": "9f79d4650430b582f4598fe0954ef4d52fbc0a8a", "reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer-runtime-api": "^2", "doctrine/cache": "^1.0|^2.0",
"doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3|^1", "doctrine/deprecations": "^0.5.3|^1",
"doctrine/event-manager": "^1.0", "doctrine/event-manager": "^1.0",
"php": "^7.3 || ^8.0", "ext-pdo": "*",
"psr/cache": "^1|^2|^3", "php": "^7.1 || ^8"
"psr/log": "^1|^2|^3"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "9.0.0", "doctrine/coding-standard": "9.0.0",
"jetbrains/phpstorm-stubs": "2022.1", "jetbrains/phpstorm-stubs": "2021.1",
"phpstan/phpstan": "1.7.13", "phpstan/phpstan": "1.4.6",
"phpstan/phpstan-strict-rules": "^1.2", "phpunit/phpunit": "^7.5.20|^8.5|9.5.16",
"phpunit/phpunit": "9.5.20",
"psalm/plugin-phpunit": "0.16.1", "psalm/plugin-phpunit": "0.16.1",
"squizlabs/php_codesniffer": "3.7.0", "squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^5.2|^6.0", "symfony/cache": "^4.4",
"symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", "symfony/console": "^2.0.5|^3.0|^4.0|^5.0",
"vimeo/psalm": "4.23.0" "vimeo/psalm": "4.22.0"
}, },
"suggest": { "suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files." "symfony/console": "For helpful console commands such as SQL execution and import of files."
@ -766,7 +763,7 @@
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Doctrine\\DBAL\\": "src" "Doctrine\\DBAL\\": "lib/Doctrine/DBAL"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -809,13 +806,14 @@
"queryobject", "queryobject",
"sasql", "sasql",
"sql", "sql",
"sqlanywhere",
"sqlite", "sqlite",
"sqlserver", "sqlserver",
"sqlsrv" "sqlsrv"
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/dbal/issues", "issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.3.7" "source": "https://github.com/doctrine/dbal/tree/2.13.9"
}, },
"funding": [ "funding": [
{ {
@ -831,7 +829,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-06-13T21:43:03+00:00" "time": "2022-05-02T20:28:55+00:00"
}, },
{ {
"name": "doctrine/deprecations", "name": "doctrine/deprecations",
@ -992,34 +990,30 @@
}, },
{ {
"name": "doctrine/doctrine-migrations-bundle", "name": "doctrine/doctrine-migrations-bundle",
"version": "3.2.2", "version": "2.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git",
"reference": "3393f411ba25ade21969c33f2053220044854d01" "reference": "0a081b55a88259a887af7be654743a8c5f703e99"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/3393f411ba25ade21969c33f2053220044854d01", "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/0a081b55a88259a887af7be654743a8c5f703e99",
"reference": "3393f411ba25ade21969c33f2053220044854d01", "reference": "0a081b55a88259a887af7be654743a8c5f703e99",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/doctrine-bundle": "~1.0|~2.0", "doctrine/doctrine-bundle": "~1.0|~2.0",
"doctrine/migrations": "^3.2", "doctrine/migrations": "^2.2",
"php": "^7.2|^8.0", "php": "^7.1|^8.0",
"symfony/framework-bundle": "~3.4|~4.0|~5.0|~6.0" "symfony/framework-bundle": "~3.4|~4.0|~5.0"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^8.0", "doctrine/coding-standard": "^8.0",
"doctrine/orm": "^2.6", "mikey179/vfsstream": "^1.6",
"doctrine/persistence": "^1.3||^2.0",
"phpstan/phpstan": "^0.12", "phpstan/phpstan": "^0.12",
"phpstan/phpstan-deprecation-rules": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpstan/phpstan-strict-rules": "^0.12", "phpstan/phpstan-strict-rules": "^0.12",
"phpunit/phpunit": "^8.0|^9.0", "phpunit/phpunit": "^7.0|^8.0|^9.0"
"vimeo/psalm": "^4.11"
}, },
"type": "symfony-bundle", "type": "symfony-bundle",
"autoload": { "autoload": {
@ -1041,11 +1035,11 @@
}, },
{ {
"name": "Doctrine Project", "name": "Doctrine Project",
"homepage": "https://www.doctrine-project.org" "homepage": "http://www.doctrine-project.org"
}, },
{ {
"name": "Symfony Community", "name": "Symfony Community",
"homepage": "https://symfony.com/contributors" "homepage": "http://symfony.com/contributors"
} }
], ],
"description": "Symfony DoctrineMigrationsBundle", "description": "Symfony DoctrineMigrationsBundle",
@ -1057,7 +1051,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues",
"source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.2.2" "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/2.2.3"
}, },
"funding": [ "funding": [
{ {
@ -1073,7 +1067,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-02-01T18:08:07+00:00" "time": "2021-03-18T20:55:50+00:00"
}, },
{ {
"name": "doctrine/event-manager", "name": "doctrine/event-manager",
@ -1408,63 +1402,49 @@
}, },
{ {
"name": "doctrine/migrations", "name": "doctrine/migrations",
"version": "3.5.1", "version": "2.3.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/migrations.git", "url": "https://github.com/doctrine/migrations.git",
"reference": "c0a01ddead0ccaf0282f3f4fcaa026d11918a481" "reference": "28d92a34348fee5daeb80879e56461b2e862fc05"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/migrations/zipball/c0a01ddead0ccaf0282f3f4fcaa026d11918a481", "url": "https://api.github.com/repos/doctrine/migrations/zipball/28d92a34348fee5daeb80879e56461b2e862fc05",
"reference": "c0a01ddead0ccaf0282f3f4fcaa026d11918a481", "reference": "28d92a34348fee5daeb80879e56461b2e862fc05",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer-runtime-api": "^2", "composer/package-versions-deprecated": "^1.8",
"doctrine/dbal": "^3.3", "doctrine/dbal": "^2.9",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.0",
"friendsofphp/proxy-manager-lts": "^1.0", "friendsofphp/proxy-manager-lts": "^1.0",
"php": "^7.4 || ^8.0", "php": "^7.1 || ^8.0",
"psr/log": "^1.1.3 || ^2 || ^3", "symfony/console": "^3.4||^4.4.16||^5.0",
"symfony/console": "^4.4.16 || ^5.4 || ^6.0", "symfony/stopwatch": "^3.4||^4.0||^5.0"
"symfony/stopwatch": "^4.4 || ^5.4 || ^6.0"
},
"conflict": {
"doctrine/orm": "<2.12"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^9", "doctrine/coding-standard": "^8.2",
"doctrine/orm": "^2.12", "doctrine/orm": "^2.6",
"doctrine/persistence": "^2 || ^3",
"doctrine/sql-formatter": "^1.0",
"ergebnis/composer-normalize": "^2.9",
"ext-pdo_sqlite": "*", "ext-pdo_sqlite": "*",
"phpstan/phpstan": "^1.5", "jdorn/sql-formatter": "^1.1",
"phpstan/phpstan-deprecation-rules": "^1", "mikey179/vfsstream": "^1.6",
"phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan": "^0.12",
"phpstan/phpstan-strict-rules": "^1.1", "phpstan/phpstan-deprecation-rules": "^0.12",
"phpstan/phpstan-symfony": "^1.1", "phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^9.5", "phpstan/phpstan-strict-rules": "^0.12",
"symfony/cache": "^4.4 || ^5.4 || ^6.0", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"symfony/process": "^4.4 || ^5.4 || ^6.0", "symfony/cache": "^4.4. || ^5.3",
"symfony/yaml": "^4.4 || ^5.4 || ^6.0" "symfony/process": "^3.4||^4.0||^5.0",
"symfony/yaml": "^3.4||^4.0||^5.0"
}, },
"suggest": { "suggest": {
"doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.",
"symfony/yaml": "Allows the use of yaml for migration configuration files." "symfony/yaml": "Allows the use of yaml for migration configuration files."
}, },
"bin": [ "bin": [
"bin/doctrine-migrations" "bin/doctrine-migrations"
], ],
"type": "library", "type": "library",
"extra": {
"composer-normalize": {
"indent-size": 4,
"indent-style": "space"
}
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Doctrine\\Migrations\\": "lib/Doctrine/Migrations" "Doctrine\\Migrations\\": "lib/Doctrine/Migrations"
@ -1493,11 +1473,12 @@
"keywords": [ "keywords": [
"database", "database",
"dbal", "dbal",
"migrations" "migrations",
"php"
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/migrations/issues", "issues": "https://github.com/doctrine/migrations/issues",
"source": "https://github.com/doctrine/migrations/tree/3.5.1" "source": "https://github.com/doctrine/migrations/tree/2.3.5"
}, },
"funding": [ "funding": [
{ {
@ -1513,7 +1494,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-09T20:24:38+00:00" "time": "2021-10-19T19:55:20+00:00"
}, },
{ {
"name": "doctrine/orm", "name": "doctrine/orm",
@ -2735,16 +2716,16 @@
}, },
{ {
"name": "php-http/discovery", "name": "php-http/discovery",
"version": "1.14.3", "version": "1.14.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-http/discovery.git", "url": "https://github.com/php-http/discovery.git",
"reference": "31d8ee46d0215108df16a8527c7438e96a4d7735" "reference": "c8d48852fbc052454af42f6de27635ddd916b959"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/31d8ee46d0215108df16a8527c7438e96a4d7735", "url": "https://api.github.com/repos/php-http/discovery/zipball/c8d48852fbc052454af42f6de27635ddd916b959",
"reference": "31d8ee46d0215108df16a8527c7438e96a4d7735", "reference": "c8d48852fbc052454af42f6de27635ddd916b959",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2796,9 +2777,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/php-http/discovery/issues", "issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.14.3" "source": "https://github.com/php-http/discovery/tree/1.14.2"
}, },
"time": "2022-07-11T14:04:40+00:00" "time": "2022-05-25T07:26:05+00:00"
}, },
{ {
"name": "php-http/httplug", "name": "php-http/httplug",
@ -3790,16 +3771,16 @@
}, },
{ {
"name": "sentry/sentry", "name": "sentry/sentry",
"version": "3.7.0", "version": "3.6.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/getsentry/sentry-php.git", "url": "https://github.com/getsentry/sentry-php.git",
"reference": "877bca3f0f0ac0fc8ec0a218c6070cccea266795" "reference": "5b8f2934b0b20bb01da11c76985ceb5bd6c6af91"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/877bca3f0f0ac0fc8ec0a218c6070cccea266795", "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/5b8f2934b0b20bb01da11c76985ceb5bd6c6af91",
"reference": "877bca3f0f0ac0fc8ec0a218c6070cccea266795", "reference": "5b8f2934b0b20bb01da11c76985ceb5bd6c6af91",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3845,7 +3826,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.7.x-dev" "dev-master": "3.6.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -3879,7 +3860,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/getsentry/sentry-php/issues", "issues": "https://github.com/getsentry/sentry-php/issues",
"source": "https://github.com/getsentry/sentry-php/tree/3.7.0" "source": "https://github.com/getsentry/sentry-php/tree/3.6.1"
}, },
"funding": [ "funding": [
{ {
@ -3891,7 +3872,7 @@
"type": "custom" "type": "custom"
} }
], ],
"time": "2022-07-18T07:55:36+00:00" "time": "2022-06-27T07:58:00+00:00"
}, },
{ {
"name": "sentry/sentry-symfony", "name": "sentry/sentry-symfony",
@ -4072,16 +4053,16 @@
}, },
{ {
"name": "spiral/roadrunner", "name": "spiral/roadrunner",
"version": "v2.10.7", "version": "v2.10.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/roadrunner-server/roadrunner.git", "url": "https://github.com/roadrunner-server/roadrunner.git",
"reference": "18a7a98bcb483a680b6ebe7da8bb61e95329daf4" "reference": "3996ba6d12f953f808408e276f69dfcf0950e906"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/18a7a98bcb483a680b6ebe7da8bb61e95329daf4", "url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/3996ba6d12f953f808408e276f69dfcf0950e906",
"reference": "18a7a98bcb483a680b6ebe7da8bb61e95329daf4", "reference": "3996ba6d12f953f808408e276f69dfcf0950e906",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4107,9 +4088,9 @@
"description": "RoadRunner: High-performance PHP application server, load-balancer and process manager written in Golang", "description": "RoadRunner: High-performance PHP application server, load-balancer and process manager written in Golang",
"support": { "support": {
"issues": "https://github.com/roadrunner-server/roadrunner/issues", "issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-server/roadrunner/tree/v2.10.7" "source": "https://github.com/roadrunner-server/roadrunner/tree/v2.10.5"
}, },
"time": "2022-07-14T09:00:44+00:00" "time": "2022-06-23T20:54:36+00:00"
}, },
{ {
"name": "spiral/roadrunner-cli", "name": "spiral/roadrunner-cli",

View file

@ -1,6 +1,5 @@
doctrine_migrations: doctrine_migrations:
migrations_paths: dir_name: '%kernel.project_dir%/src/Migrations'
'DoctrineMigrations': '%kernel.project_dir%/src/Migrations' # namespace is arbitrary but should be different from App\Migrations
storage: # as migrations classes should NOT be autoloaded
table_storage: namespace: DoctrineMigrations
table_name: 'migration_versions'

View file

@ -7,9 +7,6 @@ framework:
# https://symfony.com/doc/5.4/deployment/proxies.html#but-what-if-the-ip-of-my-reverse-proxy-changes-constantly # https://symfony.com/doc/5.4/deployment/proxies.html#but-what-if-the-ip-of-my-reverse-proxy-changes-constantly
trusted_proxies: '%env(TRUSTED_PROXIES)%' trusted_proxies: '%env(TRUSTED_PROXIES)%'
router:
utf8: true
# Enables session support. Note that the session will ONLY be started if you read or write from it. # Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support. # Remove or comment this section to explicitly disable session support.
session: session:

View file

@ -6,7 +6,6 @@ security:
class: App\Entity\User class: App\Entity\User
property: username property: username
manager_name: default manager_name: default
enable_authenticator_manager: true
password_hashers: password_hashers:
App\Entity\User: App\Entity\User:
algorithm: sodium algorithm: sodium
@ -16,11 +15,14 @@ security:
security: false security: false
api: api:
pattern: ^/api/ pattern: ^/api/
anonymous: ~
stateless: true stateless: true
custom_authenticators: guard:
- App\Security\ApiTokenAuthenticator authenticators:
- App\Security\ApiTokenAuthenticator
main: main:
pattern: ^/ pattern: ^/
anonymous: ~
provider: default_provider provider: default_provider
form_login: form_login:
login_path: user_auth_login login_path: user_auth_login
@ -38,10 +40,10 @@ security:
# Easy way to control access for large sections of your site # Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used # Note: Only the *first* access control that matches will be used
access_control: access_control:
- { path: ^/api/v1/login$, roles: PUBLIC_ACCESS } - { path: ^/api/v1/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/, roles: ROLE_USER } - { path: ^/api/, roles: ROLE_USER }
- { path: ^/$, roles: PUBLIC_ACCESS } - { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/auth/, roles: PUBLIC_ACCESS } - { path: ^/auth/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register/, roles: PUBLIC_ACCESS } - { path: ^/register/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/magnet/, roles: PUBLIC_ACCESS } - { path: ^/magnet/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER } - { path: ^/, roles: ROLE_USER }

View file

@ -11,7 +11,7 @@ abstract class AbstractApiController extends AbstractController
{ {
protected const DEFAULT_SERIALIZER_GROUPS = ['api']; protected const DEFAULT_SERIALIZER_GROUPS = ['api'];
protected function createJsonResponse($data = null, array $groups = [], int $code = Response::HTTP_OK, string $message = null, string $status = ''): JsonResponse protected function createJsonResponse($data, array $groups = [], int $code = Response::HTTP_OK, string $message = null, string $status = ''): JsonResponse
{ {
return $this->json(new ApiResponse($data, $code, $message, $status), $code, [], [ return $this->json(new ApiResponse($data, $code, $message, $status), $code, [], [
'groups' => array_merge(self::DEFAULT_SERIALIZER_GROUPS,$groups), 'groups' => array_merge(self::DEFAULT_SERIALIZER_GROUPS,$groups),

View file

@ -5,10 +5,11 @@ namespace App\Api\V1\Controller;
use App\Entity\{ApiToken, User}; use App\Entity\{ApiToken, User};
use App\Repository\{ApiTokenRepository, UserRepository}; use App\Repository\{ApiTokenRepository, UserRepository};
use App\Security\ApiTokenAuthenticator; use App\Security\Token\AuthenticatedApiToken;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\{JsonResponse, Request}; use Symfony\Component\HttpFoundation\{JsonResponse, Request};
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class SecurityController extends AbstractApiController class SecurityController extends AbstractApiController
{ {
@ -17,18 +18,18 @@ class SecurityController extends AbstractApiController
EntityManagerInterface $em, EntityManagerInterface $em,
UserRepository $userRepo, UserRepository $userRepo,
ApiTokenRepository $tokenRepo, ApiTokenRepository $tokenRepo,
UserPasswordHasherInterface $passwordHasher, UserPasswordEncoderInterface $passwordEncoder
): JsonResponse { ): JsonResponse {
$username = $request->request->get('username'); $username = $request->request->get('username');
$password = $request->request->get('password'); $password = $request->request->get('password');
/** @var User $user */ /** @var User $user */
if (null === $user = $userRepo->findOneBy(['username' => $username])) { if (null === $user = $userRepo->findOneBy(['username' => $username])) {
return $this->createJsonResponse(code: JsonResponse::HTTP_UNAUTHORIZED, message: 'User not found'); return $this->createJsonResponse(null, [], JsonResponse::HTTP_UNAUTHORIZED, 'User not found');
} }
if (!$passwordHasher->isPasswordValid($user, $password)) { if (!$passwordEncoder->isPasswordValid($user, $password)) {
return $this->createJsonResponse(code: JsonResponse::HTTP_UNAUTHORIZED, message: 'Invalid password'); return $this->createJsonResponse(null, [], JsonResponse::HTTP_UNAUTHORIZED, 'Invalid password');
} }
$apiToken = new ApiToken($user); $apiToken = new ApiToken($user);
@ -37,19 +38,28 @@ class SecurityController extends AbstractApiController
try { try {
$em->flush(); $em->flush();
} catch (\Exception $ex) { } catch (\Exception $ex) {
return $this->createJsonResponse(code: JsonResponse::HTTP_INTERNAL_SERVER_ERROR, message: 'Token persisting error'); return $this->createJsonResponse(null, [], JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Token persisting error');
} }
return $this->createJsonResponse($apiToken->getKey()); return $this->createJsonResponse($apiToken->getKey());
} }
public function logout(Request $request, ApiTokenRepository $apiTokenRepo, EntityManagerInterface $em): JsonResponse { public function logout(TokenStorageInterface $tokenStorage, ApiTokenRepository $apiTokenRepo, EntityManagerInterface $em): JsonResponse
if (null === $tokenKey = ApiTokenAuthenticator::getTokenKeyFromRequest($request)) { {
return $this->createJsonResponse(null, code:JsonResponse::HTTP_INTERNAL_SERVER_ERROR, message: 'No API token provided.'); if (null === $token = $tokenStorage->getToken()) {
return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Can\'t retrieve user token.');
}
if (!$token instanceof AuthenticatedApiToken) {
return $this->createJsonResponse(null, [], JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Invalid session token type retrieved.');
}
if (null === $tokenKey = $token->getTokenKey()) {
return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Can\'t retrieve token key from the session.');
} }
if (null === $apiToken = $apiTokenRepo->findOneBy(['key' => $tokenKey])) { if (null === $apiToken = $apiTokenRepo->findOneBy(['key' => $tokenKey])) {
return $this->createJsonResponse(null, code:JsonResponse::HTTP_INTERNAL_SERVER_ERROR, message: 'API token with such key not found in the database.'); return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'API token with such key not found in the database.');
} }
$em->remove($apiToken); $em->remove($apiToken);

View file

@ -24,7 +24,7 @@ class ApiResponse
#[Groups(['api'])] #[Groups(['api'])]
private ?string $message; private ?string $message;
/** Response body. In case of 'error' or 'fail' contains cause or exception name. */ /** @Response body. In case of 'error' or 'fail' contains cause or exception name. */
#[Groups(['api'])] #[Groups(['api'])]
private string|object|array|null $data; private string|object|array|null $data;

View file

@ -4,82 +4,125 @@ declare(strict_types=1);
namespace App\Security; namespace App\Security;
use App\Api\V1\DTO\ApiResponse; use App\Api\V1\DTO\ApiResponse;
use App\Entity\User;
use App\Repository\ApiTokenRepository; use App\Repository\ApiTokenRepository;
use Symfony\Component\HttpFoundation\{JsonResponse, Request, Response}; use App\Security\Token\AuthenticatedApiToken;
use Symfony\Component\HttpFoundation\{JsonResponse, Request, RequestStack, Response};
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\{AuthenticationException, CustomUserMessageAuthenticationException}; use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; use Symfony\Component\Security\Core\User\{UserInterface, UserProviderInterface};
use Symfony\Component\Security\Http\Authenticator\Passport\{Badge\UserBadge, Passport, SelfValidatingPassport}; use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
class ApiTokenAuthenticator extends AbstractAuthenticator /**
* @deprecated Refactor to new Authenticators system @see https://gitlab.com/skobkin/magnetico-web/-/issues/26
*/
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{ {
public const TOKEN_HEADER = 'api-token'; public const TOKEN_HEADER = 'api-token';
public function __construct( /** @var ApiTokenRepository */
private readonly SerializerInterface $serializer, private $tokenRepo;
private readonly ApiTokenRepository $tokenRepo,
) {
/** @var SerializerInterface */
private $serializer;
/** @var RequestStack */
private $requestStack;
public function __construct(SerializerInterface $serializer, ApiTokenRepository $tokenRepo, RequestStack $requestStack)
{
$this->serializer = $serializer;
$this->tokenRepo = $tokenRepo;
// Crutch for Guard simplified auth to retrieve 'api-token' header in the createAuthenticatedToken()
$this->requestStack = $requestStack;
} }
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*
* @see https://symfony.com/doc/6.1/security/custom_authenticator.html
*/
public function supports(Request $request): bool public function supports(Request $request): bool
{
return static::requestHasToken($request);
}
public function authenticate(Request $request): Passport
{
$tokenKey = static::getTokenKeyFromRequest($request);
if (null === $tokenKey) {
throw new CustomUserMessageAuthenticationException('No API token provided');
}
return new SelfValidatingPassport(
new UserBadge($tokenKey, function (string $userIdentifier) {
return $this->tokenRepo->findUserByTokenKey($userIdentifier);
})
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// No response object needed in token auth
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
{
// @todo Decouple with App\Api\V1\DTO
$json = $this->serializer->serialize(
new ApiResponse(null, JsonResponse::HTTP_UNAUTHORIZED, $exception->getMessageKey()),
'json',
['groups' => ['api']]
);
return new JsonResponse($json, JsonResponse::HTTP_UNAUTHORIZED, json: true);
}
public static function getTokenKeyFromRequest(Request $request): ?string
{
$request?->headers?->get(self::TOKEN_HEADER) ?:
$request?->cookies?->get(self::TOKEN_HEADER) ?:
$request?->query?->get(self::TOKEN_HEADER);
}
public static function requestHasToken(Request $request): bool
{ {
// Let's also support cookies and query params for some cases like torrent clients. // Let's also support cookies and query params for some cases like torrent clients.
return $request->headers->has(self::TOKEN_HEADER) || return $request->headers->has(self::TOKEN_HEADER) ||
$request->cookies->has(self::TOKEN_HEADER) || $request->cookies->has(self::TOKEN_HEADER) ||
$request->query->has(self::TOKEN_HEADER); $request->query->has(self::TOKEN_HEADER);
} }
public function getCredentials(Request $request)
{
return [
'token' => $request->headers->get(self::TOKEN_HEADER) ?:
$request->cookies->get(self::TOKEN_HEADER) ?:
$request->query->get(self::TOKEN_HEADER),
];
}
public function getUser($credentials, UserProviderInterface $userProvider): ?User
{
if (null === $token = $credentials['token']) {
return null;
}
return $this->tokenRepo->findUserByTokenKey($token);
}
public function start(Request $request, AuthenticationException $authException = null)
{
$message = sprintf('You need to use \'%s\' in your request: %s', self::TOKEN_HEADER, $authException ? $authException->getMessage() : '');
$json = $this->serializer->serialize(
new ApiResponse(null, JsonResponse::HTTP_UNAUTHORIZED, $message),
'json',
['groups' => ['api']]
);
return new JsonResponse($json, Response::HTTP_UNAUTHORIZED,[], true);
}
public function checkCredentials($credentials, UserInterface $user)
{
// No credentials check needed in case of token auth
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// No response object needed in token auth
return null;
}
public function supportsRememberMe()
{
// Remember me functionality don't needed in token auth
return false;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
{
// @todo Decouple with App\Api\V1\DTO
$json = $this->serializer->serialize(
new ApiResponse(null, JsonResponse::HTTP_UNAUTHORIZED, $exception->getMessage()),
'json',
['groups' => ['api']]
);
return new JsonResponse($json, JsonResponse::HTTP_UNAUTHORIZED,[], true);
}
/** @deprecated use AuthenticatorInterface::createToken() instead */
public function createAuthenticatedToken(UserInterface $user, $providerKey)
{
$request = $this->requestStack->getCurrentRequest();
$tokenKey = $request?->headers?->get(self::TOKEN_HEADER) ?:
$request?->cookies?->get(self::TOKEN_HEADER) ?:
$request?->query?->get(self::TOKEN_HEADER)
;
return new AuthenticatedApiToken(
$user,
$tokenKey,
$providerKey,
$user->getRoles()
);
}
} }

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Security\Token;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
/**
* This token stores ApiToken key even after eraseCredentials() called
*
* @deprecated Refactor to new Authenticators system @see https://gitlab.com/skobkin/magnetico-web/-/issues/26
*/
class AuthenticatedApiToken extends PreAuthenticatedToken implements GuardTokenInterface
{
/** @var string|null This token is stored only for this request and will not be erased by eraseCredentials() or serialized */
private $tokenKey;
public function __construct(User $user, string $credentials, string $providerKey, array $roles = [])
{
parent::__construct($user, $credentials, $providerKey, $roles);
// @todo probably separate constructor argument needed
$this->tokenKey = $credentials;
}
public function getTokenKey(): ?string
{
return $this->tokenKey;
}
}