From 1c851a14e89829bc13fb94fc7d0bc2ba1c1db7a7 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Tue, 21 May 2024 16:42:19 +0100 Subject: [PATCH 1/5] auth using token/jwt login + logout --- src/app/auth/login/page.jsx | 40 +--------------- src/app/auth/verif/page.jsx | 6 +-- src/app/layout.js | 6 ++- src/app/lib/dal.js | 12 +++-- src/app/lib/fetchRequest.js | 8 ++-- src/app/lib/isAuthenticated.js | 14 ++++-- src/app/lib/isAuthenticatedSSR.js | 21 +++++++++ src/app/lib/session.js | 3 +- src/app/ui/Header.jsx | 76 +++++++++++++++++++++++++++++++ src/app/ui/LogoutButton.js | 19 ++++++++ src/middleware.js | 23 ++++------ 11 files changed, 157 insertions(+), 71 deletions(-) create mode 100644 src/app/lib/isAuthenticatedSSR.js create mode 100644 src/app/ui/Header.jsx create mode 100644 src/app/ui/LogoutButton.js diff --git a/src/app/auth/login/page.jsx b/src/app/auth/login/page.jsx index 33b3f09..29be228 100644 --- a/src/app/auth/login/page.jsx +++ b/src/app/auth/login/page.jsx @@ -18,10 +18,6 @@ const LoginPage = () => { // Add your form submission logic here }; - const secretKey = process.env.NEXT_PUBLIC_SESSION_SECRET; - console.log(secretKey); - - // send login request to the server const login = async (event) => { event.preventDefault(); try { @@ -37,43 +33,9 @@ const LoginPage = () => { setMessages(data.error); } else { setMessages('Login successful'); - // Set the cookie - const expiresAt = new Date(new Date().getTime() + 60 * 60 * 1000); // Set cookie to expire in 1 hour - // const session = json strigify it - Cookies.set('session', data.token, { - expires: expiresAt, - secure: true, - sameSite: 'Lax', - }); - // Redirect to the dashboard - // window.location.href = '/auth/verif'; - } - } catch (error) { - setMessages('An error occurred'); - } - }; - const loginn = async (event) => { - event.preventDefault(); - try { - const response = await fetch('http://localhost:8000/login/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ username, password }), - }); - const data = await response.json(); - if (data.error) { - setMessages(data.error); - } else { - setMessages('Login successful'); - // Set the cookie - const expiresAt = new Date(new Date().getTime() + 60 * 60 * 1000); // Set cookie to expire in 1 hour - // const session = json strigify it await createSession(data); - // Redirect to the dashboard - // window.location.href = '/auth/verif'; + window.location.href = '/auth/verif'; } } catch (error) { setMessages('An error occurred'); diff --git a/src/app/auth/verif/page.jsx b/src/app/auth/verif/page.jsx index cfea4df..79eec0f 100644 --- a/src/app/auth/verif/page.jsx +++ b/src/app/auth/verif/page.jsx @@ -20,18 +20,18 @@ const Verif = () => { } }; const isAuth = async () => { - isAuthenticated(); + await isAuthenticated(); } return ( - <> +
user is redirected to this page after successful login
{/*test fetchExampleData */} - +
); } export default Verif; \ No newline at end of file diff --git a/src/app/layout.js b/src/app/layout.js index 9aef1df..3cf1c50 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -1,5 +1,6 @@ import { Inter } from "next/font/google"; import "./globals.css"; +import Header from "@/app/ui/Header"; const inter = Inter({ subsets: ["latin"] }); @@ -11,7 +12,10 @@ export const metadata = { export default function RootLayout({ children }) { return ( - {children} + +
+ {children} + ); } diff --git a/src/app/lib/dal.js b/src/app/lib/dal.js index 74c6293..67dfeaf 100644 --- a/src/app/lib/dal.js +++ b/src/app/lib/dal.js @@ -1,16 +1,18 @@ -import 'server-only' +// import 'server-only' +"use client"; import Cookies from 'js-cookie'; import { decrypt } from '@/app/lib/session' import {redirect} from "next/navigation"; -export const verifySession = cache(async () => { +export const verifySession = async () => { const cookie = Cookies.get('session').value const session = await decrypt(cookie) + console.log('session from dal', session) if (!session.token) { - redirect('/login') + redirect('/auth/login') } - return { isAuth: true, userName: session.username } -}) \ No newline at end of file + return { isAuth: true, sessionData: session.sessionData } +} \ No newline at end of file diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index 3afb1c3..b04097d 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -4,13 +4,13 @@ import Cookies from 'js-cookie'; import {decrypt} from "@/app/lib/session"; const fetchRequest = async (url, options = {}) => { - const token = Cookies.get('session'); - console.log('token', token) - const jwtDecrypted = decrypt(token); + const jwtCookie = Cookies.get('session'); + console.log('jwtCookie', jwtCookie) + const jwtDecrypted = await decrypt(jwtCookie); console.log('jwtDecrypted', jwtDecrypted) const headers = { ...options.headers, - 'Authorization': `Token ${token}`, + 'Authorization': `Token ${jwtDecrypted.sessionData.token}`, 'Content-Type': 'application/json', }; diff --git a/src/app/lib/isAuthenticated.js b/src/app/lib/isAuthenticated.js index 0a880ac..7c77a5e 100644 --- a/src/app/lib/isAuthenticated.js +++ b/src/app/lib/isAuthenticated.js @@ -1,16 +1,20 @@ //verify if the user is authenticated import Cookies from "js-cookie"; +import {decrypt} from "@/app/lib/session"; -export default function isAuthenticated() { +export default async function isAuthenticated() { if (!Cookies.get('session')) { console.log('user is not authenticated'); // redirect to auth/login page - window.location.href = '/auth/login'; - + // window.location.href = '/auth/login'; return false; } - else console.log('user is authenticated'); - return true; + + console.log('user is authenticated'); + const session = Cookies.get('session') + const cookieDecoded = await decrypt(session) + return { isAuth: true, sessionData: cookieDecoded.sessionData } + } diff --git a/src/app/lib/isAuthenticatedSSR.js b/src/app/lib/isAuthenticatedSSR.js new file mode 100644 index 0000000..09a8fd7 --- /dev/null +++ b/src/app/lib/isAuthenticatedSSR.js @@ -0,0 +1,21 @@ +//verify if the user is authenticated + +import { cookies } from 'next/headers' + +import {decrypt} from "@/app/lib/session"; + +export default async function isAuthenticatedSSR() { + if (!cookies().get('session')) { + console.log('user is not authenticated'); + // redirect to auth/login page + // window.location.href = '/auth/login'; + return false; + } + + console.log('user is authenticated'); + const cookie = cookies().get('session')?.value + const session = await decrypt(cookie) + return { isAuth: true, sessionData: session.sessionData } + +} + diff --git a/src/app/lib/session.js b/src/app/lib/session.js index 850ed74..1eb509a 100644 --- a/src/app/lib/session.js +++ b/src/app/lib/session.js @@ -2,7 +2,8 @@ import Cookies from 'js-cookie'; import { SignJWT, jwtVerify } from 'jose' -const secretKey = "password1234" +const secretKey = process.env.NEXT_PUBLIC_SESSION_SECRET; +console.log(secretKey); const encodedKey = new TextEncoder().encode(secretKey) export async function encrypt(payload) { diff --git a/src/app/ui/Header.jsx b/src/app/ui/Header.jsx new file mode 100644 index 0000000..e85b42e --- /dev/null +++ b/src/app/ui/Header.jsx @@ -0,0 +1,76 @@ +// 'use client'; + +// import { useState, useEffect } from 'react'; +import Link from 'next/link'; +// import isAuthenticated from "@/app/lib/isAuthenticated"; +import isAuthenticatedSSR from "@/app/lib/isAuthenticatedSSR"; +import LogoutButton from "@/app/ui/LogoutButton"; +// import fetchRequest from "@/app/lib/fetchRequest"; +// import Cookies from "js-cookie"; + +const Header = async () => { + + const {isAuth, sessionData} = await isAuthenticatedSSR() + console.log('isAuth', isAuth) + console.log('sessionData', sessionData) + // const [loggedInData, setLoggedInData] = useState(); + // + // + // useEffect(() => { + // isAuthenticated().then((data) => { + // console.log('data from headerv1', data) + // if (data.isAuth) { + // setLoggedInData(data.sessionData) + // console.log('data from header', loggedInData) + // } + // }) + // }, []); + + + // const logout = async () => { + // console.log('logout') + // await fetchRequest('http://localhost:8000/logout', {method: 'GET' }); + // Cookies.remove('session'); + // setLoggedInData(null); + // // reload the page + // window.location.href = '/'; + // } + return ( +
+
+ +

TeamBook

+ +
+ +
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.js new file mode 100644 index 0000000..b0aa3fd --- /dev/null +++ b/src/app/ui/LogoutButton.js @@ -0,0 +1,19 @@ +'use client'; + +import Cookies from 'js-cookie'; + +const LogoutButton = () => { + const logout = async () => { + await fetch('http://localhost:8000/logout', { method: 'GET' }); + Cookies.remove('session'); + window.location.href = '/'; + }; + + return ( + + ); +}; + +export default LogoutButton; diff --git a/src/middleware.js b/src/middleware.js index 1308f82..62024a0 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -1,12 +1,10 @@ -"use client"; - import { NextResponse } from 'next/server' import { decrypt } from '@/app/lib/session' -import Cookies from 'js-cookie'; +import { cookies } from 'next/headers' // 1. Specify protected and public routes -const protectedRoutes = ['/dashboard'] -const publicRoutes = ['/login', '/signup', '/'] +const protectedRoutes = ['/dashboard', '/auth/verif'] +const publicRoutes = ['/auth/login', '/auth/signup'] export default async function middleware(req) { // 2. Check if the current route is protected or public @@ -15,22 +13,21 @@ export default async function middleware(req) { const isPublicRoute = publicRoutes.includes(path) // 3. Decrypt the session from the cookie - const cookie = Cookies.get('session'); - console.log('cookie', cookie) + const cookie = cookies().get('session')?.value const session = await decrypt(cookie) - + console.log('session dfdf', session) // 5. Redirect to /login if the user is not authenticated - if (isProtectedRoute && !session?.token) { - return NextResponse.redirect(new URL('/login', req.nextUrl)) + if (isProtectedRoute && !session?.sessionData.token) { + return NextResponse.redirect(new URL('/auth/login', req.nextUrl)) } // 6. Redirect to /dashboard if the user is authenticated if ( isPublicRoute && - session?.token && - !req.nextUrl.pathname.startsWith('/auth') + session?.sessionData.token && + !req.nextUrl.pathname.startsWith('/auth/verif') ) { - return NextResponse.redirect(new URL('/auth', req.nextUrl)) + return NextResponse.redirect(new URL('/auth/verif', req.nextUrl)) } return NextResponse.next() -- GitLab From 7ff7ae2ec30b73a0aac738a935f93f3b8ae82817 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Wed, 22 May 2024 13:20:13 +0100 Subject: [PATCH 2/5] add forgot password & change password UI --- next.config.mjs | 6 +- src/app/auth/change-password/page.jsx | 120 +++++++++++++++++++++ src/app/auth/forgot-password/page.jsx | 98 ++++++++++++++++++ src/app/auth/login/page.jsx | 12 +-- src/app/globals.css | 40 ++----- src/app/lib/constants.js | 1 + src/app/lib/fetchRequest.js | 17 +-- src/app/page.js | 143 +------------------------- src/components/Loader/Loader.css | 46 +++++++++ src/components/Loader/Loader.jsx | 13 +++ 10 files changed, 312 insertions(+), 184 deletions(-) create mode 100644 src/app/auth/change-password/page.jsx create mode 100644 src/app/auth/forgot-password/page.jsx create mode 100644 src/app/lib/constants.js create mode 100644 src/components/Loader/Loader.css create mode 100644 src/components/Loader/Loader.jsx diff --git a/next.config.mjs b/next.config.mjs index 4678774..c9a3c0c 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + domains: ['https://res.cloudinary.com'], + } +}; export default nextConfig; diff --git a/src/app/auth/change-password/page.jsx b/src/app/auth/change-password/page.jsx new file mode 100644 index 0000000..be4882e --- /dev/null +++ b/src/app/auth/change-password/page.jsx @@ -0,0 +1,120 @@ +"use client" +import fetchRequest from '@/app/lib/fetchRequest' +import Image from 'next/image' +import Link from 'next/link' +import React, { useEffect, useState } from 'react' +import { useSearchParams } from 'next/navigation' +import Loader from '@/components/Loader/Loader' +import { passwordRegex } from '@/app/lib/constants' + +const ChangePassword = () => { + const [isSuccess, setIsSuccess] = useState(false) + const [formErrors, setFormErrors] = useState([]) + const [password, setPassword] = useState("") + const [confirmPassword, setConfirmPassword] = useState("") + const [isLoading, setIsLoading] = useState(false) + const params = useSearchParams(); + const handleChangePassword = async (event) => { + event.preventDefault() + setIsLoading(true) + const { isSuccess, data, errors } = await fetchRequest(`/password_reset/confirm/?token=${params.get("token")}`, { + method: "POST", + body: JSON.stringify({ + password, + token: params.get("token") + }) + }) + if (isSuccess) { + setIsSuccess(true) + } + else { + console.log(errors) + setIsLoading(false) + if (errors.type === "Validation Error") { + if (errors.detail.token) { + setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est invalide"]) + } + else { + setFormErrors(["Le Mot de passe est invalide"]) + } + } else if (errors?.detail?.startsWith("The OTP password entered is not valid")) { + setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est déja utilisé"]) + } + else { + console.log("Internal Server Error") + } + } + } + const isEmptyFields = !password && !confirmPassword + useEffect(() => { + var currentErrors = [] + if (password !== confirmPassword) { + currentErrors.push("Les mots de passe ne sont pas identiques.") + } + if (password.length < 8) { + currentErrors.push("Le mot de passe doit comporter au moins 8 caractères.") + } + if (!passwordRegex.test(password)) { + currentErrors.push("Le mot de passe doit contenir au moins une lettre, un chiffre et un caractère spécial.") + } + setFormErrors(currentErrors) + }, [password, confirmPassword]) + return ( +
+
+
+
+ teamwill +
+ {(!isSuccess) &&
+

Change your password. +

+
+
    +
  • Le mot de passe doit contenir au moins 8 caractères
  • +
  • Le mot de passe doit contenir au moins un chiffre
  • +
  • Le mot de passe doit contenir au moins un caractère spécial
  • +
+
+
+
+ + setPassword(event.target.value)} type="password" name="new_password1" id="new_password1" + className="rounded-md px-3 w-full duration-150 delay-75 focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none" /> +
+
+ + setConfirmPassword(event.target.value)} type="password" name="new_password2" id="new_password2" + className="rounded-md px-3 duration-150 delay-75 w-full focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none" /> +
+
    0 && !isEmptyFields ? "bg-red-100 border border-red-300" : ""} min-h-10 px-3 text-xs py-3 rounded relative mt-9 mb-6 list-inside list-disc`} role="alert"> + {!isEmptyFields && formErrors.map((error, index) => { + return
  • {error}
  • + })} +
+
+ +
+
+
} + {(isSuccess) && ( +
+

The password has been changed!

+ log in again? +
+ )} +
+
+
+ ) +} + +export default ChangePassword \ No newline at end of file diff --git a/src/app/auth/forgot-password/page.jsx b/src/app/auth/forgot-password/page.jsx new file mode 100644 index 0000000..3895740 --- /dev/null +++ b/src/app/auth/forgot-password/page.jsx @@ -0,0 +1,98 @@ +'use client' +import fetchRequest from '@/app/lib/fetchRequest' +import Loader from '@/components/Loader/Loader' +import Image from 'next/image' +import Link from 'next/link' +import React, { useState } from 'react' + +const ForgotPassword = () => { + const [email, setEmail] = useState("") + const [isSuccess, setIsSuccess] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [requestErrors, setRequestErrors] = useState([]) + const handleForgetPassword = async (event) => { + event.preventDefault() + setIsLoading(true) + const { isSuccess, errors, data } = await fetchRequest("/password_reset/", { + body: JSON.stringify({ email }), + method: "POST" + }) + if (isSuccess) { + setIsSuccess(true) + } + else { + setIsLoading(false) + if (errors.type === "Validation Error") { + // setRequestErrors(Object.keys(errors.detail).reduce((prev, current) => { + // return [...prev, ...errors.detail[current]] + // }, [])) + setRequestErrors(["Nous n'avons pas pu trouver de compte associé à cet e-mail. Veuillez essayer une autre adresse e-mail."]) + } else { + console.log("Internal server error") + } + } + } + return ( +
+
+
+ teamwill + {(!isSuccess) &&
+
+

Forgot your password!! No Problem + Reset it here +

+
+
+
+
+
+ setEmail(e.target.value)} autocomplete="off" id="email" name="email" type="text" + className="peer placeholder-transparent h-10 w-full border-b-2 border-gray-300 text-gray-900 focus:outline-none focus:borer-rose-600" + placeholder="Email address" /> + +
+ + + {(requestErrors.length !== 0) && } +
+ Back to log + in +
+ +
+
+
+
} + {(isSuccess) &&
+ + + +
+

Email Sent!

+

Check your email and open the link we sent to continue

+
+
} +
+
+
+ ) +} + +export default ForgotPassword \ No newline at end of file diff --git a/src/app/auth/login/page.jsx b/src/app/auth/login/page.jsx index 33b3f09..d9dd76c 100644 --- a/src/app/auth/login/page.jsx +++ b/src/app/auth/login/page.jsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import Link from 'next/link'; import Cookies from 'js-cookie'; -import {createSession} from "@/app/lib/session"; +import { createSession } from "@/app/lib/session"; const LoginPage = () => { const [username, setUsername] = useState(''); @@ -91,7 +91,7 @@ const LoginPage = () => {
{
{ />
- -

Mot de passe oublié?

+ +

Mot de passe oublié?

{messages && ( @@ -124,7 +124,7 @@ const LoginPage = () => { )} diff --git a/src/app/globals.css b/src/app/globals.css index 6c139df..b6ba7d3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,29 +2,6 @@ @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - } -} - -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); -} @layer utilities { .text-balance { @@ -34,18 +11,23 @@ body { /* Customize the scrollbar */ ::-webkit-scrollbar { - width: 6px; /* Set the width of the scrollbar */ + width: 6px; + /* Set the width of the scrollbar */ } ::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0.3); /* Color of the thumb */ - border-radius: 3px; /* Rounded corners of the thumb */ + background-color: rgba(0, 0, 0, 0.3); + /* Color of the thumb */ + border-radius: 3px; + /* Rounded corners of the thumb */ } ::-webkit-scrollbar-thumb:hover { - background-color: rgba(0, 0, 0, 0.5); /* Color of the thumb on hover */ + background-color: rgba(0, 0, 0, 0.5); + /* Color of the thumb on hover */ } ::-webkit-scrollbar-track { - background-color: rgba(0, 0, 0, 0.1); /* Color of the track */ -} + background-color: rgba(0, 0, 0, 0.1); + /* Color of the track */ +} \ No newline at end of file diff --git a/src/app/lib/constants.js b/src/app/lib/constants.js new file mode 100644 index 0000000..01b3d8f --- /dev/null +++ b/src/app/lib/constants.js @@ -0,0 +1 @@ +export const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; \ No newline at end of file diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index 3afb1c3..722c921 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -1,8 +1,8 @@ //custom fetch tha have the token in header import Cookies from 'js-cookie'; -import {decrypt} from "@/app/lib/session"; - +import { decrypt } from "@/app/lib/session"; +const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000" const fetchRequest = async (url, options = {}) => { const token = Cookies.get('session'); console.log('token', token) @@ -14,16 +14,21 @@ const fetchRequest = async (url, options = {}) => { 'Content-Type': 'application/json', }; - const response = await fetch(url, { + const response = await fetch(`${BASE_URL}${url}`, { ...options, headers, }); if (!response.ok) { - throw new Error('Network response was not ok'); + try { + const errorData = await response.json(); + return { isSuccess: false, errors: errorData, data: null } + } catch (error) { + return { isSuccess: false, errors: error, data: null } + } } - - return response.json(); + const data = await response.json() + return { isSuccess: true, errors: null, data: data }; }; export default fetchRequest; diff --git a/src/app/page.js b/src/app/page.js index ecfca6f..87ce7aa 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -1,149 +1,8 @@ -import Image from "next/image"; export default function Home() { return (
-
-

- Get started by editing  - src/app/page.js -

- -
- -
- Next.js Logo -
- -
- -

- Docs{" "} - - -> - -

-

- Find in-depth information about Next.js features and API. -

-
- - -

- Learn{" "} - - -> - -

-

- Learn about Next.js in an interactive course with quizzes! -

-
- - -

- Templates{" "} - - -> - -

-

- Explore starter templates for Next.js. -

-
- - -

- Deploy{" "} - - -> - -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-
-
+ Home Page
); } diff --git a/src/components/Loader/Loader.css b/src/components/Loader/Loader.css new file mode 100644 index 0000000..1e1ca65 --- /dev/null +++ b/src/components/Loader/Loader.css @@ -0,0 +1,46 @@ +@keyframes ldio-gcsicpsikdq { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +.ldio-gcsicpsikdq div { + width: 50px; + height: 50px; + border-top-color: transparent; + border: 5px solid transparent; + border-radius: 50%; +} + +.ldio-gcsicpsikdq div { + animation: ldio-gcsicpsikdq 1s linear infinite; +} + +.loadingio-spinner-rolling-daoiuzlm498 { + width: min-content; + height: 70px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: transparent; +} + +.ldio-gcsicpsikdq { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + transform: translateZ(0) scale(1); + backface-visibility: hidden; + transform-origin: 0 0; + /* see note above */ +} + +.ldio-gcsicpsikdq div { + box-sizing: content-box; +} \ No newline at end of file diff --git a/src/components/Loader/Loader.jsx b/src/components/Loader/Loader.jsx new file mode 100644 index 0000000..2430f19 --- /dev/null +++ b/src/components/Loader/Loader.jsx @@ -0,0 +1,13 @@ +import React from 'react' +import "./Loader.css" +const Loader = ({ size, border, className, color }) => { + return ( +
+
+
+
+
+ ) +} + +export default Loader \ No newline at end of file -- GitLab From ab38e63869b479441512628b9dec581559d05059 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Wed, 22 May 2024 14:30:04 +0100 Subject: [PATCH 3/5] add forgot password & change password features --- src/app/auth/change-password/page.jsx | 4 ++-- src/app/lib/constants.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/auth/change-password/page.jsx b/src/app/auth/change-password/page.jsx index be4882e..a89f484 100644 --- a/src/app/auth/change-password/page.jsx +++ b/src/app/auth/change-password/page.jsx @@ -5,7 +5,7 @@ import Link from 'next/link' import React, { useEffect, useState } from 'react' import { useSearchParams } from 'next/navigation' import Loader from '@/components/Loader/Loader' -import { passwordRegex } from '@/app/lib/constants' +import { PASSWORD_REGEX } from '@/app/lib/constants' const ChangePassword = () => { const [isSuccess, setIsSuccess] = useState(false) @@ -54,7 +54,7 @@ const ChangePassword = () => { if (password.length < 8) { currentErrors.push("Le mot de passe doit comporter au moins 8 caractères.") } - if (!passwordRegex.test(password)) { + if (!PASSWORD_REGEX.test(password)) { currentErrors.push("Le mot de passe doit contenir au moins une lettre, un chiffre et un caractère spécial.") } setFormErrors(currentErrors) diff --git a/src/app/lib/constants.js b/src/app/lib/constants.js index 01b3d8f..f4da663 100644 --- a/src/app/lib/constants.js +++ b/src/app/lib/constants.js @@ -1 +1 @@ -export const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; \ No newline at end of file +export const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; \ No newline at end of file -- GitLab From abb29479bf6bfad33f77c4934329a2f8940dec69 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Wed, 22 May 2024 14:38:01 +0100 Subject: [PATCH 4/5] fixed login with jwt --- src/app/auth/login/page.jsx | 6 +++--- src/app/auth/verif/page.jsx | 2 +- src/app/ui/Header.jsx | 22 ---------------------- src/app/ui/LogoutButton.js | 13 ++++++++++--- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/app/auth/login/page.jsx b/src/app/auth/login/page.jsx index 29be228..2d6459e 100644 --- a/src/app/auth/login/page.jsx +++ b/src/app/auth/login/page.jsx @@ -21,7 +21,7 @@ const LoginPage = () => { const login = async (event) => { event.preventDefault(); try { - const response = await fetch('http://localhost:8000/login/', { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/login/`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -29,8 +29,8 @@ const LoginPage = () => { body: JSON.stringify({ username, password }), }); const data = await response.json(); - if (data.error) { - setMessages(data.error); + if (data.non_field_errors) { + setMessages(data.non_field_errors[0]); } else { setMessages('Login successful'); diff --git a/src/app/auth/verif/page.jsx b/src/app/auth/verif/page.jsx index 79eec0f..b49360b 100644 --- a/src/app/auth/verif/page.jsx +++ b/src/app/auth/verif/page.jsx @@ -5,7 +5,7 @@ const Verif = () => { // fetch data from the server /example const fetchExampleData = async () => { try { - const data = await fetchRequest('http://localhost:8000/example/', + const data = await fetchRequest(`${process.env.NEXT_PUBLIC_API_URL}/example/`, {method: 'POST', body: JSON.stringify({ "name": "jhon", diff --git a/src/app/ui/Header.jsx b/src/app/ui/Header.jsx index e85b42e..b9d5df2 100644 --- a/src/app/ui/Header.jsx +++ b/src/app/ui/Header.jsx @@ -13,28 +13,6 @@ const Header = async () => { const {isAuth, sessionData} = await isAuthenticatedSSR() console.log('isAuth', isAuth) console.log('sessionData', sessionData) - // const [loggedInData, setLoggedInData] = useState(); - // - // - // useEffect(() => { - // isAuthenticated().then((data) => { - // console.log('data from headerv1', data) - // if (data.isAuth) { - // setLoggedInData(data.sessionData) - // console.log('data from header', loggedInData) - // } - // }) - // }, []); - - - // const logout = async () => { - // console.log('logout') - // await fetchRequest('http://localhost:8000/logout', {method: 'GET' }); - // Cookies.remove('session'); - // setLoggedInData(null); - // // reload the page - // window.location.href = '/'; - // } return (
diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.js index b0aa3fd..d3a137c 100644 --- a/src/app/ui/LogoutButton.js +++ b/src/app/ui/LogoutButton.js @@ -1,12 +1,19 @@ 'use client'; import Cookies from 'js-cookie'; +import fetchRequest from "@/app/lib/fetchRequest"; const LogoutButton = () => { const logout = async () => { - await fetch('http://localhost:8000/logout', { method: 'GET' }); - Cookies.remove('session'); - window.location.href = '/'; + const response = await fetchRequest(`${process.env.NEXT_PUBLIC_API_URL}/logout`, { + method: 'GET'}); + console.log(response); + if (response.statusCode === 200) { + console.log('logout successful'); + Cookies.remove('session'); + window.location.href = '/'; + } + }; return ( -- GitLab From 4608a92a3849b3a3d1e264d233297f2fed0ab819 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Wed, 22 May 2024 15:18:14 +0100 Subject: [PATCH 5/5] fixed fetch request /login/logout --- src/app/auth/verif/page.jsx | 2 +- src/app/lib/dal.js | 18 ------------------ src/app/lib/fetchRequest.js | 3 ++- src/app/ui/LogoutButton.js | 4 ++-- 4 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 src/app/lib/dal.js diff --git a/src/app/auth/verif/page.jsx b/src/app/auth/verif/page.jsx index b49360b..15d3d40 100644 --- a/src/app/auth/verif/page.jsx +++ b/src/app/auth/verif/page.jsx @@ -5,7 +5,7 @@ const Verif = () => { // fetch data from the server /example const fetchExampleData = async () => { try { - const data = await fetchRequest(`${process.env.NEXT_PUBLIC_API_URL}/example/`, + const data = await fetchRequest(`/example/`, {method: 'POST', body: JSON.stringify({ "name": "jhon", diff --git a/src/app/lib/dal.js b/src/app/lib/dal.js deleted file mode 100644 index 67dfeaf..0000000 --- a/src/app/lib/dal.js +++ /dev/null @@ -1,18 +0,0 @@ -// import 'server-only' -"use client"; - -import Cookies from 'js-cookie'; -import { decrypt } from '@/app/lib/session' -import {redirect} from "next/navigation"; - -export const verifySession = async () => { - const cookie = Cookies.get('session').value - const session = await decrypt(cookie) - console.log('session from dal', session) - - if (!session.token) { - redirect('/auth/login') - } - - return { isAuth: true, sessionData: session.sessionData } -} \ No newline at end of file diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index b6f73c2..9effc1e 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -10,7 +10,8 @@ const fetchRequest = async (url, options = {}) => { console.log('jwtDecrypted', jwtDecrypted) const headers = { ...options.headers, - 'Authorization': `Token ${jwtDecrypted.sessionData.token}`, + // add authorization header with token if there is jwtDecrypted.sessionData.token + Authorization: jwtDecrypted?.sessionData.token ? `Bearer ${jwtDecrypted.sessionData.token}` : undefined, 'Content-Type': 'application/json', }; diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.js index d3a137c..b219fc4 100644 --- a/src/app/ui/LogoutButton.js +++ b/src/app/ui/LogoutButton.js @@ -5,10 +5,10 @@ import fetchRequest from "@/app/lib/fetchRequest"; const LogoutButton = () => { const logout = async () => { - const response = await fetchRequest(`${process.env.NEXT_PUBLIC_API_URL}/logout`, { + const response = await fetchRequest(`/logout`, { method: 'GET'}); console.log(response); - if (response.statusCode === 200) { + if (response.isSuccess) { console.log('logout successful'); Cookies.remove('session'); window.location.href = '/'; -- GitLab