Added guard for unauthorized/authorized users
This commit is contained in:
parent
a5090d2cc3
commit
047e1f6fa9
|
|
@ -23,6 +23,7 @@
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/jsonwebtoken": "^9.0.5",
|
"@types/jsonwebtoken": "^9.0.5",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
"client-only": "^0.0.1",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ dependencies:
|
||||||
bcrypt:
|
bcrypt:
|
||||||
specifier: ^5.1.1
|
specifier: ^5.1.1
|
||||||
version: 5.1.1
|
version: 5.1.1
|
||||||
|
client-only:
|
||||||
|
specifier: ^0.0.1
|
||||||
|
version: 0.0.1
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
|
|
||||||
14
src/app/(auth)/login/layout.tsx
Normal file
14
src/app/(auth)/login/layout.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import guestOnly from "@/features/auth/actions/guestOnly";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function LoginLayout({ children }: Props) {
|
||||||
|
|
||||||
|
await guestOnly()
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import getUser from "@/features/auth/actions/getUser";
|
||||||
import signIn from "@/features/auth/actions/signIn";
|
import signIn from "@/features/auth/actions/signIn";
|
||||||
import {
|
import {
|
||||||
Paper,
|
Paper,
|
||||||
|
|
@ -12,7 +13,9 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Alert,
|
Alert,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import React from "react";
|
import { redirect } from "next/navigation";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
import { useFormState } from "react-dom";
|
import { useFormState } from "react-dom";
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
|
|
||||||
21
src/app/(auth)/logout/page.tsx
Normal file
21
src/app/(auth)/logout/page.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
"use client"
|
||||||
|
import getUser from "@//features/auth/actions/getUser";
|
||||||
|
import logout from "@/features/auth/actions/logout";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LogoutPage component handles the logout process.
|
||||||
|
* It checks if a user is logged in, logs them out, and redirects to the login page.
|
||||||
|
*/
|
||||||
|
export default function LogoutPage() {
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const logoutAction = async () => await logout()
|
||||||
|
|
||||||
|
logoutAction()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
14
src/app/(auth)/register/layout.tsx
Normal file
14
src/app/(auth)/register/layout.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import guestOnly from "@/features/auth/actions/guestOnly";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function RegisterLayout({ children }: Props) {
|
||||||
|
|
||||||
|
await guestOnly()
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
@ -13,9 +13,9 @@ interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Layout(props: Props) {
|
export default async function Layout(props: Props) {
|
||||||
|
|
||||||
const user = getUser()
|
const user = await getUser()
|
||||||
|
|
||||||
if (!user){
|
if (!user){
|
||||||
redirect("/login")
|
redirect("/login")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import logo from "@/assets/logos/logo-dsg.png";
|
||||||
import cx from "clsx";
|
import cx from "clsx";
|
||||||
import classNames from "./styles.module.css";
|
import classNames from "./styles.module.css";
|
||||||
import { TbChevronDown, TbLogout, TbSettings } from "react-icons/tb";
|
import { TbChevronDown, TbLogout, TbSettings } from "react-icons/tb";
|
||||||
import userMenuItems from "./_data/UserMenuItems";
|
import userMenuItems from "./_data/userMenuItems";
|
||||||
import UserMenuItem from "./_components/UserMenuItem/UserMenuItem";
|
import UserMenuItem from "./_components/UserMenuItem/UserMenuItem";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Menu, rem } from '@mantine/core'
|
import { Menu, rem } from '@mantine/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { UserMenuItem } from '../../_data/UserMenuItems'
|
import { UserMenuItem } from '../../_data/userMenuItems'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: UserMenuItem
|
item: UserMenuItem
|
||||||
|
|
@ -10,12 +10,14 @@ export default function UserMenuItem({item}: Props) {
|
||||||
return (
|
return (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
color={item.color}
|
color={item.color}
|
||||||
|
component='a'
|
||||||
leftSection={
|
leftSection={
|
||||||
<item.icon
|
<item.icon
|
||||||
style={{ width: rem(16), height: rem(16) }}
|
style={{ width: rem(16), height: rem(16) }}
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
href={item.href}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import { TbLogout, TbSettings } from "react-icons/tb"
|
||||||
export interface UserMenuItem {
|
export interface UserMenuItem {
|
||||||
label: string,
|
label: string,
|
||||||
icon: React.FC<any>,
|
icon: React.FC<any>,
|
||||||
color?: ThemeIconProps['color']
|
color?: ThemeIconProps['color'],
|
||||||
|
href?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const userMenuItems: UserMenuItem[] = [
|
const userMenuItems: UserMenuItem[] = [
|
||||||
|
|
@ -16,7 +17,8 @@ const userMenuItems: UserMenuItem[] = [
|
||||||
{
|
{
|
||||||
label: "Logout",
|
label: "Logout",
|
||||||
icon: TbLogout,
|
icon: TbLogout,
|
||||||
color: "red"
|
color: "red",
|
||||||
|
href: "/logout"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -4,19 +4,29 @@ import { cookies } from "next/headers"
|
||||||
import "server-only"
|
import "server-only"
|
||||||
import { decodeJwtToken } from "../authUtils";
|
import { decodeJwtToken } from "../authUtils";
|
||||||
import prisma from "@/db";
|
import prisma from "@/db";
|
||||||
|
import AuthError, { AuthErrorCode } from "../AuthError";
|
||||||
|
import logout from "./logout";
|
||||||
|
|
||||||
export default async function getUser(){
|
export default async function getUser(){
|
||||||
const token = cookies().get('token');
|
try {
|
||||||
|
const token = cookies().get('token');
|
||||||
|
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
|
|
||||||
const decodedToken = decodeJwtToken(token.value) as {id: string, iat: number};
|
const decodedToken = decodeJwtToken(token.value) as {id: string, iat: number};
|
||||||
|
console.log('token', decodedToken)
|
||||||
|
|
||||||
const user = await prisma.user.findFirst({
|
const user = await prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: decodedToken.id
|
id: decodedToken.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
} catch (e: unknown){
|
||||||
|
if (e instanceof AuthError && e.errorCode === AuthErrorCode.INVALID_JWT_TOKEN){
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
});
|
throw e;
|
||||||
|
}
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
src/features/auth/actions/guestOnly.ts
Normal file
13
src/features/auth/actions/guestOnly.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
"use server"
|
||||||
|
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import getUser from "./getUser"
|
||||||
|
|
||||||
|
export default async function guestOnly(){
|
||||||
|
|
||||||
|
const user = await getUser();
|
||||||
|
|
||||||
|
if (user){
|
||||||
|
redirect("dashboard")
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/features/auth/actions/logout.ts
Normal file
16
src/features/auth/actions/logout.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"use server"
|
||||||
|
|
||||||
|
import { cookies } from "next/headers"
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import "server-only"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles user logout by deleting the authentication token and redirecting to the login page.
|
||||||
|
* This function is intended to be used on the server side.
|
||||||
|
*
|
||||||
|
* @returns A promise that resolves when the logout process is complete.
|
||||||
|
*/
|
||||||
|
export default async function logout(){
|
||||||
|
cookies().delete("token");
|
||||||
|
redirect("/login")
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user