From 9e2249807260a8d5fb676b0fe7ff06f04c9db9bf Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 19 Feb 2025 00:47:01 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20de=20la=20configuration=20Keycloak=20et?= =?UTF-8?q?=20int=C3=A9gration=20de=20NextAuth=20pour=20l'authentification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 8 ++ compose.yml | 9 ++ front/.env.production | 7 + front/app/api/auth/[...nextauth]/route.ts | 51 +++++++ front/app/layout.tsx | 70 +++++----- front/components/main-nav.tsx | 125 ++++++++---------- front/components/ui/dropdown-menu.tsx | 78 +++++------ front/next.config.mjs | 18 +-- front/package-lock.json | 154 ++++++++++++++++++++++ front/package.json | 1 + front/tsconfig.json | 13 +- front/types/next-auth.d.ts | 23 ++++ front/yarn.lock | 97 +++++++++++++- 13 files changed, 498 insertions(+), 156 deletions(-) create mode 100644 .env create mode 100644 front/.env.production create mode 100644 front/app/api/auth/[...nextauth]/route.ts create mode 100644 front/types/next-auth.d.ts diff --git a/.env b/.env new file mode 100644 index 0000000..67caff3 --- /dev/null +++ b/.env @@ -0,0 +1,8 @@ +# URL +MAIN_DOMAIN= +KEYCLOAK_HOST=connect +KEYCLOAK_HOST_IP= + +# Keycloak +KEYCLOAK_USER= +KEYCLOAK_PASSWORD= \ No newline at end of file diff --git a/compose.yml b/compose.yml index d8fa3e7..18ba353 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,9 @@ networks: default: +volumes: + keycloak_data: + services: traefik: container_name: neah-traefik @@ -28,6 +31,8 @@ services: - "traefik.enable=true" - "traefik.http.routers.neah-front.rule=Host(`${MAIN_DOMAIN}`)" - "traefik.http.services.neah-front.loadbalancer.server.port=3000" + extra_hosts: + - "${KEYCLOAK_HOST}.${MAIN_DOMAIN}:${KEYCLOAK_HOST_IP}" networks: - default @@ -43,5 +48,9 @@ services: - "traefik.enable=true" - "traefik.http.routers.neah-keycloak.rule=Host(`${KEYCLOAK_HOST}.${MAIN_DOMAIN}`)" - "traefik.http.services.neah-keycloak.loadbalancer.server.port=8080" + volumes: + - keycloak_data:/opt/keycloak/data + ports: + - "8090:8080" networks: - default diff --git a/front/.env.production b/front/.env.production new file mode 100644 index 0000000..3d95ccf --- /dev/null +++ b/front/.env.production @@ -0,0 +1,7 @@ +NEXTAUTH_URL=http://neah.local +NEXTAUTH_SECRET=9eff5ad2f4b5ea744a34d9d8004cb5236f1931b26bf75f01a0a26203312fe1ec + +KEYCLOAK_CLIENT_ID=front +KEYCLOAK_CLIENT_SECRET=Klsbm7hzyXscypXU0wUPPVBrttFPt6Pn +KEYCLOAK_REALM=master +KEYCLOAK_ISSUER=http://connect.neah.local/realms/master \ No newline at end of file diff --git a/front/app/api/auth/[...nextauth]/route.ts b/front/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..575431e --- /dev/null +++ b/front/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,51 @@ +import NextAuth, { NextAuthOptions } from "next-auth"; +import KeycloakProvider from "next-auth/providers/keycloak"; + +export const authOptions: NextAuthOptions = { + providers: [ + KeycloakProvider({ + clientId: process.env.KEYCLOAK_CLIENT_ID!, + clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!, + issuer: process.env.KEYCLOAK_ISSUER!, + // Personnalisation de la fonction profile pour inclure le rôle de Keycloak + profile(profileData) { + return { + id: profileData.sub, + name: profileData.name || profileData.preferred_username, + email: profileData.email, + image: null, + // Extraction du rôle ou groupe de l'utilisateur depuis Keycloak + role: Array.isArray(profileData.realm_access?.roles) + ? profileData.realm_access.roles + : [], + }; + }, + }), + ], + callbacks: { + async jwt({ token, account, profile }) { + // Au moment de la première connexion, sauvegarde de l'access token et du rôle dans le JWT + if (account && profile) { + token.accessToken = account.access_token; + token.role = profile.role; + } + return token; + }, + async session({ session, token }) { + // On injecte l'access token et le rôle dans la session accessible côté client + session.accessToken = token.accessToken as string; + session.user.role = + typeof token.role == "string" || Array.isArray(token.role) + ? token.role + : []; + return session; + }, + }, + session: { + strategy: "jwt", + }, +}; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/front/app/layout.tsx b/front/app/layout.tsx index 5489a9e..14224b8 100644 --- a/front/app/layout.tsx +++ b/front/app/layout.tsx @@ -1,45 +1,47 @@ -import type { Metadata } from "next" -import { Inter } from "next/font/google" -import "./globals.css" -import { MainNav } from "@/components/main-nav" +"use client"; -const inter = Inter({ subsets: ["latin"] }) +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; +import { MainNav } from "@/components/main-nav"; +import Link from "next/link"; +import { SessionProvider } from "next-auth/react"; -export const metadata: Metadata = { - title: "Space Dashboard", - description: "A modern space-themed dashboard", +const inter = Inter({ subsets: ["latin"] }); + +interface RootLayoutProps { + children: React.ReactNode; } export default function RootLayout({ children, -}: { - children: React.ReactNode -}) { +}: Readonly): JSX.Element { return ( - + -
- -
{children}
- -
+ +
+ +
{children}
+
+
+ + Support + + + Help Center + + + Privacy + + + Terms of Service + +
+
+
+
- ) + ); } - diff --git a/front/components/main-nav.tsx b/front/components/main-nav.tsx index f9fbfb4..fe89ddc 100644 --- a/front/components/main-nav.tsx +++ b/front/components/main-nav.tsx @@ -1,93 +1,84 @@ -"use client" +"use client"; -import { useState } from 'react' -import { Calendar, MessageSquare, BotIcon as Robot, PenTool, Bell } from 'lucide-react' -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { useState } from "react"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import Image from 'next/image' -import Link from 'next/link' -import { Sidebar } from './sidebar' + Calendar, + MessageSquare, + BotIcon as Robot, + PenTool, + Bell, +} from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { Sidebar } from "./sidebar"; +import { useSession } from "next-auth/react"; export function MainNav() { - const [isSidebarOpen, setIsSidebarOpen] = useState(false) + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const { data: session } = useSession(); return ( <> -
-
- - - Logo + Logo - - + + - - + + - - + + - - + + - - + +
- - - - - U - - - - - Sign In - - - Sign Up - - - +
+ {session ? ( + {session.user.name} + ) : ( + Login + )} +
- setIsSidebarOpen(false)} - /> + setIsSidebarOpen(false)} /> - ) + ); } - diff --git a/front/components/ui/dropdown-menu.tsx b/front/components/ui/dropdown-menu.tsx index 0fc4c0e..b0bead2 100644 --- a/front/components/ui/dropdown-menu.tsx +++ b/front/components/ui/dropdown-menu.tsx @@ -1,27 +1,27 @@ -"use client" +"use client"; -import * as React from "react" -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { Check, ChevronRight, Circle } from "lucide-react" +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const DropdownMenu = DropdownMenuPrimitive.Root +const DropdownMenu = DropdownMenuPrimitive.Root; -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; -const DropdownMenuGroup = DropdownMenuPrimitive.Group +const DropdownMenuGroup = DropdownMenuPrimitive.Group; -const DropdownMenuPortal = DropdownMenuPrimitive.Portal +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; -const DropdownMenuSub = DropdownMenuPrimitive.Sub +const DropdownMenuSub = DropdownMenuPrimitive.Sub; -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean + inset?: boolean; } >(({ className, inset, children, ...props }, ref) => ( {children} - + -)) +)); DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName + DropdownMenuPrimitive.SubTrigger.displayName; const DropdownMenuSubContent = React.forwardRef< React.ElementRef, @@ -52,9 +52,9 @@ const DropdownMenuSubContent = React.forwardRef< )} {...props} /> -)) +)); DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName + DropdownMenuPrimitive.SubContent.displayName; const DropdownMenuContent = React.forwardRef< React.ElementRef, @@ -71,13 +71,13 @@ const DropdownMenuContent = React.forwardRef< {...props} /> -)) -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; const DropdownMenuItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean + inset?: boolean; } >(({ className, inset, ...props }, ref) => ( -)) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, @@ -105,16 +105,16 @@ const DropdownMenuCheckboxItem = React.forwardRef< checked={checked} {...props} > - + - + {children} -)) +)); DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName + DropdownMenuPrimitive.CheckboxItem.displayName; const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, @@ -128,20 +128,20 @@ const DropdownMenuRadioItem = React.forwardRef< )} {...props} > - + - + {children} -)) -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; const DropdownMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean + inset?: boolean; } >(({ className, inset, ...props }, ref) => ( -)) -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; const DropdownMenuSeparator = React.forwardRef< React.ElementRef, @@ -165,8 +165,8 @@ const DropdownMenuSeparator = React.forwardRef< className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} /> -)) -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; const DropdownMenuShortcut = ({ className, @@ -177,9 +177,9 @@ const DropdownMenuShortcut = ({ className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} /> - ) -} -DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; export { DropdownMenu, @@ -197,4 +197,4 @@ export { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, -} +}; diff --git a/front/next.config.mjs b/front/next.config.mjs index 060b74a..7f131b0 100644 --- a/front/next.config.mjs +++ b/front/next.config.mjs @@ -1,6 +1,6 @@ -let userConfig = undefined +let userConfig; try { - userConfig = await import('./v0-user-next.config') + userConfig = await import("./v0-user-next.config"); } catch (e) { // ignore error } @@ -21,28 +21,28 @@ const nextConfig = { parallelServerBuildTraces: true, parallelServerCompiles: true, }, -} +}; -mergeConfig(nextConfig, userConfig) +mergeConfig(nextConfig, userConfig); function mergeConfig(nextConfig, userConfig) { if (!userConfig) { - return + return; } for (const key in userConfig) { if ( - typeof nextConfig[key] === 'object' && + typeof nextConfig[key] === "object" && !Array.isArray(nextConfig[key]) ) { nextConfig[key] = { ...nextConfig[key], ...userConfig[key], - } + }; } else { - nextConfig[key] = userConfig[key] + nextConfig[key] = userConfig[key]; } } } -export default nextConfig +export default nextConfig; diff --git a/front/package-lock.json b/front/package-lock.json index 6ad3af7..bed3298 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -45,6 +45,7 @@ "input-otp": "1.4.1", "lucide-react": "^0.454.0", "next": "14.2.24", + "next-auth": "^4.24.11", "next-themes": "^0.4.4", "react": "^18", "react-day-picker": "8.10.1", @@ -373,6 +374,15 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2505,6 +2515,15 @@ "node": ">= 6" } }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2994,6 +3013,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3163,6 +3191,38 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.11", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", + "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.2", + "next": "^12.2.5 || ^13 || ^14 || ^15", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next-themes": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.4.tgz", @@ -3220,6 +3280,12 @@ "node": ">=0.10.0" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3236,6 +3302,51 @@ "node": ">= 6" } }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -3437,6 +3548,34 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/preact": { + "version": "10.26.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz", + "integrity": "sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4148,6 +4287,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vaul": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/vaul/-/vaul-0.9.9.tgz", @@ -4279,6 +4427,12 @@ "node": ">=8" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/yaml": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", diff --git a/front/package.json b/front/package.json index 3693a89..824e060 100644 --- a/front/package.json +++ b/front/package.json @@ -46,6 +46,7 @@ "input-otp": "1.4.1", "lucide-react": "^0.454.0", "next": "14.2.24", + "next-auth": "^4.24.11", "next-themes": "^0.4.4", "react": "^18", "react-day-picker": "8.10.1", diff --git a/front/tsconfig.json b/front/tsconfig.json index 4b2dc7b..d635317 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -2,17 +2,18 @@ "compilerOptions": { "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, - "target": "ES6", + "target": "ES2024", "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, - "module": "esnext", + "module": "ESNext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, + "forceConsistentCasingInFileNames": true, "plugins": [ { "name": "next" @@ -22,6 +23,12 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "types/**/*.d.ts" + ], "exclude": ["node_modules"] } diff --git a/front/types/next-auth.d.ts b/front/types/next-auth.d.ts new file mode 100644 index 0000000..ceb6b50 --- /dev/null +++ b/front/types/next-auth.d.ts @@ -0,0 +1,23 @@ +import NextAuth, { DefaultSession, DefaultUser } from "next-auth"; + +declare module "next-auth" { + interface Session { + user: { + role?: string[] | string | null; + } & DefaultSession["user"]; + accessToken?: string; + } + + interface JWT { + accessToken?: string; + role?: string[] | string | null; + } + + interface User extends DefaultUser { + role?: string[] | string | null; + } + + interface Profile { + role?: string[] | string | null; + } +} diff --git a/front/yarn.lock b/front/yarn.lock index 2c2b450..022da50 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== -"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.26.0" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz" integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== @@ -121,6 +121,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@panva/hkdf@^1.0.2": + version "1.2.1" + resolved "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz" + integrity sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" @@ -1083,6 +1088,11 @@ commander@^4.0.0: resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +cookie@^0.7.0: + version "0.7.2" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + cross-spawn@^7.0.0: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" @@ -1422,6 +1432,11 @@ jiti@^1.21.6: resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz" integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== +jose@^4.15.5, jose@^4.15.9: + version "4.15.9" + resolved "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz" + integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== + "js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -1454,6 +1469,13 @@ lru-cache@^10.2.0: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + lucide-react@^0.454.0: version "0.454.0" resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz" @@ -1498,12 +1520,27 @@ nanoid@^3.3.6, nanoid@^3.3.7: resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== +next-auth@^4.24.11: + version "4.24.11" + resolved "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz" + integrity sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw== + dependencies: + "@babel/runtime" "^7.20.13" + "@panva/hkdf" "^1.0.2" + cookie "^0.7.0" + jose "^4.15.5" + oauth "^0.9.15" + openid-client "^5.4.0" + preact "^10.6.3" + preact-render-to-string "^5.1.19" + uuid "^8.3.2" + next-themes@^0.4.4: version "0.4.4" resolved "https://registry.npmjs.org/next-themes/-/next-themes-0.4.4.tgz" integrity sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ== -next@14.2.24: +"next@^12.2.5 || ^13 || ^14 || ^15", next@14.2.24: version "14.2.24" resolved "https://registry.npmjs.org/next/-/next-14.2.24.tgz" integrity sha512-En8VEexSJ0Py2FfVnRRh8gtERwDRaJGNvsvad47ShkC2Yi8AXQPXEA2vKoDJlGFSj5WE5SyF21zNi4M5gyi+SQ== @@ -1541,16 +1578,41 @@ normalize-range@^0.1.2: resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== +oauth@^0.9.15: + version "0.9.15" + resolved "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz" + integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA== + object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-hash@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== +oidc-token-hash@^5.0.3: + version "5.0.3" + resolved "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz" + integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw== + +openid-client@^5.4.0: + version "5.7.1" + resolved "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz" + integrity sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew== + dependencies: + jose "^4.15.9" + lru-cache "^6.0.0" + object-hash "^2.2.0" + oidc-token-hash "^5.0.3" + package-json-from-dist@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz" @@ -1656,6 +1718,23 @@ postcss@8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +preact-render-to-string@^5.1.19: + version "5.2.6" + resolved "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz" + integrity sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw== + dependencies: + pretty-format "^3.8.0" + +preact@^10.6.3, preact@>=10: + version "10.26.2" + resolved "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz" + integrity sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg== + +pretty-format@^3.8.0: + version "3.8.0" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz" + integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew== + prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" @@ -1675,7 +1754,7 @@ react-day-picker@8.10.1: resolved "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz" integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA== -"react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react-dom@^18, "react-dom@^18 || ^19 || ^19.0.0-rc", "react-dom@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8.0: +"react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^17.0.2 || ^18 || ^19", react-dom@^18, "react-dom@^18 || ^19 || ^19.0.0-rc", "react-dom@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8.0: version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -1754,7 +1833,7 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -react@*, "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^18, "react@^18 || ^19 || ^19.0.0-rc", "react@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^18.2.0, react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.6.0, react@>=16.8.0: +react@*, "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^17.0.2 || ^18 || ^19", react@^18, "react@^18 || ^19 || ^19.0.0-rc", "react@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^18.2.0, react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.6.0, react@>=16.8.0: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -2051,6 +2130,11 @@ util-deprecate@^1.0.2: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + vaul@^0.9.6: version "0.9.9" resolved "https://registry.npmjs.org/vaul/-/vaul-0.9.9.tgz" @@ -2103,6 +2187,11 @@ wrap-ansi@^8.1.0: string-width "^5.0.1" strip-ansi "^7.0.1" +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yaml@^2.3.4: version "2.7.0" resolved "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz"