feat: init new auth

This commit is contained in:
swve 2024-05-25 14:56:28 +02:00
parent f838a8512c
commit 1708b36818
34 changed files with 1853 additions and 3613 deletions

View file

@ -1,11 +1,14 @@
from datetime import timedelta from datetime import timedelta
from typing import Literal, Optional
from fastapi import Depends, APIRouter, HTTPException, Response, status, Request from fastapi import Depends, APIRouter, HTTPException, Response, status, Request
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr
from sqlmodel import Session from sqlmodel import Session
from src.db.users import UserRead from src.db.users import AnonymousUser, PublicUser, UserRead
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from config.config import get_learnhouse_config from config.config import get_learnhouse_config
from src.security.auth import AuthJWT, authenticate_user from src.security.auth import AuthJWT, authenticate_user, get_current_user
from src.services.auth.utils import get_google_user_info, signWithGoogle
router = APIRouter() router = APIRouter()
@ -74,6 +77,58 @@ async def login(
return result return result
class ThirdPartyLogin(BaseModel):
email: EmailStr
provider: Literal["google"]
access_token: str
@router.post("/oauth")
async def third_party_login(
request: Request,
response: Response,
body: ThirdPartyLogin,
org_id: Optional[int] = None,
current_user: AnonymousUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
Authorize: AuthJWT = Depends(),
):
# Google
if body.provider == "google":
user = await signWithGoogle(
request, body.access_token, body.email, org_id, current_user, db_session
)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect Email or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = Authorize.create_access_token(subject=user.email)
refresh_token = Authorize.create_refresh_token(subject=user.email)
Authorize.set_refresh_cookies(refresh_token)
# set cookies using fastapi
response.set_cookie(
key="access_token_cookie",
value=access_token,
httponly=False,
domain=get_learnhouse_config().hosting_config.cookie_config.domain,
expires=int(timedelta(hours=8).total_seconds()),
)
user = UserRead.model_validate(user)
result = {
"user": user,
"tokens": {"access_token": access_token, "refresh_token": refresh_token},
}
return result
@router.delete("/logout") @router.delete("/logout")
def logout(Authorize: AuthJWT = Depends()): def logout(Authorize: AuthJWT = Depends()):
""" """

View file

@ -0,0 +1,70 @@
import random
from typing import Optional
from fastapi import Depends, HTTPException, Request
import httpx
from sqlmodel import Session, select
from src.core.events.database import get_db_session
from src.db.users import User, UserCreate, UserRead
from src.security.auth import get_current_user
from src.services.users.users import create_user, create_user_without_org
async def get_google_user_info(access_token: str):
url = "https://www.googleapis.com/oauth2/v3/userinfo"
headers = {"Authorization": f"Bearer {access_token}"}
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers)
if response.status_code != 200:
raise HTTPException(
status_code=response.status_code,
detail="Failed to fetch user info from Google",
)
return response.json()
async def signWithGoogle(
request: Request,
access_token: str,
email: str,
org_id: Optional[int] = None,
current_user=Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
# Google
google_user = await get_google_user_info(access_token)
user = db_session.exec(
select(User).where(User.email == google_user["email"])
).first()
if not user:
username = (
google_user["given_name"]
+ google_user["family_name"]
+ str(random.randint(10, 99))
)
user_object = UserCreate(
email=google_user["email"],
username=username,
password="",
first_name=google_user["given_name"],
last_name=google_user["family_name"],
avatar_image=google_user["picture"],
)
if org_id is not None:
user = await create_user(
request, db_session, current_user, user_object, org_id
)
return user
else:
user = await create_user_without_org(
request, db_session, current_user, user_object
)
return user
return UserRead.model_validate(user)

View file

@ -17,7 +17,7 @@ def send_account_creation_email(
<body> <body>
<p>Hello {user.username}</p> <p>Hello {user.username}</p>
<p>Welcome to LearnHouse! , get started by creating your own organization or join a one.</p> <p>Welcome to LearnHouse! , get started by creating your own organization or join a one.</p>
<p>Need some help to get started ? <a href="https://learn.learnhouse.io">LearnHouse Academy</a></p> <p>Need some help to get started ? <a href="https://university.learnhouse.io">LearnHouse Academy</a></p>
</body> </body>
</html> </html>
""", """,

View file

@ -0,0 +1,6 @@
import NextAuth from 'next-auth'
import { nextAuthOptions } from 'app/auth/options'
const handler = NextAuth(nextAuthOptions)
export { handler as GET, handler as POST }

View file

@ -0,0 +1,92 @@
import {
getNewAccessTokenUsingRefreshTokenServer,
getUserSession,
loginAndGetToken,
loginWithOAuthToken,
} from '@services/auth/auth'
import { getResponseMetadata } from '@services/utils/ts/requests'
import CredentialsProvider from 'next-auth/providers/credentials'
import GoogleProvider from 'next-auth/providers/google'
export const nextAuthOptions = {
providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: 'Credentials',
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
email: { label: 'Email', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials, req) {
// logic to verify if user exists
let unsanitized_req = await loginAndGetToken(
credentials?.email,
credentials?.password
)
let res = await getResponseMetadata(unsanitized_req)
if (res.success) {
// If login failed, then this is the place you could do a registration
return res.data
} else {
return null
}
},
}),
GoogleProvider({
clientId: process.env.LEARNHOUSE_GOOGLE_CLIENT_ID || '',
clientSecret: process.env.LEARNHOUSE_GOOGLE_CLIENT_SECRET || '',
}),
],
callbacks: {
async jwt({ token, user, account }) {
// First sign in with Credentials provider
if (account?.provider == 'credentials' && user) {
token.user = user
}
// Sign up with Google
if (account?.provider == 'google' && user) {
let unsanitized_req = await loginWithOAuthToken(
user.email,
'google',
account.access_token
)
let userFromOAuth = await getResponseMetadata(unsanitized_req)
token.user = userFromOAuth.data
}
// Refresh token
// TODO : Improve this implementation
if (token?.user?.tokens) {
const RefreshedToken = await getNewAccessTokenUsingRefreshTokenServer(
token?.user?.tokens?.refresh_token
)
token = {
...token,
user: {
...token.user,
tokens: {
...token.user.tokens,
access_token: RefreshedToken.access_token,
},
},
}
}
return token
},
async session({ session, token }) {
// Include user information in the session
if (token.user) {
let api_SESSION = await getUserSession(token.user.tokens.access_token)
session.user = api_SESSION.user
session.roles = api_SESSION.roles
session.tokens = token.user.tokens
}
return session
},
},
}

