mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
fix: admin pages authorization issues
This commit is contained in:
parent
f356fe9f07
commit
e53044e42c
8 changed files with 210 additions and 73 deletions
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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' >
|
||||
|
|
|
|||
139
apps/web/components/Security/AdminAuthorization.tsx
Normal file
139
apps/web/components/Security/AdminAuthorization.tsx
Normal 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
|
||||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue