2 Commits

Author SHA1 Message Date
c1094239a3 feat: Initial authentication logic and UX 2026-01-13 21:07:40 -05:00
f9958701e6 chore: pnpm approve builds 2026-01-13 11:32:55 -05:00
27 changed files with 720 additions and 9 deletions

View File

@@ -0,0 +1,13 @@
export default defineAppConfig({
ui: {
colors: {
primary: "indigo",
neutral: "neutral",
},
button: {
slots: {
base: "cursor-pointer",
},
},
},
});

View File

@@ -1,2 +1,4 @@
@import "tailwindcss"; @import "tailwindcss";
@import "@nuxt/ui"; @import "@nuxt/ui";
@import "./containers.css";

View File

@@ -0,0 +1,47 @@
:root {
--ui-container: var(--breakpoint-2xl);
}
/* Container padding */
@utility px-container {
@apply px-4 sm:px-6 lg:px-8;
}
/* Container sizes */
@utility container { @apply mx-auto px-container max-w-(--breakpoint-2xl); }
@utility container-xl { @apply container max-w-(--breakpoint-xl); }
@utility container-lg { @apply container max-w-(--breakpoint-lg); }
@utility container-md { @apply container max-w-(--breakpoint-md); }
@utility container-sm { @apply container max-w-(--breakpoint-sm); }
@utility container-fluid { @apply container max-w-screen; }
@utility container-none { @apply w-full max-w-screen; }
/* Split containers */
:root {
--container-outside-margin: 0;
--container-width: 100vw;
@variant sm {
--container-outside-margin: calc(50vw - theme("screens.sm") / 2);
--container-width: theme("screens.sm");
}
@variant md {
--container-outside-margin: calc(50vw - theme("screens.md") / 2);
--container-width: theme("screens.md");
}
@variant lg {
--container-outside-margin: calc(50vw - theme("screens.lg") / 2);
--container-width: theme("screens.lg");
}
@variant xl {
--container-outside-margin: calc(50vw - theme("screens.xl") / 2);
--container-width: theme("screens.xl");
}
@variant 2xl {
--container-outside-margin: calc(50vw - theme("screens.2xl") / 2);
--container-width: theme("screens.2xl");
}
}
@utility container-left { @apply ml-(--container-outside-margin) px-container;}
@utility container-right { @apply mr-(--container-outside-margin) px-container;}
@utility container-half { width: calc(var(--container-width) / 2);}

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
const { isLoggedIn } = useAuth();
const attrs = computed(() => {
return isLoggedIn.value
? {
label: "Déconnexion",
icon: "i-lucide-log-out",
}
: {
label: "Connexion",
icon: "i-lucide-log-in",
};
});
</script>
<template>
<AuthState>
<UButton to="/connexion" v-bind="attrs" color="neutral" />
</AuthState>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
const { login } = useAuthConnexion();
const fields = [
{
name: "username",
type: "text" as const,
label: "Courriel",
placeholder: "Entrez votre courriel",
required: true,
}, {
name: "password",
label: "Mot de passe",
type: "password" as const,
placeholder: "Entrez votre mot de passe",
required: true,
},
];
</script>
<template>
<UAuthForm
:schema="authLoginFormSchema"
:fields="fields"
title="Connexion"
description="Veuillez vous identifier."
loading-auto
@submit="login"
/>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const { logout } = useAuthConnexion();
</script>
<template>
<div class="w-full space-y-6">
<div class="flex flex-col text-center">
<div class="text-xl text-pretty font-semibold text-highlighted">
Déconnexion
</div>
<div class="mt-1 text-base text-pretty text-muted">
Veuillez confirmer la déconnexion.
</div>
</div>
<UButton
icon="i-lucide-log-out"
block
loading-auto
to="#"
label="Déconnexion"
@click="logout()"
/>
</div>
</template>

View File

@@ -0,0 +1,12 @@
<template>
<div class="w-full space-y-6">
<div class="flex flex-col text-center">
<div class="text-xl text-pretty font-semibold text-highlighted">
Redirection en cours
</div>
<div class="mt-1 text-base text-pretty text-muted">
Veuillez patienter...
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,18 @@
<script setup lang="ts">
const { isLoggedIn } = useAuth();
const { isRedirecting } = useAuthConnexion();
</script>
<template>
<section data-section-name="auth-connexion" class="py-12">
<div class="container-sm">
<AuthState>
<AuthRedirecting v-if="isRedirecting" />
<template v-else>
<AuthLogoutForm v-if="isLoggedIn" />
<AuthLoginForm v-else />
</template>
</AuthState>
</div>
</section>
</template>

View File

@@ -3,5 +3,9 @@ const title = "Moonshine";
</script> </script>
<template> <template>
<UHeader :title="title" /> <UHeader :title="title">
<template #right>
<AuthConnexionButton />
</template>
</UHeader>
</template> </template>

View File

@@ -0,0 +1,6 @@
export function useAuth() {
const { loggedIn: isLoggedIn, session } = useUserSession();
const hasRole = (role: string) => session.value?.user?.roles?.includes(role) || false;
const isAdmin = computed(() => hasRole("administrator"));
return { isLoggedIn, hasRole, isAdmin };
}

View File

@@ -0,0 +1,56 @@
import z from "zod";
import type { FormSubmitEvent } from "@nuxt/ui";
export const authLoginFormSchema = z.object({
username: z.email("Courriel invalide"),
password: z.string("Veuillez saisir votre mot de passe"),
});
export type AuthLoginForm = z.infer<typeof authLoginFormSchema>;
const isRedirecting = ref(false);
export function useAuthConnexion() {
const { fetch: refreshUserSession } = useUserSession();
const routeRedirect = useRoute().query.redirect as string || undefined;
// Helper: Redirect after login / logout
async function redirectTo(to: string | undefined) {
isRedirecting.value = true;
await delay(1000);
await refreshUserSession();
await navigateTo(to || routeRedirect || "/");
isRedirecting.value = false;
}
// Login
const { mutate: loginMutate } = useGraphQLMutation("AuthLogin");
async function login({ data: variables }: FormSubmitEvent<AuthLoginForm>, redirect?: string) {
try {
const { data } = await loginMutate(variables);
if (!data.login) {
throw new Error(`Échec de la connexion par mot de passe.`);
}
await redirectTo(redirect);
}
catch (error) {
console.log(error);
}
}
// Logout
async function logout(redirect?: string) {
try {
const result = await $fetch("/api/logout", { method: "POST" });
if (!result.success) {
throw new Error("Échec de la déconnexion.");
}
await redirectTo(redirect);
}
catch (error) {
console.log(error);
}
}
return { isRedirecting, login, logout };
}

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import { fr } from "@nuxt/ui/locale";
import type { NuxtError } from "#app";
const props = defineProps<{ error: NuxtError }>();
const formattedError = computed(() => {
const error = {
statusCode: props.error.statusCode,
statusMessage: props.error.statusMessage,
message: props.error.message,
};
switch (error.statusCode) {
case 404:
error.statusMessage = "Page non trouvée";
break;
case 500:
error.message = "Erreur interne du serveur.";
break;
}
return error;
});
</script>
<template>
<UApp :locale="fr">
<UError :error="formattedError" />
</UApp>
</template>

View File

@@ -0,0 +1,19 @@
fragment AuthUser on User {
id
email
roles {
nodes {
name
}
}
}
mutation AuthLogin($username: String!, $password: String!) {
login( input: { provider: PASSWORD, credentials: { username: $username, password: $password }}) {
authToken
refreshToken
user {
...AuthUser
}
}
}

View File

@@ -0,0 +1,5 @@
mutation AuthRefreshToken($refreshToken: String!) {
refreshToken( input: { refreshToken: $refreshToken }) {
authToken
}
}

View File

@@ -0,0 +1,9 @@
<script setup lang="ts">
</script>
<template>
<div id="page-connexion">
<SectionAuthConnexion />
</div>
</template>

View File

