From feaa9f1875f3551ba82c7fb8ea0416cf0ef2af61 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Wed, 13 May 2026 14:58:19 +0200 Subject: [PATCH] =?UTF-8?q?feat(api-token)=20:=20g=C3=A9n=C3=A9ration=20du?= =?UTF-8?q?=20token=20MCP=20depuis=20la=20page=20profil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend : - POST /api/me/regenerate-api-token : nouveau controller, ROLE_USER (exclut CLIENT) - User.apiToken exposé via groupe me:read sur GET /api/me Frontend : - Section 'Token API MCP' sur /profile (masquée pour les CLIENT du portail) - Boutons Copier + Régénérer avec modal de confirmation - Service api-token + DTO mis à jour + clés i18n fr --- frontend/i18n/locales/fr.json | 16 ++- frontend/pages/profile.vue | 108 ++++++++++++++++++ frontend/services/api-token.ts | 12 ++ frontend/services/dto/user-data.ts | 1 + .../RegenerateApiTokenController.php | 36 ++++++ src/Entity/User.php | 1 + 6 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 frontend/services/api-token.ts create mode 100644 src/Controller/RegenerateApiTokenController.php diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index 859608b..b30adc6 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -393,7 +393,21 @@ "title": "Mon profil", "changeAvatar": "Changer l'avatar", "removeAvatar": "Supprimer l'avatar", - "cropAvatar": "Recadrer l'avatar" + "cropAvatar": "Recadrer l'avatar", + "apiToken": { + "title": "Token API MCP", + "help": "Utilisé pour authentifier le serveur MCP HTTP (à coller dans le header Authorization: Bearer …). Ne pas partager.", + "label": "Token", + "empty": "Aucun token généré pour le moment.", + "generate": "Générer un token", + "regenerate": "Régénérer", + "copy": "Copier", + "copied": "Token copié dans le presse-papiers.", + "copyFailed": "Impossible de copier le token.", + "regenerated": "Nouveau token généré. L'ancien token est désormais invalide.", + "confirmTitle": "Régénérer le token MCP ?", + "confirmMessage": "L'ancien token sera immédiatement invalidé. Tous les clients MCP utilisant ce token devront être reconfigurés." + } }, "bookstack": { "settings": { diff --git a/frontend/pages/profile.vue b/frontend/pages/profile.vue index bb3b081..93ed090 100644 --- a/frontend/pages/profile.vue +++ b/frontend/pages/profile.vue @@ -37,6 +37,56 @@ + +
+

{{ $t('profile.apiToken.title') }}

+

{{ $t('profile.apiToken.help') }}

+ +
+ +
+ + +
+
+ +
+

{{ $t('profile.apiToken.empty') }}

+ +
+
+ + + + +
+
+
+

{{ $t('profile.apiToken.confirmTitle') }}

+

+ {{ $t('profile.apiToken.confirmMessage') }} +

+
+ + +
+
+
+
diff --git a/frontend/services/api-token.ts b/frontend/services/api-token.ts new file mode 100644 index 0000000..7a304de --- /dev/null +++ b/frontend/services/api-token.ts @@ -0,0 +1,12 @@ +export function useApiTokenService() { + const api = useApi() + + async function regenerate(): Promise { + const data = await api.post<{ apiToken: string }>('/me/regenerate-api-token', {}, { + toast: false, + }) + return data.apiToken + } + + return { regenerate } +} diff --git a/frontend/services/dto/user-data.ts b/frontend/services/dto/user-data.ts index 987af19..bddd1d5 100644 --- a/frontend/services/dto/user-data.ts +++ b/frontend/services/dto/user-data.ts @@ -8,6 +8,7 @@ export type UserData = { client?: { id: number; name: string } | null allowedProjects?: Project[] avatarUrl?: string | null + apiToken?: string | null } export type UserWrite = { diff --git a/src/Controller/RegenerateApiTokenController.php b/src/Controller/RegenerateApiTokenController.php new file mode 100644 index 0000000..ca209de --- /dev/null +++ b/src/Controller/RegenerateApiTokenController.php @@ -0,0 +1,36 @@ +getUser(); + + $token = bin2hex(random_bytes(32)); + $user->setApiToken($token); + $this->entityManager->flush(); + + return new JsonResponse(['apiToken' => $token]); + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index c85b9cf..11067e0 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -70,6 +70,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface private ?DateTimeImmutable $createdAt = null; #[ORM\Column(length: 64, unique: true, nullable: true)] + #[Groups(['me:read'])] private ?string $apiToken = null; #[ORM\Column(length: 255, nullable: true)]