From bafcd88fa1798ffb9e58cf285d15eccba69b3ea9 Mon Sep 17 00:00:00 2001 From: Nicholas Pease Date: Fri, 23 Feb 2024 03:44:05 +0000 Subject: [PATCH 1/4] Optimize User Info Storage & Reduce API Calls to DB --- frontend-next/src/app/api/login/route.js | 34 ++++++++++++++++++---- frontend-next/src/app/api/signout/route.js | 13 ++------- frontend-next/src/app/api/user/route.js | 34 ++-------------------- frontend-next/src/middleware.js | 9 ++---- 4 files changed, 35 insertions(+), 55 deletions(-) diff --git a/frontend-next/src/app/api/login/route.js b/frontend-next/src/app/api/login/route.js index e0c9366..aa66674 100644 --- a/frontend-next/src/app/api/login/route.js +++ b/frontend-next/src/app/api/login/route.js @@ -4,8 +4,9 @@ import { NextResponse } from "next/server"; import { auth } from "firebase-admin"; import { signInWithEmailAndPassword } from "firebase/auth"; // Lib Imports -import { auth as authConfig } from "../firebase-config"; +import { app, auth as authConfig } from "../firebase-config"; import { customInitApp } from "../firebase-admin"; +import { getDatabase, ref, get as firebaseGet } from "firebase/database"; // Needs to "init" on each call to the API customInitApp(); @@ -16,9 +17,31 @@ async function handleEmailAndPassword(email, password) { var userCredential = await signInWithEmailAndPassword(authConfig,email,password); if (userCredential.user.accessToken) { var token = await auth().verifyIdToken(userCredential.user.accessToken); + var expiresIn = 20 * 60 * 1000; // 20 minutes + var sessionCookie = await auth().createSessionCookie(userCredential.user.accessToken, {expiresIn,}); if (token) { - var expiresIn = 20 * 60 * 1000; // 20 minutes - var sessionCookie = await auth().createSessionCookie(userCredential.user.accessToken, {expiresIn,}); + var database = getDatabase(app) + var user = await firebaseGet(ref(database, `users/${userCredential.user.uid}`)); + if (!user.exists()) { + var userOptions = { + name: "user", + value: JSON.stringify({firstName: 'DNE', uid: userCredential.user.uid}), + maxAge: expiresIn, // 20 mins + httpOnly: true, + secure: true, + }; + } else { + var userData = user.val() + userData.uid = userCredential.user.uid + var userOptions = { + name: "user", + value: JSON.stringify(userData), + maxAge: expiresIn, // 20 mins + httpOnly: true, + secure: true, + }; + } + cookies().set(userOptions); var options = { name: "session", value: sessionCookie, @@ -27,14 +50,13 @@ async function handleEmailAndPassword(email, password) { secure: true, }; cookies().set(options); - var uid_options = { + cookies().set({ name: "uid", value: userCredential.user.uid, maxAge: expiresIn, // 20 mins httpOnly: true, secure: true, - }; - cookies().set(uid_options); + }); return NextResponse.json({ options }, { status: 200 }); } } diff --git a/frontend-next/src/app/api/signout/route.js b/frontend-next/src/app/api/signout/route.js index e476c1e..e33509a 100644 --- a/frontend-next/src/app/api/signout/route.js +++ b/frontend-next/src/app/api/signout/route.js @@ -3,15 +3,8 @@ import { NextResponse } from "next/server"; export async function GET(req) { - cookies().set({ - name: "session", - value: "", - maxAge: -1, - }); - cookies().set({ - name: "firstName", - value: "", - maxAge: -1, - }); + cookies().delete('user') + cookies().delete('session') + cookies().delete('uid') return NextResponse.json({}, { status: 200 }); } \ No newline at end of file diff --git a/frontend-next/src/app/api/user/route.js b/frontend-next/src/app/api/user/route.js index d521841..a81a852 100644 --- a/frontend-next/src/app/api/user/route.js +++ b/frontend-next/src/app/api/user/route.js @@ -1,37 +1,7 @@ import { NextResponse } from "next/server"; import { cookies } from "next/headers"; -import { app } from "../firebase-config"; -import { getDatabase, ref, get as firebaseGet } from "firebase/database"; - -export async function POST(req,res) { - var uid = await req?.json() - var database = getDatabase(app) - var user = await firebaseGet(ref(database, `users/${uid}`)); - if (!user.exists()) { - return NextResponse.json({ - firstName: "not-found", - lastName: "not-found", - uid: "not-found", - }); - } else { - cookies().set("firstName",user.val()?.firstName) - cookies().set("lastName",user.val()?.lastName) - cookies().set("uid",uid) - return NextResponse.json({ - firstName: user.val()?.firstName, - lastName: user.val()?.lastName, - uid: uid, - }) - } - } export async function GET(req) { - var uid = cookies().get("uid")?.value - var database = getDatabase(app) - var user = await firebaseGet(ref(database, `users/${uid}`)); - return NextResponse.json({ - firstName: user.val()?.firstName, - lastName: user.val()?.lastName, - uid: cookies().get("uid")?.value, - }) + var userData = cookies().get("user")?.value + return NextResponse.json(JSON.parse(userData)) } \ No newline at end of file diff --git a/frontend-next/src/middleware.js b/frontend-next/src/middleware.js index ec53106..d8fa4d0 100644 --- a/frontend-next/src/middleware.js +++ b/frontend-next/src/middleware.js @@ -21,13 +21,8 @@ export async function middleware(req, res) { } // If new user, redirect to onboarding - var { uid } = await responseAPI.json() - var user = await fetch(new URL("/api/user", req.url), { - method: "POST", - body: JSON.stringify(uid ? uid : {}), - }); - user = await user.json(); - if (user.firstName !== "not-found") { + var user = JSON.parse(req.cookies.get("user").value) + if (user.firstName !== "DNE") { return NextResponse.next(); } else { return NextResponse.redirect(new URL("/onboarding", req.url)); -- 2.52.0 From 4b9b46f10d1746cfe3431ea74cf400f3b2d4b112 Mon Sep 17 00:00:00 2001 From: Nicholas Pease Date: Fri, 23 Feb 2024 04:09:45 +0000 Subject: [PATCH 2/4] Improved Onboarding User Verification, Removed Login/Register Buttons on Homepage for Logged In Users --- frontend-next/src/app/api/login/route.js | 3 ++- frontend-next/src/app/api/user/route.js | 4 ++-- frontend-next/src/app/app/shared.js | 2 +- frontend-next/src/app/page.js | 25 +++++++++++++++++++----- frontend-next/src/middleware.js | 2 +- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/frontend-next/src/app/api/login/route.js b/frontend-next/src/app/api/login/route.js index aa66674..27742ea 100644 --- a/frontend-next/src/app/api/login/route.js +++ b/frontend-next/src/app/api/login/route.js @@ -25,7 +25,7 @@ async function handleEmailAndPassword(email, password) { if (!user.exists()) { var userOptions = { name: "user", - value: JSON.stringify({firstName: 'DNE', uid: userCredential.user.uid}), + value: JSON.stringify({defined: false, uid: userCredential.user.uid}), maxAge: expiresIn, // 20 mins httpOnly: true, secure: true, @@ -33,6 +33,7 @@ async function handleEmailAndPassword(email, password) { } else { var userData = user.val() userData.uid = userCredential.user.uid + userData.defined = true var userOptions = { name: "user", value: JSON.stringify(userData), diff --git a/frontend-next/src/app/api/user/route.js b/frontend-next/src/app/api/user/route.js index a81a852..1efb9e2 100644 --- a/frontend-next/src/app/api/user/route.js +++ b/frontend-next/src/app/api/user/route.js @@ -2,6 +2,6 @@ import { NextResponse } from "next/server"; import { cookies } from "next/headers"; export async function GET(req) { - var userData = cookies().get("user")?.value - return NextResponse.json(JSON.parse(userData)) + var userData = cookies().get("user")?.value || false + return userData != false? NextResponse.json(JSON.parse(userData)): NextResponse.json({},{status: 203}) } \ No newline at end of file diff --git a/frontend-next/src/app/app/shared.js b/frontend-next/src/app/app/shared.js index 4fd6826..d9021ae 100644 --- a/frontend-next/src/app/app/shared.js +++ b/frontend-next/src/app/app/shared.js @@ -1,7 +1,7 @@ export function Header() { return (
- +
) } diff --git a/frontend-next/src/app/page.js b/frontend-next/src/app/page.js index 5e1f126..ba257bf 100644 --- a/frontend-next/src/app/page.js +++ b/frontend-next/src/app/page.js @@ -1,5 +1,16 @@ +"use client" +import { useState, useEffect } from 'react' + + function Home() { - + const [statusCode, setData] = useState(null) + useEffect(() => { + fetch('/api/user') + .then((res) => res.status) + .then((status) => { + setData(status) + }) + }, []) return (
@@ -9,10 +20,14 @@ function Home() { Chat with friends!
- - + {statusCode == 203 && +
+ + +
+ } + {statusCode == 200 && } +
diff --git a/frontend-next/src/middleware.js b/frontend-next/src/middleware.js index d8fa4d0..a0413af 100644 --- a/frontend-next/src/middleware.js +++ b/frontend-next/src/middleware.js @@ -22,7 +22,7 @@ export async function middleware(req, res) { // If new user, redirect to onboarding var user = JSON.parse(req.cookies.get("user").value) - if (user.firstName !== "DNE") { + if (user.defined) { return NextResponse.next(); } else { return NextResponse.redirect(new URL("/onboarding", req.url)); -- 2.52.0 From d9bca7f1ff82a3987c0dcec530975a3d239ecfb4 Mon Sep 17 00:00:00 2001 From: Nicholas Pease Date: Fri, 23 Feb 2024 04:22:36 +0000 Subject: [PATCH 3/4] Loading Icon on Login/Register button press --- frontend-next/src/app/login/page.js | 12 ++++++++++-- frontend-next/src/app/register/page.js | 13 ++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/frontend-next/src/app/login/page.js b/frontend-next/src/app/login/page.js index 662b559..52637ce 100644 --- a/frontend-next/src/app/login/page.js +++ b/frontend-next/src/app/login/page.js @@ -6,7 +6,7 @@ import "../globals.css" function Login() { var router = useRouter(); //var { register, handleSubmit } = useForm(); - var { register, control, setError, formState: { errors } } = useForm() + var { register, control, setError, formState: { errors, isSubmitting, isSubmitted } } = useForm() return (
@@ -32,7 +32,15 @@ function Login() { >

- +
Need an account? Sign Up
diff --git a/frontend-next/src/app/register/page.js b/frontend-next/src/app/register/page.js index 027eba0..5b23a72 100644 --- a/frontend-next/src/app/register/page.js +++ b/frontend-next/src/app/register/page.js @@ -4,7 +4,7 @@ import { useForm, Form } from "react-hook-form"; import "../globals.css" function Register() { - var { register, control, setError, formState: { errors } } = useForm() + var { register, control, setError, formState: { errors, isSubmitting, isSubmitted } } = useForm() var router = useRouter(); var emailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/ return ( @@ -25,8 +25,15 @@ function Register() { >

-
+
Have an account? Log In
-- 2.52.0 From 8737d10a1e05dcbfc1e30d064e4f2fe7482c9a27 Mon Sep 17 00:00:00 2001 From: Nicholas Pease Date: Fri, 23 Feb 2024 04:37:03 +0000 Subject: [PATCH 4/4] If user is authenticated, redirect to app from /login and /register pages --- frontend-next/src/middleware.js | 60 +++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/frontend-next/src/middleware.js b/frontend-next/src/middleware.js index a0413af..a283cb6 100644 --- a/frontend-next/src/middleware.js +++ b/frontend-next/src/middleware.js @@ -3,35 +3,53 @@ import { NextResponse } from "next/server"; import { cookies } from "next/headers"; export async function middleware(req, res) { - const session = req.cookies.get("session"); - // Login if not logged in - if (!session) { - return NextResponse.redirect(new URL("/login", req.url)); - } - //Call the authentication endpoint - const responseAPI = await fetch(new URL("/api/login", req.url), { - headers: { - Cookie: `session=${session?.value}`, - }, - }); + const session = await req.cookies.get("session"); + if (req.nextUrl.pathname !== "/login" && req.nextUrl.pathname != "/register") { + // Login if not logged in + if (!session) { + return NextResponse.redirect(new URL("/login", req.url)); + } + //Call the authentication endpoint + const responseAPI = await fetch(new URL("/api/login", req.url), { + headers: { + Cookie: `session=${session?.value}`, + }, + }); - // Login if unauthorized - if (responseAPI.status !== 200) { - return NextResponse.redirect(new URL("/login", req.url)); - } + // Login if unauthorized + if (responseAPI.status !== 200) { + return NextResponse.redirect(new URL("/login", req.url)); + } - // If new user, redirect to onboarding - var user = JSON.parse(req.cookies.get("user").value) - if (user.defined) { - return NextResponse.next(); + // If new user, redirect to onboarding + var user = JSON.parse(req.cookies.get("user").value) + if (user.defined) { + return NextResponse.next(); + } else { + return NextResponse.redirect(new URL("/onboarding", req.url)); + } } else { - return NextResponse.redirect(new URL("/onboarding", req.url)); + // Currently in the /login or /register, if user is authenticated, go ahead and direct them to the app + if (session) { + const responseAPI = await fetch(new URL("/api/login", req.url), { + headers: { + Cookie: `session=${session?.value}`, + }, + }); + if (responseAPI.status == 200) { + return NextResponse.redirect(new URL("/app", req.url)) + } else { + return NextResponse.next() // Unauthenticated, continue + } + } else { + return NextResponse.next() // Not logged in, direct to login + } } } //Protected routes export const config = { - matcher: ['/((?!login|register|onboarding|api|_next/static|_next/image|auth|favicon.ico|robots.txt|images|logo|$).*)',], + matcher: ['/((?!onboarding|api|_next/static|_next/image|auth|favicon.ico|robots.txt|images|logo|$).*)',], missing: [ { type: 'header', key: 'next-router-prefetch' }, { type: 'header', key: 'purpose', value: 'prefetch' }, -- 2.52.0