@@ -5,6 +5,7 @@ export default defineNuxtConfig({
"@lewebsimple/nuxt-graphql", "@lewebsimple/nuxt-graphql",
"@nuxt/eslint", "@nuxt/eslint",
"@nuxt/ui", "@nuxt/ui",
"nuxt-auth-utils",
], ],
components: { components: {
@@ -36,10 +37,12 @@ export default defineNuxtConfig({
}, },
graphql: { graphql: {
context: "server/graphql/context.ts",
schemas: { schemas: {
wp: { wp: {
type: "remote", type: "remote",
url: `${process.env.NUXT_WP_URL || "https://wp-headless.ledevsimple.ca"}/graphql`, url: `${process.env.NUXT_WP_URL || "https://wp-headless.ledevsimple.ca"}/graphql`,
middleware: "server/graphql/wp-middleware.ts",
}, },
}, },
saveSdl: "server/graphql/schema.graphql", saveSdl: "server/graphql/schema.graphql",

View File

@@ -6,7 +6,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "nuxt build", "build": "nuxt build",
"dev": "nuxt dev --host 0.0.0.0", "dev": "nuxt dev",
"lint": "eslint --fix .", "lint": "eslint --fix .",
"postinstall": "pnpm --sequential /postinstall:.*/", "postinstall": "pnpm --sequential /postinstall:.*/",
"postinstall:nuxt": "nuxt prepare", "postinstall:nuxt": "nuxt prepare",
@@ -18,10 +18,13 @@
"@iconify-json/lucide": "^1.2.84", "@iconify-json/lucide": "^1.2.84",
"@lewebsimple/nuxt-graphql": "^0.3.5", "@lewebsimple/nuxt-graphql": "^0.3.5",
"@nuxt/ui": "4.3.0", "@nuxt/ui": "4.3.0",
"jwt-decode": "^4.0.0",
"nuxt": "^4.2.2", "nuxt": "^4.2.2",
"nuxt-auth-utils": "^0.5.27",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
"vue": "^3.5.26", "vue": "^3.5.26",
"vue-router": "^4.6.4" "vue-router": "^4.6.4",
"zod": "^4.3.5"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint": "^1.12.1", "@nuxt/eslint": "^1.12.1",

View File

@@ -16,10 +16,16 @@ importers:
version: 0.3.5(@parcel/watcher@2.5.4)(@types/node@25.0.8)(crossws@0.3.5)(db0@0.3.4)(ioredis@5.9.1)(magicast@0.5.1)(typescript@5.9.3)(zod@4.3.5) version: 0.3.5(@parcel/watcher@2.5.4)(@types/node@25.0.8)(crossws@0.3.5)(db0@0.3.4)(ioredis@5.9.1)(magicast@0.5.1)(typescript@5.9.3)(zod@4.3.5)
'@nuxt/ui': '@nuxt/ui':
specifier: 4.3.0 specifier: 4.3.0
version: 4.3.0(65418aa895d42cecf2e624fc048ac280) version: 4.3.0(4d7ad7220595df7306fe9f7759cbbe27)
jwt-decode:
specifier: ^4.0.0
version: 4.0.0
nuxt: nuxt:
specifier: ^4.2.2 specifier: ^4.2.2
version: 4.2.2(@parcel/watcher@2.5.4)(@types/node@25.0.8)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.1)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.55.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) version: 4.2.2(@parcel/watcher@2.5.4)(@types/node@25.0.8)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.1)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.55.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2)
nuxt-auth-utils:
specifier: ^0.5.27
version: 0.5.27(magicast@0.5.1)
tailwindcss: tailwindcss:
specifier: ^4.1.18 specifier: ^4.1.18
version: 4.1.18 version: 4.1.18
@@ -29,6 +35,9 @@ importers:
vue-router: vue-router:
specifier: ^4.6.4 specifier: ^4.6.4
version: 4.6.4(vue@3.5.26(typescript@5.9.3)) version: 4.6.4(vue@3.5.26(typescript@5.9.3))
zod:
specifier: ^4.3.5
version: 4.3.5
devDependencies: devDependencies:
'@nuxt/eslint': '@nuxt/eslint':
specifier: ^1.12.1 specifier: ^1.12.1
@@ -48,6 +57,18 @@ importers:
packages: packages:
'@adonisjs/hash@9.1.1':
resolution: {integrity: sha512-ZkRguwjAp4skKvKDdRAfdJ2oqQ0N7p9l3sioyXO1E8o0WcsyDgEpsTQtuVNoIdMiw4sn4gJlmL3nyF4BcK1ZDQ==}
engines: {node: '>=20.6.0'}
peerDependencies:
argon2: ^0.31.2 || ^0.41.0 || ^0.43.0
bcrypt: ^5.1.1 || ^6.0.0
peerDependenciesMeta:
argon2:
optional: true
bcrypt:
optional: true
'@alloc/quick-lru@5.2.0': '@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1686,6 +1707,10 @@ packages:
resolution: {integrity: sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==} resolution: {integrity: sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
'@phc/format@1.0.0':
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
engines: {node: '>=10'}
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -1702,6 +1727,17 @@ packages:
'@poppinss/exception@1.2.3': '@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@poppinss/object-builder@1.1.0':
resolution: {integrity: sha512-FOrOq52l7u8goR5yncX14+k+Ewi5djnrt1JwXeS/FvnwAPOiveFhiczCDuvXdssAwamtrV2hp5Rw9v+n2T7hQg==}
engines: {node: '>=20.6.0'}
'@poppinss/string@1.7.1':
resolution: {integrity: sha512-OrLzv/nGDU6l6dLXIQHe8nbNSWWfuSbpB/TW5nRpZFf49CLuQlIHlSPN9IdSUv2vG+59yGM6LoibsaHn8B8mDw==}
'@poppinss/utils@6.10.1':
resolution: {integrity: sha512-da+MMyeXhBaKtxQiWPfy7+056wk3lVIhioJnXHXkJ2/OHDaZfFcyKHNl1R06sdYO8lIRXcXdoZ6LO2ARmkAREA==}
engines: {node: '>=18.16.0'}
'@remirror/core-constants@3.0.0': '@remirror/core-constants@3.0.0':
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
@@ -2297,6 +2333,9 @@ packages:
resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==}
deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed. deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed.
'@types/pluralize@0.0.33':
resolution: {integrity: sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==}
'@types/resolve@1.20.2': '@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
@@ -2884,6 +2923,10 @@ packages:
capital-case@1.0.4: capital-case@1.0.4:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
case-anything@3.1.2:
resolution: {integrity: sha512-wljhAjDDIv/hM2FzgJnYQg90AWmZMNtESCjTeLH680qTzdo0nErlCxOmgzgX4ZsZAtIvqHyD87ES8QyriXB+BQ==}
engines: {node: '>=18'}
chalk@4.1.2: chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -3647,6 +3690,10 @@ packages:
flatted@3.3.3: flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
flattie@1.1.1:
resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
engines: {node: '>=8'}
fontaine@0.7.0: fontaine@0.7.0:
resolution: {integrity: sha512-vlaWLyoJrOnCBqycmFo/CA8ZmPzuyJHYmgu261KYKByZ4YLz9sTyHZ4qoHgWSYiDsZXhiLo2XndVMz0WOAyZ8Q==} resolution: {integrity: sha512-vlaWLyoJrOnCBqycmFo/CA8ZmPzuyJHYmgu261KYKByZ4YLz9sTyHZ4qoHgWSYiDsZXhiLo2XndVMz0WOAyZ8Q==}
engines: {node: '>=18.12.0'} engines: {node: '>=18.12.0'}
@@ -4094,6 +4141,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true hasBin: true
jose@6.1.3:
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -4141,6 +4191,10 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
hasBin: true hasBin: true
jwt-decode@4.0.0:
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
engines: {node: '>=18'}
keyv@4.5.4: keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -4576,6 +4630,23 @@ packages:
nullthrows@1.1.1: nullthrows@1.1.1:
resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==}
nuxt-auth-utils@0.5.27:
resolution: {integrity: sha512-nXV/479IaP+g8z3+OqTgv7SNcNqPn0zyhD1t8tBxtskt+LFX1nKCZ6ZbpBGLL2wFdobh+gvzNHQjIIzqigr5Bw==}
peerDependencies:
'@atproto/api': ^0.13.15
'@atproto/oauth-client-node': ^0.2.0
'@simplewebauthn/browser': ^11.0.0
'@simplewebauthn/server': ^11.0.0
peerDependenciesMeta:
'@atproto/api':
optional: true
'@atproto/oauth-client-node':
optional: true
'@simplewebauthn/browser':
optional: true
'@simplewebauthn/server':
optional: true
nuxt@4.2.2: nuxt@4.2.2:
resolution: {integrity: sha512-n6oYFikgLEb70J4+K19jAzfx4exZcRSRX7yZn09P5qlf2Z59VNOBqNmaZO5ObzvyGUZ308SZfL629/Q2v2FVjw==} resolution: {integrity: sha512-n6oYFikgLEb70J4+K19jAzfx4exZcRSRX7yZn09P5qlf2Z59VNOBqNmaZO5ObzvyGUZ308SZfL629/Q2v2FVjw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
@@ -4594,6 +4665,9 @@ packages:
engines: {node: ^14.16.0 || >=16.10.0} engines: {node: ^14.16.0 || >=16.10.0}
hasBin: true hasBin: true
oauth4webapi@3.8.3:
resolution: {integrity: sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==}
object-assign@4.1.1: object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -4634,6 +4708,9 @@ packages:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
openid-client@6.8.1:
resolution: {integrity: sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -5203,6 +5280,10 @@ packages:
safe-buffer@5.2.1: safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
safer-buffer@2.1.2: safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
@@ -5217,6 +5298,9 @@ packages:
scule@1.3.0: scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
secure-json-parse@4.1.0:
resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
semver@6.3.1: semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true hasBin: true
@@ -5294,6 +5378,10 @@ packages:
resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
engines: {node: '>=18'} engines: {node: '>=18'}
slugify@1.6.6:
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
engines: {node: '>=8.0.0'}
smob@1.5.0: smob@1.5.0:
resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
@@ -6056,6 +6144,11 @@ packages:
snapshots: snapshots:
'@adonisjs/hash@9.1.1':
dependencies:
'@phc/format': 1.0.0
'@poppinss/utils': 6.10.1
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
'@antfu/install-pkg@1.1.0': '@antfu/install-pkg@1.1.0':
@@ -7837,7 +7930,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- magicast - magicast
'@nuxt/ui@4.3.0(65418aa895d42cecf2e624fc048ac280)': '@nuxt/ui@4.3.0(4d7ad7220595df7306fe9f7759cbbe27)':
dependencies: dependencies:
'@iconify/vue': 5.0.0(vue@3.5.26(typescript@5.9.3)) '@iconify/vue': 5.0.0(vue@3.5.26(typescript@5.9.3))
'@internationalized/date': 3.10.1 '@internationalized/date': 3.10.1
@@ -7867,7 +7960,7 @@ snapshots:
'@tiptap/vue-3': 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(vue@3.5.26(typescript@5.9.3)) '@tiptap/vue-3': 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(vue@3.5.26(typescript@5.9.3))
'@unhead/vue': 2.1.2(vue@3.5.26(typescript@5.9.3)) '@unhead/vue': 2.1.2(vue@3.5.26(typescript@5.9.3))
'@vueuse/core': 14.1.0(vue@3.5.26(typescript@5.9.3)) '@vueuse/core': 14.1.0(vue@3.5.26(typescript@5.9.3))
'@vueuse/integrations': 14.1.0(change-case@5.4.4)(fuse.js@7.1.0)(vue@3.5.26(typescript@5.9.3)) '@vueuse/integrations': 14.1.0(change-case@5.4.4)(fuse.js@7.1.0)(jwt-decode@4.0.0)(vue@3.5.26(typescript@5.9.3))
colortranslator: 5.0.0 colortranslator: 5.0.0
consola: 3.4.2 consola: 3.4.2
defu: 6.1.4 defu: 6.1.4
@@ -8221,6 +8314,8 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.4 '@parcel/watcher-win32-ia32': 2.5.4
'@parcel/watcher-win32-x64': 2.5.4 '@parcel/watcher-win32-x64': 2.5.4
'@phc/format@1.0.0': {}
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
@@ -8238,6 +8333,24 @@ snapshots:
'@poppinss/exception@1.2.3': {} '@poppinss/exception@1.2.3': {}
'@poppinss/object-builder@1.1.0': {}
'@poppinss/string@1.7.1':
dependencies:
'@types/pluralize': 0.0.33
case-anything: 3.1.2
pluralize: 8.0.0
slugify: 1.6.6
'@poppinss/utils@6.10.1':
dependencies:
'@poppinss/exception': 1.2.3
'@poppinss/object-builder': 1.1.0
'@poppinss/string': 1.7.1
flattie: 1.1.1
safe-stable-stringify: 2.5.0
secure-json-parse: 4.1.0
'@remirror/core-constants@3.0.0': {} '@remirror/core-constants@3.0.0': {}
'@repeaterjs/repeater@3.0.6': {} '@repeaterjs/repeater@3.0.6': {}
@@ -8764,6 +8877,8 @@ snapshots:
dependencies: dependencies:
parse-path: 7.1.0 parse-path: 7.1.0
'@types/pluralize@0.0.33': {}
'@types/resolve@1.20.2': {} '@types/resolve@1.20.2': {}
'@types/web-bluetooth@0.0.20': {} '@types/web-bluetooth@0.0.20': {}
@@ -9136,7 +9251,7 @@ snapshots:
'@vueuse/shared': 14.1.0(vue@3.5.26(typescript@5.9.3)) '@vueuse/shared': 14.1.0(vue@3.5.26(typescript@5.9.3))
vue: 3.5.26(typescript@5.9.3) vue: 3.5.26(typescript@5.9.3)
'@vueuse/integrations@14.1.0(change-case@5.4.4)(fuse.js@7.1.0)(vue@3.5.26(typescript@5.9.3))': '@vueuse/integrations@14.1.0(change-case@5.4.4)(fuse.js@7.1.0)(jwt-decode@4.0.0)(vue@3.5.26(typescript@5.9.3))':
dependencies: dependencies:
'@vueuse/core': 14.1.0(vue@3.5.26(typescript@5.9.3)) '@vueuse/core': 14.1.0(vue@3.5.26(typescript@5.9.3))
'@vueuse/shared': 14.1.0(vue@3.5.26(typescript@5.9.3)) '@vueuse/shared': 14.1.0(vue@3.5.26(typescript@5.9.3))
@@ -9144,6 +9259,7 @@ snapshots:
optionalDependencies: optionalDependencies:
change-case: 5.4.4 change-case: 5.4.4
fuse.js: 7.1.0 fuse.js: 7.1.0
jwt-decode: 4.0.0
'@vueuse/metadata@10.11.1': {} '@vueuse/metadata@10.11.1': {}
@@ -9417,6 +9533,8 @@ snapshots:
tslib: 2.6.3 tslib: 2.6.3
upper-case-first: 2.0.2 upper-case-first: 2.0.2
case-anything@3.1.2: {}
chalk@4.1.2: chalk@4.1.2:
dependencies: dependencies:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
@@ -9562,7 +9680,7 @@ snapshots:
constant-case@3.0.4: constant-case@3.0.4:
dependencies: dependencies:
no-case: 3.0.4 no-case: 3.0.4
tslib: 2.8.1 tslib: 2.6.3
upper-case: 2.0.2 upper-case: 2.0.2
convert-gitmoji@0.1.5: {} convert-gitmoji@0.1.5: {}
@@ -10249,6 +10367,8 @@ snapshots:
flatted@3.3.3: {} flatted@3.3.3: {}
flattie@1.1.1: {}
fontaine@0.7.0: fontaine@0.7.0:
dependencies: dependencies:
'@capsizecss/unpack': 3.0.1 '@capsizecss/unpack': 3.0.1
@@ -10730,6 +10850,8 @@ snapshots:
jiti@2.6.1: {} jiti@2.6.1: {}
jose@6.1.3: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-tokens@9.0.1: {} js-tokens@9.0.1: {}
@@ -10764,6 +10886,8 @@ snapshots:
json5@2.2.3: {} json5@2.2.3: {}
jwt-decode@4.0.0: {}
keyv@4.5.4: keyv@4.5.4:
dependencies: dependencies:
json-buffer: 3.0.1 json-buffer: 3.0.1
@@ -11245,6 +11369,24 @@ snapshots:
nullthrows@1.1.1: {} nullthrows@1.1.1: {}
nuxt-auth-utils@0.5.27(magicast@0.5.1):
dependencies:
'@adonisjs/hash': 9.1.1
'@nuxt/kit': 4.2.2(magicast@0.5.1)
defu: 6.1.4
h3: 1.15.4
hookable: 6.0.1
jose: 6.1.3
ofetch: 1.5.1
openid-client: 6.8.1
pathe: 2.0.3
scule: 1.3.0
uncrypto: 0.1.3
transitivePeerDependencies:
- argon2
- bcrypt
- magicast
nuxt@4.2.2(@parcel/watcher@2.5.4)(@types/node@25.0.8)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.1)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.55.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2): nuxt@4.2.2(@parcel/watcher@2.5.4)(@types/node@25.0.8)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.1)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.55.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2):
dependencies: dependencies:
'@dxup/nuxt': 0.2.2(magicast@0.5.1) '@dxup/nuxt': 0.2.2(magicast@0.5.1)
@@ -11376,6 +11518,8 @@ snapshots:
pkg-types: 2.3.0 pkg-types: 2.3.0
tinyexec: 1.0.2 tinyexec: 1.0.2
oauth4webapi@3.8.3: {}
object-assign@4.1.1: {} object-assign@4.1.1: {}
object-deep-merge@2.0.0: {} object-deep-merge@2.0.0: {}
@@ -11417,6 +11561,11 @@ snapshots:
is-docker: 2.2.1 is-docker: 2.2.1
is-wsl: 2.2.0 is-wsl: 2.2.0
openid-client@6.8.1:
dependencies:
jose: 6.1.3
oauth4webapi: 3.8.3
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@@ -12076,6 +12225,8 @@ snapshots:
safe-buffer@5.2.1: {} safe-buffer@5.2.1: {}
safe-stable-stringify@2.5.0: {}
safer-buffer@2.1.2: {} safer-buffer@2.1.2: {}
sax@1.4.4: {} sax@1.4.4: {}
@@ -12088,6 +12239,8 @@ snapshots:
scule@1.3.0: {} scule@1.3.0: {}
secure-json-parse@4.1.0: {}
semver@6.3.1: {} semver@6.3.1: {}
semver@7.7.3: {} semver@7.7.3: {}
@@ -12174,6 +12327,8 @@ snapshots:
ansi-styles: 6.2.3 ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0 is-fullwidth-code-point: 5.1.0
slugify@1.6.6: {}
smob@1.5.0: {} smob@1.5.0: {}
snake-case@3.0.4: snake-case@3.0.4:

View File

@@ -0,0 +1,5 @@
onlyBuiltDependencies:
- '@parcel/watcher'
- esbuild
- unrs-resolver
- vue-demi

View File

@@ -0,0 +1,12 @@
import { defineEventHandler } from "h3";
export default defineEventHandler(async (event) => {
try {
await handleLogout(event);
return { success: true, message: "Déconnexion réussie" };
}
catch (error) {
const message = error instanceof Error ? error.message : "Une erreur est survenue lors de la déconnexion.";
return { success: false, message };
}
});

View File

@@ -0,0 +1,11 @@
import { defineGraphQLContext } from "@lewebsimple/nuxt-graphql/helpers";
import type { AuthLoginMutation } from "#graphql/typed-documents";
export default defineGraphQLContext(async (event) => {
const authToken = await getAuthToken(event);
return {
authToken,
handleLogin: async (loginData: AuthLoginMutation) => handleLogin(event, loginData),
};
});

View File

@@ -39,6 +39,18 @@ type ACFE_AdvancedLink_Url implements ACFE_AdvancedLink {
url: String url: String
} }
"""A Field Group managed by ACF"""
interface AcfFieldGroup {
"""The name of the field group"""
fieldGroupName: String @deprecated(reason: "Use __typename instead")
}
"""Fields associated with an ACF Field Group"""
interface AcfFieldGroupFields {
"""The name of the field group"""
fieldGroupName: String @deprecated(reason: "Use __typename instead")
}
"""The Headless Login authentication data.""" """The Headless Login authentication data."""
type AuthenticationData { type AuthenticationData {
"""A new authentication token to use in future requests.""" """A new authentication token to use in future requests."""
@@ -3295,6 +3307,92 @@ enum GoogleProviderPromptTypeEnum {
SELECT_ACCOUNT SELECT_ACCOUNT
} }
"""
The &quot;GroupAbstractBuilder&quot; Field Group. Added to the Schema by &quot;WPGraphQL for ACF&quot;.
"""
type GroupAbstractBuilder implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilder_Fields {
"""The name of the field group"""
fieldGroupName: String @deprecated(reason: "Use __typename instead")
"""
Field of the &quot;flexible_content&quot; Field Type added to the schema as part of the &quot;GroupAbstractBuilder&quot; Field Group
"""
sections: [GroupAbstractBuilderSections_Layout]
}
"""
The &quot;GroupAbstractBuilderSectionsTextBlockLayout&quot; Field Group. Added to the Schema by &quot;WPGraphQL for ACF&quot;.
"""
type GroupAbstractBuilderSectionsTextBlockLayout implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilderSectionsTextBlockLayout_Fields & GroupAbstractBuilderSections_Layout {
"""
Field of the &quot;wysiwyg&quot; Field Type added to the schema as part of the &quot;GroupAbstractBuilderSectionsTextBlockLayout&quot; Field Group
"""
content: String!
"""The name of the field group"""
fieldGroupName: String @deprecated(reason: "Use __typename instead")
}
"""
Interface representing fields of the ACF &quot;GroupAbstractBuilderSectionsTextBlockLayout&quot; Field Group
"""
interface GroupAbstractBuilderSectionsTextBlockLayout_Fields implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilderSections_Layout {
"""
Field of the &quot;wysiwyg&quot; Field Type added to the schema as part of the &quot;GroupAbstractBuilderSectionsTextBlockLayout&quot; Field Group
"""
content: String!
"""The name of the field group"""
fieldGroupName: String @deprecated(reason: "Use __typename instead")
}
"""
Layout of the &quot;sections&quot; Field of the &quot;GroupAbstractBuilder&quot; Field Group Field
"""
interface GroupAbstractBuilderSections_Layout {
"""The name of the ACF Flex Field Layout"""
fieldGroupName: String
}
"""
Interface representing fields of the ACF &quot;GroupAbstractBuilder&quot; Field Group
"""
interface GroupAbstractBuilder_Fields implements AcfFieldGroup & AcfFieldGroupFields {
"""The name of the field group"""
fieldGroupName: String @deprecated(reason: "Use __typename instead")
"""
Field of the &quot;flexible_content&quot; Field Type added to the schema as part of the &quot;GroupAbstractBuilder&quot; Field Group
"""
sections: [GroupAbstractBuilderSections_Layout]
}
"""
The &quot;GroupPostPage&quot; Field Group. Added to the Schema by &quot;WPGraphQL for ACF&quot;.
"""
type GroupPostPage implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilder_Fields & GroupPostPage_Fields {
"""The name of the field group"""
fieldGroupName: String @deprecated(reason: "Use __typename instead")
"""
Field of the &quot;flexible_content&quot; Field Type added to the schema as part of the &quot;GroupAbstractBuilder&quot; Field Group
"""
sections: [GroupAbstractBuilderSections_Layout]
}
"""
Interface representing fields of the ACF &quot;GroupPostPage&quot; Field Group
"""
interface GroupPostPage_Fields implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilder_Fields {
"""The name of the field group"""
fieldGroupName: String @deprecated(reason: "Use __typename instead")
"""
Field of the &quot;flexible_content&quot; Field Type added to the schema as part of the &quot;GroupAbstractBuilder&quot; Field Group
"""
sections: [GroupAbstractBuilderSections_Layout]
}
""" """
Content that can be organized in a parent-child structure. Provides fields for navigating up and down the hierarchy and maintaining structured relationships. Content that can be organized in a parent-child structure. Provides fields for navigating up and down the hierarchy and maintaining structured relationships.
""" """
@@ -5759,7 +5857,7 @@ enum OrderEnum {
""" """
A standalone content entry generally used for static, non-chronological content such as &quot;About Us&quot; or &quot;Contact&quot; pages. A standalone content entry generally used for static, non-chronological content such as &quot;About Us&quot; or &quot;Contact&quot; pages.
""" """
type Page implements ContentNode & DatabaseIdentifier & HierarchicalContentNode & HierarchicalNode & MenuItemLinkable & Node & NodeWithAuthor & NodeWithContentEditor & NodeWithFeaturedImage & NodeWithPageAttributes & NodeWithRevisions & NodeWithTemplate & NodeWithTitle & Previewable & UniformResourceIdentifiable { type Page implements ContentNode & DatabaseIdentifier & HierarchicalContentNode & HierarchicalNode & MenuItemLinkable & Node & NodeWithAuthor & NodeWithContentEditor & NodeWithFeaturedImage & NodeWithPageAttributes & NodeWithRevisions & NodeWithTemplate & NodeWithTitle & Previewable & UniformResourceIdentifiable & WithAcfGroupPostPage {
""" """
Returns ancestors of the node. Default ordered as lowest (closest to the child) to highest (closest to the root). Returns ancestors of the node. Default ordered as lowest (closest to the child) to highest (closest to the root).
""" """
@@ -5902,6 +6000,9 @@ type Page implements ContentNode & DatabaseIdentifier & HierarchicalContentNode
"""Globally unique ID of the featured image assigned to the node""" """Globally unique ID of the featured image assigned to the node"""
featuredImageId: ID featuredImageId: ID
"""Fields of the GroupPostPage ACF Field Group"""
groupPostPage: GroupPostPage
""" """
The global unique identifier for this post. This currently matches the value stored in WP_Post-&gt;guid and the guid column in the &quot;post_objects&quot; database table. The global unique identifier for this post. This currently matches the value stored in WP_Post-&gt;guid and the guid column in the &quot;post_objects&quot; database table.
""" """
@@ -13884,6 +13985,14 @@ interface WPPageInfo implements PageInfo {
startCursor: String startCursor: String
} }
"""
Provides access to fields of the &quot;GroupPostPage&quot; ACF Field Group via the &quot;groupPostPage&quot; field
"""
interface WithAcfGroupPostPage {
"""Fields of the GroupPostPage ACF Field Group"""
groupPostPage: GroupPostPage
}
"""The writing setting type""" """The writing setting type"""
type WritingSettings { type WritingSettings {
"""Catégorie darticle par défaut.""" """Catégorie darticle par défaut."""

View File

@@ -0,0 +1,20 @@
import type { AuthLoginMutation } from "#build/graphql/typed-documents";
import { defineRemoteExecMiddleware } from "@lewebsimple/nuxt-graphql/helpers";
export default defineRemoteExecMiddleware({
onRequest({ context, fetchOptions }) {
// Attach auth token from context to request headers
if (context.authToken) {
fetchOptions.headers.set("Authorization", `Bearer ${context.authToken}`);
}
},
async onResponse({ operationName, response, context }) {
// Save auth token in user session
if (operationName === "AuthLogin") {
const { data } = await response.json() as { data?: AuthLoginMutation };
if (data) {
await context.handleLogin(data);
}
}
},
});

View File

@@ -0,0 +1,75 @@
import type { H3Event } from "h3";
import { GraphQLClient } from "graphql-request";
import { jwtDecode } from "jwt-decode";
import type { User } from "#auth-utils";
import { type AuthUserFragment, type AuthLoginMutation, AuthRefreshTokenDocument } from "#graphql/typed-documents";
// Handle login result and store user session
export async function handleLogin(event: H3Event, loginData: AuthLoginMutation) {
if (!loginData?.login) {
return;
}
const { user, authToken, refreshToken } = loginData.login;
if (!user || !authToken || !refreshToken) {
return;
}
await setUserSession(event, {
user: getAuthUser(user),
secure: {
authToken,
refreshToken,
},
loggedInAt: new Date().toISOString(),
});
}
// Handle user logout by clearing session
export async function handleLogout(event: H3Event) {
await clearUserSession(event);
}
// Convert AuthUserFragment to nuxt-auth-utils User
function getAuthUser(user: AuthUserFragment): User {
return {
id: Number(user.id),
email: user.email!,
roles: extractNodes(user.roles).map(({ name }) => name!) || [],
};
}
// Refresh auth token by calling remote GraphQL endpoint directly
export async function refreshAuthToken(refreshToken: string): Promise<string | undefined> {
const client = new GraphQLClient(`${process.env.NUXT_WP_URL || "https://wp-headless.ledevsimple.ca"}/graphql`);
const data = await client.request(AuthRefreshTokenDocument, { refreshToken });
return data.refreshToken?.authToken || undefined;
}
// Get auth token from user session (refresh if needed)
export async function getAuthToken(event: H3Event): Promise<string | undefined> {
// Retrieve user session, return if none
const session = await getUserSession(event);
if (!session.secure) {
return;
}
// Extract tokens and check expiration
const { authToken, refreshToken } = session.secure;
const decoded = jwtDecode<{ exp: number }>(authToken);
const isExpired = decoded.exp * 1000 < Date.now();
if (isExpired) {
try {
const newAuthToken = await refreshAuthToken(refreshToken);
if (!newAuthToken) {
throw new Error("Impossible de rafraîchir le jeton d'authentification.");
}
session.secure.authToken = newAuthToken;
await setUserSession(event, session);
}
catch {
await clearUserSession(event);
return;
}
}
return session.secure.authToken;
}

View File

@@ -0,0 +1,19 @@
// auth.d.ts
declare module "#auth-utils" {
interface User {
id: number;
email: string;
roles: string[];
}
interface UserSession {
loggedInAt: string;
}
interface SecureSessionData {
authToken: string;
refreshToken: string;
}
}
export { };

View File

@@ -0,0 +1,3 @@
export async function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -0,0 +1,4 @@
// Helper: Extracts nodes from a GraphQL connection object, returning an empty array if nodes are absent.
export function extractNodes<T>(connection: { nodes?: T[] } | null | undefined): T[] {
return connection?.nodes || [] as T[];
}