diff --git a/cypress/e2e/authentication.cy.ts b/cypress/e2e/authentication.cy.ts index ba4d5005..e4d0bb29 100644 --- a/cypress/e2e/authentication.cy.ts +++ b/cypress/e2e/authentication.cy.ts @@ -2,7 +2,7 @@ import authSuccessResponse1 from "../fixtures/authSuccessResponse1.json"; import authSuccessResponse2 from "../fixtures/authSuccessResponse2.json"; import authSuccessRequestHeaders from "../fixtures/authSuccessRequestHeaders.json"; -import { URLS } from "@/../../frontend/app/consts"; +import { URLS } from "../../frontend/app/consts"; describe("Authentication", () => { describe("login", () => { diff --git a/frontend/app/(cms)/(auth)/login/page.tsx b/frontend/app/(cms)/(auth)/login/page.tsx index 282899a9..cac3029e 100644 --- a/frontend/app/(cms)/(auth)/login/page.tsx +++ b/frontend/app/(cms)/(auth)/login/page.tsx @@ -1,47 +1,46 @@ -"use client"; +"use client" -import { useAuth } from "@/app/cms-authentication/AuthContext"; -import React, { useState } from "react"; +import { useAuth } from "@/app/cms-authentication/AuthContext" +import React, { useState } from "react" export default function Login() { - const { login } = useAuth(); - const [emailInput, setEmailInput] = useState(""); - const [passwordInput, setPasswordInput] = useState(""); - const [errorMessage, setErrorMessage] = useState(null); + const { login } = useAuth() + const [emailInput, setEmailInput] = useState("") + const [passwordInput, setPasswordInput] = useState("") + const [errorMessage, setErrorMessage] = useState(null) - return ( -
- + return ( + + - + - - {errorMessage &&

{errorMessage}

} -
- ); + setErrorMessage(login(emailInput, passwordInput)) // login returns undefined or error message + }}> + Login + + {errorMessage &&

{errorMessage}

} + + ) } diff --git a/frontend/app/(cms)/layout.tsx b/frontend/app/(cms)/layout.tsx index 6e103653..52083481 100644 --- a/frontend/app/(cms)/layout.tsx +++ b/frontend/app/(cms)/layout.tsx @@ -1,31 +1,31 @@ -"use client"; +"use client" -import React from "react"; -import AuthProvider, { useAuth } from "@/app/cms-authentication/AuthContext"; +import React from "react" +import AuthProvider, { useAuth } from "@/app/cms-authentication/AuthContext" export default function CMSLayout({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); + return ( + + {children} + + ) } function CMSLayoutWithAuth({ children }: { children: React.ReactNode }) { - const { currentUser, logout } = useAuth(); + const { currentUser, logout } = useAuth() - return ( -
- {currentUser && ( - <> -

Logged in as Admin

- - - )} + return ( +
+ {currentUser && ( + <> +

Logged in as Admin

+ + + )} - {children} -
- ); + {children} +
+ ) } diff --git a/frontend/app/(content)/news/page.tsx b/frontend/app/(content)/news/page.tsx index c111ed59..ed1e27b3 100644 --- a/frontend/app/(content)/news/page.tsx +++ b/frontend/app/(content)/news/page.tsx @@ -7,7 +7,7 @@ import ButtonLink from "@/app/components/ButtonLink" import { getMetadata } from "@/app/util" // 👇 so npm run build passes (don't attempt to static render this page) -export const dynamic = "force-dynamic"; +export const dynamic = "force-dynamic" export const metadata = getMetadata("All News") diff --git a/frontend/app/(content)/research/page.tsx b/frontend/app/(content)/research/page.tsx index eb236362..b730967d 100644 --- a/frontend/app/(content)/research/page.tsx +++ b/frontend/app/(content)/research/page.tsx @@ -6,9 +6,9 @@ import ButtonLink from "@/app/components/ButtonLink" import { getMetadata } from "@/app/util" // 👇 so npm run build passes (don't attempt to static render this page) -export const dynamic = "force-dynamic"; +export const dynamic = "force-dynamic" -export const metadata = getMetadata("All Research"); +export const metadata = getMetadata("All Research") export default async function AllResearchPage() { const research = new Paginator( diff --git a/frontend/app/cms-authentication/AuthContext.tsx b/frontend/app/cms-authentication/AuthContext.tsx index 2d7738e2..7a3d4a90 100644 --- a/frontend/app/cms-authentication/AuthContext.tsx +++ b/frontend/app/cms-authentication/AuthContext.tsx @@ -1,112 +1,100 @@ -import React, { useContext, useState, useEffect } from "react"; +import React, { useContext, useState, useEffect } from "react" import { - AuthenticationDetails, - CognitoUser, - CognitoUserPool, - CognitoUserSession, - CognitoIdToken, -} from "amazon-cognito-identity-js"; -import { redirect } from "next/navigation"; -import { OPEN_CMS_ROUTES, ROUTES } from "@/app/consts"; + AuthenticationDetails, + CognitoUser, + CognitoUserPool, + CognitoUserSession, + CognitoIdToken, +} from "amazon-cognito-identity-js" +import { redirect } from "next/navigation" +import { OPEN_CMS_ROUTES, ROUTES } from "@/app/consts" type AuthContextValue = { - currentUser: null | CognitoIdToken["payload"]; - login: any; - logout: any; -}; + currentUser: null | CognitoIdToken["payload"] + login: any + logout: any +} // where to authenticate users from const adminUserPool = new CognitoUserPool({ - ClientId: "7dv8g7lsl2ht3uv29q9npt0a84", - UserPoolId: "us-east-1_1GuGm8wMs", -}); + ClientId: "7dv8g7lsl2ht3uv29q9npt0a84", + UserPoolId: "us-east-1_1GuGm8wMs", +}) const AuthContext = React.createContext({ - currentUser: null, - login: () => {}, -} as AuthContextValue); + currentUser: null, + login: () => {}, +} as AuthContextValue) export function useAuth() { - return useContext(AuthContext); + return useContext(AuthContext) } export default function AuthProvider({ children }: React.PropsWithChildren) { - const [currentUserSession, setCurrentUserSession] = useState( - null as AuthContextValue["currentUser"] - ); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const currentUser = adminUserPool.getCurrentUser(); - - if (!currentUser) return setLoading(false); - - currentUser.getSession( - (error: Error | null, session: CognitoUserSession | null) => { - error && console.log("Error in getting user session:", error); - session && setCurrentUserSession(session.getIdToken().payload); - setLoading(false); - } - ); - }, []); - - useEffect(() => { - if (loading) return; // we don't know if user is logged in or not - - const currentRoute = window.location.pathname; - - if (!currentUserSession && !OPEN_CMS_ROUTES.includes(currentRoute)) - redirect(OPEN_CMS_ROUTES[0]); - - if (currentUserSession && OPEN_CMS_ROUTES.includes(currentRoute)) - redirect(ROUTES.REDIRECT_AFTER_LOGIN); - }, [currentUserSession, loading]); - - // returns the error if there is one - // if login successful, returns void but sets the current user - async function login(email: string, password: string) { - const user = new CognitoUser({ - Username: email, - Pool: adminUserPool, - }); - - const authDetails = new AuthenticationDetails({ - Username: email, - Password: password, - }); - - try { - const result: CognitoUserSession = await new Promise( - (resolve, reject) => { - user.authenticateUser(authDetails, { - onSuccess: resolve, - onFailure: reject, - }); + const [currentUserSession, setCurrentUserSession] = useState(null as AuthContextValue["currentUser"]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const currentUser = adminUserPool.getCurrentUser() + + if (!currentUser) return setLoading(false) + + currentUser.getSession((error: Error | null, session: CognitoUserSession | null) => { + error && console.log("Error in getting user session:", error) + session && setCurrentUserSession(session.getIdToken().payload) + setLoading(false) + }) + }, []) + + useEffect(() => { + if (loading) return // we don't know if user is logged in or not + + const currentRoute = window.location.pathname + + if (!currentUserSession && !OPEN_CMS_ROUTES.includes(currentRoute)) redirect(OPEN_CMS_ROUTES[0]) + + if (currentUserSession && OPEN_CMS_ROUTES.includes(currentRoute)) redirect(ROUTES.REDIRECT_AFTER_LOGIN) + }, [currentUserSession, loading]) + + // returns the error if there is one + // if login successful, returns void but sets the current user + async function login(email: string, password: string) { + const user = new CognitoUser({ + Username: email, + Pool: adminUserPool, + }) + + const authDetails = new AuthenticationDetails({ + Username: email, + Password: password, + }) + + try { + const result: CognitoUserSession = await new Promise((resolve, reject) => { + user.authenticateUser(authDetails, { + onSuccess: resolve, + onFailure: reject, + }) + }) + + setCurrentUserSession(result.getIdToken().payload) + } catch (error: any) { + console.error("Login failed:", { error }) + return error.message } - ); + } - setCurrentUserSession(result.getIdToken().payload); - } catch (error: any) { - console.error("Login failed:", { error }); - return error.message; + function logout() { + adminUserPool.getCurrentUser()?.signOut() + setCurrentUserSession(null) } - } - - function logout() { - adminUserPool.getCurrentUser()?.signOut(); - setCurrentUserSession(null); - } - - // expose useful auth methods/values - const authDetails = { - currentUser: currentUserSession, - login, - logout, - }; - - return ( - - {!loading && children} - - ); + + // expose useful auth methods/values + const authDetails = { + currentUser: currentUserSession, + login, + logout, + } + + return {!loading && children} } diff --git a/frontend/app/components/ArticleForm.tsx b/frontend/app/components/ArticleForm.tsx index b9c35ea7..dee8b4b4 100644 --- a/frontend/app/components/ArticleForm.tsx +++ b/frontend/app/components/ArticleForm.tsx @@ -1,10 +1,18 @@ -import ContentEditor from "@/app/components/ContentEditor"; -import { ArticleFromCMS } from "@/app/components/ArticleFromCMS"; -import { Article, ArticleType, IArticle } from "@aapc/types"; -import React, { useState } from "react"; -import { API_URI } from "@/app/consts"; +import ContentEditor from "@/app/components/ContentEditor" +import { ArticleFromCMS } from "@/app/components/ArticleFromCMS" +import { Article, ArticleType, IArticle } from "@aapc/types" +import React, { useState } from "react" +import { API_URI } from "@/app/consts" -export default function ArticleForm ({ articleType, actionType, article }: { articleType: ArticleType, article?: IArticle, actionType: "create" | "edit" }) { +export default function ArticleForm({ + articleType, + actionType, + article, +}: { + articleType: ArticleType + article?: IArticle + actionType: "create" | "edit" +}) { const [editorContent, setEditorContent] = useState(article?.content ?? "") const [title, setTitle] = useState(article?.title ?? "") const [subtitle, setSubtitle] = useState(article?.subtitle ?? "") @@ -14,13 +22,13 @@ export default function ArticleForm ({ articleType, actionType, article }: { art title: title, subtitle: subtitle, content: editorContent, - media: [] + media: [], } switch (actionType) { case "create": { - fetch(`${API_URI}/content/${articleType === ArticleType.news? 'news': 'research'}`, { + fetch(`${API_URI}/content/${articleType === ArticleType.news ? "news" : "research"}`, { method: "post", - body: JSON.stringify(a) + body: JSON.stringify(a), }).then() } } @@ -39,32 +47,38 @@ export default function ArticleForm ({ articleType, actionType, article }: { art return ( <>

Title

-