diff --git a/apps/api/src/db/users.py b/apps/api/src/db/users.py index 79ee9788..95f263f2 100644 --- a/apps/api/src/db/users.py +++ b/apps/api/src/db/users.py @@ -1,6 +1,10 @@ from typing import Optional +from pydantic import BaseModel from sqlmodel import Field, SQLModel +from src.db.roles import RoleRead +from src.db.organizations import OrganizationRead + class UserBase(SQLModel): username: str @@ -33,14 +37,27 @@ class UserRead(UserBase): id: int user_uuid: str + class PublicUser(UserRead): pass + +class UserRoleWithOrg(BaseModel): + role: RoleRead + org: OrganizationRead + + +class UserSession(BaseModel): + user: UserRead + roles: list[UserRoleWithOrg] + + class AnonymousUser(SQLModel): id: int = 0 user_uuid: str = "user_anonymous" username: str = "anonymous" + class User(UserBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) password: str = "" diff --git a/apps/api/src/routers/users.py b/apps/api/src/routers/users.py index 71ec2741..808f77b5 100644 --- a/apps/api/src/routers/users.py +++ b/apps/api/src/routers/users.py @@ -9,6 +9,7 @@ from src.db.users import ( User, UserCreate, UserRead, + UserSession, UserUpdate, UserUpdatePassword, ) @@ -17,6 +18,7 @@ from src.services.users.users import ( create_user, create_user_without_org, delete_user_by_id, + get_user_session, read_user_by_id, read_user_by_uuid, update_user, @@ -35,6 +37,18 @@ async def api_get_current_user(current_user: User = Depends(get_current_user)): return current_user.dict() +@router.get("/session") +async def api_get_current_user_session( + request: Request, + db_session: Session = Depends(get_db_session), + current_user: PublicUser = Depends(get_current_user), +) -> UserSession: + """ + Get current user + """ + return await get_user_session(request, db_session, current_user) + + @router.get("/authorize/ressource/{ressource_uuid}/action/{action}") async def api_get_authorization_status( request: Request, diff --git a/apps/api/src/security/rbac/rbac.py b/apps/api/src/security/rbac/rbac.py index 34098744..6aec4117 100644 --- a/apps/api/src/security/rbac/rbac.py +++ b/apps/api/src/security/rbac/rbac.py @@ -23,7 +23,7 @@ async def authorization_verify_if_element_is_public( if element_nature == "courses": print("looking for course") statement = select(Course).where( - Course.public == True, Course.course_uuid == element_uuid + Course.public is True, Course.course_uuid == element_uuid ) course = db_session.exec(statement).first() if course: @@ -33,7 +33,7 @@ async def authorization_verify_if_element_is_public( if element_nature == "collections": statement = select(Collection).where( - Collection.public == True, Collection.collection_uuid == element_uuid + Collection.public is True, Collection.collection_uuid == element_uuid ) collection = db_session.exec(statement).first() diff --git a/apps/api/src/services/courses/courses.py b/apps/api/src/services/courses/courses.py index df69ce75..72d78d01 100644 --- a/apps/api/src/services/courses/courses.py +++ b/apps/api/src/services/courses/courses.py @@ -327,7 +327,7 @@ async def get_courses_orgslug( statement_public = ( select(Course) .join(Organization) - .where(Organization.slug == org_slug, Course.public == True) + .where(Organization.slug == org_slug, Course.public is True) ) statement_all = ( select(Course).join(Organization).where(Organization.slug == org_slug) diff --git a/apps/api/src/services/users/users.py b/apps/api/src/services/users/users.py index 4a9c5ee6..cc6fbca5 100644 --- a/apps/api/src/services/users/users.py +++ b/apps/api/src/services/users/users.py @@ -3,17 +3,20 @@ from typing import Literal from uuid import uuid4 from fastapi import HTTPException, Request, status from sqlmodel import Session, select +from src.db.roles import Role, RoleRead from src.security.rbac.rbac import ( authorization_verify_based_on_roles_and_authorship, authorization_verify_if_user_is_anon, ) -from src.db.organizations import Organization +from src.db.organizations import Organization, OrganizationRead from src.db.users import ( AnonymousUser, PublicUser, User, UserCreate, UserRead, + UserRoleWithOrg, + UserSession, UserUpdate, UserUpdatePassword, ) @@ -279,6 +282,57 @@ async def read_user_by_uuid( return user +async def get_user_session( + request: Request, + db_session: Session, + current_user: PublicUser | AnonymousUser, +) -> UserSession: + # Get user + statement = select(User).where(User.user_uuid == current_user.user_uuid) + user = db_session.exec(statement).first() + + if not user: + raise HTTPException( + status_code=400, + detail="User does not exist", + ) + + user = UserRead.from_orm(user) + + # Get roles and orgs + statement = ( + select(UserOrganization) + .where(UserOrganization.user_id == user.id) + .join(Organization) + ) + user_organizations = db_session.exec(statement).all() + + roles = [] + + for user_organization in user_organizations: + role_statement = select(Role).where(Role.id == user_organization.role_id) + role = db_session.exec(role_statement).first() + + org_statement = select(Organization).where( + Organization.id == user_organization.org_id + ) + org = db_session.exec(org_statement).first() + + roles.append( + UserRoleWithOrg( + role=RoleRead.from_orm(role), + org=OrganizationRead.from_orm(org), + ) + ) + + user_session = UserSession( + user=user, + roles=roles, + ) + + return user_session + + async def authorize_user_action( request: Request, db_session: Session, diff --git a/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx b/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx index 55948525..06483373 100644 --- a/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx +++ b/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx @@ -1,5 +1,4 @@ import { default as React, } from "react"; -import AuthProvider from "@components/Security/AuthProviderDepreceated"; import EditorWrapper from "@components/Objects/Editor/EditorWrapper"; import { getCourseMetadataWithAuthHeader } from "@services/courses/courses"; import { cookies } from "next/headers"; @@ -7,6 +6,7 @@ import { Metadata } from "next"; import { getActivityWithAuthHeader } from "@services/courses/activities"; import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth"; import { getOrganizationContextInfo, getOrganizationContextInfoWithId } from "@services/organizations/orgs"; +import SessionProvider from "@components/Contexts/SessionContext"; type MetadataProps = { params: { orgslug: string, courseid: string, activityid: string }; @@ -35,13 +35,13 @@ const EditActivity = async (params: any) => { const courseInfo = await getCourseMetadataWithAuthHeader(courseid, { revalidate: 0, tags: ['courses'] }, access_token ? access_token : null) const activity = await getActivityWithAuthHeader(activityuuid, { revalidate: 0, tags: ['activities'] }, access_token ? access_token : null) const org = await getOrganizationContextInfoWithId(courseInfo.org_id, { revalidate: 1800, tags: ['organizations'] }); - console.log('courseInfo', courseInfo ) + console.log('courseInfo', courseInfo) return (
- + - +
); } diff --git a/apps/web/app/organizations/page.tsx b/apps/web/app/organizations/page.tsx index 009c0b19..00120e03 100644 --- a/apps/web/app/organizations/page.tsx +++ b/apps/web/app/organizations/page.tsx @@ -5,7 +5,6 @@ import { deleteOrganizationFromBackend } from "@services/organizations/orgs"; import useSWR, { mutate } from "swr"; import { swrFetcher } from "@services/utils/ts/requests"; import { getAPIUrl, getUriWithOrg } from "@services/config/config"; -import AuthProvider from "@components/Security/AuthProviderDepreceated"; const Organizations = () => { const { data: organizations, error } = useSWR(`${getAPIUrl()}orgs/user/page/1/limit/10`, swrFetcher) @@ -17,7 +16,6 @@ const Organizations = () => { return ( <> -
Your Organizations{" "} diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/collections/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/collections/page.tsx index ee4890b7..7a65ca2c 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/collections/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/collections/page.tsx @@ -57,7 +57,7 @@ const CollectionsPage = async (params: any) => {
@@ -85,7 +85,7 @@ const CollectionsPage = async (params: any) => {

Create a collection to group courses together

diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx index 5c1348ba..0cab8203 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx @@ -34,7 +34,7 @@ function Courses(props: CourseProps) { - + {children} - + ); } diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx index aaed6f07..999456ec 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx @@ -69,7 +69,7 @@ const OrgHomePage = async (params: any) => {
@@ -110,7 +110,7 @@ const OrgHomePage = async (params: any) => { diff --git a/apps/web/app/orgs/[orgslug]/dash/courses/client.tsx b/apps/web/app/orgs/[orgslug]/dash/courses/client.tsx index 541368fa..9a290f15 100644 --- a/apps/web/app/orgs/[orgslug]/dash/courses/client.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/courses/client.tsx @@ -37,7 +37,7 @@ function CoursesHome(params: CourseProps) {
Courses
- +
{children}
-
+ ) } diff --git a/apps/web/app/orgs/[orgslug]/dash/user/settings/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/user/settings/[subpage]/page.tsx index 56de4bcf..575b235f 100644 --- a/apps/web/app/orgs/[orgslug]/dash/user/settings/[subpage]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/user/settings/[subpage]/page.tsx @@ -7,7 +7,7 @@ import Link from 'next/link'; import { getUriWithOrg } from '@services/config/config'; import { Info, Lock } from 'lucide-react'; import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'; -import { useAuth } from '@components/Security/AuthContext'; +import { useSession } from '@components/Contexts/SessionContext'; export type SettingsParams = { subpage: string @@ -15,17 +15,17 @@ export type SettingsParams = { } function SettingsPage({ params }: { params: SettingsParams }) { - const auth = useAuth() as any; + const session = useSession() as any; useEffect(() => { } - , [auth]) + , [session]) return (
- +
Account Settings
diff --git a/apps/web/app/orgs/[orgslug]/layout.tsx b/apps/web/app/orgs/[orgslug]/layout.tsx index ce0f624f..e9b5b4a9 100644 --- a/apps/web/app/orgs/[orgslug]/layout.tsx +++ b/apps/web/app/orgs/[orgslug]/layout.tsx @@ -1,20 +1,17 @@ 'use client'; import { OrgProvider } from "@components/Contexts/OrgContext"; -import AuthProvider from "@components/Security/AuthContext"; -import { getAPIUrl } from "@services/config/config"; -import { swrFetcher } from "@services/utils/ts/requests"; +import SessionProvider from "@components/Contexts/SessionContext"; import "@styles/globals.css"; -import useSWR from "swr"; export default function RootLayout({ children, params }: { children: React.ReactNode, params: any }) { return (
- - + + {children} - - + +
); } diff --git a/apps/web/components/Contexts/SessionContext.tsx b/apps/web/components/Contexts/SessionContext.tsx new file mode 100644 index 00000000..7094b94e --- /dev/null +++ b/apps/web/components/Contexts/SessionContext.tsx @@ -0,0 +1,65 @@ +'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; + roles: any; + isLoading: boolean; + isAuthenticated: boolean; +} + +function SessionProvider({ children }: { children: React.ReactNode }) { + const [session, setSession] = React.useState({ 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(); + return data.access_token; + } + + async function checkSession() { + // Get new access token using refresh token + const access_token = await getNewAccessTokenUsingRefreshTokenUI(); + + if (access_token) { + // Get user session info + const user_session = await getUserSession(access_token); + + // Set session + setSession({ access_token: access_token, user: user_session.user, roles: user_session.roles, isLoading: false, isAuthenticated: true }); + } + } + + + + useEffect(() => { + // Check session + checkSession(); + + + + }, []) + + return ( + + {children} + + ) +} + +export function useSession() { + return useContext(SessionContext); +} + +export default SessionProvider \ No newline at end of file diff --git a/apps/web/components/Dashboard/UI/LeftMenu.tsx b/apps/web/components/Dashboard/UI/LeftMenu.tsx index 1a4d2ca4..06e50fa6 100644 --- a/apps/web/components/Dashboard/UI/LeftMenu.tsx +++ b/apps/web/components/Dashboard/UI/LeftMenu.tsx @@ -1,6 +1,6 @@ 'use client'; import { useOrg } from '@components/Contexts/OrgContext'; -import { useAuth } from '@components/Security/AuthContext'; +import { useSession } from '@components/Contexts/SessionContext'; import ToolTip from '@components/StyledElements/Tooltip/Tooltip' import LearnHouseDashboardLogo from '@public/dashLogo.png'; import Avvvatars from 'avvvatars-react'; @@ -11,11 +11,11 @@ import React, { use, useEffect } from 'react' function LeftMenu() { const org = useOrg() as any; - const auth = useAuth() as any; + const session = useSession() as any; const [loading, setLoading] = React.useState(true); function waitForEverythingToLoad() { - if (org && auth) { + if (org && session) { return true; } return false; @@ -59,15 +59,15 @@ function LeftMenu() {
- +
- +
- +
diff --git a/apps/web/components/Dashboard/User/UserEditGeneral/UserEditGeneral.tsx b/apps/web/components/Dashboard/User/UserEditGeneral/UserEditGeneral.tsx index edb12d11..165e34d7 100644 --- a/apps/web/components/Dashboard/User/UserEditGeneral/UserEditGeneral.tsx +++ b/apps/web/components/Dashboard/User/UserEditGeneral/UserEditGeneral.tsx @@ -1,33 +1,33 @@ -import { useAuth } from '@components/Security/AuthContext'; import { updateProfile } from '@services/settings/profile'; import React, { useEffect } from 'react' import { Formik, Form, Field, ErrorMessage } from 'formik'; +import { useSession } from '@components/Contexts/SessionContext'; function UserEditGeneral() { - const auth = useAuth() as any; + const session = useSession() as any; useEffect(() => { } - , [auth, auth.user]) + , [session, session.user]) return (
- {auth.user && ( + {session.user && ( { setTimeout(() => { setSubmitting(false); - updateProfile(values,auth.user.id) + updateProfile(values,session.user.id) }, 400); }} > diff --git a/apps/web/components/Dashboard/User/UserEditPassword/UserEditPassword.tsx b/apps/web/components/Dashboard/User/UserEditPassword/UserEditPassword.tsx index f11a962b..996aa53c 100644 --- a/apps/web/components/Dashboard/User/UserEditPassword/UserEditPassword.tsx +++ b/apps/web/components/Dashboard/User/UserEditPassword/UserEditPassword.tsx @@ -1,19 +1,19 @@ -import { useAuth } from '@components/Security/AuthContext'; +import { useSession } from '@components/Contexts/SessionContext'; import { updatePassword } from '@services/settings/password'; import { Formik, Form, Field, ErrorMessage } from 'formik'; import React, { useEffect } from 'react' function UserEditPassword() { - const auth = useAuth() as any; + const session = useSession() as any; const updatePasswordUI = async (values: any) => { - let user_id = auth.userInfo.user_object.user_id; + let user_id = session.user.user_id; await updatePassword(user_id, values) } useEffect(() => { } - , [auth]) + , [session]) return ( diff --git a/apps/web/components/Objects/Editor/Editor.tsx b/apps/web/components/Objects/Editor/Editor.tsx index 4db1cc14..5d9028dd 100644 --- a/apps/web/components/Objects/Editor/Editor.tsx +++ b/apps/web/components/Objects/Editor/Editor.tsx @@ -2,7 +2,6 @@ import React from "react"; import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; -import { AuthContext } from "../../Security/AuthProviderDepreceated"; import learnhouseIcon from "public/learnhouse_icon.png"; import { ToolbarButtons } from "./Toolbar/ToolbarButtons"; import { motion } from "framer-motion"; @@ -38,6 +37,7 @@ import python from 'highlight.js/lib/languages/python' import java from 'highlight.js/lib/languages/java' import { CourseProvider } from "@components/Contexts/CourseContext"; import { OrgProvider } from "@components/Contexts/OrgContext"; +import { useSession } from "@components/Contexts/SessionContext"; interface Editor { @@ -51,7 +51,7 @@ interface Editor { } function Editor(props: Editor) { - const auth: any = React.useContext(AuthContext); + const session = useSession() as any; // remove course_ from course_uuid const course_uuid = props.course.course_uuid.substring(7); @@ -164,8 +164,8 @@ function Editor(props: Editor) { - {!auth.isAuthenticated && Loading} - {auth.isAuthenticated && } + {!session.isAuthenticated && Loading} + {session.isAuthenticated && } diff --git a/apps/web/components/Objects/Thumbnails/CollectionThumbnail.tsx b/apps/web/components/Objects/Thumbnails/CollectionThumbnail.tsx index 242d935b..e669654f 100644 --- a/apps/web/components/Objects/Thumbnails/CollectionThumbnail.tsx +++ b/apps/web/components/Objects/Thumbnails/CollectionThumbnail.tsx @@ -58,7 +58,7 @@ const CollectionAdminEditsArea = (props: any) => { return (
diff --git a/apps/web/components/Security/AuthContext.tsx b/apps/web/components/Security/AuthContext.tsx deleted file mode 100644 index a1742832..00000000 --- a/apps/web/components/Security/AuthContext.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { getNewAccessTokenUsingRefreshToken, getUserInfo } from '@services/auth/auth'; -import { getAPIUrl } from '@services/config/config'; -import { swrFetcher } from '@services/utils/ts/requests'; -import React, { useEffect } from 'react' -import useSWR from 'swr'; - -const AuthContext = React.createContext({}) - -type Auth = { - access_token: string; - isAuthenticated: boolean; - user: any; -} - -function AuthProvider({ children, orgslug }: { children: React.ReactNode, orgslug: string }) { - const [auth, setAuth] = React.useState({ access_token: "", isAuthenticated: false, user: {} }); - - async function checkRefreshToken() { - //deleteCookie("access_token_cookie"); - let data = await getNewAccessTokenUsingRefreshToken(); - if (data) { - return data.access_token; - } - } - - async function checkAuth() { - try { - let access_token = await checkRefreshToken(); - let userInfo = {}; - - if (access_token) { - userInfo = await getUserInfo(access_token); - setAuth({ access_token: access_token, isAuthenticated: true, user: userInfo }); - - } else { - setAuth({ access_token: access_token, isAuthenticated: false, user: userInfo }); - } - } catch (error) { - console.log(error); - } - } - - useEffect(() => { - checkAuth(); - }, []) - - return ( - - {children} - - ) -} - -export function useAuth() { - return React.useContext(AuthContext); -} - -export default AuthProvider \ No newline at end of file diff --git a/apps/web/components/Security/AuthProviderDepreceated.tsx b/apps/web/components/Security/AuthProviderDepreceated.tsx deleted file mode 100644 index a2beecfa..00000000 --- a/apps/web/components/Security/AuthProviderDepreceated.tsx +++ /dev/null @@ -1,79 +0,0 @@ -"use client"; -import React, { useEffect } from "react"; -import { getNewAccessTokenUsingRefreshToken, getUserInfo } from "../../services/auth/auth"; -import { useRouter, usePathname } from "next/navigation"; - -export const AuthContext: any = React.createContext({}); - -const PRIVATE_ROUTES = ["/course/*/edit", "/settings*", "/trail"]; -const NON_AUTHENTICATED_ROUTES = ["/login", "/register"]; - -export interface Auth { - access_token: string; - isAuthenticated: boolean; - userInfo: {}; - isLoading: boolean; -} - -const AuthProvider = ({ children }: any) => { - const router = useRouter(); - const pathname = usePathname(); - - const [auth, setAuth] = React.useState({ access_token: "", isAuthenticated: false, userInfo: {}, isLoading: true }); - - function deleteCookie(cookieName: string) { - document.cookie = cookieName + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; - } - - - async function checkRefreshToken() { - //deleteCookie("access_token_cookie"); - let data = await getNewAccessTokenUsingRefreshToken(); - if (data) { - return data.access_token; - } - } - - - async function checkAuth() { - try { - let access_token = await checkRefreshToken(); - let userInfo = {}; - let isLoading = false; - - if (access_token) { - userInfo = await getUserInfo(access_token); - setAuth({ access_token, isAuthenticated: true, userInfo, isLoading }); - - // Redirect to home if user is trying to access a NON_AUTHENTICATED_ROUTES route - - if (NON_AUTHENTICATED_ROUTES.some((route) => new RegExp(`^${route.replace("*", ".*")}$`).test(pathname))) { - router.push("/"); - } - - - } else { - setAuth({ access_token, isAuthenticated: false, userInfo, isLoading }); - - // Redirect to login if user is trying to access a private route - if (PRIVATE_ROUTES.some((route) => new RegExp(`^${route.replace("*", ".*")}$`).test(pathname))) { - router.push("/login"); - } - - } - } catch (error) { - console.log(error); - } - } - - useEffect(() => { - checkAuth(); - return () => { - auth.isLoading = false; - }; - }, [pathname]); - - return {children}; -}; - -export default AuthProvider; diff --git a/apps/web/components/Security/AuthenticatedClientElement.tsx b/apps/web/components/Security/AuthenticatedClientElement.tsx index 693aafec..a02ead11 100644 --- a/apps/web/components/Security/AuthenticatedClientElement.tsx +++ b/apps/web/components/Security/AuthenticatedClientElement.tsx @@ -1,49 +1,73 @@ 'use client'; import React from "react"; -import { AuthContext } from "./AuthProviderDepreceated"; 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"; interface AuthenticatedClientElementProps { children: React.ReactNode; checkMethod: 'authentication' | 'roles'; orgId?: string; - ressourceType?: 'collection' | 'course' | 'activity' | 'user' | 'organization'; + ressourceType?: 'collections' | 'courses' | 'activities' | 'users' | 'organizations'; action?: 'create' | 'update' | 'delete' | 'read'; } -function generateRessourceId(ressourceType: string) { - // for every type of ressource, we need to generate a ressource id, example for a collection: col_XXXXX - if (ressourceType == 'collection') { - return `collection_xxxx` - } - else if (ressourceType == 'course') { - return `course_xxxx` - } - else if (ressourceType == 'activity') { - return `activity_xxxx` - } - else if (ressourceType == 'user') { - return `user_xxxx` - } - else if (ressourceType == 'organization') { - return `org_xxxx` - } - else if (ressourceType === null) { - return `n/a` - } -} + export const AuthenticatedClientElement = (props: AuthenticatedClientElementProps) => { - const auth: any = React.useContext(AuthContext); - const { data: authorization_status, error: error } = useSWR(props.checkMethod == 'roles' && props.ressourceType ? `${getAPIUrl()}users/authorize/ressource/${generateRessourceId(props.ressourceType)}/action/${props.action}` : null, swrFetcher); - console.log(authorization_status); + const [isAllowed, setIsAllowed] = React.useState(false); + const session = useSession() as any; + const org = useOrg() as any; + - if ((props.checkMethod == 'authentication' && auth.isAuthenticated) || (auth.isAuthenticated && props.checkMethod == 'roles' && authorization_status)) { - return <>{props.children}; + function isUserAllowed(roles: any[], action: string, resourceType: string, org_uuid: string): boolean { + // Iterate over the user's roles + for (const role of roles) { + + // Check if the role is for the right organization + if (role.org.org_uuid === org_uuid) { + // Check if the user has the role for the resource type + if (role.role.rights && role.role.rights[resourceType]) { + + + // Check if the user is allowed to execute the action + const actionKey = `action_${action}`; + if (role.role.rights[resourceType][actionKey] === true) { + return true; + } + } + } + } + + // If no role matches the organization, resource type, and action, return false + return false; } - return <>; + + function check() { + + if (props.checkMethod === 'authentication') { + setIsAllowed(session.isAuthenticated); + } else if (props.checkMethod === 'roles') { + return setIsAllowed(isUserAllowed(session.roles, props.action!, props.ressourceType!, org.org_uuid)); + } + + } + + React.useEffect(() => { + if (session.isLoading) { + return; + } + + check(); + }, [session, org]) + + return ( + <> + {isAllowed && props.children} + + ) } diff --git a/apps/web/components/Security/HeaderProfileBox.tsx b/apps/web/components/Security/HeaderProfileBox.tsx index 2e4dc7b6..5d1e5464 100644 --- a/apps/web/components/Security/HeaderProfileBox.tsx +++ b/apps/web/components/Security/HeaderProfileBox.tsx @@ -1,18 +1,18 @@ 'use client'; -import React from "react"; +import React, { use, useEffect } from "react"; import styled from "styled-components"; import Link from "next/link"; -import { AuthContext } from "./AuthProviderDepreceated"; import Avvvatars from "avvvatars-react"; import { GearIcon } from "@radix-ui/react-icons"; import { Settings } from "lucide-react"; +import { useSession } from "@components/Contexts/SessionContext"; export const HeaderProfileBox = () => { - const auth: any = React.useContext(AuthContext); + const session = useSession() as any; return ( - {!auth.isAuthenticated && ( + {!session.isAuthenticated && (
  • @@ -28,13 +28,13 @@ export const HeaderProfileBox = () => {
)} - {auth.isAuthenticated && ( + {session.isAuthenticated && (
-
{auth.userInfo.username}
+
{session.user.username}
- +
diff --git a/apps/web/services/auth/auth.ts b/apps/web/services/auth/auth.ts index 9821f2fd..e01e3951 100644 --- a/apps/web/services/auth/auth.ts +++ b/apps/web/services/auth/auth.ts @@ -45,6 +45,22 @@ export async function getUserInfo(token: string): Promise { .catch((error) => console.log("error", error)); } +export async function getUserSession(token: string): Promise { + const origin = window.location.origin; + const HeadersConfig = new Headers({ Authorization: `Bearer ${token}`, Origin: origin }); + + const requestOptions: any = { + method: "GET", + headers: HeadersConfig, + redirect: "follow", + credentials: "include", + }; + + return fetch(`${getAPIUrl()}users/session`, requestOptions) + .then((result) => result.json()) + .catch((error) => console.log("error", error)); +} + export async function getNewAccessTokenUsingRefreshToken(): Promise { const requestOptions: any = { method: "POST",