diff --git a/src/app/api/graphql/route.ts b/src/app/api/graphql/route.ts index 151ee38..e18c9d9 100644 --- a/src/app/api/graphql/route.ts +++ b/src/app/api/graphql/route.ts @@ -1,3 +1,5 @@ import { yoga } from "@/graphql"; -export { yoga as GET, yoga as POST, yoga as OPTIONS }; +export const GET = (request: Request) => yoga.handleRequest(request, { request, db: {} }); +export const POST = (request: Request) => yoga.handleRequest(request, { request, db: {} }); +export const OPTIONS = (request: Request) => yoga.handleRequest(request, { request, db: {} }); diff --git a/src/app/callback/page.tsx b/src/app/callback/page.tsx deleted file mode 100644 index 4676ca5..0000000 --- a/src/app/callback/page.tsx +++ /dev/null @@ -1,114 +0,0 @@ -"use client"; - -import { useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; - -export default function CallbackPage() { - const [status, setStatus] = useState<"loading" | "success" | "error">("loading"); - const [message, setMessage] = useState(""); - const searchParams = useSearchParams(); - - useEffect(() => { - const handleCallback = async () => { - try { - const code = searchParams.get("code"); - const state = searchParams.get("state"); - const error = searchParams.get("error"); - - if (error) { - setStatus("error"); - setMessage(`Erreur d'authentification: ${error}`); - return; - } - - if (!code || !state) { - setStatus("error"); - setMessage("Paramètres de callback manquants"); - return; - } - - // Décoder le state pour récupérer les informations du type d'authentification - const stateData = JSON.parse(decodeURIComponent(state)); - const { type, connector } = stateData; - - // Ici vous pouvez traiter le code d'autorisation pour obtenir les tokens - // et créer ou connecter l'utilisateur avec votre API - - setStatus("success"); - setMessage( - `Authentification ${type === "login" ? "connexion" : "inscription"} réussie avec le connecteur ${connector}` - ); - - // Rediriger vers la page appropriée après un délai - setTimeout(() => { - if (type === "login") { - window.location.href = "/profile"; - } else { - window.location.href = "/profile"; - } - }, 2000); - } catch (err) { - console.error("Erreur lors du traitement du callback:", err); - setStatus("error"); - setMessage("Erreur lors du traitement de l'authentification"); - } - }; - - handleCallback(); - }, [searchParams]); - - return ( -
-
-
- {status === "loading" && ( - <> -
-

Authentification en cours...

-

Veuillez patienter pendant que nous traitons votre authentification.

- - )} - - {status === "success" && ( - <> -
- - - -
-

Authentification réussie !

-

{message}

-

Redirection en cours...

- - )} - - {status === "error" && ( - <> -
- - - -
-

Erreur d'authentification

-

{message}

-
- - Retour à la connexion - - - Retour à l'inscription - -
- - )} -
-
-
- ); -} diff --git a/src/app/connectors/page.tsx b/src/app/connectors/page.tsx index e0c6037..fcc2234 100644 --- a/src/app/connectors/page.tsx +++ b/src/app/connectors/page.tsx @@ -68,19 +68,6 @@ const CREATE_CONNECTOR_MUTATION = gql` } `; -const UPDATE_CONNECTOR_MUTATION = gql` - mutation UpdateConnector($input: UpdateConnectorInput!) { - updateConnector(input: $input) { - success - message - connector { - id - enabled - } - } - } -`; - const DELETE_CONNECTOR_MUTATION = gql` mutation DeleteConnector($id: ID!) { deleteConnector(id: $id) { diff --git a/src/app/roles/page.tsx b/src/app/roles/page.tsx index 9730644..24bdd31 100644 --- a/src/app/roles/page.tsx +++ b/src/app/roles/page.tsx @@ -23,7 +23,7 @@ export default function RolesPage() { // Fetch roles useEffect(() => { client - .request(gql` + .request<{ roles: Role[] }>(gql` { roles { id @@ -37,7 +37,7 @@ export default function RolesPage() { } } `) - .then((data: any) => { + .then(data => { setRoles(data.roles); setLoading(false); }) diff --git a/src/app/roles/permissions.tsx b/src/app/roles/permissions.tsx index 483dd75..7191fad 100644 --- a/src/app/roles/permissions.tsx +++ b/src/app/roles/permissions.tsx @@ -70,11 +70,6 @@ export default function PermissionsManager({ } } `; - console.log("[DEBUG] createResourceScope called", { - resourceId: selectedResource, - name: form.name, - description: form.description, - }); const variables = { resourceId: selectedResource, name: form.name, description: form.description || undefined }; const data: { createResourceScope: Permission } = await client.request(mutation, variables); setScopes([...scopes, data.createResourceScope]); diff --git a/src/app/test-signup/page.tsx b/src/app/test-signup/page.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/graphql/context/context.ts b/src/graphql/context/context.ts index 0be8ce9..f13d660 100644 --- a/src/graphql/context/context.ts +++ b/src/graphql/context/context.ts @@ -1,6 +1,5 @@ import setAuth from "@/graphql/context/features/auth"; import initGuard from "@/graphql/context/features/guard"; -import { patContextCache } from "@/graphql/context/features/patCache"; import { GraphQLContext } from "@/graphql/context/types"; import { YogaInitialContext } from "graphql-yoga"; @@ -13,7 +12,6 @@ export async function context({ request }: YogaInitialContext): Promise diff --git a/src/graphql/context/features/patCache.ts b/src/graphql/context/features/patCache.ts deleted file mode 100644 index 8fce554..0000000 --- a/src/graphql/context/features/patCache.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Cache pour les associations PAT -> userId intégré au contexte GraphQL -export interface PATCacheEntry { - userId: string; - expiry: number; -} - -class PATContextCache { - private cache: Map = new Map(); - - // Enregistrer une association PAT -> userId - register(pat: string, userId: string, ttlHours: number = 24): void { - this.cache.set(pat, { - userId: userId, - expiry: Date.now() + ttlHours * 60 * 60 * 1000, - }); - } - - // Récupérer l'userId depuis un PAT - getUserId(pat: string): string | null { - const entry = this.cache.get(pat); - if (entry && Date.now() < entry.expiry) { - return entry.userId; - } - - // Supprimer l'entrée expirée - if (entry) { - this.cache.delete(pat); - } - - return null; - } - - // Supprimer une entrée du cache - remove(pat: string): void { - this.cache.delete(pat); - } - - // Nettoyer les entrées expirées - cleanup(): void { - const now = Date.now(); - for (const [pat, entry] of this.cache.entries()) { - if (now >= entry.expiry) { - this.cache.delete(pat); - } - } - } - - // Obtenir les statistiques du cache - getStats(): { size: number; entries: number } { - this.cleanup(); // Nettoyer avant de retourner les stats - return { - size: this.cache.size, - entries: this.cache.size, - }; - } - - // Vérifier si un PAT existe dans le cache - has(pat: string): boolean { - const entry = this.cache.get(pat); - if (entry && Date.now() < entry.expiry) { - return true; - } - - // Supprimer l'entrée expirée - if (entry) { - this.cache.delete(pat); - } - - return false; - } -} - -// Instance singleton du cache PAT -export const patContextCache = new PATContextCache(); - -// Nettoyer le cache toutes les heures -setInterval( - () => { - patContextCache.cleanup(); - }, - 60 * 60 * 1000 -); // 1 heure diff --git a/src/graphql/context/types.ts b/src/graphql/context/types.ts index e857368..1aeae54 100644 --- a/src/graphql/context/types.ts +++ b/src/graphql/context/types.ts @@ -1,11 +1,9 @@ import type { Resource } from "@/graphql/features/Resource/resourceService"; import type { Role } from "@/graphql/features/Role/roleService"; -export interface DB { - // Define your database types here -} +export type DB = object; -export interface Guard {} +export type Guard = object; export interface GuardFunction { (auth?: AuthTypes): Guard; @@ -50,5 +48,4 @@ export interface GraphQLContext { accessToken?: string; personalAccessToken?: string; userId?: string; - patCache: PATCache; // Cache PAT intégré au contexte } diff --git a/src/graphql/features/Organization/resolver.ts b/src/graphql/features/Organization/resolver.ts index a763281..441e014 100644 --- a/src/graphql/features/Organization/resolver.ts +++ b/src/graphql/features/Organization/resolver.ts @@ -192,12 +192,6 @@ export const organizationResolvers = { context: GraphQLContext ): Promise { try { - // DEBUG: log du contexte d'authentification - console.log("[myOrganizations] context.user:", context.user); - console.log("[myOrganizations] context.userId:", context.userId); - if (context.request && typeof context.request.headers?.get === "function") { - console.log("[myOrganizations] Authorization header:", context.request.headers.get("Authorization")); - } const userId = context.user?.id || context.userId; if (!userId) { return { diff --git a/src/graphql/features/Resource/index.ts b/src/graphql/features/Resource/index.ts index dfd80b0..423ec9f 100644 --- a/src/graphql/features/Resource/index.ts +++ b/src/graphql/features/Resource/index.ts @@ -6,16 +6,14 @@ import { resourceScopeResolvers } from "./scopeResolver"; // Fusionne les Query et Mutation des deux resolvers export const Resource = { typeDefs: [typeDefs, resourceScopeTypeDefs], - resolvers: [ - { - Query: { - ...resourceResolvers.Query, - ...resourceScopeResolvers.Query, - }, - Mutation: { - ...resourceResolvers.Mutation, - ...resourceScopeResolvers.Mutation, - }, + resolvers: { + Query: { + ...resourceResolvers.Query, + ...resourceScopeResolvers.Query, }, - ], + Mutation: { + ...resourceResolvers.Mutation, + ...resourceScopeResolvers.Mutation, + }, + }, }; diff --git a/src/graphql/features/Resource/resourceService.ts b/src/graphql/features/Resource/resourceService.ts index 318bb88..244371c 100644 --- a/src/graphql/features/Resource/resourceService.ts +++ b/src/graphql/features/Resource/resourceService.ts @@ -8,8 +8,9 @@ function getAccessTokenFromContext(context: GraphQLContext): string | null { return context.accessToken || null; } -export async function listResources(context: GraphQLContext): Promise { - const token = getAccessTokenFromContext(context) || (await getLogtoAccessToken()); +export async function listResources(_context: GraphQLContext): Promise { + // Toujours utiliser le token M2M pour les endpoints admin + const token = await getLogtoAccessToken(); try { const response = await axios.get(`${process.env.LOGTO_ENDPOINT}/api/resources`, { headers: { Authorization: `Bearer ${token}` }, @@ -18,10 +19,13 @@ export async function listResources(context: GraphQLContext): Promise { } } -export async function getUserRoles(userId: string, context: GraphQLContext): Promise { - const token = getAccessTokenFromContext(context) || (await getLogtoAccessToken()); +export async function getUserRoles(userId: string, _context: GraphQLContext): Promise { + // Toujours utiliser le token M2M pour les endpoints admin + const token = await getLogtoAccessToken(); try { const response = await axios.get(`${process.env.LOGTO_ENDPOINT}/api/users/${userId}/roles`, { headers: { Authorization: `Bearer ${token}` }, }); - const roles = response.data as Role[]; + const roles = Array.isArray(response.data) ? (response.data as Role[]) : []; + if (!roles.length) { + console.warn(`[getUserRoles] Aucun rôle trouvé pour l'utilisateur ${userId}`); + return []; + } return await Promise.all( roles.map(async role => ({ ...role, permissions: await fetchRolePermissions(role.id, token), })) ); - } catch { - throw new GraphQLError("Erreur lors de la récupération des rôles de l'utilisateur", { - extensions: { code: "USER_ROLES_FETCH_FAILED" }, - }); + } catch (err) { + if (axios.isAxiosError(err)) { + console.error(`[getUserRoles] Axios error:`, err.response?.data || err.message); + } else { + console.error(`[getUserRoles] Unknown error:`, err); + } + return []; } } diff --git a/src/graphql/features/User/resolver.ts b/src/graphql/features/User/resolver.ts index 8e30825..567483f 100644 --- a/src/graphql/features/User/resolver.ts +++ b/src/graphql/features/User/resolver.ts @@ -1,4 +1,3 @@ -import { patContextCache } from "@/graphql/context/features/patCache"; import axios from "axios"; import { GraphQLError } from "graphql"; import { @@ -191,16 +190,14 @@ async function getUserByEmail(email: string): Promise { } } -// Cache pour les associations PAT -> userId pour éviter les recherches répétées -// Note: Maintenant géré via le contexte GraphQL (context.patCache) - async function getUserByPersonalAccessToken(pat: string, context: GraphQLContext): Promise { // Récupérer l'userId directement depuis le cache du contexte - let userId = context.patCache.getUserId(pat); + let userId = context.userId; // Si pas trouvé dans le cache, essayer de valider le PAT avec l'API Logto if (!userId) { - userId = await validatePATWithLogtoAPI(pat, context); + const validatedUserId = await validatePATWithLogtoAPI(pat, context); + userId = validatedUserId === null ? undefined : validatedUserId; if (!userId) { throw new GraphQLError("Token d'accès personnel invalide ou non reconnu", { @@ -219,8 +216,6 @@ async function getUserByPersonalAccessToken(pat: string, context: GraphQLContext return response.data; } catch (error) { console.error("Erreur lors de la récupération de l'utilisateur par PAT:", error); - // Si l'utilisateur n'existe plus, nettoyer le cache - context.patCache.remove(pat); throw new GraphQLError("Utilisateur associé au token non trouvé", { extensions: { code: "PAT_USER_NOT_FOUND" }, }); @@ -228,7 +223,7 @@ async function getUserByPersonalAccessToken(pat: string, context: GraphQLContext } // Fonction pour valider un PAT avec l'API Logto et le remettre en cache -async function validatePATWithLogtoAPI(pat: string, context: GraphQLContext): Promise { +async function validatePATWithLogtoAPI(pat: string, _context: GraphQLContext): Promise { const m2mToken = await getLogtoAccessToken(); try { @@ -260,8 +255,6 @@ async function validatePATWithLogtoAPI(pat: string, context: GraphQLContext): Pr ); if (matchingToken) { - // Remettre en cache pour éviter de refaire cette recherche - context.patCache.register(pat, user.id, 24); // 24 heures return user.id; } } catch { @@ -278,7 +271,7 @@ async function validatePATWithLogtoAPI(pat: string, context: GraphQLContext): Pr } async function createPersonalAccessToken( userId: string, - context: GraphQLContext + _context: GraphQLContext ): Promise<{ token: string; expiresIn: number; userId: string }> { const m2mToken = await getLogtoAccessToken(); const expiresAtTimestamp = Date.now() + 365 * 24 * 60 * 60 * 1000; // 1 an en millisecondes @@ -302,9 +295,6 @@ async function createPersonalAccessToken( const { value: token, expiresAt } = response.data; const expiresIn = Math.floor((expiresAt - Date.now()) / 1000); // Convertir en secondes - // Enregistrer l'association PAT -> userId dans le cache du contexte - context.patCache.register(token, userId, 24); // 24 heures - return { token, expiresIn, userId }; } catch (error) { console.error("Erreur lors de la création du PAT:", error); @@ -347,9 +337,6 @@ async function createPersonalAccessToken( console.warn("PAT existant trouvé et encore valide, réutilisation..."); const expiresIn = Math.floor((expiresAt - now) / 1000); - // Enregistrer l'association PAT -> userId dans le cache du contexte - context.patCache.register(existingPat.value, userId, 24); - return { token: existingPat.value, expiresIn, @@ -383,9 +370,6 @@ async function createPersonalAccessToken( const { value: token, expiresAt: newExpiresAt } = recreateResponse.data; const expiresIn = Math.floor((newExpiresAt - Date.now()) / 1000); - // Enregistrer l'association PAT -> userId dans le cache du contexte - context.patCache.register(token, userId, 24); - return { token, expiresIn, userId }; } } @@ -529,7 +513,6 @@ async function authenticateUserFallback(email: string, password: string): Promis export const resolvers = { Query: { async me(_: unknown, __: unknown, context: GraphQLContext) { - // Priorité 1: Utilisation du Personal Access Token (PAT) if (context.personalAccessToken) { const user = await getUserByPersonalAccessToken(context.personalAccessToken, context); if (user) { @@ -583,16 +566,6 @@ export const resolvers = { }); } }, - - // Query de debug pour les statistiques du cache PAT - async patCacheStats(_: unknown, __: unknown, context: GraphQLContext) { - const stats = context.patCache.getStats(); - return { - cacheSize: stats.size, - totalEntries: stats.entries, - timestamp: new Date().toISOString(), - }; - }, }, Mutation: { @@ -623,7 +596,6 @@ export const resolvers = { if (tokenData) { accessToken = tokenData.token; tokenExpiry = tokenData.expiresIn; - patContextCache.register(accessToken, user.id, 24); } } catch { // On continue même si la création du token échoue @@ -707,7 +679,6 @@ export const resolvers = { if (tokenData) { accessToken = tokenData.token; tokenExpiry = tokenData.expiresIn; - context.patCache.register(accessToken, user.id, 24); // Enregistre dans le cache du contexte context.personalAccessToken = accessToken; context.userId = user.id; context.user = user; @@ -768,7 +739,6 @@ export const resolvers = { } catch (error) { console.error("Erreur lors de la suppression du PAT dans Logto:", error); } - context.patCache.remove(context.personalAccessToken); } return { @@ -785,4 +755,4 @@ export const resolvers = { }, }; -export { getLogtoAccessToken }; +export { getLogtoAccessToken, validatePATWithLogtoAPI }; diff --git a/src/graphql/index.ts b/src/graphql/index.ts index 4b19962..a529c96 100644 --- a/src/graphql/index.ts +++ b/src/graphql/index.ts @@ -14,7 +14,7 @@ export const yoga = createYoga({ plugins: [ // eslint-disable-next-line react-hooks/rules-of-hooks useCSRFPrevention({ - requestHeaders: ["x-graphql-yoga-csrf"], // default + requestHeaders: ["x-graphql-yoga-csrf"], }), depthLimitPlugin, introspectionPlugin,