From 37f73928bc977332107e262435d9dd156444ed50 Mon Sep 17 00:00:00 2001 From: "omardieh@gmail.com" Date: Mon, 31 Mar 2025 20:52:43 +0200 Subject: [PATCH 1/5] feat: enhance user authentication flow with OAuth support and conditional email/password requirements --- backend/src/models/User.model.ts | 14 +++++- .../features/auth-flow/hooks/useAuth.hook.js | 6 ++- .../auth-flow/pages/LoginGithub.page.jsx | 46 ++++++++++++------- .../auth-flow/pages/LoginGoogle.page.jsx | 44 ++++++++++++------ .../pages/UserEditProfile.page.jsx | 12 ++--- 5 files changed, 79 insertions(+), 43 deletions(-) diff --git a/backend/src/models/User.model.ts b/backend/src/models/User.model.ts index 56d075d..12dd1a3 100644 --- a/backend/src/models/User.model.ts +++ b/backend/src/models/User.model.ts @@ -17,7 +17,12 @@ const userSchema = new Schema( lowercase: true, trim: true, match: [/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'Please enter a valid email address.'], - required: [true, 'Email is required.'], + required: [ + function (this: IUserModel) { + return !this.githubID && !this.googleID; + }, + 'Email is required.', + ], }, password: { type: String, @@ -25,7 +30,12 @@ const userSchema = new Schema( /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/, 'Password must have at least 6 characters and contain at least one number, one lowercase, and one uppercase letter.', ], - required: [true, 'Password is required.'], + required: [ + function (this: IUserModel) { + return !this.githubID && !this.googleID; + }, + 'Password is required.', + ], }, avatar: { type: String, diff --git a/frontend/features/auth-flow/hooks/useAuth.hook.js b/frontend/features/auth-flow/hooks/useAuth.hook.js index d59dc9f..e53080c 100644 --- a/frontend/features/auth-flow/hooks/useAuth.hook.js +++ b/frontend/features/auth-flow/hooks/useAuth.hook.js @@ -28,18 +28,20 @@ export function useAuthHook() { }, // OAuth providers - logGithubUserIn: (code) => + logGithubUserIn: (code, isFetching) => fetcher({ method: "POST", endPoint: "/auth/github", reqBody: { code }, + isFetching, }), - logGoogleUserIn: (code) => + logGoogleUserIn: (code, isFetching) => fetcher({ method: "POST", endPoint: "/auth/google", reqBody: { code }, + isFetching, }), // Token management diff --git a/frontend/features/auth-flow/pages/LoginGithub.page.jsx b/frontend/features/auth-flow/pages/LoginGithub.page.jsx index fee784c..f4fa23a 100644 --- a/frontend/features/auth-flow/pages/LoginGithub.page.jsx +++ b/frontend/features/auth-flow/pages/LoginGithub.page.jsx @@ -1,28 +1,42 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; -import { useAuthContext } from "../context"; -import AuthService from "/common/services/AuthService"; +import { useAuthContext } from "/features/auth-flow/context"; +import { useAuthHook } from "/features/auth-flow/hooks"; +import { LoadingSpinner } from "/common/components"; export function LoginGithub() { - const { storeToken, authenticateUser } = useAuthContext(); + const [isFetching, setIsFetching] = useState(false); + const { authenticateUser } = useAuthContext(); const [searchParams] = useSearchParams(); const code = searchParams.get("code"); + const { logGithubUserIn, storeUserToken } = useAuthHook(); + const { data, isLoading, isValidating, error } = logGithubUserIn( + code, + isFetching + ); + + const headersAuth = data?.headers?.authorization; useEffect(() => { - if (code) { - AuthService.loginGithub(code) - .then((response) => { - const accessToken = response.headers.authorization.split(" ")[1]; - storeToken(accessToken); - authenticateUser(); - }) - .catch((error) => { - console.error("GithubAuth : ", error); - }); + if (code) setIsFetching(true); + + if (headersAuth) { + const accessToken = headersAuth.split(" ")[1]; + storeUserToken(accessToken); + authenticateUser(); + return; + } + + if (error) { + console.log(error); return; } - window.location.replace(`${import.meta.env.VITE_SERVER_URL}/auth/github`); - }, [code, authenticateUser, storeToken]); + + if (!code) + window.location.replace(`${import.meta.env.VITE_SERVER_URL}/auth/github`); + }, [code, headersAuth, error]); + + if (isLoading || isValidating) return ; return (
diff --git a/frontend/features/auth-flow/pages/LoginGoogle.page.jsx b/frontend/features/auth-flow/pages/LoginGoogle.page.jsx index 493dbfe..98968b9 100644 --- a/frontend/features/auth-flow/pages/LoginGoogle.page.jsx +++ b/frontend/features/auth-flow/pages/LoginGoogle.page.jsx @@ -1,28 +1,42 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { useAuthContext } from "../context"; -import AuthService from "/common/services/AuthService"; +import { useAuthHook } from "/features/auth-flow/hooks"; +import { LoadingSpinner } from "/common/components"; export function LoginGoogle() { - const { storeToken, authenticateUser } = useAuthContext(); + const [isFetching, setIsFetching] = useState(false); + const { authenticateUser } = useAuthContext(); const [searchParams] = useSearchParams(); const code = searchParams.get("code"); + const { logGoogleUserIn, storeUserToken } = useAuthHook(); + const { data, isLoading, isValidating, error } = logGoogleUserIn( + code, + isFetching + ); + + const headersAuth = data?.headers?.authorization; useEffect(() => { - if (code) { - AuthService.loginGoogle(code) - .then((response) => { - const accessToken = response.headers.authorization.split(" ")[1]; - storeToken(accessToken); - authenticateUser(); - }) - .catch((error) => { - console.error("GoogleAuth:", error); - }); + if (code) setIsFetching(true); + + if (headersAuth) { + const accessToken = headersAuth.split(" ")[1]; + storeUserToken(accessToken); + authenticateUser(); + return; + } + + if (error) { + console.log(error); return; } - window.location.replace(`${import.meta.env.VITE_SERVER_URL}/auth/google`); - }, [code, authenticateUser, storeToken]); + + if (!code) + window.location.replace(`${import.meta.env.VITE_SERVER_URL}/auth/google`); + }, [code, headersAuth, error]); + + if (isLoading || isValidating) return ; return (
diff --git a/frontend/features/user-section/pages/UserEditProfile.page.jsx b/frontend/features/user-section/pages/UserEditProfile.page.jsx index 086926d..79d8454 100644 --- a/frontend/features/user-section/pages/UserEditProfile.page.jsx +++ b/frontend/features/user-section/pages/UserEditProfile.page.jsx @@ -15,16 +15,12 @@ import { NavLink } from "react-router-dom"; import validator from "validator"; import countries from "/common/assets/countries.json"; import { useAuthContext } from "../../auth-flow/context"; +import { useUserContext } from "../context"; export function UserEditProfile() { - const { - user, - authenticateUser, - updateUserInfo, - errorMessage, - setErrorMessage, - } = useAuthContext(); - + const { authenticateUser, updateUserInfo, errorMessage, setErrorMessage } = + useAuthContext(); + const { user } = useUserContext(); useEffect(() => { authenticateUser(); }, [authenticateUser]); From eb58caa0f02b5574cd9aabf4cad5d7c382d31777 Mon Sep 17 00:00:00 2001 From: "omardieh@gmail.com" Date: Mon, 31 Mar 2025 21:06:39 +0200 Subject: [PATCH 2/5] fix: update import path for useUserContext in Auth.context.jsx --- frontend/features/auth-flow/context/Auth.context.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/features/auth-flow/context/Auth.context.jsx b/frontend/features/auth-flow/context/Auth.context.jsx index c9e8eaf..8f131e7 100644 --- a/frontend/features/auth-flow/context/Auth.context.jsx +++ b/frontend/features/auth-flow/context/Auth.context.jsx @@ -1,7 +1,7 @@ import { createContext, useContext, useEffect, useState } from "react"; import AuthService from "/common/services/AuthService"; import { LoadingSpinner } from "/common/components"; -import { useUserContext } from "../../user-section/context"; +import { useUserContext } from "/features/user-section/context"; const AuthContext = createContext(); From af6feaa30fda4d86fcc86fcd0fe45bb71085c9e0 Mon Sep 17 00:00:00 2001 From: "omardieh@gmail.com" Date: Wed, 16 Apr 2025 23:40:32 +0200 Subject: [PATCH 3/5] fix: simplify deployment script in GitHub Actions workflow --- .github/workflows/deploy.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ede9b5e..9acc807 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -31,12 +31,5 @@ jobs: if: success() run: | ssh -p ${{ secrets.SSH_PORT }} ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} ' - cd /home/coding-family/__app__ && \ - git fetch origin production && \ - git pull origin production && \ - git reset --hard origin/production && \ - node -v && \ - yarn app:install && \ - yarn app:build && \ - yarn app:restart + ${{ secrets.DEPLOY_SCRIPT }} ' From 5651a8877ef76e595d59bb108b4ce39e2d28331e Mon Sep 17 00:00:00 2001 From: "omardieh@gmail.com" Date: Thu, 17 Apr 2025 00:04:42 +0200 Subject: [PATCH 4/5] fix: update app:restart script to stop, flush, and start all processes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b213a62..ab743de 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "app:dev": "yarn kill-port && concurrently \"yarn --cwd ./backend dev\" \"yarn --cwd frontend dev\"", "app:build": "yarn --cwd ./frontend build && yarn --cwd ./backend build", "app:start": "yarn --cwd ./backend start", - "app:restart": "pm2 restart all", + "app:restart": "pm2 stop all && pm2 flush && pm2 start all", "format": "yarn --cwd ./frontend format && yarn --cwd ./backend format" }, "keywords": [], From 3071659bd00b6c80bd871dab6d7720019e25499a Mon Sep 17 00:00:00 2001 From: "omardieh@gmail.com" Date: Thu, 17 Apr 2025 15:56:51 +0200 Subject: [PATCH 5/5] fix: remove unnecessary entry from .gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4e36700..30bc162 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -/node_modules -ssh-temp \ No newline at end of file +/node_modules \ No newline at end of file