From 8d5710a7264eac0907b407c5319f5f1d30e9b701 Mon Sep 17 00:00:00 2001 From: Josep Piguillem Date: Wed, 7 Oct 2026 19:30:40 +0200 Subject: [PATCH] fix: personal access tokens failed with a 500 in production Passport needs a "personal access client" to issue personal access tokens, and only the seeder created it (production runs migrations, not the seeder), so createToken threw "Personal access client not found". - New idempotent command passport:ensure-personal-client, run by the container entrypoint after the migrations. - The tokens page now shows an error when the server fails instead of doing nothing (es, en, ca). - Tests: the command creates the client once; tokens can be created. Co-Authored-By: Claude Opus 5.5 --- docker/php/entrypoint.sh | 3 ++ resources/js/i18n/ca.ts | 1 + resources/js/i18n/en.ts | 1 + resources/js/i18n/es.ts | 1 + resources/js/pages/Tokens.vue | 6 +++ routes/console.php | 16 ++++++++ tests/Feature/PersonalAccessTokenTest.php | 46 +++++++++++++++++++++++ 7 files changed, 74 insertions(+) create mode 100644 tests/Feature/PersonalAccessTokenTest.php diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh index efbf6b8..86fec71 100644 --- a/docker/php/entrypoint.sh +++ b/docker/php/entrypoint.sh @@ -14,6 +14,9 @@ if [ "$CONTAINER_ROLE" = "app" ]; then echo "Aplicando migraciones..." su-exec www-data php artisan migrate --force + echo "Comprobando el cliente de Personal Access Tokens..." + su-exec www-data php artisan passport:ensure-personal-client + echo "Cacheando configuración, rutas y vistas..." su-exec www-data php artisan config:cache su-exec www-data php artisan route:cache diff --git a/resources/js/i18n/ca.ts b/resources/js/i18n/ca.ts index 8c7bc7e..1bdf154 100644 --- a/resources/js/i18n/ca.ts +++ b/resources/js/i18n/ca.ts @@ -23,6 +23,7 @@ const ca: MessageSchema = { edit: 'Editar', delete: 'Esborrar', none: '(cap)', + unexpectedError: "Alguna cosa ha fallat. Torna-ho a provar d'aquí a una estona.", }, login: { title: 'Entrar', diff --git a/resources/js/i18n/en.ts b/resources/js/i18n/en.ts index 03bef1a..787ed0c 100644 --- a/resources/js/i18n/en.ts +++ b/resources/js/i18n/en.ts @@ -23,6 +23,7 @@ const en: MessageSchema = { edit: 'Edit', delete: 'Delete', none: '(none)', + unexpectedError: 'Something went wrong. Please try again in a moment.', }, login: { title: 'Sign in', diff --git a/resources/js/i18n/es.ts b/resources/js/i18n/es.ts index 3a3b499..2633d1e 100644 --- a/resources/js/i18n/es.ts +++ b/resources/js/i18n/es.ts @@ -21,6 +21,7 @@ const es = { edit: 'Editar', delete: 'Borrar', none: '(ninguno)', + unexpectedError: 'Algo salió mal. Probá de nuevo en un momento.', }, login: { title: 'Ingresar', diff --git a/resources/js/pages/Tokens.vue b/resources/js/pages/Tokens.vue index 0a2a177..8309149 100644 --- a/resources/js/pages/Tokens.vue +++ b/resources/js/pages/Tokens.vue @@ -14,6 +14,7 @@ const newToken = ref(null); const form = reactive<{ name: string; scopes: string[] }>({ name: '', scopes: [] }); const errors = ref>({}); +const failed = ref(false); async function load(): Promise { loading.value = true; @@ -29,6 +30,7 @@ async function load(): Promise { async function create(): Promise { creating.value = true; errors.value = {}; + failed.value = false; newToken.value = null; try { @@ -39,6 +41,8 @@ async function create(): Promise { await load(); } catch (e: any) { errors.value = e.response?.data?.errors ?? {}; + // Sin errores de validación (p. ej. un 500): avisar en vez de no hacer nada. + failed.value = !e.response?.data?.errors; } finally { creating.value = false; } @@ -84,6 +88,8 @@ onMounted(load); +

{{ t('common.unexpectedError') }}

+