From 2ff781feea389fd8a2c570d7a8aa5705e8c6508e Mon Sep 17 00:00:00 2001 From: M1000fr Date: Thu, 12 Dec 2024 15:12:57 +0100 Subject: [PATCH] feat: Auth implementation - Add Login & Logout page - Add middleware to protect pages - Add Header Server Side Component --- .env.example | 1 + src/app/auth/login/page.tsx | 45 ++++++++++++++++++++ src/app/auth/logout/page.tsx | 18 ++++++++ src/app/components/Auth.tsx | 56 ------------------------- src/app/components/FetchWithSession.tsx | 35 ---------------- src/app/components/Header.tsx | 28 +++++++++++++ src/app/layout.tsx | 2 +- src/app/page.tsx | 13 +++--- src/authOptions.ts | 10 +++-- src/lib/stringUtils.ts | 3 ++ src/middleware.ts | 25 +++++++++++ src/types/env.d.ts | 1 + tailwind.config.ts | 10 +++++ 13 files changed, 145 insertions(+), 102 deletions(-) create mode 100644 src/app/auth/login/page.tsx create mode 100644 src/app/auth/logout/page.tsx delete mode 100644 src/app/components/Auth.tsx delete mode 100644 src/app/components/FetchWithSession.tsx create mode 100644 src/app/components/Header.tsx create mode 100644 src/lib/stringUtils.ts create mode 100644 src/middleware.ts diff --git a/.env.example b/.env.example index f37897a..245ce02 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,7 @@ NEXT_PUBLIC_API_URL= NEXTAUTH_URL= NEXTAUTH_SECRET= +OAUTH_PROVIDER_NAME= OAUTH_CLIENT_ID= OAUTH_CLIENT_SECRET= OAUTH_ISSUER= diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx new file mode 100644 index 0000000..d6af802 --- /dev/null +++ b/src/app/auth/login/page.tsx @@ -0,0 +1,45 @@ +"use client"; +import { authOptions } from "@/authOptions"; +import { signIn } from "next-auth/react"; + +const LoginPage = () => { + const providers = authOptions.providers; + + return ( +
+
+
+
+

Toogether

+
+
+ +
+

Connexion

+

+ Pour accéder à la plateforme, merci de vous + authentifier. +

+
+ +
+ +

Via

+
    + {providers.map((provider) => ( +
  • + +
  • + ))} +
+
+
+ ); +}; + +export default LoginPage; diff --git a/src/app/auth/logout/page.tsx b/src/app/auth/logout/page.tsx new file mode 100644 index 0000000..9f582af --- /dev/null +++ b/src/app/auth/logout/page.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { useEffect } from "react"; +import { signOut, useSession } from "next-auth/react"; + +const LogoutPage = () => { + const session = useSession(); + + useEffect(() => { + if (session) { + signOut(); + } + }, [session]); + + return null; +}; + +export default LogoutPage; diff --git a/src/app/components/Auth.tsx b/src/app/components/Auth.tsx deleted file mode 100644 index c43e4a2..0000000 --- a/src/app/components/Auth.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client" -import { signIn, signOut, useSession } from "next-auth/react"; - -const Auth = () => { - const { data: session } = useSession(); - - return ( -
-

NextAuth Login

- {!session ? ( -
-

You are not signed in.

- -
- ) : ( -
-

Welcome, {session.user?.name || "User"}!

- -
- )} -
- ); -}; - -export default Auth; diff --git a/src/app/components/FetchWithSession.tsx b/src/app/components/FetchWithSession.tsx deleted file mode 100644 index 79ca24b..0000000 --- a/src/app/components/FetchWithSession.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { axiosInstance } from "@/lib/axios"; -import axios, { AxiosError } from "axios"; -import { useEffect, useState } from "react"; - -const FetchWithSession = () => { - const [data, setData] = useState<{ message: string } | null>(null); - const [error, setError] = useState(null); - - useEffect(() => { - const fetchData = async () => { - await axiosInstance - .get<{ - message: string; - }>(`/ping`) - .then((response) => setData(response.data)) - .catch((err: Error | AxiosError) => { - if (axios.isAxiosError(err)) setError(err.response?.data); - else setError(err); - }); - }; - - setInterval(() => { - fetchData(); - }, 1000); - }, []); - - return ( -
- {error &&

Error: {error.message}

} - {data &&
{JSON.stringify(data, null, 2)}
} -
- ); -}; - -export default FetchWithSession; diff --git a/src/app/components/Header.tsx b/src/app/components/Header.tsx new file mode 100644 index 0000000..9dd1f9c --- /dev/null +++ b/src/app/components/Header.tsx @@ -0,0 +1,28 @@ +import { getServerSession, Session } from "next-auth"; +import Link from "next/link"; + +const Header = async () => { + const session = (await getServerSession()) as Session; + + return ( +
+
+

Toogether

+
+

+ Logged in as {session.user.name} +

+ +
+
+
+ ); +}; + +export default Header; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a466eed..8b0e731 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,7 +8,7 @@ export default function RootLayout({ }) { return ( - + {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index 1473b0f..99f6387 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,11 @@ -"use client"; -import Auth from "./components/Auth"; -import FetchWithSession from "./components/FetchWithSession"; +import Header from "./components/Header"; -export default function Home() { +const HomePage = () => { return ( <> - - +
); -} +}; + +export default HomePage; diff --git a/src/authOptions.ts b/src/authOptions.ts index ce18d0f..0838799 100644 --- a/src/authOptions.ts +++ b/src/authOptions.ts @@ -6,8 +6,8 @@ import { JWT } from "next-auth/jwt"; export const authOptions: AuthOptions = { providers: [ { - id: "oauth2", - name: "oauth2", + id: "oauth", + name: process.env.OAUTH_PROVIDER_NAME, type: "oauth", clientId: process.env.OAUTH_CLIENT_ID, clientSecret: process.env.OAUTH_CLIENT_SECRET, @@ -39,7 +39,7 @@ export const authOptions: AuthOptions = { async jwt({ token, account, user }) { if (account && user) { token.accessToken = account.access_token; - token.accessTokenExpires = moment(account.expires_at).subtract(5, "s"); + token.accessTokenExpires = moment(account.expires_at * 1000).subtract(5, "s"); token.refreshToken = account.refresh_token; token.user = user; return token; @@ -62,6 +62,10 @@ export const authOptions: AuthOptions = { return session; }, }, + pages: { + signIn: "/auth/login", + signOut: "/auth/logout", + } }; const refreshAccessToken = async (token: JWT): Promise => { diff --git a/src/lib/stringUtils.ts b/src/lib/stringUtils.ts new file mode 100644 index 0000000..bc1566c --- /dev/null +++ b/src/lib/stringUtils.ts @@ -0,0 +1,3 @@ +export const UppercaseFirstLetter = (str: string) => { + return str.slice(0, 1).toLocaleUpperCase() + str.slice(1); +} \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..4910976 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,25 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getToken } from "next-auth/jwt"; + +export async function middleware(req: NextRequest) { + const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); + const isAuth = !!token; + + const url = req.nextUrl.clone(); + + if (isAuth && url.pathname === "/auth/login") { + url.pathname = "/"; + return NextResponse.redirect(url); + } + + if (!isAuth && url.pathname !== "/auth/login") { + url.pathname = "/auth/login"; + return NextResponse.redirect(url); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ["/((?!api|_next|static|favicon.ico).*)"], +}; diff --git a/src/types/env.d.ts b/src/types/env.d.ts index b5b3be9..c704b2d 100644 --- a/src/types/env.d.ts +++ b/src/types/env.d.ts @@ -1,5 +1,6 @@ declare namespace NodeJS { interface ProcessEnv { + OAUTH_PROVIDER_NAME: string; OAUTH_CLIENT_ID: string; OAUTH_CLIENT_SECRET: string; OAUTH_AUTHORIZATION_URL: string; diff --git a/tailwind.config.ts b/tailwind.config.ts index 5d3c1bd..40e239b 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -12,6 +12,16 @@ export default { background: "var(--background)", foreground: "var(--foreground)", }, + animation: { + "gradient-x": "gradient-x 5s ease infinite", + }, + keyframes: { + "gradient-x": { + "0%": { "background-position": "0% 50%" }, + "50%": { "background-position": "100% 50%" }, + "100%": { "background-position": "0% 50%" }, + }, + }, }, }, plugins: [],