feature/auth #2
@ -3,6 +3,7 @@ NEXT_PUBLIC_API_URL=
|
|||||||
NEXTAUTH_URL=
|
NEXTAUTH_URL=
|
||||||
NEXTAUTH_SECRET=
|
NEXTAUTH_SECRET=
|
||||||
|
|
||||||
|
OAUTH_PROVIDER_NAME=
|
||||||
OAUTH_CLIENT_ID=
|
OAUTH_CLIENT_ID=
|
||||||
OAUTH_CLIENT_SECRET=
|
OAUTH_CLIENT_SECRET=
|
||||||
OAUTH_ISSUER=
|
OAUTH_ISSUER=
|
||||||
|
45
src/app/auth/login/page.tsx
Normal file
45
src/app/auth/login/page.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
import { authOptions } from "@/authOptions";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
|
||||||
|
const LoginPage = () => {
|
||||||
|
const providers = authOptions.providers;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-screen items-center justify-center bg-gradient-to-br from-green-700 to-green-950 bg-[length:200%_200%] animate-gradient-x">
|
||||||
|
<div className="flex flex-col justify-center items-center w-1/2 gap-6 bg-black bg-opacity-40 p-4 rounded-md">
|
||||||
|
<div className="flex items-center w-full gap-5">
|
||||||
|
<div className="border-t border-white flex-grow"></div>
|
||||||
|
<h1 className="text-3xl font-bold">Toogether</h1>
|
||||||
|
<div className="border-t border-white flex-grow"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<h2 className="font-bold text-2xl">Connexion</h2>
|
||||||
|
<p>
|
||||||
|
Pour accéder à la plateforme, merci de vous
|
||||||
|
authentifier.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full border-t border-white"></div>
|
||||||
|
|
||||||
|
<h3 className="font-bold text-xl">Via</h3>
|
||||||
|
<ul>
|
||||||
|
{providers.map((provider) => (
|
||||||
|
<li key={provider.id}>
|
||||||
|
<button
|
||||||
|
className="bg-white text-black p-2 rounded-md"
|
||||||
|
onClick={() => signIn(provider.id)}
|
||||||
|
>
|
||||||
|
{provider.name}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginPage;
|
18
src/app/auth/logout/page.tsx
Normal file
18
src/app/auth/logout/page.tsx
Normal file
@ -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;
|
@ -1,56 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import { signIn, signOut, useSession } from "next-auth/react";
|
|
||||||
|
|
||||||
const Auth = () => {
|
|
||||||
const { data: session } = useSession();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
marginTop: "50px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1>NextAuth Login</h1>
|
|
||||||
{!session ? (
|
|
||||||
<div>
|
|
||||||
<p>You are not signed in.</p>
|
|
||||||
<button
|
|
||||||
style={{
|
|
||||||
padding: "10px 20px",
|
|
||||||
backgroundColor: "#7289DA",
|
|
||||||
color: "white",
|
|
||||||
border: "none",
|
|
||||||
borderRadius: "5px",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
onClick={() => signIn("oauth2")}
|
|
||||||
>
|
|
||||||
Sign in with OAuth2
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<p>Welcome, {session.user?.name || "User"}!</p>
|
|
||||||
<button
|
|
||||||
style={{
|
|
||||||
padding: "10px 20px",
|
|
||||||
backgroundColor: "red",
|
|
||||||
color: "white",
|
|
||||||
border: "none",
|
|
||||||
borderRadius: "5px",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
onClick={() => signOut()}
|
|
||||||
>
|
|
||||||
Sign out
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Auth;
|
|
@ -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<Error | AxiosError | null>(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 (
|
|
||||||
<div>
|
|
||||||
{error && <p>Error: {error.message}</p>}
|
|
||||||
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FetchWithSession;
|
|
28
src/app/components/Header.tsx
Normal file
28
src/app/components/Header.tsx
Normal file
@ -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 (
|
||||||
|
<header className="bg-gray-900 text-white p-4">
|
||||||
|
<div className="container mx-auto flex justify-between items-center">
|
||||||
|
<h1 className="text-xl font-bold">Toogether</h1>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<p>
|
||||||
|
Logged in as <strong>{session.user.name}</strong>
|
||||||
|
</p>
|
||||||
|
<nav className="space-x-4">
|
||||||
|
<Link href="/auth/logout">
|
||||||
|
<button className="bg-gray-800 px-4 py-2 rounded hover:scale-105 transition duration-200">
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
@ -8,7 +8,7 @@ export default function RootLayout({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body className="text-white">
|
||||||
<SessionProviderWrapper>{children}</SessionProviderWrapper>
|
<SessionProviderWrapper>{children}</SessionProviderWrapper>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
"use client";
|
import Header from "./components/Header";
|
||||||
import Auth from "./components/Auth";
|
|
||||||
import FetchWithSession from "./components/FetchWithSession";
|
|
||||||
|
|
||||||
export default function Home() {
|
const HomePage = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Auth />
|
<Header />
|
||||||
<FetchWithSession />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
|
@ -6,8 +6,8 @@ import { JWT } from "next-auth/jwt";
|
|||||||
export const authOptions: AuthOptions = {
|
export const authOptions: AuthOptions = {
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
id: "oauth2",
|
id: "oauth",
|
||||||
name: "oauth2",
|
name: process.env.OAUTH_PROVIDER_NAME,
|
||||||
type: "oauth",
|
type: "oauth",
|
||||||
clientId: process.env.OAUTH_CLIENT_ID,
|
clientId: process.env.OAUTH_CLIENT_ID,
|
||||||
clientSecret: process.env.OAUTH_CLIENT_SECRET,
|
clientSecret: process.env.OAUTH_CLIENT_SECRET,
|
||||||
@ -39,7 +39,7 @@ export const authOptions: AuthOptions = {
|
|||||||
async jwt({ token, account, user }) {
|
async jwt({ token, account, user }) {
|
||||||
if (account && user) {
|
if (account && user) {
|
||||||
token.accessToken = account.access_token;
|
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.refreshToken = account.refresh_token;
|
||||||
token.user = user;
|
token.user = user;
|
||||||
return token;
|
return token;
|
||||||
@ -62,6 +62,10 @@ export const authOptions: AuthOptions = {
|
|||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: "/auth/login",
|
||||||
|
signOut: "/auth/logout",
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshAccessToken = async (token: JWT): Promise<JWT> => {
|
const refreshAccessToken = async (token: JWT): Promise<JWT> => {
|
||||||
|
3
src/lib/stringUtils.ts
Normal file
3
src/lib/stringUtils.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const UppercaseFirstLetter = (str: string) => {
|
||||||
|
return str.slice(0, 1).toLocaleUpperCase() + str.slice(1);
|
||||||
|
}
|
25
src/middleware.ts
Normal file
25
src/middleware.ts
Normal file
@ -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).*)"],
|
||||||
|
};
|
1
src/types/env.d.ts
vendored
1
src/types/env.d.ts
vendored
@ -1,5 +1,6 @@
|
|||||||
declare namespace NodeJS {
|
declare namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
|
OAUTH_PROVIDER_NAME: string;
|
||||||
OAUTH_CLIENT_ID: string;
|
OAUTH_CLIENT_ID: string;
|
||||||
OAUTH_CLIENT_SECRET: string;
|
OAUTH_CLIENT_SECRET: string;
|
||||||
OAUTH_AUTHORIZATION_URL: string;
|
OAUTH_AUTHORIZATION_URL: string;
|
||||||
|
@ -12,6 +12,16 @@ export default {
|
|||||||
background: "var(--background)",
|
background: "var(--background)",
|
||||||
foreground: "var(--foreground)",
|
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: [],
|
plugins: [],
|
||||||
|
Loading…
Reference in New Issue
Block a user