diff --git a/apps/web/.gitignore b/apps/web/.gitignore index e1753567..59d3e5b9 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -43,3 +43,5 @@ next.config.original.js # Sentry Config File .sentryclirc + +certificates \ No newline at end of file diff --git a/apps/web/app/auth/cookies.ts b/apps/web/app/auth/cookies.ts new file mode 100644 index 00000000..7c461f05 --- /dev/null +++ b/apps/web/app/auth/cookies.ts @@ -0,0 +1,70 @@ +import { LEARNHOUSE_TOP_DOMAIN } from '@services/config/config' + +const cookiePrefix = '__LRN-' +const cookieDomain = + LEARNHOUSE_TOP_DOMAIN == `.${LEARNHOUSE_TOP_DOMAIN}` +const cookieSecure = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? true : true +const cookieSameSite = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? 'lax' : 'None' + +export const cookiesOptions = { + sessionToken: { + name: `__Secure-next-auth.session-token`, + options: { + domain: cookieDomain, + httpOnly: true, + sameSite: cookieSameSite, + path: '/', + secure: cookieSecure, + }, + }, + callbackUrl: { + name: `__Secure-next-auth.callback-url`, + options: { + domain: cookieDomain, + httpOnly: true, + sameSite: cookieSameSite, + path: '/', + secure: cookieSecure, + }, + }, + csrfToken: { + name: `__Host-next-auth.csrf-token`, + options: { + domain: cookieDomain, + httpOnly: true, + sameSite: cookieSameSite, + path: '/', + secure: cookieSecure, + }, + }, + pkceCodeVerifier: { + name: `${cookiePrefix}next-auth.pkce.code_verifier`, + options: { + domain: cookieDomain, + httpOnly: true, + sameSite: cookieSameSite, + path: '/', + secure: cookieSecure, + }, + }, + state: { + name: `${cookiePrefix}next-auth.state`, + options: { + domain: cookieDomain, + httpOnly: true, + sameSite: cookieSameSite, + path: '/', + secure: cookieSecure, + }, + }, + nonce: { + name: `${cookiePrefix}next-auth.nonce`, + options: { + domain: cookieDomain, + httpOnly: true, + sameSite: cookieSameSite, + path: '/', + secure: cookieSecure, + }, + }, +} diff --git a/apps/web/app/auth/options.ts b/apps/web/app/auth/options.ts index 6559887d..9a7e751d 100644 --- a/apps/web/app/auth/options.ts +++ b/apps/web/app/auth/options.ts @@ -4,11 +4,16 @@ import { loginAndGetToken, loginWithOAuthToken, } from '@services/auth/auth' +import { LEARNHOUSE_TOP_DOMAIN, getUriWithOrg } from '@services/config/config' import { getResponseMetadata } from '@services/utils/ts/requests' import CredentialsProvider from 'next-auth/providers/credentials' import GoogleProvider from 'next-auth/providers/google' +import { cookiesOptions } from './cookies' + +const isDevEnv = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? true : false export const nextAuthOptions = { + debug: true, providers: [ CredentialsProvider({ // The name to display on the sign in form (e.g. 'Sign in with...') @@ -41,6 +46,24 @@ export const nextAuthOptions = { clientSecret: process.env.LEARNHOUSE_GOOGLE_CLIENT_SECRET || '', }), ], + pages: { + signIn: getUriWithOrg('auth', '/'), + verifyRequest: getUriWithOrg('auth', '/'), + error: getUriWithOrg('auth', '/'), // Error code passed in query string as ?error= + }, + cookies: { + sessionToken: { + name: `${!isDevEnv ? '__Secure-' : ''}next-auth.session-token`, + options: { + httpOnly: true, + sameSite: 'lax', + path: '/', + // When working on localhost, the cookie domain must be omitted entirely (https://stackoverflow.com/a/1188145) + domain: `.${LEARNHOUSE_TOP_DOMAIN}`, + secure: !isDevEnv, + }, + }, + }, callbacks: { async jwt({ token, user, account }: any) { // First sign in with Credentials provider diff --git a/apps/web/app/orgs/[orgslug]/login/login.tsx b/apps/web/app/login/login.tsx similarity index 94% rename from apps/web/app/orgs/[orgslug]/login/login.tsx rename to apps/web/app/login/login.tsx index ec533a31..fec973fc 100644 --- a/apps/web/app/orgs/[orgslug]/login/login.tsx +++ b/apps/web/app/login/login.tsx @@ -57,16 +57,16 @@ const LoginClient = (props: LoginClientProps) => { redirect: false, email: values.email, password: values.password, - callbackUrl: '/' + callbackUrl: '/redirect_from_auth' }); if (res && res.error) { setError("Wrong Email or password"); setIsSubmitting(false); - }else { + } else { await signIn('credentials', { email: values.email, password: values.password, - callbackUrl: '/' + callbackUrl: '/redirect_from_auth' }); } }, @@ -177,7 +177,7 @@ const LoginClient = (props: LoginClientProps) => {
- diff --git a/apps/web/app/orgs/[orgslug]/login/page.tsx b/apps/web/app/login/page.tsx similarity index 69% rename from apps/web/app/orgs/[orgslug]/login/page.tsx rename to apps/web/app/login/page.tsx index dce15e8b..9baf1cc7 100644 --- a/apps/web/app/orgs/[orgslug]/login/page.tsx +++ b/apps/web/app/login/page.tsx @@ -3,14 +3,14 @@ import LoginClient from './login' import { Metadata } from 'next' type MetadataProps = { - params: { orgslug: string; courseid: string } + params: { orgslug: string } searchParams: { [key: string]: string | string[] | undefined } } -export async function generateMetadata({ - params, -}: MetadataProps): Promise { - const orgslug = params.orgslug +export async function generateMetadata(params: MetadataProps): Promise { + const orgslug = params.searchParams.orgslug + + //const orgslug = params.orgslug // Get Org context information const org = await getOrganizationContextInfo(orgslug, { revalidate: 0, @@ -22,8 +22,8 @@ export async function generateMetadata({ } } -const Login = async (params: any) => { - const orgslug = params.params.orgslug +const Login = async (params: MetadataProps) => { + const orgslug = params.searchParams.orgslug const org = await getOrganizationContextInfo(orgslug, { revalidate: 0, tags: ['organizations'], diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/error.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/error.tsx index d5320406..bb8f7f65 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/error.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/error.tsx @@ -17,7 +17,7 @@ export default function Error({ return (
- +
) } diff --git a/apps/web/components/Contexts/CourseContext.tsx b/apps/web/components/Contexts/CourseContext.tsx index ddcf711c..585f698a 100644 --- a/apps/web/components/Contexts/CourseContext.tsx +++ b/apps/web/components/Contexts/CourseContext.tsx @@ -38,7 +38,7 @@ export function CourseProvider({ } }, [courseStructureData,session]) - if (!courseStructureData) return + if (!courseStructureData) return return ( diff --git a/apps/web/components/Contexts/LHSessionContext.tsx b/apps/web/components/Contexts/LHSessionContext.tsx index 5b2bdcad..d60413eb 100644 --- a/apps/web/components/Contexts/LHSessionContext.tsx +++ b/apps/web/components/Contexts/LHSessionContext.tsx @@ -10,16 +10,17 @@ function LHSessionProvider({ children }: { children: React.ReactNode }) { useEffect(() => { console.log('useLHSession', session); - }, [session]) + }, []) - if (session.status == 'loading') { + if (session && session.status == 'loading') { return } - else { + else if (session) { return ( + {console.log('rendered')} {children} ) diff --git a/apps/web/components/Contexts/OrgContext.tsx b/apps/web/components/Contexts/OrgContext.tsx index 28ab6c94..b2e77f3f 100644 --- a/apps/web/components/Contexts/OrgContext.tsx +++ b/apps/web/components/Contexts/OrgContext.tsx @@ -1,7 +1,7 @@ 'use client' import { getAPIUrl } from '@services/config/config' import { swrFetcher } from '@services/utils/ts/requests' -import React, { useContext, useEffect } from 'react' +import React, { useContext, useEffect, useState } from 'react' import useSWR from 'swr' import { createContext } from 'react' import { useRouter } from 'next/navigation' @@ -20,24 +20,31 @@ export function OrgProvider({ const session = useLHSession() as any; const access_token = session?.data?.tokens?.access_token; const { data: org } = useSWR(`${getAPIUrl()}orgs/slug/${orgslug}`, (url) => swrFetcher(url, access_token)) + const [isLoading, setIsLoading] = useState(true); + const [isOrgActive, setIsOrgActive] = useState(true); - const router = useRouter() // Check if Org is Active const verifyIfOrgIsActive = () => { if (org && org?.config.config.GeneralConfig.active === false) { - router.push('/404') + setIsOrgActive(false) + } + else { + setIsOrgActive(true) } - } - useEffect(() => { - verifyIfOrgIsActive() - }, [org]) - if (org) { + useEffect(() => { + if (org && session) { + verifyIfOrgIsActive() + setIsLoading(false) + } + }, [org, session]) + + if (!isLoading) { return {children} } - else { - return + if (!isOrgActive) { + return } } diff --git a/apps/web/components/Dashboard/UI/LeftMenu.tsx b/apps/web/components/Dashboard/UI/LeftMenu.tsx index 468e6d41..ba951506 100644 --- a/apps/web/components/Dashboard/UI/LeftMenu.tsx +++ b/apps/web/components/Dashboard/UI/LeftMenu.tsx @@ -11,12 +11,12 @@ import React, { useEffect } from 'react' import UserAvatar from '../../Objects/UserAvatar' import AdminAuthorization from '@components/Security/AdminAuthorization' import { useLHSession } from '@components/Contexts/LHSessionContext' +import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config' function LeftMenu() { const org = useOrg() as any const session = useLHSession() as any const [loading, setLoading] = React.useState(true) - const route = useRouter() function waitForEverythingToLoad() { if (org && session) { @@ -26,9 +26,9 @@ function LeftMenu() { } async function logOutUI() { - const res = await signOut() + const res = await signOut({ redirect: true, callbackUrl: getUriWithoutOrg('/login?orgslug=' + org.slug) }) if (res) { - route.push('/login') + getUriWithOrg(org.slug, '/') } } diff --git a/apps/web/components/Security/HeaderProfileBox.tsx b/apps/web/components/Security/HeaderProfileBox.tsx index 0bd3ca53..b64b45d7 100644 --- a/apps/web/components/Security/HeaderProfileBox.tsx +++ b/apps/web/components/Security/HeaderProfileBox.tsx @@ -6,13 +6,16 @@ import { Settings } from 'lucide-react' import UserAvatar from '@components/Objects/UserAvatar' import useAdminStatus from '@components/Hooks/useAdminStatus' import { useLHSession } from '@components/Contexts/LHSessionContext' +import { useOrg } from '@components/Contexts/OrgContext' +import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config' export const HeaderProfileBox = () => { const session = useLHSession() as any const isUserAdmin = useAdminStatus() as any + const org = useOrg() as any - useEffect(() => {} - , [session]) + useEffect(() => { } + , [session]) return ( @@ -20,10 +23,11 @@ export const HeaderProfileBox = () => {
  • - Login + Login
  • - Sign up + Sign up
diff --git a/apps/web/components/StyledElements/Error/Error.tsx b/apps/web/components/StyledElements/Error/Error.tsx index ead83f1f..ef923c0a 100644 --- a/apps/web/components/StyledElements/Error/Error.tsx +++ b/apps/web/components/StyledElements/Error/Error.tsx @@ -3,7 +3,7 @@ import { AlertTriangle, RefreshCcw } from 'lucide-react' import { useRouter } from 'next/navigation' import React from 'react' -function ErrorUI() { +function ErrorUI(params: { message?: string }) { const router = useRouter() function reloadPage() { @@ -15,7 +15,7 @@ function ErrorUI() {
-

Something went wrong

+

{params.message ? params.message : 'Something went wrong'}