Compare commits
2 Commits
v0.0.78
...
feat/202-c
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e1b431e57 | |||
| ce49785c79 |
7
.env
7
.env
@@ -39,3 +39,10 @@ DEFAULT_URI=http://localhost
|
|||||||
###> nelmio/cors-bundle ###
|
###> nelmio/cors-bundle ###
|
||||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||||
###< nelmio/cors-bundle ###
|
###< nelmio/cors-bundle ###
|
||||||
|
|
||||||
|
###> lexik/jwt-authentication-bundle ###
|
||||||
|
JWT_SECRET_KEY=
|
||||||
|
JWT_PUBLIC_KEY=
|
||||||
|
JWT_PASSPHRASE=
|
||||||
|
COOKIE_SECURE=1
|
||||||
|
###< lexik/jwt-authentication-bundle ###
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -8,6 +8,7 @@
|
|||||||
/var/
|
/var/
|
||||||
/vendor/
|
/vendor/
|
||||||
/LOG/
|
/LOG/
|
||||||
|
/config/jwt/*.pem
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
###> friendsofphp/php-cs-fixer ###
|
###> friendsofphp/php-cs-fixer ###
|
||||||
@@ -23,3 +24,7 @@
|
|||||||
###> docker ###
|
###> docker ###
|
||||||
docker/.env.docker.local
|
docker/.env.docker.local
|
||||||
###< docker ###
|
###< docker ###
|
||||||
|
|
||||||
|
###> lexik/jwt-authentication-bundle ###
|
||||||
|
/config/jwt/*.pem
|
||||||
|
###< lexik/jwt-authentication-bundle ###
|
||||||
|
|||||||
6
.idea/data_source_mapping.xml
generated
Normal file
6
.idea/data_source_mapping.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourcePerFileMappings">
|
||||||
|
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f407a514-c6b4-4b26-9555-445a85892502/console.sql" value="f407a514-c6b4-4b26-9555-445a85892502" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
3
.idea/ferme.iml
generated
3
.idea/ferme.iml
generated
@@ -145,6 +145,9 @@
|
|||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/masterminds/html5" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/masterminds/html5" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sabberworm/php-css-parser" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/sabberworm/php-css-parser" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/thecodingmachine/safe" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/thecodingmachine/safe" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/vendor/lcobucci/jwt" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/vendor/lexik/jwt-authentication-bundle" />
|
||||||
|
<excludePattern pattern="reference.php" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
2
.idea/php.xml
generated
2
.idea/php.xml
generated
@@ -151,6 +151,8 @@
|
|||||||
<path value="$PROJECT_DIR$/vendor/dompdf/php-svg-lib" />
|
<path value="$PROJECT_DIR$/vendor/dompdf/php-svg-lib" />
|
||||||
<path value="$PROJECT_DIR$/vendor/sabberworm/php-css-parser" />
|
<path value="$PROJECT_DIR$/vendor/sabberworm/php-css-parser" />
|
||||||
<path value="$PROJECT_DIR$/vendor/dompdf/php-font-lib" />
|
<path value="$PROJECT_DIR$/vendor/dompdf/php-font-lib" />
|
||||||
|
<path value="$PROJECT_DIR$/vendor/lexik/jwt-authentication-bundle" />
|
||||||
|
<path value="$PROJECT_DIR$/vendor/lcobucci/jwt" />
|
||||||
</include_path>
|
</include_path>
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
|
<component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
|
||||||
|
|||||||
69
.idea/workspace.xml
generated
69
.idea/workspace.xml
generated
@@ -4,13 +4,8 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : ajout de l'authentification avec lexik">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/CHANGELOG.md" beforeDir="false" afterPath="$PROJECT_DIR$/CHANGELOG.md" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -195,6 +190,8 @@
|
|||||||
<path value="$PROJECT_DIR$/vendor/dompdf/php-svg-lib" />
|
<path value="$PROJECT_DIR$/vendor/dompdf/php-svg-lib" />
|
||||||
<path value="$PROJECT_DIR$/vendor/sabberworm/php-css-parser" />
|
<path value="$PROJECT_DIR$/vendor/sabberworm/php-css-parser" />
|
||||||
<path value="$PROJECT_DIR$/vendor/dompdf/php-font-lib" />
|
<path value="$PROJECT_DIR$/vendor/dompdf/php-font-lib" />
|
||||||
|
<path value="$PROJECT_DIR$/vendor/lexik/jwt-authentication-bundle" />
|
||||||
|
<path value="$PROJECT_DIR$/vendor/lcobucci/jwt" />
|
||||||
</include_path>
|
</include_path>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectColorInfo">{
|
<component name="ProjectColorInfo">{
|
||||||
@@ -207,28 +204,31 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent">{
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"git-widget-placeholder": "feat/reception-generation-bon",
|
"git-widget-placeholder": "feat/202-connexion-utilisateur",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"settings.editor.selected.configurable": "reference.webide.settings.project.settings.php.debug",
|
"settings.editor.selected.configurable": "editor.preferences.tabs",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
},
|
},
|
||||||
"keyToStringList": {
|
"keyToStringList": {
|
||||||
"vue.recent.templates": [
|
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
||||||
"Vue Composition API Component"
|
"TEXT"
|
||||||
|
],
|
||||||
|
"vue.recent.templates": [
|
||||||
|
"Vue Composition API Component"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}</component>
|
}]]></component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
<recent name="\\wsl.localhost\Ubuntu-24.04\home\tristan\workspace\ferme\templates" />
|
<recent name="\\wsl.localhost\Ubuntu-24.04\home\tristan\workspace\ferme\templates" />
|
||||||
@@ -331,7 +331,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1768555180530</updated>
|
<updated>1768555180530</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="10" />
|
<task id="LOCAL-00010" summary="feat : ajout de l'authentification avec lexik">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1768832208350</created>
|
||||||
|
<option name="number" value="00010" />
|
||||||
|
<option name="presentableId" value="LOCAL-00010" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1768832208350</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="11" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -387,6 +395,11 @@
|
|||||||
<MESSAGE value="test : ajout de TU sur les services et providers" />
|
<MESSAGE value="test : ajout de TU sur les services et providers" />
|
||||||
<MESSAGE value="feat : ajout de la génération du bon de reception, correction de la base du formulaire multi-etape de reception et ajout d'une gestion d'erreur global" />
|
<MESSAGE value="feat : ajout de la génération du bon de reception, correction de la base du formulaire multi-etape de reception et ajout d'une gestion d'erreur global" />
|
||||||
<MESSAGE value="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur" />
|
<MESSAGE value="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur" />
|
<MESSAGE value="feat : ajout de l'authentification avec lexik" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat : ajout de l'authentification avec lexik" />
|
||||||
|
</component>
|
||||||
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
|
<expand />
|
||||||
|
<select />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -9,12 +9,17 @@ Ajouter dans le fichier .env
|
|||||||
- DATABASE_URL
|
- DATABASE_URL
|
||||||
- PONT_BASCULE_BYPASS (doit être à true en dev)
|
- PONT_BASCULE_BYPASS (doit être à true en dev)
|
||||||
- PONT_BASCULE_URL
|
- PONT_BASCULE_URL
|
||||||
|
- JWT_SECRET_KEY (à générer avec la commande php bin/console lexik:jwt:generate-keypair)
|
||||||
|
- JWT_PUBLIC_KEY
|
||||||
|
- JWT_PASSPHRASE (à généré dans le conteneur avec la commande php -r "echo bin2hex(random_bytes(32));")
|
||||||
|
- COOKIE_SECURE=0 (en dev 0 et en prod 1)
|
||||||
|
|
||||||
Ajouter dans le fichier .env du frontend
|
Ajouter dans le fichier .env du frontend
|
||||||
- NUXT_PUBLIC_API_BASE
|
- NUXT_PUBLIC_API_BASE
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* [#203] Réceptions — Parcours de pesée multi-étapes (début)
|
* [#203] Réceptions — Parcours de pesée multi-étapes (début)
|
||||||
|
* [#202] Authentification — Connexion utilisateur (JWT)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -21,13 +21,17 @@ Dans le cas ou le `make start` plante à cause du port de la bdd, il faut modifi
|
|||||||
Pour les variables d'environnement, il faut demander un .env.local pour le backend et un .env pour le frontend à votre collègue.
|
Pour les variables d'environnement, il faut demander un .env.local pour le backend et un .env pour le frontend à votre collègue.
|
||||||
|
|
||||||
Vérifier que dans le .env.local, vous avez :
|
Vérifier que dans le .env.local, vous avez :
|
||||||
* APP_SECRET (doit être différent de celui de votre collègue, puisque utilisé pour signer des tokens)
|
* APP_SECRET (à généré dans le conteneur avec la commande php -r "echo bin2hex(random_bytes(32));" et doit être différent de celui de votre collègue, puisque utilisé pour signer des tokens)
|
||||||
* DATABASE_URL
|
* DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/${POSTGRES_DB}?serverVersion=16&charset=utf8"
|
||||||
* PONT_BASCULE_BYPASS (doit être à true en dev)
|
* PONT_BASCULE_BYPASS (doit être à true en dev)
|
||||||
* PONT_BASCULE_URL
|
* PONT_BASCULE_URL
|
||||||
|
* JWT_SECRET_KEY (à générer avec la commande php bin/console lexik:jwt:generate-keypair)
|
||||||
|
* JWT_PUBLIC_KEY
|
||||||
|
* JWT_PASSPHRASE (à généré dans le conteneur avec la commande php -r "echo bin2hex(random_bytes(32));")
|
||||||
|
* COOKIE_SECURE=0 (en dev 0 et en prod 1)
|
||||||
|
|
||||||
Vérifier que dans le .env du dossier frontend, vous avez :
|
Vérifier que dans le .env du dossier frontend, vous avez :
|
||||||
* NUXT_PUBLIC_API_BASE
|
* NUXT_PUBLIC_API_BASE="http://localhost:8080/api"
|
||||||
|
|
||||||
### Configuration xdebug
|
### Configuration xdebug
|
||||||
Pour configurer xdebug, il faut ajouter un serveur sur phpstorm. <br>
|
Pour configurer xdebug, il faut ajouter un serveur sur phpstorm. <br>
|
||||||
@@ -54,6 +58,19 @@ make dev-nuxt
|
|||||||
```
|
```
|
||||||
Le front sera accessible sur http://localhost:3000
|
Le front sera accessible sur http://localhost:3000
|
||||||
|
|
||||||
|
### Authentification
|
||||||
|
Ce projet utilise l'authentification JWT avec un cookie httpOnly (LexikJWTAuthenticationBundle).
|
||||||
|
Le frontend ne lit jamais directement le token, le navigateur envoie automatiquement le cookie.
|
||||||
|
|
||||||
|
### Login flow
|
||||||
|
- Frontend envoie les identifiants à:
|
||||||
|
- `POST /api/login_check`
|
||||||
|
- Backend returns:
|
||||||
|
- `204 No Content` (normal)
|
||||||
|
- `Set-Cookie: BEARER=...; HttpOnly`
|
||||||
|
- Le cookie est automatiquement envoyé pour les futures requêtes.
|
||||||
|
- La déconnexion utilise `POST /api/logout` et redirige vers `/login`.
|
||||||
|
|
||||||
## Commandes utiles
|
## Commandes utiles
|
||||||
Pour restart le container
|
Pour restart le container
|
||||||
```bash
|
```bash
|
||||||
@@ -71,3 +88,17 @@ Pour clear le cache Symfony
|
|||||||
```bash
|
```bash
|
||||||
make cache-clear
|
make cache-clear
|
||||||
```
|
```
|
||||||
|
Faire une migration
|
||||||
|
```bash
|
||||||
|
make migration-migrate
|
||||||
|
```
|
||||||
|
Pour générer un password pour un user
|
||||||
|
```bash
|
||||||
|
make shell
|
||||||
|
php bin/console security:hash-password
|
||||||
|
```
|
||||||
|
Sélectionner entity User, taper sont mdp, le copier et l'ajouter dans l'insert de bdd suivant :
|
||||||
|
```sql
|
||||||
|
INSERT INTO "user" (username, roles, password)
|
||||||
|
VALUES ('Mon user', '["ROLE_USER"]', 'Mon mdp hashé');
|
||||||
|
```
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"doctrine/doctrine-migrations-bundle": "^4.0",
|
"doctrine/doctrine-migrations-bundle": "^4.0",
|
||||||
"doctrine/orm": "^3.6",
|
"doctrine/orm": "^3.6",
|
||||||
"dompdf/dompdf": "^3.1",
|
"dompdf/dompdf": "^3.1",
|
||||||
|
"lexik/jwt-authentication-bundle": "*",
|
||||||
"nelmio/cors-bundle": "^2.6",
|
"nelmio/cors-bundle": "^2.6",
|
||||||
"phpdocumentor/reflection-docblock": "^5.6",
|
"phpdocumentor/reflection-docblock": "^5.6",
|
||||||
"phpstan/phpdoc-parser": "^2.3",
|
"phpstan/phpdoc-parser": "^2.3",
|
||||||
|
|||||||
191
composer.lock
generated
191
composer.lock
generated
@@ -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": "5cd56256b984963ecd4eaa17f2612f57",
|
"content-hash": "f619208e7dd3272e671e7c2b139afa87",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "api-platform/doctrine-common",
|
"name": "api-platform/doctrine-common",
|
||||||
@@ -2516,6 +2516,195 @@
|
|||||||
},
|
},
|
||||||
"time": "2026-01-02T16:01:13+00:00"
|
"time": "2026-01-02T16:01:13+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "lcobucci/jwt",
|
||||||
|
"version": "5.6.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/lcobucci/jwt.git",
|
||||||
|
"reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e",
|
||||||
|
"reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-openssl": "*",
|
||||||
|
"ext-sodium": "*",
|
||||||
|
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||||
|
"psr/clock": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"infection/infection": "^0.29",
|
||||||
|
"lcobucci/clock": "^3.2",
|
||||||
|
"lcobucci/coding-standard": "^11.0",
|
||||||
|
"phpbench/phpbench": "^1.2",
|
||||||
|
"phpstan/extension-installer": "^1.2",
|
||||||
|
"phpstan/phpstan": "^1.10.7",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^1.1.3",
|
||||||
|
"phpstan/phpstan-phpunit": "^1.3.10",
|
||||||
|
"phpstan/phpstan-strict-rules": "^1.5.0",
|
||||||
|
"phpunit/phpunit": "^11.1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"lcobucci/clock": ">= 3.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Lcobucci\\JWT\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Luís Cobucci",
|
||||||
|
"email": "lcobucci@gmail.com",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
|
||||||
|
"keywords": [
|
||||||
|
"JWS",
|
||||||
|
"jwt"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/lcobucci/jwt/issues",
|
||||||
|
"source": "https://github.com/lcobucci/jwt/tree/5.6.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/lcobucci",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.patreon.com/lcobucci",
|
||||||
|
"type": "patreon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-10-17T11:30:53+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lexik/jwt-authentication-bundle",
|
||||||
|
"version": "v3.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/lexik/LexikJWTAuthenticationBundle.git",
|
||||||
|
"reference": "60df75dc70ee6f597929cb2f0812adda591dfa4b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/lexik/LexikJWTAuthenticationBundle/zipball/60df75dc70ee6f597929cb2f0812adda591dfa4b",
|
||||||
|
"reference": "60df75dc70ee6f597929cb2f0812adda591dfa4b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-openssl": "*",
|
||||||
|
"lcobucci/jwt": "^5.0",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/clock": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/config": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/deprecation-contracts": "^2.4|^3.0",
|
||||||
|
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/http-foundation": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/http-kernel": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/property-access": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/security-bundle": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/translation-contracts": "^1.0|^2.0|^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"api-platform/core": "^3.0|^4.0",
|
||||||
|
"rector/rector": "^1.2",
|
||||||
|
"symfony/browser-kit": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/console": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/dom-crawler": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/filesystem": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/phpunit-bridge": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/var-dumper": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/yaml": "^6.4|^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"gesdinet/jwt-refresh-token-bundle": "Implements a refresh token system over Json Web Tokens in Symfony",
|
||||||
|
"spomky-labs/lexik-jose-bridge": "Provides a JWT Token encoder with encryption support"
|
||||||
|
},
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Lexik\\Bundle\\JWTAuthenticationBundle\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jeremy Barthe",
|
||||||
|
"email": "j.barthe@lexik.fr",
|
||||||
|
"homepage": "https://github.com/jeremyb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nicolas Cabot",
|
||||||
|
"email": "n.cabot@lexik.fr",
|
||||||
|
"homepage": "https://github.com/slashfan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cedric Girard",
|
||||||
|
"email": "c.girard@lexik.fr",
|
||||||
|
"homepage": "https://github.com/cedric-g"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dev Lexik",
|
||||||
|
"email": "dev@lexik.fr",
|
||||||
|
"homepage": "https://github.com/lexik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Robin Chalas",
|
||||||
|
"email": "robin.chalas@gmail.com",
|
||||||
|
"homepage": "https://github.com/chalasr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lexik Community",
|
||||||
|
"homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "This bundle provides JWT authentication for your Symfony REST API",
|
||||||
|
"homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle",
|
||||||
|
"keywords": [
|
||||||
|
"Authentication",
|
||||||
|
"JWS",
|
||||||
|
"api",
|
||||||
|
"bundle",
|
||||||
|
"jwt",
|
||||||
|
"rest",
|
||||||
|
"symfony"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/lexik/LexikJWTAuthenticationBundle/issues",
|
||||||
|
"source": "https://github.com/lexik/LexikJWTAuthenticationBundle/tree/v3.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/chalasr",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/lexik/jwt-authentication-bundle",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-12-20T17:47:00+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "masterminds/html5",
|
"name": "masterminds/html5",
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
|
||||||
|
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
|
||||||
|
use Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle;
|
||||||
|
use Nelmio\CorsBundle\NelmioCorsBundle;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||||
|
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
||||||
|
use Symfony\Bundle\TwigBundle\TwigBundle;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
FrameworkBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
TwigBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
SecurityBundle::class => ['all' => true],
|
||||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
DoctrineBundle::class => ['all' => true],
|
||||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
DoctrineMigrationsBundle::class => ['all' => true],
|
||||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
NelmioCorsBundle::class => ['all' => true],
|
||||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
LexikJWTAuthenticationBundle::class => ['all' => true],
|
||||||
|
ApiPlatformBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
|||||||
20
config/packages/lexik_jwt_authentication.yaml
Normal file
20
config/packages/lexik_jwt_authentication.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
lexik_jwt_authentication:
|
||||||
|
secret_key: '%kernel.project_dir%/config/jwt/private.pem'
|
||||||
|
public_key: '%kernel.project_dir%/config/jwt/public.pem'
|
||||||
|
pass_phrase: '%env(JWT_PASSPHRASE)%'
|
||||||
|
token_ttl: 86400
|
||||||
|
token_extractors:
|
||||||
|
authorization_header:
|
||||||
|
enabled: true
|
||||||
|
prefix: Bearer
|
||||||
|
name: Authorization
|
||||||
|
cookie:
|
||||||
|
enabled: true
|
||||||
|
name: BEARER
|
||||||
|
set_cookies:
|
||||||
|
BEARER:
|
||||||
|
lifetime: 86400
|
||||||
|
path: /
|
||||||
|
samesite: lax
|
||||||
|
secure: '%env(bool:COOKIE_SECURE)%'
|
||||||
|
httpOnly: true
|
||||||
@@ -4,6 +4,7 @@ nelmio_cors:
|
|||||||
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
||||||
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||||
allow_headers: ['Content-Type', 'Authorization']
|
allow_headers: ['Content-Type', 'Authorization']
|
||||||
|
allow_credentials: true
|
||||||
expose_headers: ['Link']
|
expose_headers: ['Link']
|
||||||
max_age: 3600
|
max_age: 3600
|
||||||
paths:
|
paths:
|
||||||
|
|||||||
4
config/packages/prod/api_platform.yaml
Normal file
4
config/packages/prod/api_platform.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
api_platform:
|
||||||
|
enable_docs: false
|
||||||
|
enable_swagger: false
|
||||||
|
enable_swagger_ui: false
|
||||||
@@ -1,20 +1,43 @@
|
|||||||
security:
|
security:
|
||||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||||
password_hashers:
|
password_hashers:
|
||||||
|
App\Entity\User: 'auto'
|
||||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||||
|
|
||||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||||
providers:
|
providers:
|
||||||
users_in_memory: { memory: null }
|
app_user_provider:
|
||||||
|
entity:
|
||||||
|
class: App\Entity\User
|
||||||
|
property: username
|
||||||
|
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
dev:
|
||||||
# Ensure dev tools and static assets are always allowed
|
# Ensure dev tools and static assets are always allowed
|
||||||
pattern: ^/(_profiler|_wdt|assets|build)/
|
pattern: ^/(_profiler|_wdt|assets|build)/
|
||||||
security: false
|
security: false
|
||||||
main:
|
login:
|
||||||
lazy: true
|
pattern: ^/login_check
|
||||||
provider: users_in_memory
|
stateless: true
|
||||||
|
provider: app_user_provider
|
||||||
|
json_login:
|
||||||
|
check_path: /login_check
|
||||||
|
username_path: username
|
||||||
|
password_path: password
|
||||||
|
success_handler: lexik_jwt_authentication.handler.authentication_success
|
||||||
|
failure_handler: lexik_jwt_authentication.handler.authentication_failure
|
||||||
|
api:
|
||||||
|
pattern: ^/
|
||||||
|
stateless: true
|
||||||
|
provider: app_user_provider
|
||||||
|
jwt: ~
|
||||||
|
logout:
|
||||||
|
path: /logout
|
||||||
|
target: /login
|
||||||
|
enable_csrf: false
|
||||||
|
delete_cookies:
|
||||||
|
BEARER:
|
||||||
|
path: /
|
||||||
|
|
||||||
# Activate different ways to authenticate:
|
# Activate different ways to authenticate:
|
||||||
# https://symfony.com/doc/current/security.html#the-firewall
|
# https://symfony.com/doc/current/security.html#the-firewall
|
||||||
@@ -24,8 +47,9 @@ security:
|
|||||||
|
|
||||||
# Note: Only the *first* matching rule is applied
|
# Note: Only the *first* matching rule is applied
|
||||||
access_control:
|
access_control:
|
||||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
- { path: ^/login_check, roles: PUBLIC_ACCESS }
|
||||||
# - { path: ^/profile, roles: ROLE_USER }
|
- { path: ^/users, roles: PUBLIC_ACCESS, methods: [GET] }
|
||||||
|
- { path: ^/, roles: IS_AUTHENTICATED_FULLY }
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
security:
|
security:
|
||||||
|
|||||||
@@ -770,6 +770,9 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* property?: scalar|null|Param, // Default: null
|
* property?: scalar|null|Param, // Default: null
|
||||||
* manager_name?: scalar|null|Param, // Default: null
|
* manager_name?: scalar|null|Param, // Default: null
|
||||||
* },
|
* },
|
||||||
|
* lexik_jwt?: array{
|
||||||
|
* class?: scalar|null|Param, // Default: "Lexik\\Bundle\\JWTAuthenticationBundle\\Security\\User\\JWTUser"
|
||||||
|
* },
|
||||||
* }>,
|
* }>,
|
||||||
* firewalls: array<string, array{ // Default: []
|
* firewalls: array<string, array{ // Default: []
|
||||||
* pattern?: scalar|null|Param,
|
* pattern?: scalar|null|Param,
|
||||||
@@ -828,6 +831,10 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* provider?: scalar|null|Param,
|
* provider?: scalar|null|Param,
|
||||||
* user?: scalar|null|Param, // Default: "REMOTE_USER"
|
* user?: scalar|null|Param, // Default: "REMOTE_USER"
|
||||||
* },
|
* },
|
||||||
|
* jwt?: array{
|
||||||
|
* provider?: scalar|null|Param, // Default: null
|
||||||
|
* authenticator?: scalar|null|Param, // Default: "lexik_jwt_authentication.security.jwt_authenticator"
|
||||||
|
* },
|
||||||
* login_link?: array{
|
* login_link?: array{
|
||||||
* check_route: scalar|null|Param, // Route that will validate the login link - e.g. "app_login_link_verify".
|
* check_route: scalar|null|Param, // Route that will validate the login link - e.g. "app_login_link_verify".
|
||||||
* check_post_only?: scalar|null|Param, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false
|
* check_post_only?: scalar|null|Param, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false
|
||||||
@@ -1261,6 +1268,91 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* skip_same_as_origin?: bool|Param,
|
* skip_same_as_origin?: bool|Param,
|
||||||
* }>,
|
* }>,
|
||||||
* }
|
* }
|
||||||
|
* @psalm-type LexikJwtAuthenticationConfig = array{
|
||||||
|
* public_key?: scalar|null|Param, // The key used to sign tokens (useless for HMAC). If not set, the key will be automatically computed from the secret key. // Default: null
|
||||||
|
* additional_public_keys?: list<scalar|null|Param>,
|
||||||
|
* secret_key?: scalar|null|Param, // The key used to sign tokens. It can be a raw secret (for HMAC), a raw RSA/ECDSA key or the path to a file itself being plaintext or PEM. // Default: null
|
||||||
|
* pass_phrase?: scalar|null|Param, // The key passphrase (useless for HMAC) // Default: ""
|
||||||
|
* token_ttl?: scalar|null|Param, // Default: 3600
|
||||||
|
* allow_no_expiration?: bool|Param, // Allow tokens without "exp" claim (i.e. indefinitely valid, no lifetime) to be considered valid. Caution: usage of this should be rare. // Default: false
|
||||||
|
* clock_skew?: scalar|null|Param, // Default: 0
|
||||||
|
* encoder?: array{
|
||||||
|
* service?: scalar|null|Param, // Default: "lexik_jwt_authentication.encoder.lcobucci"
|
||||||
|
* signature_algorithm?: scalar|null|Param, // Default: "RS256"
|
||||||
|
* },
|
||||||
|
* user_id_claim?: scalar|null|Param, // Default: "username"
|
||||||
|
* token_extractors?: array{
|
||||||
|
* authorization_header?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: true
|
||||||
|
* prefix?: scalar|null|Param, // Default: "Bearer"
|
||||||
|
* name?: scalar|null|Param, // Default: "Authorization"
|
||||||
|
* },
|
||||||
|
* cookie?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* name?: scalar|null|Param, // Default: "BEARER"
|
||||||
|
* },
|
||||||
|
* query_parameter?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* name?: scalar|null|Param, // Default: "bearer"
|
||||||
|
* },
|
||||||
|
* split_cookie?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* cookies?: list<scalar|null|Param>,
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* remove_token_from_body_when_cookies_used?: scalar|null|Param, // Default: true
|
||||||
|
* set_cookies?: array<string, array{ // Default: []
|
||||||
|
* lifetime?: scalar|null|Param, // The cookie lifetime. If null, the "token_ttl" option value will be used // Default: null
|
||||||
|
* samesite?: "none"|"lax"|"strict"|Param, // Default: "lax"
|
||||||
|
* path?: scalar|null|Param, // Default: "/"
|
||||||
|
* domain?: scalar|null|Param, // Default: null
|
||||||
|
* secure?: scalar|null|Param, // Default: true
|
||||||
|
* httpOnly?: scalar|null|Param, // Default: true
|
||||||
|
* partitioned?: scalar|null|Param, // Default: false
|
||||||
|
* split?: list<scalar|null|Param>,
|
||||||
|
* }>,
|
||||||
|
* api_platform?: bool|array{ // API Platform compatibility: add check_path in OpenAPI documentation.
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* check_path?: scalar|null|Param, // The login check path to add in OpenAPI. // Default: null
|
||||||
|
* username_path?: scalar|null|Param, // The path to the username in the JSON body. // Default: null
|
||||||
|
* password_path?: scalar|null|Param, // The path to the password in the JSON body. // Default: null
|
||||||
|
* },
|
||||||
|
* access_token_issuance?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* signature?: array{
|
||||||
|
* algorithm: scalar|null|Param, // The algorithm use to sign the access tokens.
|
||||||
|
* key: scalar|null|Param, // The signature key. It shall be JWK encoded.
|
||||||
|
* },
|
||||||
|
* encryption?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* key_encryption_algorithm: scalar|null|Param, // The key encryption algorithm is used to encrypt the token.
|
||||||
|
* content_encryption_algorithm: scalar|null|Param, // The key encryption algorithm is used to encrypt the token.
|
||||||
|
* key: scalar|null|Param, // The encryption key. It shall be JWK encoded.
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* access_token_verification?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* signature?: array{
|
||||||
|
* header_checkers?: list<scalar|null|Param>,
|
||||||
|
* claim_checkers?: list<scalar|null|Param>,
|
||||||
|
* mandatory_claims?: list<scalar|null|Param>,
|
||||||
|
* allowed_algorithms?: list<scalar|null|Param>,
|
||||||
|
* keyset: scalar|null|Param, // The signature keyset. It shall be JWKSet encoded.
|
||||||
|
* },
|
||||||
|
* encryption?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* continue_on_decryption_failure?: bool|Param, // If enable, non-encrypted tokens or tokens that failed during decryption or verification processes are accepted. // Default: false
|
||||||
|
* header_checkers?: list<scalar|null|Param>,
|
||||||
|
* allowed_key_encryption_algorithms?: list<scalar|null|Param>,
|
||||||
|
* allowed_content_encryption_algorithms?: list<scalar|null|Param>,
|
||||||
|
* keyset: scalar|null|Param, // The encryption keyset. It shall be JWKSet encoded.
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* blocklist_token?: bool|array{
|
||||||
|
* enabled?: bool|Param, // Default: false
|
||||||
|
* cache?: scalar|null|Param, // Storage to track blocked tokens // Default: "cache.app"
|
||||||
|
* },
|
||||||
|
* }
|
||||||
* @psalm-type ApiPlatformConfig = array{
|
* @psalm-type ApiPlatformConfig = array{
|
||||||
* title?: scalar|null|Param, // The title of the API. // Default: ""
|
* title?: scalar|null|Param, // The title of the API. // Default: ""
|
||||||
* description?: scalar|null|Param, // The description of the API. // Default: ""
|
* description?: scalar|null|Param, // The description of the API. // Default: ""
|
||||||
@@ -1526,6 +1618,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* doctrine?: DoctrineConfig,
|
* doctrine?: DoctrineConfig,
|
||||||
* doctrine_migrations?: DoctrineMigrationsConfig,
|
* doctrine_migrations?: DoctrineMigrationsConfig,
|
||||||
* nelmio_cors?: NelmioCorsConfig,
|
* nelmio_cors?: NelmioCorsConfig,
|
||||||
|
* lexik_jwt_authentication?: LexikJwtAuthenticationConfig,
|
||||||
* api_platform?: ApiPlatformConfig,
|
* api_platform?: ApiPlatformConfig,
|
||||||
* "when@dev"?: array{
|
* "when@dev"?: array{
|
||||||
* imports?: ImportsConfig,
|
* imports?: ImportsConfig,
|
||||||
@@ -1537,6 +1630,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* doctrine?: DoctrineConfig,
|
* doctrine?: DoctrineConfig,
|
||||||
* doctrine_migrations?: DoctrineMigrationsConfig,
|
* doctrine_migrations?: DoctrineMigrationsConfig,
|
||||||
* nelmio_cors?: NelmioCorsConfig,
|
* nelmio_cors?: NelmioCorsConfig,
|
||||||
|
* lexik_jwt_authentication?: LexikJwtAuthenticationConfig,
|
||||||
* api_platform?: ApiPlatformConfig,
|
* api_platform?: ApiPlatformConfig,
|
||||||
* },
|
* },
|
||||||
* "when@prod"?: array{
|
* "when@prod"?: array{
|
||||||
@@ -1549,6 +1643,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* doctrine?: DoctrineConfig,
|
* doctrine?: DoctrineConfig,
|
||||||
* doctrine_migrations?: DoctrineMigrationsConfig,
|
* doctrine_migrations?: DoctrineMigrationsConfig,
|
||||||
* nelmio_cors?: NelmioCorsConfig,
|
* nelmio_cors?: NelmioCorsConfig,
|
||||||
|
* lexik_jwt_authentication?: LexikJwtAuthenticationConfig,
|
||||||
* api_platform?: ApiPlatformConfig,
|
* api_platform?: ApiPlatformConfig,
|
||||||
* },
|
* },
|
||||||
* "when@test"?: array{
|
* "when@test"?: array{
|
||||||
@@ -1561,6 +1656,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* doctrine?: DoctrineConfig,
|
* doctrine?: DoctrineConfig,
|
||||||
* doctrine_migrations?: DoctrineMigrationsConfig,
|
* doctrine_migrations?: DoctrineMigrationsConfig,
|
||||||
* nelmio_cors?: NelmioCorsConfig,
|
* nelmio_cors?: NelmioCorsConfig,
|
||||||
|
* lexik_jwt_authentication?: LexikJwtAuthenticationConfig,
|
||||||
* api_platform?: ApiPlatformConfig,
|
* api_platform?: ApiPlatformConfig,
|
||||||
* },
|
* },
|
||||||
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
|
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
_security_logout:
|
_security_logout:
|
||||||
resource: security.route_loader.logout
|
resource: security.route_loader.logout
|
||||||
type: service
|
type: service
|
||||||
|
|
||||||
|
api_login:
|
||||||
|
path: /login_check
|
||||||
|
methods: [POST]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { FetchOptions } from 'ofetch'
|
import type { FetchOptions } from 'ofetch'
|
||||||
import { $fetch, FetchError } from 'ofetch'
|
import { $fetch, FetchError } from 'ofetch'
|
||||||
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|
||||||
export type AnyObject = Record<string, unknown>
|
export type AnyObject = Record<string, unknown>
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export const useApi = (): ApiClient => {
|
|||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const baseURL = config.public.apiBase ?? '/api'
|
const baseURL = config.public.apiBase ?? '/api'
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const auth = useAuthStore()
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const i18n = nuxtApp.$i18n as
|
const i18n = nuxtApp.$i18n as
|
||||||
| {
|
| {
|
||||||
@@ -70,12 +72,17 @@ export const useApi = (): ApiClient => {
|
|||||||
const client = $fetch.create({
|
const client = $fetch.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
retry: 0,
|
retry: 0,
|
||||||
onResponse({ options }) {
|
credentials: 'include',
|
||||||
|
onResponse({ options, response }) {
|
||||||
const apiOptions = options as ApiFetchOptions<'json'>
|
const apiOptions = options as ApiFetchOptions<'json'>
|
||||||
if (apiOptions?.toast === false) {
|
if (apiOptions?.toast === false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response?.status && response.status >= 400) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const successKey = apiOptions?.toastSuccessKey
|
const successKey = apiOptions?.toastSuccessKey
|
||||||
const successMessage =
|
const successMessage =
|
||||||
apiOptions?.toastSuccessMessage ||
|
apiOptions?.toastSuccessMessage ||
|
||||||
|
|||||||
@@ -13,11 +13,20 @@
|
|||||||
"create": "Impossible de créer la réception.",
|
"create": "Impossible de créer la réception.",
|
||||||
"update": "Impossible de mettre à jour la réception.",
|
"update": "Impossible de mettre à jour la réception.",
|
||||||
"weigh": "Impossible de récupérer la pesée."
|
"weigh": "Impossible de récupérer la pesée."
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login": "Identifiants invalides.",
|
||||||
|
"users": "Impossible de récupérer les utilisateurs.",
|
||||||
|
"logout": "Impossible de se déconnecter."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"reception": {
|
"reception": {
|
||||||
"update": "Réception mise à jour avec succès."
|
"update": "Réception mise à jour avec succès."
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login": "Connexion réussie.",
|
||||||
|
"logout": "Déconnexion réussie."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
frontend/layouts/auth.vue
Normal file
7
frontend/layouts/auth.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-primary-500 from-primary-50 via-white to-neutral-100 text-neutral-900">
|
||||||
|
<main class="mx-auto flex min-h-screen w-full max-w-[720px] items-center px-6 py-12">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
LOGO
|
LOGO
|
||||||
</span>
|
</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<nav class="mx-8 flex gap-8 text-2xl font-bold uppercase text-white">
|
<nav class="mx-8 flex flex-1 gap-8 text-2xl font-bold uppercase text-white">
|
||||||
<NuxtLink to="/" custom v-slot="{ href, navigate, isExactActive }">
|
<NuxtLink to="/" custom v-slot="{ href, navigate, isExactActive }">
|
||||||
<a
|
<a
|
||||||
:href="href"
|
:href="href"
|
||||||
@@ -29,6 +29,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</nav>
|
</nav>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-auto text-xl font-bold uppercase text-white transition hover:opacity-80"
|
||||||
|
@click="handleLogout"
|
||||||
|
>
|
||||||
|
Déconnexion
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="mx-auto w-full max-w-[1050px] px-6 pt-[90px] pb-0">
|
<main class="mx-auto w-full max-w-[1050px] px-6 pt-[90px] pb-0">
|
||||||
@@ -38,6 +45,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const auth = useAuthStore()
|
||||||
const isReceptionActive = computed(() => route.path.startsWith('/reception'))
|
const isReceptionActive = computed(() => route.path.startsWith('/reception'))
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await auth.logout()
|
||||||
|
} finally {
|
||||||
|
await navigateTo('/login')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
17
frontend/middleware/auth.global.ts
Normal file
17
frontend/middleware/auth.global.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|
||||||
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
if (to.path === '/login') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!auth.isAuthenticated) {
|
||||||
|
await auth.ensureSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!auth.isAuthenticated) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
})
|
||||||
95
frontend/pages/login.vue
Normal file
95
frontend/pages/login.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mx-auto w-full max-w-lg">
|
||||||
|
<span
|
||||||
|
class="flex items-center justify-center bg-white text-xl font-bold uppercase text-primary-500 p-4"
|
||||||
|
>
|
||||||
|
LOGO
|
||||||
|
</span>
|
||||||
|
<form
|
||||||
|
class="mt-8 space-y-6 rounded-lg border border-neutral-200 bg-white p-6 shadow-sm"
|
||||||
|
@submit.prevent="handleSubmit"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-semibold text-neutral-700" for="user-select">
|
||||||
|
Utilisateur
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="user-select"
|
||||||
|
v-model="selectedUsername"
|
||||||
|
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-200"
|
||||||
|
:disabled="isLoadingUsers"
|
||||||
|
>
|
||||||
|
<option value="" disabled>Choisir un utilisateur</option>
|
||||||
|
<option v-for="user in users" :key="user.username" :value="user.username">
|
||||||
|
{{ user.username }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-semibold text-neutral-700" for="password">
|
||||||
|
Mot de passe
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
v-model="password"
|
||||||
|
type="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-200"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full rounded-md bg-primary-500 px-4 py-2 text-base font-semibold text-white transition hover:bg-primary-600 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Connexion
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
|
import { getUsers } from '~/services/auth'
|
||||||
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'auth'
|
||||||
|
})
|
||||||
|
|
||||||
|
const users = ref<UserData[]>([])
|
||||||
|
const isLoadingUsers = ref(true)
|
||||||
|
const selectedUsername = ref('')
|
||||||
|
const password = ref('')
|
||||||
|
|
||||||
|
const isSubmitting = computed(() => {
|
||||||
|
return auth.isLoading || !selectedUsername.value || !password.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadUsers = async () => {
|
||||||
|
isLoadingUsers.value = true
|
||||||
|
try {
|
||||||
|
users.value = await getUsers()
|
||||||
|
} finally {
|
||||||
|
isLoadingUsers.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (isSubmitting.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await auth.login(selectedUsername.value, password.value)
|
||||||
|
await router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void loadUsers()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
38
frontend/services/auth.ts
Normal file
38
frontend/services/auth.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { useApi } from '~/composables/useApi'
|
||||||
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
|
|
||||||
|
export async function getUsers() {
|
||||||
|
const api = useApi()
|
||||||
|
const data = await api.get<UserData[] | { 'hydra:member': UserData[] }>('users', {}, {
|
||||||
|
toastErrorKey: 'errors.auth.users'
|
||||||
|
})
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return data['hydra:member'] ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurrentUser() {
|
||||||
|
const api = useApi()
|
||||||
|
return api.get<UserData>('me', {}, {
|
||||||
|
toast: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function login(username: string, password: string) {
|
||||||
|
const api = useApi()
|
||||||
|
return api.post<{ token: string }>('login_check', { username, password }, {
|
||||||
|
toastErrorKey: 'errors.auth.login',
|
||||||
|
toastSuccessKey: 'success.auth.login'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logout() {
|
||||||
|
const api = useApi()
|
||||||
|
return api.post<void>('logout', {}, {
|
||||||
|
toastErrorKey: 'errors.auth.logout',
|
||||||
|
toastSuccessKey: 'success.auth.logout',
|
||||||
|
redirect: 'manual'
|
||||||
|
})
|
||||||
|
}
|
||||||
3
frontend/services/dto/user-data.ts
Normal file
3
frontend/services/dto/user-data.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface UserData {
|
||||||
|
username: string
|
||||||
|
}
|
||||||
58
frontend/stores/auth.ts
Normal file
58
frontend/stores/auth.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
|
import { getCurrentUser, login, logout } from '~/services/auth'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', {
|
||||||
|
state: () => ({
|
||||||
|
user: null as UserData | null,
|
||||||
|
isLoading: false,
|
||||||
|
checked: false
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
isAuthenticated: (state) => Boolean(state.user)
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async ensureSession() {
|
||||||
|
if (this.checked) {
|
||||||
|
return this.user
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checked = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const me = await getCurrentUser()
|
||||||
|
this.user = me
|
||||||
|
return me
|
||||||
|
} catch {
|
||||||
|
this.user = null
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async login(username: string, password: string) {
|
||||||
|
this.isLoading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await login(username, password)
|
||||||
|
const me = await getCurrentUser()
|
||||||
|
this.user = me
|
||||||
|
this.checked = true
|
||||||
|
return me
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async logout() {
|
||||||
|
this.isLoading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await logout()
|
||||||
|
} catch {
|
||||||
|
// Ignore logout errors so we can still clear local auth state.
|
||||||
|
} finally {
|
||||||
|
this.user = null
|
||||||
|
this.checked = true
|
||||||
|
this.isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
9
makefile
9
makefile
@@ -40,13 +40,14 @@ restart: env-init
|
|||||||
$(DOCKER_COMPOSE) down
|
$(DOCKER_COMPOSE) down
|
||||||
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
|
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
|
||||||
|
|
||||||
install: copy-git-hook composer-install cache-clear node-use build-nuxtJS
|
install: copy-git-hook composer-install cache-clear node-use build-nuxtJS migration-migrate
|
||||||
|
|
||||||
# Supprime tout est réinstalle tout (Attention ça supprime la bdd aussi)
|
# Supprime tout est réinstalle tout (Attention ça supprime la bdd aussi)
|
||||||
reset: delete_built_dir remove_orphans build-without-cache start wait install
|
reset: delete_built_dir remove_orphans build-without-cache start wait install
|
||||||
|
|
||||||
composer-install:
|
composer-install:
|
||||||
$(EXEC_PHP) composer install
|
$(EXEC_PHP) composer install
|
||||||
|
$(SYMFONY_CONSOLE) lexik:jwt:generate-keypair --skip-if-exists
|
||||||
|
|
||||||
build-nuxtJS:
|
build-nuxtJS:
|
||||||
# $(EXEC_PHP) cp -n frontend/.env.dist frontend/.env.local
|
# $(EXEC_PHP) cp -n frontend/.env.dist frontend/.env.local
|
||||||
@@ -72,10 +73,16 @@ build-without-cache:
|
|||||||
--build-arg="CURRENT_GID=$(shell id -g)" \
|
--build-arg="CURRENT_GID=$(shell id -g)" \
|
||||||
--no-cache
|
--no-cache
|
||||||
|
|
||||||
|
migration-migrate:
|
||||||
|
$(SYMFONY_CONSOLE) bin/console doctrine:migrations:migrate --no-interaction
|
||||||
|
|
||||||
# Attention, supprime votre bdd local
|
# Attention, supprime votre bdd local
|
||||||
db-reset:
|
db-reset:
|
||||||
$(DOCKER_COMPOSE) down -v
|
$(DOCKER_COMPOSE) down -v
|
||||||
$(DOCKER_COMPOSE) up -d
|
$(DOCKER_COMPOSE) up -d
|
||||||
|
$(MAKE) wait
|
||||||
|
$(SYMFONY_CONSOLE) doctrine:database:create --if-not-exists
|
||||||
|
$(MAKE) migration-migrate
|
||||||
|
|
||||||
# Restart la bdd
|
# Restart la bdd
|
||||||
db-restart:
|
db-restart:
|
||||||
|
|||||||
27
migrations/Version20260112000700.php
Normal file
27
migrations/Version20260112000700.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260112000700 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Create user table for authentication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE TABLE "user" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, username VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_USER_USERNAME ON "user" (username)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP TABLE "user"');
|
||||||
|
}
|
||||||
|
}
|
||||||
30
migrations/Version20260112000800.php
Normal file
30
migrations/Version20260112000800.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260112000800 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Ensure user table exists and align weight type default';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE TABLE IF NOT EXISTS "user" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, username VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, PRIMARY KEY (id))');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX IF NOT EXISTS UNIQ_8D93D649F85E0677 ON "user" (username)');
|
||||||
|
$this->addSql('ALTER TABLE weight ALTER type DROP DEFAULT');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE weight ALTER type SET DEFAULT \'gross\'');
|
||||||
|
$this->addSql('DROP INDEX IF EXISTS UNIQ_8D93D649F85E0677');
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS "user"');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
provider: ReceptionReceiptProvider::class,
|
provider: ReceptionReceiptProvider::class,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
class Reception
|
class Reception
|
||||||
{
|
{
|
||||||
|
|||||||
104
src/Entity/User.php
Normal file
104
src/Entity/User.php
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use App\State\MeProvider;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'user', schema: 'public')]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(
|
||||||
|
uriTemplate: '/me',
|
||||||
|
normalizationContext: ['groups' => ['user:read']],
|
||||||
|
security: "is_granted('ROLE_USER')",
|
||||||
|
provider: MeProvider::class
|
||||||
|
),
|
||||||
|
new GetCollection(
|
||||||
|
normalizationContext: ['groups' => ['user:read']],
|
||||||
|
security: "is_granted('PUBLIC_ACCESS')"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
normalizationContext: ['groups' => ['user:read']],
|
||||||
|
paginationEnabled: false
|
||||||
|
)]
|
||||||
|
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(type: 'integer')]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 180, unique: true)]
|
||||||
|
#[Groups(['user:read'])]
|
||||||
|
private string $username = '';
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'json')]
|
||||||
|
private array $roles = [];
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private string $password = '';
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsername(): string
|
||||||
|
{
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUsername(string $username): self
|
||||||
|
{
|
||||||
|
$this->username = $username;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdentifier(): string
|
||||||
|
{
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoles(): array
|
||||||
|
{
|
||||||
|
$roles = $this->roles;
|
||||||
|
$roles[] = 'ROLE_USER';
|
||||||
|
|
||||||
|
return array_unique($roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRoles(array $roles): self
|
||||||
|
{
|
||||||
|
$this->roles = $roles;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword(): string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPassword(string $password): self
|
||||||
|
{
|
||||||
|
$this->password = $password;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eraseCredentials(): void
|
||||||
|
{
|
||||||
|
// No-op: we don't store temporary sensitive data on the entity.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
denormalizationContext: ['groups' => ['weight:write']],
|
denormalizationContext: ['groups' => ['weight:write']],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
#[UniqueEntity(fields: ['reception', 'type'], message: 'A weighing already exists for this type.')]
|
#[UniqueEntity(fields: ['reception', 'type'], message: 'A weighing already exists for this type.')]
|
||||||
class Weight
|
class Weight
|
||||||
|
|||||||
27
src/State/MeProvider.php
Normal file
27
src/State/MeProvider.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\State;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation;
|
||||||
|
use ApiPlatform\State\ProviderInterface;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
|
||||||
|
|
||||||
|
final readonly class MeProvider implements ProviderInterface
|
||||||
|
{
|
||||||
|
public function __construct(private Security $security) {}
|
||||||
|
|
||||||
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?object
|
||||||
|
{
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new AccessDeniedException('User not authenticated.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
symfony.lock
12
symfony.lock
@@ -61,6 +61,18 @@
|
|||||||
".php-cs-fixer.dist.php"
|
".php-cs-fixer.dist.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"lexik/jwt-authentication-bundle": {
|
||||||
|
"version": "3.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.5",
|
||||||
|
"ref": "e9481b233a11ef7e15fe055a2b21fd3ac1aa2bb7"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/lexik_jwt_authentication.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"nelmio/cors-bundle": {
|
"nelmio/cors-bundle": {
|
||||||
"version": "2.6",
|
"version": "2.6",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
Reference in New Issue
Block a user