From 59e194b373665ba741f4962d616dbd4e000f282b Mon Sep 17 00:00:00 2001 From: GustaveWPM Date: Sun, 21 Apr 2024 22:27:25 +0200 Subject: [PATCH] feat: upgraded next auth to v5 and now handling guest and protected routes --- .env_example | 2 +- src/auth.ts | 4 +- .../layouts/navbar/NavbarButton.tsx | 75 ++++++++++--------- .../layouts/navbar/NavbarLoginButton.tsx | 50 ++++++++----- src/components/ui/cta/SignupButton.tsx | 12 +-- src/config/auth.ts | 12 +-- src/lib/authServer.ts | 17 +++++ src/lib/misc/handleSignOut.ts | 22 ------ 8 files changed, 96 insertions(+), 98 deletions(-) create mode 100644 src/lib/authServer.ts delete mode 100644 src/lib/misc/handleSignOut.ts diff --git a/.env_example b/.env_example index 1bdbd3eb2..3741b77fb 100644 --- a/.env_example +++ b/.env_example @@ -2,7 +2,7 @@ NEXT_SITEMAP_SITE_URL="http://localhost:3000" AUTH_URL="http://localhost:3000" AUTH_TRUST_HOST="http://localhost:3000" -AUTH_SECRETS="YOUR RANDOM SECRETS, separated using ';;;;;;' (without quotes)" # https://next-auth.js.org/configuration/options#nextauth_secret +AUTH_SECRET="YOUR RANDOM STRING - https://next-auth.js.org/configuration/options#nextauth_secret" DISCORD_CLIENT_ID="YOUR DISCORD CLIENT ID - https://discord.com/developers/applications" DISCORD_CLIENT_SECRET="YOUR DISCORD CLIENT SECRET - https://discord.com/developers/applications" diff --git a/src/auth.ts b/src/auth.ts index 09cf6767c..3b9c05de5 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -53,5 +53,5 @@ export async function getSession(session: Session, __discordApi: IDiscordApi = d } satisfies Session; } -const { handlers, auth } = NextAuth(config); -export { handlers, auth }; +const { handlers, signIn, auth } = NextAuth(config); +export { handlers, signIn, auth }; diff --git a/src/components/layouts/navbar/NavbarButton.tsx b/src/components/layouts/navbar/NavbarButton.tsx index ae84c5748..b826001f4 100644 --- a/src/components/layouts/navbar/NavbarButton.tsx +++ b/src/components/layouts/navbar/NavbarButton.tsx @@ -1,8 +1,8 @@ 'use client'; -import type { FunctionComponent, MouseEventHandler, ReactNode } from 'react'; +import type { ButtonHTMLAttributes as ReactButtonHTMLAttributes, FunctionComponent, ReactNode } from 'react'; +import type { WithClassname, AppPath } from '@rtm/shared-types/Next'; import type { AtomicNavDataEntity } from '@/types/NavData'; -import type { AppPath } from '@rtm/shared-types/Next'; import NavbarButtonStyle from '@/components/config/styles/navbar/NavbarButtonStyle'; import { hrefAndPathnameExactMatch, hrefMatchesPathname } from '@/lib/str'; @@ -15,47 +15,48 @@ import Link from 'next/link'; type OptionalIcon = { icon?: ReactNode }; type OptionalPath = { path?: AppPath }; -type OptionalOnClick = { onClick?: MouseEventHandler }; -type RequiredPath = Required; -type RequiredOnClick = Required; +interface INavbarButtonProps + extends Pick, + OptionalPath, + OptionalIcon, + ReactButtonHTMLAttributes {} -interface INavbarButtonProps extends Pick, OptionalPath, OptionalOnClick, OptionalIcon {} -type NavbarButtonProps = (RequiredOnClick | RequiredPath) & INavbarButtonProps; +type NavbarButtonProps = INavbarButtonProps & OptionalPath; const { isNotActiveClassList, isActiveClassList } = NavbarButtonStyle; -const ButtonAsIs: FunctionComponent & OptionalIcon> = ({ onClick: onClickFun, i18nTitle, icon }) => { +const ButtonAsIs: FunctionComponent & Partial & OptionalIcon> = ({ + className: classNameValue, + i18nTitle, + onClick, + icon, + type +}) => { const globalT = getClientSideI18n(); - if (onClickFun) { - return icon ? ( - - ) : ( - - ); - } - return icon ? ( - ) : ( - + ); }; -const ButtonAsLink: FunctionComponent & OptionalIcon> = ({ path: href, i18nTitle, icon }) => { +const ButtonAsLink: FunctionComponent & Partial & OptionalIcon> = ({ + className: classNameValue, + path: href, + i18nTitle, + icon +}) => { const globalT = getClientSideI18n(); const currentPathname = usePathname(); const isActive = hrefMatchesPathname(href, currentPathname); - const className = isActive ? isActiveClassList : isNotActiveClassList; + const className = cn(isActive ? isActiveClassList : isNotActiveClassList, classNameValue); const target = getLinkTarget(href); const exactMatch = hrefAndPathnameExactMatch(href, currentPathname); @@ -75,18 +76,20 @@ const ButtonAsLink: FunctionComponent = ({ i18nTitle, onClick, path, icon }) => { - const generateNavbarButtonWithoutIcon: () => ReactNode = () => { - if (onClick) return ; - else if (path) return ; - return null; - }; +const NavbarButton: FunctionComponent = ({ i18nTitle, className, onClick, path, icon, type }) => { + const generateNavbarButtonWithoutIcon: () => ReactNode = () => + path ? ( + + ) : ( + + ); - const generateNavbarButtonWithIcon: () => ReactNode = () => { - if (onClick) return ; - else if (path) return ; - return null; - }; + const generateNavbarButtonWithIcon: () => ReactNode = () => + path ? ( + + ) : ( + + ); return icon ? generateNavbarButtonWithIcon() : generateNavbarButtonWithoutIcon(); }; diff --git a/src/components/layouts/navbar/NavbarLoginButton.tsx b/src/components/layouts/navbar/NavbarLoginButton.tsx index a764b480c..7eac8263c 100644 --- a/src/components/layouts/navbar/NavbarLoginButton.tsx +++ b/src/components/layouts/navbar/NavbarLoginButton.tsx @@ -5,34 +5,34 @@ import type { FunctionComponent } from 'react'; import NAVBAR_ICON_STYLE from '@/components/config/styles/navbar/NavbarIconStyle'; import { SignalSlashIcon, KeyIcon } from '@heroicons/react/16/solid'; -import { signInProviderActionFlag } from '@/config/authMisc'; import UserImage from '@/components/ui/hoc/UserImage'; -import { useSession, signIn } from 'next-auth/react'; -import handleSignOut from '@/lib/misc/handleSignOut'; +import { useSession, signOut } from 'next-auth/react'; +import { signInAction } from '@/lib/authServer'; import { Button } from '@/components/ui/Button'; import { useScopedI18n } from '@/i18n/client'; -import { usePathname } from 'next/navigation'; -import ROUTES_ROOTS from '##/config/routes'; import { i18ns } from '##/config/i18n'; import NavbarButton from './NavbarButton'; -interface NavbarLoginButtonMobileProps extends WithSession { - currentPathname: string; -} +interface NavbarLoginButtonMobileProps extends WithSession {} interface NavbarLoginButtonProps extends WithIsMobile {} const { SIZE_PX_VALUE: SIZE } = NAVBAR_ICON_STYLE; -const provider = signInProviderActionFlag; -const NavbarLoginButtonMobile: FunctionComponent = ({ currentPathname, session }) => { +const NavbarLoginButtonMobile: FunctionComponent = ({ session }) => { const scopedT = useScopedI18n(i18ns.auth); const className = 'h-full min-w-0 p-0'; if (session) { return ( - +
+ +
); }; const NavbarLoginButton: FunctionComponent = ({ isMobile }) => { const { data: session } = useSession(); - const currentPathname = usePathname(); const { auth } = i18ns; - if (isMobile) return ; + if (isMobile) return ; - if (session) + if (session) { return ( } - onClick={() => handleSignOut(currentPathname)} + onClick={() => { + signOut(); + }} i18nTitle={`${auth}.logout`} + className="flex gap-2" + type="submit" /> ); + } - return signIn(provider, { callbackUrl: ROUTES_ROOTS.DASHBOARD })} i18nTitle={`${auth}.login`} />; + return ( +
+ + + ); }; export default NavbarLoginButton; diff --git a/src/components/ui/cta/SignupButton.tsx b/src/components/ui/cta/SignupButton.tsx index 4d6292670..1e424caee 100644 --- a/src/components/ui/cta/SignupButton.tsx +++ b/src/components/ui/cta/SignupButton.tsx @@ -6,11 +6,9 @@ import type { FunctionComponent } from 'react'; import BUTTON_CONFIG from '@/components/config/styles/buttons'; -import { signInProviderActionFlag } from '@/config/authMisc'; import { Button } from '@/components/ui/Button'; +import { signInAction } from '@/lib/authServer'; import { useScopedI18n } from '@/i18n/client'; -import ROUTES_ROOTS from '##/config/routes'; -import { signIn } from 'next-auth/react'; import { i18ns } from '##/config/i18n'; import { capitalize } from '@/lib/str'; @@ -21,9 +19,11 @@ const SignupButton: FunctionComponent = () => { const className = BUTTON_CONFIG.CLASSNAME; return ( - +
+ +
); }; diff --git a/src/config/auth.ts b/src/config/auth.ts index e4dc7c6c1..1f17c63cc 100644 --- a/src/config/auth.ts +++ b/src/config/auth.ts @@ -3,14 +3,6 @@ import type { NextAuthConfig } from 'next-auth'; import Discord from 'next-auth/providers/discord'; import { getSession } from '@/auth'; -// eslint-disable-next-line -const AUTH_SECRETS_SEP = ';;;;;;'; - -// const secret = process.env.AUTH_SECRETS!.split(AUTH_SECRETS_SEP); -// {ToDo} Find a way to fix this -// See also: https://github.com/nextauthjs/next-auth/issues/10633 -const secret = 'huuuummmmm'; - const config = { providers: [ Discord({ @@ -29,9 +21,7 @@ const config = { const s = await getSession(session); return s; } - }, - - secret + } } as const satisfies NextAuthConfig; export default config; diff --git a/src/lib/authServer.ts b/src/lib/authServer.ts new file mode 100644 index 000000000..1ca498605 --- /dev/null +++ b/src/lib/authServer.ts @@ -0,0 +1,17 @@ +/* v8 ignore start */ +// Stryker disable all + +'use server'; + +import { signInProviderActionFlag } from '@/config/authMisc'; +import ROUTES_ROOTS from '##/config/routes'; +import { signIn } from '@/auth'; + +// eslint-disable-next-line require-await +export async function signInAction() { + 'use server'; + await signIn(signInProviderActionFlag, { callbackUrl: ROUTES_ROOTS.DASHBOARD }); +} + +// Stryker restore all +/* v8 ignore stop */ diff --git a/src/lib/misc/handleSignOut.ts b/src/lib/misc/handleSignOut.ts deleted file mode 100644 index 2f21d1ec8..000000000 --- a/src/lib/misc/handleSignOut.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* v8 ignore start */ -// Stryker disable all - -import type { AppPath } from '@rtm/shared-types/Next'; - -import ROUTES_ROOTS from '##/config/routes'; -import { signOut } from 'next-auth/react'; - -import isProtectedRoute from './isProtectedRoute'; - -function handleSignOut(currentUrl: AppPath) { - if (isProtectedRoute(currentUrl)) { - signOut({ callbackUrl: ROUTES_ROOTS.WEBSITE }); - return; - } - signOut(); -} - -export default handleSignOut; - -// Stryker restore all -/* v8 ignore stop */