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

@ -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';
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() {
</ToolTip>
</div>
<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'>
<Settings className='mx-auto text-neutral-400 cursor-pointer' size={18} />
</Link>
</ToolTip>
<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">
<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>
</ToolTip>
<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 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 (
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5'>
{auth.user && (
{session.user && (
<Formik
enableReinitialize
initialValues={{
username: auth.user.username,
first_name: auth.user.first_name,
last_name: auth.user.last_name,
email: auth.user.email,
bio: auth.user.bio,
username: session.user.username,
first_name: session.user.first_name,
last_name: session.user.last_name,
email: session.user.email,
bio: session.user.bio,
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false);
updateProfile(values,auth.user.id)
updateProfile(values,session.user.id)
}, 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 { 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 (

View file

@ -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) {
</EditorDocSection>
<EditorUsersSection>
<EditorUserProfileWrapper>
{!auth.isAuthenticated && <span>Loading</span>}
{auth.isAuthenticated && <Avvvatars value={auth.userInfo.user_uuid} style="shape" />}
{!session.isAuthenticated && <span>Loading</span>}
{session.isAuthenticated && <Avvvatars value={session.user.user_uuid} style="shape" />}
</EditorUserProfileWrapper>
<DividerVerticalIcon style={{ marginTop: "auto", marginBottom: "auto", color: "grey", opacity: '0.5' }} />
<EditorLeftOptionsSection className="space-x-2 pl-2 pr-3">

View file

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

View file

@ -54,7 +54,7 @@ const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any,
return (
<AuthenticatedClientElement
action="update"
ressourceType="course"
ressourceType="courses"
checkMethod='roles' orgId={props.course.org_id}>
<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")}>

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';
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}
</>
)
}

View file

@ -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 (
<ProfileArea>
{!auth.isAuthenticated && (
{!session.isAuthenticated && (
<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">
<li>
@ -28,13 +28,13 @@ export const HeaderProfileBox = () => {
</ul>
</UnidentifiedArea>
)}
{auth.isAuthenticated && (
{session.isAuthenticated && (
<AccountArea className="space-x-0">
<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="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>
<Link className="text-gray-600" href={"/dash"}><Settings size={14} /></Link>