feat: use new session and auth provider for the frontend

This commit is contained in:
swve 2023-12-26 22:32:08 +01:00
parent d939dc16eb
commit 6aa849b305
27 changed files with 283 additions and 235 deletions

View file

@ -1,6 +1,10 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel
from sqlmodel import Field, SQLModel from sqlmodel import Field, SQLModel
from src.db.roles import RoleRead
from src.db.organizations import OrganizationRead
class UserBase(SQLModel): class UserBase(SQLModel):
username: str username: str
@ -33,14 +37,27 @@ class UserRead(UserBase):
id: int id: int
user_uuid: str user_uuid: str
class PublicUser(UserRead): class PublicUser(UserRead):
pass pass
class UserRoleWithOrg(BaseModel):
role: RoleRead
org: OrganizationRead
class UserSession(BaseModel):
user: UserRead
roles: list[UserRoleWithOrg]
class AnonymousUser(SQLModel): class AnonymousUser(SQLModel):
id: int = 0 id: int = 0
user_uuid: str = "user_anonymous" user_uuid: str = "user_anonymous"
username: str = "anonymous" username: str = "anonymous"
class User(UserBase, table=True): class User(UserBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[int] = Field(default=None, primary_key=True)
password: str = "" password: str = ""

View file

@ -9,6 +9,7 @@ from src.db.users import (
User, User,
UserCreate, UserCreate,
UserRead, UserRead,
UserSession,
UserUpdate, UserUpdate,
UserUpdatePassword, UserUpdatePassword,
) )
@ -17,6 +18,7 @@ from src.services.users.users import (
create_user, create_user,
create_user_without_org, create_user_without_org,
delete_user_by_id, delete_user_by_id,
get_user_session,
read_user_by_id, read_user_by_id,
read_user_by_uuid, read_user_by_uuid,
update_user, update_user,
@ -35,6 +37,18 @@ async def api_get_current_user(current_user: User = Depends(get_current_user)):
return current_user.dict() 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}") @router.get("/authorize/ressource/{ressource_uuid}/action/{action}")
async def api_get_authorization_status( async def api_get_authorization_status(
request: Request, request: Request,

View file

@ -23,7 +23,7 @@ async def authorization_verify_if_element_is_public(
if element_nature == "courses": if element_nature == "courses":
print("looking for course") print("looking for course")
statement = select(Course).where( 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() course = db_session.exec(statement).first()
if course: if course:
@ -33,7 +33,7 @@ async def authorization_verify_if_element_is_public(
if element_nature == "collections": if element_nature == "collections":
statement = select(Collection).where( 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() collection = db_session.exec(statement).first()

View file

@ -327,7 +327,7 @@ async def get_courses_orgslug(
statement_public = ( statement_public = (
select(Course) select(Course)
.join(Organization) .join(Organization)
.where(Organization.slug == org_slug, Course.public == True) .where(Organization.slug == org_slug, Course.public is True)
) )
statement_all = ( statement_all = (
select(Course).join(Organization).where(Organization.slug == org_slug) select(Course).join(Organization).where(Organization.slug == org_slug)

View file

@ -3,17 +3,20 @@ from typing import Literal
from uuid import uuid4 from uuid import uuid4
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from sqlmodel import Session, select from sqlmodel import Session, select
from src.db.roles import Role, RoleRead
from src.security.rbac.rbac import ( from src.security.rbac.rbac import (
authorization_verify_based_on_roles_and_authorship, authorization_verify_based_on_roles_and_authorship,
authorization_verify_if_user_is_anon, authorization_verify_if_user_is_anon,
) )
from src.db.organizations import Organization from src.db.organizations import Organization, OrganizationRead
from src.db.users import ( from src.db.users import (
AnonymousUser, AnonymousUser,
PublicUser, PublicUser,
User, User,
UserCreate, UserCreate,
UserRead, UserRead,
UserRoleWithOrg,
UserSession,
UserUpdate, UserUpdate,
UserUpdatePassword, UserUpdatePassword,
) )
@ -279,6 +282,57 @@ async def read_user_by_uuid(
return user 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( async def authorize_user_action(
request: Request, request: Request,
db_session: Session, db_session: Session,

View file

@ -1,5 +1,4 @@
import { default as React, } from "react"; import { default as React, } from "react";
import AuthProvider from "@components/Security/AuthProviderDepreceated";
import EditorWrapper from "@components/Objects/Editor/EditorWrapper"; import EditorWrapper from "@components/Objects/Editor/EditorWrapper";
import { getCourseMetadataWithAuthHeader } from "@services/courses/courses"; import { getCourseMetadataWithAuthHeader } from "@services/courses/courses";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
@ -7,6 +6,7 @@ import { Metadata } from "next";
import { getActivityWithAuthHeader } from "@services/courses/activities"; import { getActivityWithAuthHeader } from "@services/courses/activities";
import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth"; import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth";
import { getOrganizationContextInfo, getOrganizationContextInfoWithId } from "@services/organizations/orgs"; import { getOrganizationContextInfo, getOrganizationContextInfoWithId } from "@services/organizations/orgs";
import SessionProvider from "@components/Contexts/SessionContext";
type MetadataProps = { type MetadataProps = {
params: { orgslug: string, courseid: string, activityid: string }; 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 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 activity = await getActivityWithAuthHeader(activityuuid, { revalidate: 0, tags: ['activities'] }, access_token ? access_token : null)
const org = await getOrganizationContextInfoWithId(courseInfo.org_id, { revalidate: 1800, tags: ['organizations'] }); const org = await getOrganizationContextInfoWithId(courseInfo.org_id, { revalidate: 1800, tags: ['organizations'] });
console.log('courseInfo', courseInfo ) console.log('courseInfo', courseInfo)
return ( return (
<div> <div>
<AuthProvider> <SessionProvider>
<EditorWrapper org={org} course={courseInfo} activity={activity} content={activity.content}></EditorWrapper> <EditorWrapper org={org} course={courseInfo} activity={activity} content={activity.content}></EditorWrapper>
</AuthProvider> </SessionProvider>
</div> </div>
); );
} }

View file

@ -5,7 +5,6 @@ import { deleteOrganizationFromBackend } from "@services/organizations/orgs";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { swrFetcher } from "@services/utils/ts/requests"; import { swrFetcher } from "@services/utils/ts/requests";
import { getAPIUrl, getUriWithOrg } from "@services/config/config"; import { getAPIUrl, getUriWithOrg } from "@services/config/config";
import AuthProvider from "@components/Security/AuthProviderDepreceated";
const Organizations = () => { const Organizations = () => {
const { data: organizations, error } = useSWR(`${getAPIUrl()}orgs/user/page/1/limit/10`, swrFetcher) const { data: organizations, error } = useSWR(`${getAPIUrl()}orgs/user/page/1/limit/10`, swrFetcher)
@ -17,7 +16,6 @@ const Organizations = () => {
return ( return (
<> <>
<AuthProvider />
<div className="font-bold text-lg"> <div className="font-bold text-lg">
Your Organizations{" "} Your Organizations{" "}
<Link href="/organizations/new"> <Link href="/organizations/new">

View file

@ -57,7 +57,7 @@ const CollectionsPage = async (params: any) => {
<div className="flex justify-between" > <div className="flex justify-between" >
<TypeOfContentTitle title="Collections" type="col" /> <TypeOfContentTitle title="Collections" type="col" />
<AuthenticatedClientElement <AuthenticatedClientElement
ressourceType="collection" ressourceType="collections"
action="create" action="create"
checkMethod='roles' orgId={org_id}> checkMethod='roles' orgId={org_id}>
<Link className="flex justify-center" href={getUriWithOrg(orgslug, "/collections/new")}> <Link className="flex justify-center" href={getUriWithOrg(orgslug, "/collections/new")}>
@ -85,7 +85,7 @@ const CollectionsPage = async (params: any) => {
<p className="text-lg text-gray-400">Create a collection to group courses together</p> <p className="text-lg text-gray-400">Create a collection to group courses together</p>
</div> </div>
<AuthenticatedClientElement checkMethod='roles' <AuthenticatedClientElement checkMethod='roles'
ressourceType="collection" ressourceType="collections"
action="create" action="create"
orgId={org_id}> orgId={org_id}>
<Link href={getUriWithOrg(orgslug, "/collections/new")}> <Link href={getUriWithOrg(orgslug, "/collections/new")}>

View file

@ -34,7 +34,7 @@ function Courses(props: CourseProps) {
<TypeOfContentTitle title="Courses" type="cou" /> <TypeOfContentTitle title="Courses" type="cou" />
<AuthenticatedClientElement checkMethod='roles' <AuthenticatedClientElement checkMethod='roles'
action='create' action='create'
ressourceType='course' ressourceType='courses'
orgId={props.org_id}> orgId={props.org_id}>
<Modal <Modal
isDialogOpen={newCourseModal} isDialogOpen={newCourseModal}
@ -78,7 +78,7 @@ function Courses(props: CourseProps) {
</div> </div>
<AuthenticatedClientElement <AuthenticatedClientElement
action='create' action='create'
ressourceType='course' ressourceType='courses'
checkMethod='roles' orgId={props.org_id}> checkMethod='roles' orgId={props.org_id}>
<Modal <Modal
isDialogOpen={newCourseModal} isDialogOpen={newCourseModal}

View file

@ -1,14 +1,14 @@
import "@styles/globals.css"; import "@styles/globals.css";
import { Menu } from "@components/Objects/Menu/Menu"; import { Menu } from "@components/Objects/Menu/Menu";
import AuthProvider from "@components/Security/AuthProviderDepreceated"; import SessionProvider from "@components/Contexts/SessionContext";
export default function RootLayout({ children, params }: { children: React.ReactNode , params :any}) { export default function RootLayout({ children, params }: { children: React.ReactNode , params :any}) {
return ( return (
<> <>
<AuthProvider> <SessionProvider>
<Menu orgslug={params?.orgslug}></Menu> <Menu orgslug={params?.orgslug}></Menu>
{children} {children}
</AuthProvider> </SessionProvider>
</> </>
); );
} }

View file

@ -69,7 +69,7 @@ const OrgHomePage = async (params: any) => {
</div> </div>
<AuthenticatedClientElement <AuthenticatedClientElement
checkMethod='roles' checkMethod='roles'
ressourceType='collection' ressourceType='collections'
action='create' action='create'
orgId={org_id}> orgId={org_id}>
<Link href={getUriWithOrg(orgslug, "/collections/new")}> <Link href={getUriWithOrg(orgslug, "/collections/new")}>
@ -110,7 +110,7 @@ const OrgHomePage = async (params: any) => {
<TypeOfContentTitle title="Courses" type="cou" /> <TypeOfContentTitle title="Courses" type="cou" />
</div> </div>
<AuthenticatedClientElement <AuthenticatedClientElement
ressourceType='course' ressourceType='courses'
action='create' action='create'
checkMethod='roles' checkMethod='roles'
orgId={org_id}> orgId={org_id}>

View file

@ -37,7 +37,7 @@ function CoursesHome(params: CourseProps) {
<div className='pt-3 flex font-bold text-4xl'>Courses</div> <div className='pt-3 flex font-bold text-4xl'>Courses</div>
<AuthenticatedClientElement checkMethod='roles' <AuthenticatedClientElement checkMethod='roles'
action='create' action='create'
ressourceType='course' ressourceType='courses'
orgId={params.org_id}> orgId={params.org_id}>
<Modal <Modal
isDialogOpen={newCourseModal} isDialogOpen={newCourseModal}
@ -80,7 +80,7 @@ function CoursesHome(params: CourseProps) {
</div> </div>
<AuthenticatedClientElement <AuthenticatedClientElement
action='create' action='create'
ressourceType='course' ressourceType='courses'
checkMethod='roles' orgId={params.org_id}> checkMethod='roles' orgId={params.org_id}>
<Modal <Modal
isDialogOpen={newCourseModal} isDialogOpen={newCourseModal}

View file

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

View file

@ -7,7 +7,7 @@ import Link from 'next/link';
import { getUriWithOrg } from '@services/config/config'; import { getUriWithOrg } from '@services/config/config';
import { Info, Lock } from 'lucide-react'; import { Info, Lock } from 'lucide-react';
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'; import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs';
import { useAuth } from '@components/Security/AuthContext'; import { useSession } from '@components/Contexts/SessionContext';
export type SettingsParams = { export type SettingsParams = {
subpage: string subpage: string
@ -15,17 +15,17 @@ export type SettingsParams = {
} }
function SettingsPage({ params }: { params: SettingsParams }) { function SettingsPage({ params }: { params: SettingsParams }) {
const auth = useAuth() as any; const session = useSession() as any;
useEffect(() => { useEffect(() => {
} }
, [auth]) , [session])
return ( return (
<div className='h-full w-full bg-[#f8f8f8]'> <div className='h-full w-full bg-[#f8f8f8]'>
<div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]'> <div className='pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]'>
<BreadCrumbs type='user' last_breadcrumb={auth?.user?.username} ></BreadCrumbs> <BreadCrumbs type='user' last_breadcrumb={session?.user?.username} ></BreadCrumbs>
<div className='my-2 tracking-tighter'> <div className='my-2 tracking-tighter'>
<div className='w-100 flex justify-between'> <div className='w-100 flex justify-between'>
<div className='pt-3 flex font-bold text-4xl'>Account Settings</div> <div className='pt-3 flex font-bold text-4xl'>Account Settings</div>

View file

@ -1,20 +1,17 @@
'use client'; 'use client';
import { OrgProvider } from "@components/Contexts/OrgContext"; import { OrgProvider } from "@components/Contexts/OrgContext";
import AuthProvider from "@components/Security/AuthContext"; import SessionProvider from "@components/Contexts/SessionContext";
import { getAPIUrl } from "@services/config/config";
import { swrFetcher } from "@services/utils/ts/requests";
import "@styles/globals.css"; import "@styles/globals.css";
import useSWR from "swr";
export default function RootLayout({ children, params }: { children: React.ReactNode, params: any }) { export default function RootLayout({ children, params }: { children: React.ReactNode, params: any }) {
return ( return (
<div> <div>
<AuthProvider orgslug={params.orgslug}>
<OrgProvider orgslug={params.orgslug}> <OrgProvider orgslug={params.orgslug}>
<SessionProvider>
{children} {children}
</SessionProvider>
</OrgProvider> </OrgProvider>
</AuthProvider>
</div> </div>
); );
} }

View file

@ -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<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();
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 (
<SessionContext.Provider value={session}>
{children}
</SessionContext.Provider>
)
}
export function useSession() {
return useContext(SessionContext);
}
export default SessionProvider

View file

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useOrg } from '@components/Contexts/OrgContext'; 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 ToolTip from '@components/StyledElements/Tooltip/Tooltip'
import LearnHouseDashboardLogo from '@public/dashLogo.png'; import LearnHouseDashboardLogo from '@public/dashLogo.png';
import Avvvatars from 'avvvatars-react'; import Avvvatars from 'avvvatars-react';
@ -11,11 +11,11 @@ import React, { use, useEffect } from 'react'
function LeftMenu() { function LeftMenu() {
const org = useOrg() as any; const org = useOrg() as any;
const auth = useAuth() as any; const session = useSession() as any;
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
function waitForEverythingToLoad() { function waitForEverythingToLoad() {
if (org && auth) { if (org && session) {
return true; return true;
} }
return false; return false;
@ -59,15 +59,15 @@ function LeftMenu() {
</ToolTip> </ToolTip>
</div> </div>
<div className='flex flex-col mx-auto pb-7 space-y-2'> <div className='flex flex-col mx-auto pb-7 space-y-2'>
<ToolTip content={auth.user.username + "'s Settings"} slateBlack sideOffset={8} side='right' > <ToolTip content={session.user.username + "'s Settings"} slateBlack sideOffset={8} side='right' >
<Link href={'/dash/user/settings/general'} className='py-3'> <Link href={'/dash/user/settings/general'} className='py-3'>
<Settings className='mx-auto text-neutral-400 cursor-pointer' size={18} /> <Settings className='mx-auto text-neutral-400 cursor-pointer' size={18} />
</Link> </Link>
</ToolTip> </ToolTip>
<div className="flex items-center flex-col space-y-4"> <div className="flex items-center flex-col space-y-4">
<ToolTip content={auth.user.username} slateBlack sideOffset={8} side='right' > <ToolTip content={session.user.username} slateBlack sideOffset={8} side='right' >
<div className="mx-auto shadow-lg"> <div className="mx-auto shadow-lg">
<Avvvatars radius={3} border borderColor='white' borderSize={3} size={35} value={auth.user.user_uuid} style="shape" /> <Avvvatars radius={3} border borderColor='white' borderSize={3} size={35} value={session.user.user_uuid} style="shape" />
</div> </div>
</ToolTip> </ToolTip>
<ToolTip content={'Learnhouse Version'} slateBlack sideOffset={8} side='right' > <ToolTip content={'Learnhouse Version'} slateBlack sideOffset={8} side='right' >

View file

@ -1,33 +1,33 @@
import { useAuth } from '@components/Security/AuthContext';
import { updateProfile } from '@services/settings/profile'; import { updateProfile } from '@services/settings/profile';
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { Formik, Form, Field, ErrorMessage } from 'formik'; import { Formik, Form, Field, ErrorMessage } from 'formik';
import { useSession } from '@components/Contexts/SessionContext';
function UserEditGeneral() { function UserEditGeneral() {
const auth = useAuth() as any; const session = useSession() as any;
useEffect(() => { useEffect(() => {
} }
, [auth, auth.user]) , [session, session.user])
return ( return (
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5'> <div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5'>
{auth.user && ( {session.user && (
<Formik <Formik
enableReinitialize enableReinitialize
initialValues={{ initialValues={{
username: auth.user.username, username: session.user.username,
first_name: auth.user.first_name, first_name: session.user.first_name,
last_name: auth.user.last_name, last_name: session.user.last_name,
email: auth.user.email, email: session.user.email,
bio: auth.user.bio, bio: session.user.bio,
}} }}
onSubmit={(values, { setSubmitting }) => { onSubmit={(values, { setSubmitting }) => {
setTimeout(() => { setTimeout(() => {
setSubmitting(false); setSubmitting(false);
updateProfile(values,auth.user.id) updateProfile(values,session.user.id)
}, 400); }, 400);
}} }}
> >

View file

@ -1,19 +1,19 @@
import { useAuth } from '@components/Security/AuthContext'; import { useSession } from '@components/Contexts/SessionContext';
import { updatePassword } from '@services/settings/password'; import { updatePassword } from '@services/settings/password';
import { Formik, Form, Field, ErrorMessage } from 'formik'; import { Formik, Form, Field, ErrorMessage } from 'formik';
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
function UserEditPassword() { function UserEditPassword() {
const auth = useAuth() as any; const session = useSession() as any;
const updatePasswordUI = async (values: 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) await updatePassword(user_id, values)
} }
useEffect(() => { useEffect(() => {
} }
, [auth]) , [session])
return ( return (

View file

@ -2,7 +2,6 @@
import React from "react"; import React from "react";
import { useEditor, EditorContent } from "@tiptap/react"; import { useEditor, EditorContent } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit"; import StarterKit from "@tiptap/starter-kit";
import { AuthContext } from "../../Security/AuthProviderDepreceated";
import learnhouseIcon from "public/learnhouse_icon.png"; import learnhouseIcon from "public/learnhouse_icon.png";
import { ToolbarButtons } from "./Toolbar/ToolbarButtons"; import { ToolbarButtons } from "./Toolbar/ToolbarButtons";
import { motion } from "framer-motion"; 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 java from 'highlight.js/lib/languages/java'
import { CourseProvider } from "@components/Contexts/CourseContext"; import { CourseProvider } from "@components/Contexts/CourseContext";
import { OrgProvider } from "@components/Contexts/OrgContext"; import { OrgProvider } from "@components/Contexts/OrgContext";
import { useSession } from "@components/Contexts/SessionContext";
interface Editor { interface Editor {
@ -51,7 +51,7 @@ interface Editor {
} }
function Editor(props: Editor) { function Editor(props: Editor) {
const auth: any = React.useContext(AuthContext); const session = useSession() as any;
// remove course_ from course_uuid // remove course_ from course_uuid
const course_uuid = props.course.course_uuid.substring(7); const course_uuid = props.course.course_uuid.substring(7);
@ -164,8 +164,8 @@ function Editor(props: Editor) {
</EditorDocSection> </EditorDocSection>
<EditorUsersSection> <EditorUsersSection>
<EditorUserProfileWrapper> <EditorUserProfileWrapper>
{!auth.isAuthenticated && <span>Loading</span>} {!session.isAuthenticated && <span>Loading</span>}
{auth.isAuthenticated && <Avvvatars value={auth.userInfo.user_uuid} style="shape" />} {session.isAuthenticated && <Avvvatars value={session.user.user_uuid} style="shape" />}
</EditorUserProfileWrapper> </EditorUserProfileWrapper>
<DividerVerticalIcon style={{ marginTop: "auto", marginBottom: "auto", color: "grey", opacity: '0.5' }} /> <DividerVerticalIcon style={{ marginTop: "auto", marginBottom: "auto", color: "grey", opacity: '0.5' }} />
<EditorLeftOptionsSection className="space-x-2 pl-2 pr-3"> <EditorLeftOptionsSection className="space-x-2 pl-2 pr-3">

View file

@ -58,7 +58,7 @@ const CollectionAdminEditsArea = (props: any) => {
return ( return (
<AuthenticatedClientElement <AuthenticatedClientElement
action="delete" action="delete"
ressourceType="collection" ressourceType="collections"
orgId={props.org_id} checkMethod='roles'> orgId={props.org_id} checkMethod='roles'>
<div className="flex space-x-1 justify-center mx-auto z-20 "> <div className="flex space-x-1 justify-center mx-auto z-20 ">
<ConfirmationModal <ConfirmationModal

View file

@ -54,7 +54,7 @@ const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any,
return ( return (
<AuthenticatedClientElement <AuthenticatedClientElement
action="update" action="update"
ressourceType="course" ressourceType="courses"
checkMethod='roles' orgId={props.course.org_id}> checkMethod='roles' orgId={props.course.org_id}>
<div className="flex space-x-2 absolute z-20 bottom-14 right-[15px] transform"> <div className="flex space-x-2 absolute z-20 bottom-14 right-[15px] transform">
<Link href={getUriWithOrg(props.orgSlug, "/dash/courses/course/" + removeCoursePrefix(props.courseId) + "/general")}> <Link href={getUriWithOrg(props.orgSlug, "/dash/courses/course/" + removeCoursePrefix(props.courseId) + "/general")}>

View file

@ -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<Auth>({ 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 (
<AuthContext.Provider value={auth}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
return React.useContext(AuthContext);
}
export default AuthProvider

View file

@ -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<Auth>({ 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 <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
};
export default AuthProvider;

View file

@ -1,49 +1,73 @@
'use client'; 'use client';
import React from "react"; import React from "react";
import { AuthContext } from "./AuthProviderDepreceated";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { getAPIUrl } from "@services/config/config"; import { getAPIUrl } from "@services/config/config";
import { swrFetcher } from "@services/utils/ts/requests"; import { swrFetcher } from "@services/utils/ts/requests";
import { useSession } from "@components/Contexts/SessionContext";
import { useOrg } from "@components/Contexts/OrgContext";
interface AuthenticatedClientElementProps { interface AuthenticatedClientElementProps {
children: React.ReactNode; children: React.ReactNode;
checkMethod: 'authentication' | 'roles'; checkMethod: 'authentication' | 'roles';
orgId?: string; orgId?: string;
ressourceType?: 'collection' | 'course' | 'activity' | 'user' | 'organization'; ressourceType?: 'collections' | 'courses' | 'activities' | 'users' | 'organizations';
action?: 'create' | 'update' | 'delete' | 'read'; 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) => { export const AuthenticatedClientElement = (props: AuthenticatedClientElementProps) => {
const auth: any = React.useContext(AuthContext); const [isAllowed, setIsAllowed] = React.useState(false);
const { data: authorization_status, error: error } = useSWR(props.checkMethod == 'roles' && props.ressourceType ? `${getAPIUrl()}users/authorize/ressource/${generateRessourceId(props.ressourceType)}/action/${props.action}` : null, swrFetcher); const session = useSession() as any;
console.log(authorization_status); 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;
} }
return <></>; }
}
}
// If no role matches the organization, resource type, and action, return false
return false;
}
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}
</>
)
} }

View file

@ -1,18 +1,18 @@
'use client'; 'use client';
import React from "react"; import React, { use, useEffect } from "react";
import styled from "styled-components"; import styled from "styled-components";
import Link from "next/link"; import Link from "next/link";
import { AuthContext } from "./AuthProviderDepreceated";
import Avvvatars from "avvvatars-react"; import Avvvatars from "avvvatars-react";
import { GearIcon } from "@radix-ui/react-icons"; import { GearIcon } from "@radix-ui/react-icons";
import { Settings } from "lucide-react"; import { Settings } from "lucide-react";
import { useSession } from "@components/Contexts/SessionContext";
export const HeaderProfileBox = () => { export const HeaderProfileBox = () => {
const auth: any = React.useContext(AuthContext); const session = useSession() as any;
return ( return (
<ProfileArea> <ProfileArea>
{!auth.isAuthenticated && ( {!session.isAuthenticated && (
<UnidentifiedArea className="flex text-sm text-gray-700 font-bold p-1.5 px-2 rounded-lg"> <UnidentifiedArea className="flex text-sm text-gray-700 font-bold p-1.5 px-2 rounded-lg">
<ul className="flex space-x-3 items-center"> <ul className="flex space-x-3 items-center">
<li> <li>
@ -28,13 +28,13 @@ export const HeaderProfileBox = () => {
</ul> </ul>
</UnidentifiedArea> </UnidentifiedArea>
)} )}
{auth.isAuthenticated && ( {session.isAuthenticated && (
<AccountArea className="space-x-0"> <AccountArea className="space-x-0">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="text-xs">{auth.userInfo.username} </div> <div className="text-xs">{session.user.username} </div>
<div className="py-4"> <div className="py-4">
<div className="shadow-sm rounded-xl"> <div className="shadow-sm rounded-xl">
<Avvvatars radius={3} size={30} value={auth.userInfo.user_uuid} style="shape" /> <Avvvatars radius={3} size={30} value={session.user.user_uuid} style="shape" />
</div> </div>
</div> </div>
<Link className="text-gray-600" href={"/dash"}><Settings size={14} /></Link> <Link className="text-gray-600" href={"/dash"}><Settings size={14} /></Link>

View file

@ -45,6 +45,22 @@ export async function getUserInfo(token: string): Promise<any> {
.catch((error) => console.log("error", error)); .catch((error) => console.log("error", error));
} }
export async function getUserSession(token: string): Promise<any> {
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<any> { export async function getNewAccessTokenUsingRefreshToken(): Promise<any> {
const requestOptions: any = { const requestOptions: any = {
method: "POST", method: "POST",