fix: admin pages authorization issues

This commit is contained in:
swve 2024-02-04 19:00:46 +01:00
parent f356fe9f07
commit e53044e42c
8 changed files with 210 additions and 73 deletions

View file

@ -1,17 +1,12 @@
'use client'
import React, { use, useEffect } from 'react'
import React, { useEffect } from 'react'
import { INSTALL_STEPS } from './steps/steps'
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'
import { useRouter, useSearchParams } from 'next/navigation'
import { Suspense } from 'react'
function InstallClient() {
return (
<GeneralWrapperStyled>
<Suspense>

View file

@ -1,17 +1,20 @@
import SessionProvider from '@components/Contexts/SessionContext'
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
import AdminAuthorization from '@components/Security/AdminAuthorization'
import React from 'react'
function DashboardLayout({ children, params }: { children: React.ReactNode, params: any }) {
return (
<>
<SessionProvider>
<div className='flex'>
<LeftMenu/>
<div className='flex w-full'>
{children}
</div>
</div>
<AdminAuthorization authorizationMode="page">
<div className='flex'>
<LeftMenu />
<div className='flex w-full'>
{children}
</div>
</div>
</AdminAuthorization>
</SessionProvider>
</>
)

View file

@ -4,6 +4,7 @@ import learnhousetextlogo from '../../../../public/learnhouse_logo.png'
import learnhouseiconlogo from '../../../../public/learnhouse_bigicon.png'
import { BookCopy, School, Settings, Users } from 'lucide-react'
import Link from 'next/link'
import AdminAuthorization from '@components/Security/AdminAuthorization'
function DashboardHome() {
return (
@ -11,39 +12,44 @@ function DashboardHome() {
<div className='mx-auto pb-10'>
<Image alt='learnhouse logo' width={230} src={learnhousetextlogo}></Image>
</div>
<div className='flex space-x-10'>
<Link href={`/dash/courses`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
<div className='flex flex-col mx-auto space-y-2'>
<BookCopy className='mx-auto text-gray-500' size={50}></BookCopy>
<div className='text-center font-bold text-gray-500'>Courses</div>
<p className='text-center text-sm text-gray-400'>Create and manage courses, chapters and ativities </p>
</div>
</Link>
<Link href={`/dash/org/settings/general`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
<div className='flex flex-col mx-auto space-y-2'>
<School className='mx-auto text-gray-500' size={50}></School>
<div className='text-center font-bold text-gray-500'>Organization</div>
<p className='text-center text-sm text-gray-400'>Configure your Organization general settings </p>
</div>
</Link>
<Link href={`/dash/users/settings/users`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
<div className='flex flex-col mx-auto space-y-2'>
<Users className='mx-auto text-gray-500' size={50}></Users>
<div className='text-center font-bold text-gray-500'>Users</div>
<p className='text-center text-sm text-gray-400'>Manage your Organization's users, roles </p>
</div>
</Link>
</div>
<div className='flex flex-col space-y-10 '>
<div className='h-1 w-[100px] bg-neutral-200 rounded-full mx-auto'></div>
<div className="flex justify-center items-center">
<Link href={'https://learn.learnhouse.io/'} className='flex mt-[40px] bg-black space-x-2 items-center py-3 px-7 rounded-lg shadow-lg hover:scale-105 transition-all ease-linear cursor-pointer'>
<BookCopy className=' text-gray-100' size={20}></BookCopy>
<div className=' text-sm font-bold text-gray-100'>Learn LearnHouse</div>
<AdminAuthorization authorizationMode="component">
<div className='flex space-x-10'>
<Link href={`/dash/courses`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
<div className='flex flex-col mx-auto space-y-2'>
<BookCopy className='mx-auto text-gray-500' size={50}></BookCopy>
<div className='text-center font-bold text-gray-500'>Courses</div>
<p className='text-center text-sm text-gray-400'>Create and manage courses, chapters and ativities </p>
</div>
</Link>
<Link href={`/dash/org/settings/general`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
<div className='flex flex-col mx-auto space-y-2'>
<School className='mx-auto text-gray-500' size={50}></School>
<div className='text-center font-bold text-gray-500'>Organization</div>
<p className='text-center text-sm text-gray-400'>Configure your Organization general settings </p>
</div>
</Link>
<Link href={`/dash/users/settings/users`} className='flex bg-white shadow-lg p-[35px] w-[250px] rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
<div className='flex flex-col mx-auto space-y-2'>
<Users className='mx-auto text-gray-500' size={50}></Users>
<div className='text-center font-bold text-gray-500'>Users</div>
<p className='text-center text-sm text-gray-400'>Manage your Organization's users, roles </p>
</div>
</Link>
</div>
<div className='mx-auto mt-[40px] w-28 h-1 bg-neutral-200 rounded-full'></div>
</AdminAuthorization>
<div className='flex flex-col space-y-10 '>
<AdminAuthorization authorizationMode="component">
<div className='h-1 w-[100px] bg-neutral-200 rounded-full mx-auto'></div>
<div className="flex justify-center items-center">
<Link href={'https://learn.learnhouse.io/'} className='flex mt-[40px] bg-black space-x-2 items-center py-3 px-7 rounded-lg shadow-lg hover:scale-105 transition-all ease-linear cursor-pointer'>
<BookCopy className=' text-gray-100' size={20}></BookCopy>
<div className=' text-sm font-bold text-gray-100'>Learn LearnHouse</div>
</Link>
</div>
<div className='mx-auto mt-[40px] w-28 h-1 bg-neutral-200 rounded-full'></div>
</AdminAuthorization>
<Link href={'/dash/user-account/settings/general'} className='flex bg-white shadow-lg p-[15px] items-center rounded-lg items-center mx-auto hover:scale-105 transition-all ease-linear cursor-pointer'>
<div className='flex flex-row mx-auto space-x-3 items-center'>
<Settings className=' text-gray-500' size={20}></Settings>

View file

@ -1,13 +1,9 @@
'use client';
import { getNewAccessTokenUsingRefreshToken, getUserSession } from '@services/auth/auth';
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import React, { useContext, createContext, useEffect } from 'react'
import { useOrg } from './OrgContext';
export const SessionContext = createContext({}) as any;
const PATHS_THAT_REQUIRE_AUTH = ['/dash'];
type Session = {
access_token: string;
user: any;
@ -18,10 +14,6 @@ type Session = {
function SessionProvider({ children }: { children: React.ReactNode }) {
const [session, setSession] = React.useState<Session>({ access_token: "", user: {}, roles: {}, isLoading: true, isAuthenticated: false });
const org = useOrg() as any;
const pathname = usePathname()
const router = useRouter()
async function getNewAccessTokenUsingRefreshTokenUI() {
let data = await getNewAccessTokenUsingRefreshToken();
@ -39,6 +31,10 @@ function SessionProvider({ children }: { children: React.ReactNode }) {
// Set session
setSession({ access_token: access_token, user: user_session.user, roles: user_session.roles, isLoading: false, isAuthenticated: true });
}
if (!access_token) {
setSession({ access_token: "", user: {}, roles: {}, isLoading: false, isAuthenticated: false });
}
}
@ -47,8 +43,6 @@ function SessionProvider({ children }: { children: React.ReactNode }) {
// Check session
checkSession();
}, [])
return (

View file

@ -8,8 +8,9 @@ import { ArrowLeft, Book, BookCopy, Home, LogOut, School, Settings, Users } from
import Image from 'next/image';
import Link from 'next/link'
import { useRouter } from 'next/navigation';
import React, { use, useEffect } from 'react'
import React, { useEffect } from 'react'
import UserAvatar from '../../Objects/UserAvatar';
import AdminAuthorization from '@components/Security/AdminAuthorization';
function LeftMenu() {
const org = useOrg() as any;
@ -42,7 +43,7 @@ function LeftMenu() {
return (
<div
style={{ background: "linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%), radial-gradient(271.56% 105.16% at 50% -5.16%, rgba(255, 255, 255, 0.18) 0%, rgba(0, 0, 0, 0) 100%), rgb(20 19 19)"}}
style={{ background: "linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%), radial-gradient(271.56% 105.16% at 50% -5.16%, rgba(255, 255, 255, 0.18) 0%, rgba(0, 0, 0, 0) 100%), rgb(20 19 19)" }}
className='flex flex-col w-[90px] bg-black h-screen text-white shadow-xl'>
<div className='flex flex-col h-full'>
<div className='flex h-20 mt-6'>
@ -59,26 +60,28 @@ function LeftMenu() {
{/* <ToolTip content={"Back to " + org?.name + "'s Home"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white text-black hover:text-white rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/`} ><ArrowLeft className='hover:text-white' size={18} /></Link>
</ToolTip> */}
<ToolTip content={"Home"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash`} ><Home size={18} /></Link>
</ToolTip>
<ToolTip content={"Courses"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/courses`} ><BookCopy size={18} /></Link>
</ToolTip>
<ToolTip content={"Users"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/users/settings/users`} ><Users size={18} /></Link>
</ToolTip>
<ToolTip content={"Organization"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/org/settings/general`} ><School size={18} /></Link>
</ToolTip>
<AdminAuthorization authorizationMode="component">
<ToolTip content={"Home"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash`} ><Home size={18} /></Link>
</ToolTip>
<ToolTip content={"Courses"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/courses`} ><BookCopy size={18} /></Link>
</ToolTip>
<ToolTip content={"Users"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/users/settings/users`} ><Users size={18} /></Link>
</ToolTip>
<ToolTip content={"Organization"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/org/settings/general`} ><School size={18} /></Link>
</ToolTip>
</AdminAuthorization>
</div>
<div className='flex flex-col mx-auto pb-7 space-y-2'>
<div className="flex items-center flex-col space-y-2">
<ToolTip content={'@'+session.user.username} slateBlack sideOffset={8} side='right' >
<ToolTip content={'@' + session.user.username} slateBlack sideOffset={8} side='right' >
<div className='mx-auto'>
<UserAvatar border='border-4' width={35} />
</div>
</div>
</ToolTip>
<div className='flex items-center flex-col space-y-1'>
<ToolTip content={session.user.username + "'s Settings"} slateBlack sideOffset={8} side='right' >

View file

@ -0,0 +1,139 @@
'use client';
import { useOrg } from '@components/Contexts/OrgContext';
import { useSession } from '@components/Contexts/SessionContext';
import { usePathname, useRouter } from 'next/navigation';
import React from 'react'
type AuthorizationProps = {
children: React.ReactNode;
// Authorize components rendering or page rendering
authorizationMode: 'component' | 'page';
}
const ADMIN_PATHS = [
'/dash/org/*',
'/dash/org',
'/dash/users/*',
'/dash/users',
'/dash/courses/*',
'/dash/courses',
'/dash/org/settings/general',
]
function AdminAuthorization(props: AuthorizationProps) {
const session = useSession() as any;
const org = useOrg() as any;
const pathname = usePathname();
const router = useRouter();
// States
const [isLoading, setIsLoading] = React.useState(true);
const [isAuthorized, setIsAuthorized] = React.useState(false);
// Verify if the user is authenticated
const isUserAuthenticated = () => {
if (session.isAuthenticated === true) {
return true;
}
else {
return false;
}
}
// Verify if the user is an Admin (1), Maintainer (2) or Member (3) of the organization
const isUserAdmin = () => {
const isAdmin = session.roles.some((role: any) => {
return (
role.org.id === org.id &&
(role.role.id === 1 ||
role.role.id === 2 ||
role.role.role_uuid === 'role_global_admin' ||
role.role.role_uuid === 'role_global_maintainer'
)
);
});
return isAdmin;
};
function checkPathname(pattern: string, pathname: string) {
// Escape special characters in the pattern and replace '*' with a regex pattern
const regexPattern = new RegExp(`^${pattern.replace(/\//g, '\\/').replace(/\*/g, '.*')}$`);
// Test if the pathname matches the regex pattern
const isMatch = regexPattern.test(pathname);
return isMatch;
}
const Authorize = () => {
if (props.authorizationMode === 'page') {
// Check if user is in an admin path
if (ADMIN_PATHS.some((path) => checkPathname(path, pathname))) {
console.log('Admin path')
if (isUserAuthenticated()) {
// Check if the user is an Admin
if (isUserAdmin()) {
setIsAuthorized(true);
}
else {
setIsAuthorized(false);
router.push('/dash');
}
}
else {
router.push('/login');
}
}
else {
if (isUserAuthenticated()) {
setIsAuthorized(true);
}
else {
setIsAuthorized(false);
router.push('/login');
}
}
}
if (props.authorizationMode === 'component') {
// Component mode
if (isUserAuthenticated() && isUserAdmin()) {
setIsAuthorized(true);
}
else {
setIsAuthorized(false);
}
}
}
React.useEffect(() => {
if (session.isLoading) {
return;
}
Authorize();
setIsLoading(false);
}, [session, org, pathname])
return (
<>
{props.authorizationMode === 'component' && isAuthorized === true && props.children}
{props.authorizationMode === 'page' && isAuthorized === true && !isLoading && props.children}
{props.authorizationMode === 'page' && isAuthorized === false && !isLoading &&
<div className='flex justify-center items-center h-screen'>
<h1 className='text-2xl'>You are not authorized to access this page</h1>
</div>
}
</>
)
}
export default AdminAuthorization

View file

@ -1,8 +1,5 @@
'use client';
import React from "react";
import useSWR, { mutate } from "swr";
import { getAPIUrl } from "@services/config/config";
import { swrFetcher } from "@services/utils/ts/requests";
import { useSession } from "@components/Contexts/SessionContext";
import { useOrg } from "@components/Contexts/OrgContext";

View file

@ -1,5 +1,5 @@
'use client';
import React, { use, useEffect } from "react";
import React, { useEffect } from "react";
import styled from "styled-components";
import Link from "next/link";
import Avvvatars from "avvvatars-react";