View file

@ -1,6 +0,0 @@
import PageLoading from '@components/Objects/Loaders/PageLoading'
export default function Loading() {
// Or a custom loading skeleton component
return <PageLoading></PageLoading>
}

View file

@ -6,7 +6,6 @@ import { Metadata } from 'next'
import { getActivityWithAuthHeader } from '@services/courses/activities' import { getActivityWithAuthHeader } from '@services/courses/activities'
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth' import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
import { getOrganizationContextInfoWithId } from '@services/organizations/orgs' import { getOrganizationContextInfoWithId } from '@services/organizations/orgs'
import SessionProvider from '@components/Contexts/SessionContext'
import EditorOptionsProvider from '@components/Contexts/Editor/EditorContext' import EditorOptionsProvider from '@components/Contexts/Editor/EditorContext'
import AIEditorProvider from '@components/Contexts/AI/AIEditorContext' import AIEditorProvider from '@components/Contexts/AI/AIEditorContext'
const EditorWrapper = dynamic(() => import('@components/Objects/Editor/EditorWrapper'), { ssr: false }) const EditorWrapper = dynamic(() => import('@components/Objects/Editor/EditorWrapper'), { ssr: false })
@ -58,14 +57,12 @@ const EditActivity = async (params: any) => {
return ( return (
<EditorOptionsProvider options={{ isEditable: true }}> <EditorOptionsProvider options={{ isEditable: true }}>
<AIEditorProvider> <AIEditorProvider>
<SessionProvider>
<EditorWrapper <EditorWrapper
org={org} org={org}
course={courseInfo} course={courseInfo}
activity={activity} activity={activity}
content={activity.content} content={activity.content}
></EditorWrapper> ></EditorWrapper>
</SessionProvider>
</AIEditorProvider> </AIEditorProvider>
</EditorOptionsProvider> </EditorOptionsProvider>
) )

View file

@ -1,8 +1,8 @@
'use client' 'use client'
import '../styles/globals.css' import '../styles/globals.css'
import StyledComponentsRegistry from '../components/Utils/libs/styled-registry' import StyledComponentsRegistry from '../components/Utils/libs/styled-registry'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { SessionProvider } from 'next-auth/react'
export default function RootLayout({ export default function RootLayout({
children, children,
@ -18,18 +18,20 @@ export default function RootLayout({
<html className="" lang="en"> <html className="" lang="en">
<head /> <head />
<body> <body>
<StyledComponentsRegistry> <SessionProvider>
<motion.main <StyledComponentsRegistry>
variants={variants} // Pass the variant object into Framer Motion <motion.main
initial="hidden" // Set the initial state to variants.hidden variants={variants} // Pass the variant object into Framer Motion
animate="enter" // Animated state to variants.enter initial="hidden" // Set the initial state to variants.hidden
exit="exit" // Exit state (used later) to variants.exit animate="enter" // Animated state to variants.enter
transition={{ type: 'linear' }} // Set the transition to linear exit="exit" // Exit state (used later) to variants.exit
className="" transition={{ type: 'linear' }} // Set the transition to linear
> className=""
{children} >
</motion.main> {children}
</StyledComponentsRegistry> </motion.main>
</StyledComponentsRegistry>
</SessionProvider>
</body> </body>
</html> </html>
) )

View file

@ -1,6 +1,7 @@
'use client'
import '@styles/globals.css' import '@styles/globals.css'
import { Menu } from '@components/Objects/Menu/Menu' import { Menu } from '@components/Objects/Menu/Menu'
import SessionProvider from '@components/Contexts/SessionContext' import { SessionProvider } from 'next-auth/react'
export default function RootLayout({ export default function RootLayout({
children, children,

View file

@ -1,7 +1,8 @@
import SessionProvider from '@components/Contexts/SessionContext'
import LeftMenu from '@components/Dashboard/UI/LeftMenu' import LeftMenu from '@components/Dashboard/UI/LeftMenu'
import AdminAuthorization from '@components/Security/AdminAuthorization' import AdminAuthorization from '@components/Security/AdminAuthorization'
import ClientComponentSkeleton from '@components/Utils/ClientComp'
import { Metadata } from 'next' import { Metadata } from 'next'
import { SessionProvider } from 'next-auth/react'
import React from 'react' import React from 'react'
export const metadata: Metadata = { export const metadata: Metadata = {
@ -17,14 +18,12 @@ function DashboardLayout({
}) { }) {
return ( return (
<> <>
<SessionProvider> <AdminAuthorization authorizationMode="page">
<AdminAuthorization authorizationMode="page"> <div className="flex">
<div className="flex"> <LeftMenu />
<LeftMenu /> <div className="flex w-full">{children}</div>
<div className="flex w-full">{children}</div> </div>
</div> </AdminAuthorization>
</AdminAuthorization>
</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 { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
export type SettingsParams = { export type SettingsParams = {
subpage: string subpage: string

View file

@ -5,7 +5,7 @@ import Link from 'next/link'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { ScanEye, SquareUserRound, UserPlus, Users } from 'lucide-react' import { ScanEye, SquareUserRound, UserPlus, Users } from 'lucide-react'
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import OrgUsers from '@components/Dashboard/Users/OrgUsers/OrgUsers' import OrgUsers from '@components/Dashboard/Users/OrgUsers/OrgUsers'
import OrgAccess from '@components/Dashboard/Users/OrgAccess/OrgAccess' import OrgAccess from '@components/Dashboard/Users/OrgAccess/OrgAccess'

View file

@ -1,8 +1,8 @@
'use client' 'use client'
import { OrgProvider } from '@components/Contexts/OrgContext' import { OrgProvider } from '@components/Contexts/OrgContext'
import SessionProvider from '@components/Contexts/SessionContext'
import Toast from '@components/StyledElements/Toast/Toast' import Toast from '@components/StyledElements/Toast/Toast'
import '@styles/globals.css' import '@styles/globals.css'
import { SessionProvider } from 'next-auth/react'
export default function RootLayout({ export default function RootLayout({
children, children,

View file

@ -14,6 +14,7 @@ import { loginAndGetToken } from '@services/auth/auth'
import { AlertTriangle } from 'lucide-react' import { AlertTriangle } from 'lucide-react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import Link from 'next/link' import Link from 'next/link'
import { signIn, useSession } from "next-auth/react"
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
interface LoginClientProps { interface LoginClientProps {
@ -40,7 +41,9 @@ const validate = (values: any) => {
const LoginClient = (props: LoginClientProps) => { const LoginClient = (props: LoginClientProps) => {
const [isSubmitting, setIsSubmitting] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false)
const router = useRouter() const router = useRouter();
const session = useSession();
const [error, setError] = React.useState('') const [error, setError] = React.useState('')
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
@ -50,25 +53,23 @@ const LoginClient = (props: LoginClientProps) => {
validate, validate,
onSubmit: async (values) => { onSubmit: async (values) => {
setIsSubmitting(true) setIsSubmitting(true)
let res = await loginAndGetToken(values.email, values.password) //let res = await loginAndGetToken(values.email, values.password)
let message = await res.json() const res = await signIn('credentials', {
if (res.status == 200) { redirect: false,
email: values.email,
password: values.password,
});
if (res && res.error) {
setError("Wrong Email or password");
setIsSubmitting(false);
}
else {
router.push(`/`) router.push(`/`)
setIsSubmitting(false) setIsSubmitting(false)
} else if (
res.status == 401 ||
res.status == 400 ||
res.status == 404 ||
res.status == 409
) {
setError(message.detail)
setIsSubmitting(false)
} else {
setError('Something went wrong')
setIsSubmitting(false)
} }
}, },
}) })
return ( return (
<div className="grid grid-flow-col justify-stretch h-screen"> <div className="grid grid-flow-col justify-stretch h-screen">
<div <div
@ -165,7 +166,6 @@ const LoginClient = (props: LoginClientProps) => {
Forgot password? Forgot password?
</Link> </Link>
</div> </div>
<div className="flex py-4"> <div className="flex py-4">
<Form.Submit asChild> <Form.Submit asChild>
<button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer"> <button className="w-full bg-black text-white font-bold text-center p-2 rounded-md shadow-md hover:cursor-pointer">
@ -174,6 +174,11 @@ const LoginClient = (props: LoginClientProps) => {
</Form.Submit> </Form.Submit>
</div> </div>
</FormLayout> </FormLayout>
<div className='flex h-0.5 rounded-2xl bg-slate-100 mt-5 mb-5 mx-10'></div>
<button onClick={() => signIn('google')} className="flex justify-center py-3 text-md w-full bg-white text-slate-600 space-x-3 font-semibold text-center p-2 rounded-md shadow hover:cursor-pointer">
<img src="https://fonts.gstatic.com/s/i/productlogos/googleg/v6/24px.svg" alt="" />
<span>Sign in with Google</span>
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -13,6 +13,7 @@ import { AlertTriangle, Check, User } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import { signup } from '@services/auth/auth' import { signup } from '@services/auth/auth'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { signIn } from 'next-auth/react'
const validate = (values: any) => { const validate = (values: any) => {
const errors: any = {} const errors: any = {}
@ -176,6 +177,13 @@ function OpenSignUpComponent() {
</Form.Submit> </Form.Submit>
</div> </div>
</FormLayout> </FormLayout>
<div>
<div className='flex h-0.5 rounded-2xl bg-slate-100 mt-5 mb-5 mx-10'></div>
<button onClick={() => signIn('google')} className="flex justify-center py-3 text-md w-full bg-white text-slate-600 space-x-3 font-semibold text-center p-2 rounded-md shadow hover:cursor-pointer">
<img src="https://fonts.gstatic.com/s/i/productlogos/googleg/v6/24px.svg" alt="" />
<span>Sign in with Google</span>
</button>
</div>
</div> </div>
) )
} }

View file

@ -4,7 +4,7 @@ import Image from 'next/image'
import { getOrgLogoMediaDirectory } from '@services/media/media' import { getOrgLogoMediaDirectory } from '@services/media/media'
import Link from 'next/link' import Link from 'next/link'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { MailWarning, Shield, Ticket, UserPlus } from 'lucide-react' import { MailWarning, Shield, Ticket, UserPlus } from 'lucide-react'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
@ -92,14 +92,14 @@ function SignUpClient(props: SignUpClientProps) {
</div> </div>
<div className="left-join-part bg-white flex flex-row"> <div className="left-join-part bg-white flex flex-row">
{joinMethod == 'open' && {joinMethod == 'open' &&
(session.isAuthenticated ? ( (session.status == 'authenticated' ? (
<LoggedInJoinScreen inviteCode={inviteCode} /> <LoggedInJoinScreen inviteCode={inviteCode} />
) : ( ) : (
<OpenSignUpComponent /> <OpenSignUpComponent />
))} ))}
{joinMethod == 'inviteOnly' && {joinMethod == 'inviteOnly' &&
(inviteCode ? ( (inviteCode ? (
session.isAuthenticated ? ( session.status == 'authenticated' ? (
<LoggedInJoinScreen /> <LoggedInJoinScreen />
) : ( ) : (
<InviteOnlySignUpComponent inviteCode={inviteCode} /> <InviteOnlySignUpComponent inviteCode={inviteCode} />
@ -130,7 +130,7 @@ const LoggedInJoinScreen = (props: any) => {
<span className="items-center">Hi</span> <span className="items-center">Hi</span>
<span className="capitalize flex space-x-2 items-center"> <span className="capitalize flex space-x-2 items-center">
<UserAvatar rounded="rounded-xl" border="border-4" width={35} /> <UserAvatar rounded="rounded-xl" border="border-4" width={35} />
<span>{session.user.username},</span> <span>{session.data.username},</span>
</span> </span>
<span>join {org?.name} ?</span> <span>join {org?.name} ?</span>
</p> </p>
@ -157,7 +157,7 @@ const NoTokenScreen = (props: any) => {
const validateCode = async () => { const validateCode = async () => {
setIsLoading(true) setIsLoading(true)
let res = await validateInviteCode(org?.id, inviteCode) let res = await validateInviteCode(org?.id, inviteCode,session?.user?.tokens.access_token)
//wait for 1s //wait for 1s
if (res.success) { if (res.success) {
toast.success( toast.success(

View file

@ -7,7 +7,7 @@ import useAdminStatus from './Hooks/useAdminStatus'
function ContentPlaceHolderIfUserIsNotAdmin({ text }: { text: string }) { function ContentPlaceHolderIfUserIsNotAdmin({ text }: { text: string }) {
const isUserAdmin = useAdminStatus() as any const isUserAdmin = useAdminStatus() as any
return ( return (
<div>{isUserAdmin ? text : 'No content yet'}</div> <span>{isUserAdmin ? text : 'No content yet'}</span>
) )
} }

View file

@ -1,77 +0,0 @@
'use client'
import {
getNewAccessTokenUsingRefreshToken,
getUserSession,
} from '@services/auth/auth'
import React, { useContext, createContext, useEffect } from 'react'
export const SessionContext = createContext({}) as any
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,
})
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,
})
}
if (!access_token) {
setSession({
access_token: '',
user: {},
roles: {},
isLoading: false,
isAuthenticated: false,
})
}
}
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 { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
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 { logout } from '@services/auth/auth' import { logout } from '@services/auth/auth'
@ -123,7 +123,7 @@ function LeftMenu() {
<div className="flex flex-col mx-auto pb-7 space-y-2"> <div className="flex flex-col mx-auto pb-7 space-y-2">
<div className="flex items-center flex-col space-y-2"> <div className="flex items-center flex-col space-y-2">
<ToolTip <ToolTip
content={'@' + session.user.username} content={'@' + session.data.user.username}
slateBlack slateBlack
sideOffset={8} sideOffset={8}
side="right" side="right"
@ -134,7 +134,7 @@ function LeftMenu() {
</ToolTip> </ToolTip>
<div className="flex items-center flex-col space-y-1"> <div className="flex items-center flex-col space-y-1">
<ToolTip <ToolTip
content={session.user.username + "'s Settings"} content={session.data.user.username + "'s Settings"}
slateBlack slateBlack
sideOffset={8} sideOffset={8}
side="right" side="right"

View file

@ -1,7 +1,8 @@
'use client';
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 } from 'formik' import { Formik, Form, Field } from 'formik'
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import { import {
ArrowBigUpDash, ArrowBigUpDash,
Check, Check,
@ -23,7 +24,7 @@ function UserEditGeneral() {
const file = event.target.files[0] const file = event.target.files[0]
setLocalAvatar(file) setLocalAvatar(file)
setIsLoading(true) setIsLoading(true)
const res = await updateUserAvatar(session.user.user_uuid, file) const res = await updateUserAvatar(session.data.user_uuid, file)
// wait for 1 second to show loading animation // wait for 1 second to show loading animation
await new Promise((r) => setTimeout(r, 1500)) await new Promise((r) => setTimeout(r, 1500))
if (res.success === false) { if (res.success === false) {
@ -35,24 +36,24 @@ function UserEditGeneral() {
} }
} }
useEffect(() => {}, [session, session.user]) useEffect(() => {}, [session, session.data])
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">
{session.user && ( {session.data.user && (
<Formik <Formik
enableReinitialize enableReinitialize
initialValues={{ initialValues={{
username: session.user.username, username: session.data.user.username,
first_name: session.user.first_name, first_name: session.data.user.first_name,
last_name: session.user.last_name, last_name: session.data.user.last_name,
email: session.user.email, email: session.data.user.email,
bio: session.user.bio, bio: session.data.user.bio,
}} }}
onSubmit={(values, { setSubmitting }) => { onSubmit={(values, { setSubmitting }) => {
setTimeout(() => { setTimeout(() => {
setSubmitting(false) setSubmitting(false)
updateProfile(values, session.user.id) updateProfile(values, session.data.user.id)
}, 400) }, 400)
}} }}
> >

View file

@ -1,4 +1,4 @@
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import { updatePassword } from '@services/settings/password' import { updatePassword } from '@services/settings/password'
import { Formik, Form, Field } from 'formik' import { Formik, Form, Field } from 'formik'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
@ -7,7 +7,7 @@ function UserEditPassword() {
const session = useSession() as any const session = useSession() as any
const updatePasswordUI = async (values: any) => { const updatePasswordUI = async (values: any) => {
let user_id = session.user.id let user_id = session.data.user.id
await updatePassword(user_id, values) await updatePassword(user_id, values)
} }

View file

@ -1,24 +1,27 @@
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import { useEffect } from 'react' import { useEffect } from 'react'
function useAdminStatus() { function useAdminStatus() {
const session = useSession() as any const session = useSession() as any
const org = useOrg() as any const org = useOrg() as any
console.log('useAdminStatus', {
session,
})
// If session is not loaded, redirect to login // If session is not loaded, redirect to login
useEffect(() => { useEffect(() => {
if (session.isLoading) { if (session.status == 'loading') {
return return
} }
} }
, [session]) , [session])
const isUserAdmin = () => { const isUserAdmin = () => {
if (session.isAuthenticated) { if (session.status == 'authenticated') {
const isAdmin = session.roles.some((role: any) => { const isAdmin = session?.data?.roles.some((role: any) => {
return ( return (
role.org.id === org.id && role.org.id === org.id &&
(role.role.id === 1 || (role.role.id === 1 ||
@ -31,7 +34,7 @@ function useAdminStatus() {
} }
return false return false
} }
// Return the user admin status // Return the user admin status
return isUserAdmin() return isUserAdmin()

View file

@ -1,4 +1,4 @@
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import { import {
sendActivityAIChatMessage, sendActivityAIChatMessage,
startActivityAIChatSession, startActivityAIChatSession,
@ -409,7 +409,7 @@ const AIMessagePlaceHolder = (props: {
<span className="items-center">Hello</span> <span className="items-center">Hello</span>
<span className="capitalize flex space-x-2 items-center"> <span className="capitalize flex space-x-2 items-center">
<UserAvatar rounded="rounded-lg" border="border-2" width={35} /> <UserAvatar rounded="rounded-lg" border="border-2" width={35} />
<span>{session.user.username},</span> <span>{session.data.user.username},</span>
</span> </span>
<span>how can we help today ?</span> <span>how can we help today ?</span>
</p> </p>

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import UserAvatar from '../UserAvatar' import UserAvatar from '../UserAvatar'
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import { getUserAvatarMediaDirectory } from '@services/media/media'; import { getUserAvatarMediaDirectory } from '@services/media/media';
import { getCollaborationServerUrl } from '@services/config/config'; import { getCollaborationServerUrl } from '@services/config/config';
import { useOrg } from '@components/Contexts/OrgContext'; import { useOrg } from '@components/Contexts/OrgContext';
@ -27,11 +27,11 @@ function ActiveAvatars(props: ActiveAvatarsProps) {
}); });
// Remove the current user from the list // Remove the current user from the list
delete users[session.user.user_uuid]; delete users[session.data.user.user_uuid];
setActiveUsers(users); setActiveUsers(users);
} }
, [props.mouseMovements, session.user, org]); , [props.mouseMovements, session.data.user, org]);
return ( return (
@ -50,7 +50,7 @@ function ActiveAvatars(props: ActiveAvatarsProps) {
<div className="h-2 w-2 rounded-full" style={{ position: 'absolute', bottom: -5, right: 16, backgroundColor: props.mouseMovements[key].color }} /> <div className="h-2 w-2 rounded-full" style={{ position: 'absolute', bottom: -5, right: 16, backgroundColor: props.mouseMovements[key].color }} />
</div> </div>
))} ))}
{session.isAuthenticated && ( {session.status && (
<div className='z-50'> <div className='z-50'>
<UserAvatar <UserAvatar
width={40} width={40}

View file

@ -41,7 +41,7 @@ import html from 'highlight.js/lib/languages/xml'
import python from 'highlight.js/lib/languages/python' 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 { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import AIEditorToolkit from './AI/AIEditorToolkit' import AIEditorToolkit from './AI/AIEditorToolkit'
import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures' import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures'
import UserAvatar from '../UserAvatar' import UserAvatar from '../UserAvatar'
@ -145,7 +145,7 @@ function Editor(props: Editor) {
CollaborationCursor.configure({ CollaborationCursor.configure({
provider: props.hocuspocusProvider, provider: props.hocuspocusProvider,
user: { user: {
name: props.session.user.first_name + ' ' + props.session.user.last_name, name: props.session.data.user.first_name + ' ' + props.session.data.user.last_name,
color: props.userRandomColor, color: props.userRandomColor,
}, },
}), }),

View file

@ -5,7 +5,7 @@ import { updateActivity } from '@services/courses/activities'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import Toast from '@components/StyledElements/Toast/Toast' import Toast from '@components/StyledElements/Toast/Toast'
import { OrgProvider } from '@components/Contexts/OrgContext' import { OrgProvider } from '@components/Contexts/OrgContext'
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
// Collaboration // Collaboration
import { HocuspocusProvider } from '@hocuspocus/provider' import { HocuspocusProvider } from '@hocuspocus/provider'
@ -57,7 +57,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
document.addEventListener("mousemove", (event) => { document.addEventListener("mousemove", (event) => {
// Share any information you like // Share any information you like
provider?.setAwarenessField("userMouseMovement", { provider?.setAwarenessField("userMouseMovement", {
user: session.user, user: session.data.user,
mouseX: event.clientX, mouseX: event.clientX,
mouseY: event.clientY, mouseY: event.clientY,
color: thisPageColor, color: thisPageColor,
@ -72,10 +72,10 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
provider?.setAwarenessField("savings_states", { provider?.setAwarenessField("savings_states", {
[session.user.user_uuid]: { [session.data.user.user_uuid]: {
status: 'action_save', status: 'action_save',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
user: session.user user: session.data.user
} }
}); });

View file

@ -1,28 +1,32 @@
import { useSession } from '@components/Contexts/SessionContext'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { getUriWithOrg } from '@services/config/config' import { getUriWithOrg } from '@services/config/config'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { getUserAvatarMediaDirectory } from '@services/media/media' import { getUserAvatarMediaDirectory } from '@services/media/media'
import { useSession } from 'next-auth/react'
type UserAvatarProps = { type UserAvatarProps = {
width?: number width?: number
avatar_url?: string avatar_url?: string
use_with_session?: boolean use_with_session?: boolean
rounded?: rounded?:
| 'rounded-md' | 'rounded-md'
| 'rounded-xl' | 'rounded-xl'
| 'rounded-lg' | 'rounded-lg'
| 'rounded-full' | 'rounded-full'
| 'rounded' | 'rounded'
border?: 'border-2' | 'border-4' | 'border-8' border?: 'border-2' | 'border-4' | 'border-8'
borderColor?: string borderColor?: string
predefined_avatar?: 'ai' | 'empty' predefined_avatar?: 'ai' | 'empty'
} }
function UserAvatar(props: UserAvatarProps) { function UserAvatar(props: UserAvatarProps) {
const session = useSession() as any const session = useSession() as any
const params = useParams() as any const params = useParams() as any
function checkUrlProtocol(url: string): boolean {
return url.startsWith('https://') || url.startsWith('http://');
}
const predefinedAvatarFunc = () => { const predefinedAvatarFunc = () => {
if (props.predefined_avatar === 'ai') { if (props.predefined_avatar === 'ai') {
return getUriWithOrg(params.orgslug, '/ai_avatar.png') return getUriWithOrg(params.orgslug, '/ai_avatar.png')
@ -35,10 +39,10 @@ function UserAvatar(props: UserAvatarProps) {
const predefinedAvatar = predefinedAvatarFunc() const predefinedAvatar = predefinedAvatarFunc()
const emptyAvatar = getUriWithOrg(params.orgslug, '/empty_avatar.png') as any const emptyAvatar = getUriWithOrg(params.orgslug, '/empty_avatar.png') as any
const uploadedAvatar = getUserAvatarMediaDirectory( const uploadedAvatar = (session.status == 'authenticated') && (checkUrlProtocol(session?.data?.user?.avatar_image)) ? session?.data?.user?.avatar_image : getUserAvatarMediaDirectory(
session.user.user_uuid, session?.data?.user?.user_uuid,
session.user.avatar_image session?.data?.user?.avatar_image
) as any )
const useAvatar = () => { const useAvatar = () => {
if (props.predefined_avatar) { if (props.predefined_avatar) {
@ -47,7 +51,7 @@ function UserAvatar(props: UserAvatarProps) {
if (props.avatar_url) { if (props.avatar_url) {
return props.avatar_url return props.avatar_url
} else { } else {
if (session.user.avatar_image) { if (session?.data?.user?.avatar_image) {
return uploadedAvatar return uploadedAvatar
} else { } else {
return emptyAvatar return emptyAvatar
@ -57,8 +61,10 @@ function UserAvatar(props: UserAvatarProps) {
} }
useEffect(() => { useEffect(() => {
}, [session])
}, [session.status])
return ( return (
<img <img
@ -66,13 +72,10 @@ function UserAvatar(props: UserAvatarProps) {
width={props.width ? props.width : 50} width={props.width ? props.width : 50}
height={props.width ? props.width : 50} height={props.width ? props.width : 50}
src={useAvatar()} src={useAvatar()}
className={`${ className={`${props.avatar_url && session?.data?.user?.avatar_image ? '' : 'bg-gray-700'
props.avatar_url && session.user.avatar_image ? '' : 'bg-gray-700' } ${props.border ? 'border ' + props.border : ''} ${props.borderColor ? props.borderColor : 'border-white'
} ${props.border ? 'border ' + props.border : ''} ${ } shadow-xl aspect-square w-[${props.width ? props.width : 50}px] h-[${props.width ? props.width : 50
props.borderColor ? props.borderColor : 'border-white' }px] ${props.rounded ? props.rounded : 'rounded-xl'}`}
} shadow-xl aspect-square w-[${props.width ? props.width : 50}px] h-[${
props.width ? props.width : 50
}px] ${props.rounded ? props.rounded : 'rounded-xl'}`}
/> />
) )
} }

View file

@ -1,6 +1,6 @@
'use client' 'use client'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import useAdminStatus from '@components/Hooks/useAdminStatus' import useAdminStatus from '@components/Hooks/useAdminStatus'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import React from 'react' import React from 'react'
@ -33,7 +33,7 @@ function AdminAuthorization(props: AuthorizationProps) {
// Verify if the user is authenticated // Verify if the user is authenticated
const isUserAuthenticated = () => { const isUserAuthenticated = () => {
if (session.isAuthenticated === true) { if (session.status === 'authenticated') {
return true return true
} else { } else {
return false return false
@ -59,7 +59,6 @@ function AdminAuthorization(props: AuthorizationProps) {
if (props.authorizationMode === 'page') { if (props.authorizationMode === 'page') {
// Check if user is in an admin path // Check if user is in an admin path
if (ADMIN_PATHS.some((path) => checkPathname(path, pathname))) { if (ADMIN_PATHS.some((path) => checkPathname(path, pathname))) {
console.log('Admin path')
if (isUserAuthenticated()) { if (isUserAuthenticated()) {
// Check if the user is an Admin // Check if the user is an Admin
if (isUserAdmin) { if (isUserAdmin) {
@ -92,7 +91,7 @@ function AdminAuthorization(props: AuthorizationProps) {
} }
React.useEffect(() => { React.useEffect(() => {
if (session.isLoading) { if (session.status == 'loading') {
return return
} }

View file

@ -1,6 +1,6 @@
'use client' 'use client'
import React from 'react' import React from 'react'
import { useSession } from '@components/Contexts/SessionContext' import { useSession } from 'next-auth/react'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
interface AuthenticatedClientElementProps { interface AuthenticatedClientElementProps {
@ -8,11 +8,11 @@ interface AuthenticatedClientElementProps {
checkMethod: 'authentication' | 'roles' checkMethod: 'authentication' | 'roles'
orgId?: string orgId?: string
ressourceType?: ressourceType?:
| 'collections' | 'collections'
| 'courses' | 'courses'
| 'activities' | 'activities'
| 'users' | 'users'
| 'organizations' | 'organizations'
action?: 'create' | 'update' | 'delete' | 'read' action?: 'create' | 'update' | 'delete' | 'read'
} }
@ -49,19 +49,19 @@ export const AuthenticatedClientElement = (
} }
function check() { function check() {
if (session.isAuthenticated === false) { if (session.status == 'authenticated') {
setIsAllowed(false) setIsAllowed(false)
return return
} else { } else {
if (props.checkMethod === 'authentication') { if (props.checkMethod === 'authentication') {
setIsAllowed(session.isAuthenticated) setIsAllowed(session.status == 'authenticated')
} else if (props.checkMethod === 'roles') { } else if (props.checkMethod === 'roles' && session.status == 'authenticated') {
return setIsAllowed( return setIsAllowed(
isUserAllowed( isUserAllowed(
session.roles, session?.data?.roles,
props.action!, props.action!,
props.ressourceType!, props.ressourceType!,
org.org_uuid org?.org_uuid
) )
) )
} }
@ -69,7 +69,7 @@ export const AuthenticatedClientElement = (
} }
React.useEffect(() => { React.useEffect(() => {
if (session.isLoading) { if (session.status == 'loading') {
return return
} }

View file

@ -3,21 +3,22 @@ import React, { useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Link from 'next/link' import Link from 'next/link'
import { Settings } from 'lucide-react' import { Settings } from 'lucide-react'
import { useSession } from '@components/Contexts/SessionContext'
import UserAvatar from '@components/Objects/UserAvatar' import UserAvatar from '@components/Objects/UserAvatar'
import useAdminStatus from '@components/Hooks/useAdminStatus' import useAdminStatus from '@components/Hooks/useAdminStatus'
import { useSession } from 'next-auth/react'
export const HeaderProfileBox = () => { export const HeaderProfileBox = () => {
const session = useSession() as any const session = useSession() as any
const isUserAdmin = useAdminStatus() as any const isUserAdmin = useAdminStatus() as any
useEffect(() => { useEffect(() => {
console.log(session)
} }
, [session]) , [session])
return ( return (
<ProfileArea> <ProfileArea>
{!session.isAuthenticated && ( {session.status == 'unauthenticated' && (
<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>
@ -29,11 +30,11 @@ export const HeaderProfileBox = () => {
</ul> </ul>
</UnidentifiedArea> </UnidentifiedArea>
)} )}
{session.isAuthenticated && ( {session.status == 'authenticated' && (
<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='flex items-center space-x-2' > <div className='flex items-center space-x-2' >
<p className='text-sm'>{session.user.username}</p> <p className='text-sm'>{session.data.user.username}</p>
{isUserAdmin && <div className="text-[10px] bg-rose-300 px-2 font-bold rounded-md shadow-inner py-1">ADMIN</div>} {isUserAdmin && <div className="text-[10px] bg-rose-300 px-2 font-bold rounded-md shadow-inner py-1">ADMIN</div>}
</div> </div>
<div className="py-4"> <div className="py-4">

View file

@ -39,6 +39,7 @@
"lowlight": "^3.1.0", "lowlight": "^3.1.0",
"lucide-react": "^0.363.0", "lucide-react": "^0.363.0",
"next": "14.2.3", "next": "14.2.3",
"next-auth": "^4.24.7",
"prosemirror-state": "^1.4.3", "prosemirror-state": "^1.4.3",
"randomcolor": "^0.6.2", "randomcolor": "^0.6.2",
"re-resizable": "^6.9.11", "re-resizable": "^6.9.11",

4804
apps/web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -10,8 +10,8 @@ interface LoginAndGetTokenResponse {
// TODO : everything in this file need to be refactored including security issues fix // TODO : everything in this file need to be refactored including security issues fix
export async function loginAndGetToken( export async function loginAndGetToken(
username: string, username: any,
password: string password: any
): Promise<any> { ): Promise<any> {
// Request Config // Request Config
@ -37,6 +37,37 @@ export async function loginAndGetToken(
return response return response
} }
export async function loginWithOAuthToken(
email: any,
provider: any,
accessToken: string
): Promise<any> {
// Request Config
// get origin
const HeadersConfig = new Headers({
'Content-Type': 'application/json',
})
const body = {
email: email,
provider: provider,
access_token: accessToken,
}
const jsonBody = JSON.stringify(body);
const requestOptions: any = {
method: 'POST',
headers: HeadersConfig,
body: jsonBody,
redirect: 'follow',
credentials: 'include',
}
// fetch using await and async
const response = await fetch(`${getAPIUrl()}auth/oauth`, requestOptions)
return response
}
export async function sendResetLink(email: string, org_id: number) { export async function sendResetLink(email: string, org_id: number) {
const result = await fetch( const result = await fetch(
`${getAPIUrl()}users/reset_password/send_reset_code/${email}?org_id=${org_id}`, `${getAPIUrl()}users/reset_password/send_reset_code/${email}?org_id=${org_id}`,
@ -102,10 +133,8 @@ export async function getUserInfo(token: string): Promise<any> {
} }
export async function getUserSession(token: string): Promise<any> { export async function getUserSession(token: string): Promise<any> {
const origin = window.location.origin
const HeadersConfig = new Headers({ const HeadersConfig = new Headers({
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
Origin: origin,
}) })
const requestOptions: any = { const requestOptions: any = {

View file

@ -1,19 +1,27 @@
import { getAPIUrl } from '@services/config/config' import { getAPIUrl } from '@services/config/config'
import { RequestBody, getResponseMetadata } from '@services/utils/ts/requests' import {
RequestBody,
RequestBodyWithAuthHeader,
getResponseMetadata,
} from '@services/utils/ts/requests'
export async function createInviteCode(org_id: any) { export async function createInviteCode(org_id: any, access_token: any) {
const result = await fetch( const result = await fetch(
`${getAPIUrl()}orgs/${org_id}/invites`, `${getAPIUrl()}orgs/${org_id}/invites`,
RequestBody('POST', null, null) RequestBodyWithAuthHeader('POST', null, null, access_token)
) )
const res = await getResponseMetadata(result) const res = await getResponseMetadata(result)
return res return res
} }
export async function createInviteCodeWithUserGroup(org_id: any, usergroup_id: number) { export async function createInviteCodeWithUserGroup(
org_id: any,
usergroup_id: number,
access_token: any
) {
const result = await fetch( const result = await fetch(
`${getAPIUrl()}orgs/${org_id}/invites_with_usergroups?usergroup_id=${usergroup_id}`, `${getAPIUrl()}orgs/${org_id}/invites_with_usergroups?usergroup_id=${usergroup_id}`,
RequestBody('POST', null, null) RequestBodyWithAuthHeader('POST', null, null, access_token)
) )
const res = await getResponseMetadata(result) const res = await getResponseMetadata(result)
return res return res
@ -21,11 +29,12 @@ export async function createInviteCodeWithUserGroup(org_id: any, usergroup_id: n
export async function deleteInviteCode( export async function deleteInviteCode(
org_id: any, org_id: any,
org_invite_code_uuid: string org_invite_code_uuid: string,
access_token: any
) { ) {
const result = await fetch( const result = await fetch(
`${getAPIUrl()}orgs/${org_id}/invites/${org_invite_code_uuid}`, `${getAPIUrl()}orgs/${org_id}/invites/${org_invite_code_uuid}`,
RequestBody('DELETE', null, null) RequestBodyWithAuthHeader('DELETE', null, null, access_token)
) )
const res = await getResponseMetadata(result) const res = await getResponseMetadata(result)
return res return res
@ -33,20 +42,25 @@ export async function deleteInviteCode(
export async function changeSignupMechanism( export async function changeSignupMechanism(
org_id: any, org_id: any,
signup_mechanism: string signup_mechanism: string,
access_token: any
) { ) {
const result = await fetch( const result = await fetch(
`${getAPIUrl()}orgs/${org_id}/signup_mechanism?signup_mechanism=${signup_mechanism}`, `${getAPIUrl()}orgs/${org_id}/signup_mechanism?signup_mechanism=${signup_mechanism}`,
RequestBody('PUT', null, null) RequestBodyWithAuthHeader('PUT', null, null, access_token)
) )
const res = await getResponseMetadata(result) const res = await getResponseMetadata(result)
return res return res
} }
export async function validateInviteCode(org_id: any, invite_code: string) { export async function validateInviteCode(
org_id: any,
invite_code: string,
access_token: any
) {
const result = await fetch( const result = await fetch(
`${getAPIUrl()}orgs/${org_id}/invites/code/${invite_code}`, `${getAPIUrl()}orgs/${org_id}/invites/code/${invite_code}`,
RequestBody('GET', null, null) RequestBodyWithAuthHeader('GET', null, null, access_token)
) )
const res = await getResponseMetadata(result) const res = await getResponseMetadata(result)
return res return res
@ -55,11 +69,12 @@ export async function validateInviteCode(org_id: any, invite_code: string) {
export async function inviteBatchUsers( export async function inviteBatchUsers(
org_id: any, org_id: any,
emails: string, emails: string,
invite_code_uuid: string invite_code_uuid: string,
access_token: any
) { ) {
const result = await fetch( const result = await fetch(
`${getAPIUrl()}orgs/${org_id}/invites/users/batch?emails=${emails}&invite_code_uuid=${invite_code_uuid}`, `${getAPIUrl()}orgs/${org_id}/invites/users/batch?emails=${emails}&invite_code_uuid=${invite_code_uuid}`,
RequestBody('POST', null, null) RequestBodyWithAuthHeader('POST', null, null, access_token)
) )
const res = await getResponseMetadata(result) const res = await getResponseMetadata(result)
return res return res