mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: init new auth
This commit is contained in:
parent
f838a8512c
commit
1708b36818
34 changed files with 1853 additions and 3613 deletions
6
apps/web/app/api/auth/[...nextauth]/route.ts
Normal file
6
apps/web/app/api/auth/[...nextauth]/route.ts
Normal 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 }
|
||||
92
apps/web/app/auth/options.ts
Normal file
92
apps/web/app/auth/options.ts
Normal 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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||
|
||||
export default function Loading() {
|
||||
// Or a custom loading skeleton component
|
||||
return <PageLoading></PageLoading>
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import { Metadata } from 'next'
|
|||
import { getActivityWithAuthHeader } from '@services/courses/activities'
|
||||
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'
|
||||
import { getOrganizationContextInfoWithId } from '@services/organizations/orgs'
|
||||
import SessionProvider from '@components/Contexts/SessionContext'
|
||||
import EditorOptionsProvider from '@components/Contexts/Editor/EditorContext'
|
||||
import AIEditorProvider from '@components/Contexts/AI/AIEditorContext'
|
||||
const EditorWrapper = dynamic(() => import('@components/Objects/Editor/EditorWrapper'), { ssr: false })
|
||||
|
|
@ -58,14 +57,12 @@ const EditActivity = async (params: any) => {
|
|||
return (
|
||||
<EditorOptionsProvider options={{ isEditable: true }}>
|
||||
<AIEditorProvider>
|
||||
<SessionProvider>
|
||||
<EditorWrapper
|
||||
org={org}
|
||||
course={courseInfo}
|
||||
activity={activity}
|
||||
content={activity.content}
|
||||
></EditorWrapper>
|
||||
</SessionProvider>
|
||||
</AIEditorProvider>
|
||||
</EditorOptionsProvider>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
'use client'
|
||||
import '../styles/globals.css'
|
||||
import StyledComponentsRegistry from '../components/Utils/libs/styled-registry'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
|
|
@ -18,18 +18,20 @@ export default function RootLayout({
|
|||
<html className="" lang="en">
|
||||
<head />
|
||||
<body>
|
||||
<StyledComponentsRegistry>
|
||||
<motion.main
|
||||
variants={variants} // Pass the variant object into Framer Motion
|
||||
initial="hidden" // Set the initial state to variants.hidden
|
||||
animate="enter" // Animated state to variants.enter
|
||||
exit="exit" // Exit state (used later) to variants.exit
|
||||
transition={{ type: 'linear' }} // Set the transition to linear
|
||||
className=""
|
||||
>
|
||||
{children}
|
||||
</motion.main>
|
||||
</StyledComponentsRegistry>
|
||||
<SessionProvider>
|
||||
<StyledComponentsRegistry>
|
||||
<motion.main
|
||||
variants={variants} // Pass the variant object into Framer Motion
|
||||
initial="hidden" // Set the initial state to variants.hidden
|
||||
animate="enter" // Animated state to variants.enter
|
||||
exit="exit" // Exit state (used later) to variants.exit
|
||||
transition={{ type: 'linear' }} // Set the transition to linear
|
||||
className=""
|
||||
>
|
||||
{children}
|
||||
</motion.main>
|
||||
</StyledComponentsRegistry>
|
||||
</SessionProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client'
|
||||
import '@styles/globals.css'
|
||||
import { Menu } from '@components/Objects/Menu/Menu'
|
||||
import SessionProvider from '@components/Contexts/SessionContext'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import SessionProvider from '@components/Contexts/SessionContext'
|
||||
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
|
||||
import AdminAuthorization from '@components/Security/AdminAuthorization'
|
||||
import ClientComponentSkeleton from '@components/Utils/ClientComp'
|
||||
import { Metadata } from 'next'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
import React from 'react'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
|
@ -17,14 +18,12 @@ function DashboardLayout({
|
|||
}) {
|
||||
return (
|
||||
<>
|
||||
<SessionProvider>
|
||||
<AdminAuthorization authorizationMode="page">
|
||||
<div className="flex">
|
||||
<LeftMenu />
|
||||
<div className="flex w-full">{children}</div>
|
||||
</div>
|
||||
</AdminAuthorization>
|
||||
</SessionProvider>
|
||||
<AdminAuthorization authorizationMode="page">
|
||||
<div className="flex">
|
||||
<LeftMenu />
|
||||
<div className="flex w-full">{children}</div>
|
||||
</div>
|
||||
</AdminAuthorization>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import Link from 'next/link'
|
|||
import { getUriWithOrg } from '@services/config/config'
|
||||
import { Info, Lock } from 'lucide-react'
|
||||
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useSession } from 'next-auth/react'
|
||||
|
||||
export type SettingsParams = {
|
||||
subpage: string
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import Link from 'next/link'
|
|||
import { getUriWithOrg } from '@services/config/config'
|
||||
import { ScanEye, SquareUserRound, UserPlus, Users } from 'lucide-react'
|
||||
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 OrgUsers from '@components/Dashboard/Users/OrgUsers/OrgUsers'
|
||||
import OrgAccess from '@components/Dashboard/Users/OrgAccess/OrgAccess'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
'use client'
|
||||
import { OrgProvider } from '@components/Contexts/OrgContext'
|
||||
import SessionProvider from '@components/Contexts/SessionContext'
|
||||
import Toast from '@components/StyledElements/Toast/Toast'
|
||||
import '@styles/globals.css'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { loginAndGetToken } from '@services/auth/auth'
|
|||
import { AlertTriangle } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { signIn, useSession } from "next-auth/react"
|
||||
import { getUriWithOrg } from '@services/config/config'
|
||||
|
||||
interface LoginClientProps {
|
||||
|
|
@ -40,7 +41,9 @@ const validate = (values: any) => {
|
|||
|
||||
const LoginClient = (props: LoginClientProps) => {
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const session = useSession();
|
||||
|
||||
const [error, setError] = React.useState('')
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
|
|
@ -50,25 +53,23 @@ const LoginClient = (props: LoginClientProps) => {
|
|||
validate,
|
||||
onSubmit: async (values) => {
|
||||
setIsSubmitting(true)
|
||||
let res = await loginAndGetToken(values.email, values.password)
|
||||
let message = await res.json()
|
||||
if (res.status == 200) {
|
||||
//let res = await loginAndGetToken(values.email, values.password)
|
||||
const res = await signIn('credentials', {
|
||||
redirect: false,
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
});
|
||||
if (res && res.error) {
|
||||
setError("Wrong Email or password");
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
else {
|
||||
router.push(`/`)
|
||||
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 (
|
||||
<div className="grid grid-flow-col justify-stretch h-screen">
|
||||
<div
|
||||
|
|
@ -165,7 +166,6 @@ const LoginClient = (props: LoginClientProps) => {
|
|||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex py-4">
|
||||
<Form.Submit asChild>
|
||||
<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>
|
||||
</div>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { AlertTriangle, Check, User } from 'lucide-react'
|
|||
import Link from 'next/link'
|
||||
import { signup } from '@services/auth/auth'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { signIn } from 'next-auth/react'
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {}
|
||||
|
|
@ -176,6 +177,13 @@ function OpenSignUpComponent() {
|
|||
</Form.Submit>
|
||||
</div>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Image from 'next/image'
|
|||
import { getOrgLogoMediaDirectory } from '@services/media/media'
|
||||
import Link from 'next/link'
|
||||
import { getUriWithOrg } from '@services/config/config'
|
||||
import { useSession } from '@components/Contexts/SessionContext'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { MailWarning, Shield, Ticket, UserPlus } from 'lucide-react'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
|
|
@ -92,14 +92,14 @@ function SignUpClient(props: SignUpClientProps) {
|
|||
</div>
|
||||
<div className="left-join-part bg-white flex flex-row">
|
||||
{joinMethod == 'open' &&
|
||||
(session.isAuthenticated ? (
|
||||
(session.status == 'authenticated' ? (
|
||||
<LoggedInJoinScreen inviteCode={inviteCode} />
|
||||
) : (
|
||||
<OpenSignUpComponent />
|
||||
))}
|
||||
{joinMethod == 'inviteOnly' &&
|
||||
(inviteCode ? (
|
||||
session.isAuthenticated ? (
|
||||
session.status == 'authenticated' ? (
|
||||
<LoggedInJoinScreen />
|
||||
) : (
|
||||
<InviteOnlySignUpComponent inviteCode={inviteCode} />
|
||||
|
|
@ -130,7 +130,7 @@ const LoggedInJoinScreen = (props: any) => {
|
|||
<span className="items-center">Hi</span>
|
||||
<span className="capitalize flex space-x-2 items-center">
|
||||
<UserAvatar rounded="rounded-xl" border="border-4" width={35} />
|
||||
<span>{session.user.username},</span>
|
||||
<span>{session.data.username},</span>
|
||||
</span>
|
||||
<span>join {org?.name} ?</span>
|
||||
</p>
|
||||
|
|
@ -157,7 +157,7 @@ const NoTokenScreen = (props: any) => {
|
|||
|
||||
const validateCode = async () => {
|
||||
setIsLoading(true)
|
||||
let res = await validateInviteCode(org?.id, inviteCode)
|
||||
let res = await validateInviteCode(org?.id, inviteCode,session?.user?.tokens.access_token)
|
||||
//wait for 1s
|
||||
if (res.success) {
|
||||
toast.success(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue