diff --git a/nextjs/src/app/api/Auth.ts b/nextjs/src/app/api/Auth.ts index bf5ec9318..1ca5ab911 100644 --- a/nextjs/src/app/api/Auth.ts +++ b/nextjs/src/app/api/Auth.ts @@ -7,8 +7,7 @@ import { getHeaderConfigs } from '@/config/GetHeaderConfigs' export interface IUserSignUpData { email: string - clientId: string - clientSecret: string + clientAlias?: string } export interface IAddPasswordDetails { email: string @@ -40,7 +39,7 @@ export const sendVerificationMail = async ( const config = getHeaderConfigs() const details = { - url: apiRoutes.auth.sendMail, + url: `${apiRoutes.auth.sendMail}?clientAlias=${payload?.clientAlias}`, payload, config, } diff --git a/nextjs/src/app/organizations/credentials/issue/bulk-issuance/history/[requestId]/page.tsx b/nextjs/src/app/organizations/credentials/issue/bulk-issuance/history/[requestId]/page.tsx index 2497c9da0..91d8b98ff 100644 --- a/nextjs/src/app/organizations/credentials/issue/bulk-issuance/history/[requestId]/page.tsx +++ b/nextjs/src/app/organizations/credentials/issue/bulk-issuance/history/[requestId]/page.tsx @@ -9,9 +9,5 @@ export default async function Page({ params: Params }): Promise { const { requestId } = await params - return ( - - - - ) + return } diff --git a/nextjs/src/app/reset-password/PasswordSuggestionBox.tsx b/nextjs/src/app/reset-password/PasswordSuggestionBox.tsx index bd77890eb..43b17d096 100644 --- a/nextjs/src/app/reset-password/PasswordSuggestionBox.tsx +++ b/nextjs/src/app/reset-password/PasswordSuggestionBox.tsx @@ -71,7 +71,7 @@ const PasswordSuggestionBox = ({ return (
-
+
{show === true ? ( <> {restrictedChar ? ( diff --git a/nextjs/src/components/Modal.tsx b/nextjs/src/components/Modal.tsx index 9b7d91da8..cc6adec87 100644 --- a/nextjs/src/components/Modal.tsx +++ b/nextjs/src/components/Modal.tsx @@ -25,7 +25,9 @@ const modalVariants = cva( }, ) -export interface ModalProps extends React.HTMLAttributes, VariantProps { +export interface ModalProps + extends React.HTMLAttributes, + VariantProps { open: boolean onClose: () => void closeOnOutsideClick?: boolean diff --git a/nextjs/src/features/auth/components/EmailVerificationForm.tsx b/nextjs/src/features/auth/components/EmailVerificationForm.tsx index 1105a012a..0c659c391 100644 --- a/nextjs/src/features/auth/components/EmailVerificationForm.tsx +++ b/nextjs/src/features/auth/components/EmailVerificationForm.tsx @@ -5,17 +5,13 @@ import * as Yup from 'yup' import { Formik, Form as FormikForm } from 'formik' import React, { useState } from 'react' import { apiStatusCodes, emailRegex } from '@/config/CommonConstant' -import { - checkUserExist, - passwordEncryption, - sendVerificationMail, -} from '@/app/api/Auth' +import { checkUserExist, sendVerificationMail } from '@/app/api/Auth' import { AlertComponent } from '@/components/AlertComponent' import { AxiosResponse } from 'axios' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' -import { envConfig } from '@/config/envConfig' +import { useSearchParams } from 'next/navigation' interface StepEmailProps { readonly email: string @@ -33,6 +29,9 @@ export default function EmailVerificationForm({ const [emailSuccess, setEmailSuccess] = useState(null) const [addFailure, setAddFailure] = useState(null) + const searchParams = useSearchParams() + const clientAliasValue = searchParams?.get('clientAlias') + const validationSchema = Yup.object().shape({ email: Yup.string() .email('Invalid email address') @@ -46,8 +45,7 @@ export default function EmailVerificationForm({ const payload = { email, - clientId: passwordEncryption(envConfig.PLATFORM_DATA.clientId), - clientSecret: passwordEncryption(envConfig.PLATFORM_DATA.clientSecret), + clientAlias: clientAliasValue ? clientAliasValue : '', } const userRsp = await sendVerificationMail(payload) diff --git a/nextjs/src/features/auth/components/SessionCheck.tsx b/nextjs/src/features/auth/components/SessionCheck.tsx index 8e4816b06..b09f259d4 100644 --- a/nextjs/src/features/auth/components/SessionCheck.tsx +++ b/nextjs/src/features/auth/components/SessionCheck.tsx @@ -1,47 +1,48 @@ 'use client' -import React, { ReactNode, useEffect, useState } from 'react' -import { usePathname, useRouter } from 'next/navigation' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' -import Loader from '@/components/Loader' -import { sessionExcludedPaths } from '@/config/CommonConstant' +import { useEffect } from 'react' import { useSession } from 'next-auth/react' -interface SessionProps { - children: ReactNode -} - -const signInPath = '/auth/sign-in' -const dashboardPath = '/dashboard' - -const SessionCheck: React.FC = ({ children }) => { +const SessionCheck = ({ + children, +}: { + children: React.ReactNode +}): JSX.Element | null => { + const { data: session, status } = useSession() const router = useRouter() + const searchParams = useSearchParams() const pathname = usePathname() - const { data: session } = useSession() - const token = session?.accessToken - const [checkingSession, setCheckingSession] = useState(true) + const redirectTo = searchParams.get('redirectTo') ?? '/dashboard' + + const preventRedirectOnPaths = [ + '/organizations/create-organization', + '/organizations/agent-config', + '/organizations/dashboard', + ] useEffect(() => { - const isExcluded = sessionExcludedPaths.some((path) => - pathname?.startsWith(path), + if (status === 'loading') { + return + } + + const isOnRestrictedPage = preventRedirectOnPaths.some((page) => + pathname.startsWith(page), ) - if (!token && !isExcluded) { - router.push(signInPath) - } else if (token && pathname === signInPath) { - router.push(dashboardPath) + if (session && redirectTo && !isOnRestrictedPage) { + router.push(redirectTo) } - setCheckingSession(false) - }, [pathname, token, router]) + if (session === null) { + localStorage.removeItem('persist:root') + } + }, [session, status, redirectTo, router, pathname]) - if (checkingSession) { - return ( -
- -
- ) + if (status === 'loading') { + return
Loading session...
} return <>{children} diff --git a/nextjs/src/features/auth/components/SignUpUser.tsx b/nextjs/src/features/auth/components/SignUpUser.tsx index 0988be11d..d7bb8df77 100644 --- a/nextjs/src/features/auth/components/SignUpUser.tsx +++ b/nextjs/src/features/auth/components/SignUpUser.tsx @@ -12,6 +12,13 @@ export default function SignUpUser(): React.JSX.Element { const [email, setEmail] = useState('') const searchParam = useSearchParams() const userEmail = searchParam.get('email') + const redirectTo = searchParam.get('redirectTo') + const clientAlias = searchParam.get('clientAlias') + + const signInUrl = + redirectTo && clientAlias + ? `/auth/sign-in?redirectTo=${encodeURIComponent(redirectTo)}&clientAlias=${clientAlias}` + : '/auth/sign-in' return (
@@ -56,7 +63,7 @@ export default function SignUpUser(): React.JSX.Element {
Already have an account?{' '} - + Sign in diff --git a/nextjs/src/features/auth/components/UserInfoForm.tsx b/nextjs/src/features/auth/components/UserInfoForm.tsx index 54e9a0fa4..0970e72ed 100644 --- a/nextjs/src/features/auth/components/UserInfoForm.tsx +++ b/nextjs/src/features/auth/components/UserInfoForm.tsx @@ -19,12 +19,12 @@ import { } from '@/app/api/Fido' import { addPasswordDetails, passwordEncryption } from '@/app/api/Auth' import { apiStatusCodes, passwordRegex } from '@/config/CommonConstant' +import { useRouter, useSearchParams } from 'next/navigation' import { AlertComponent } from '@/components/AlertComponent' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { startRegistration } from '@simplewebauthn/browser' -import { useRouter } from 'next/navigation' interface StepUserInfoProps { email: string @@ -87,6 +87,10 @@ export default function UserInfoForm({ const [success, setSuccess] = useState(null) const [failure, setFailure] = useState(null) + const searchParams = useSearchParams() + const redirectTo = searchParams.get('redirectTo') + const clientAlias = searchParams.get('clientAlias') + const onSubmit = async (values: { firstName: string lastName: string @@ -112,9 +116,12 @@ export default function UserInfoForm({ if (data?.statusCode === apiStatusCodes.API_STATUS_CREATED) { setSuccess(data?.message || 'Account created successfully!') + setTimeout(() => { router.push( - `/auth/sign-in?signup=true&email=${email}&fidoFlag=false&method=password`, + redirectTo && clientAlias + ? `/auth/sign-in?signup=true&email=${email}&redirectTo=${encodeURIComponent(redirectTo)}&clientAlias=${clientAlias}&fidoFlag=false&method=password` + : `/auth/sign-up?email=${email}&fidoFlag=false&method=password`, ) }, 2000) } else { diff --git a/nextjs/src/features/auth/components/VerifyEmail.tsx b/nextjs/src/features/auth/components/VerifyEmail.tsx index 2ea0710dc..2d2dfc80c 100644 --- a/nextjs/src/features/auth/components/VerifyEmail.tsx +++ b/nextjs/src/features/auth/components/VerifyEmail.tsx @@ -21,7 +21,6 @@ export default function VerifyEmailPage(): React.JSX.Element { const [email, setEmail] = useState('') const hasVerifiedRef = useRef(false) - useEffect(() => { if (hasVerifiedRef.current) { return @@ -65,7 +64,14 @@ export default function VerifyEmailPage(): React.JSX.Element { }, [searchParams]) const handleRedirect = (): void => { - router.push(`/auth/sign-up?email=${email}`) + const redirectTo = searchParams.get('redirectTo') + const clientAlias = searchParams.get('clientAlias') + + router.push( + redirectTo && clientAlias + ? `/auth/sign-up?email=${email}&redirectTo=${encodeURIComponent(redirectTo)}&clientAlias=${clientAlias}` + : `/auth/sign-up?email=${email}`, + ) } return ( diff --git a/nextjs/src/features/auth/components/user-auth-form.tsx b/nextjs/src/features/auth/components/user-auth-form.tsx index f33eab8a3..e5cfdcc67 100644 --- a/nextjs/src/features/auth/components/user-auth-form.tsx +++ b/nextjs/src/features/auth/components/user-auth-form.tsx @@ -11,14 +11,14 @@ import { FormLabel, FormMessage, } from '@/components/ui/form' +import React, { useState } from 'react' import { - IUserSignInData, forgotPassword, getUserProfile, passwordEncryption, } from '@/app/api/Auth' -import React, { useState } from 'react' import { signIn, useSession } from 'next-auth/react' +import { useRouter, useSearchParams } from 'next/navigation' import { AlertComponent } from '@/components/AlertComponent' import { AxiosResponse } from 'axios' @@ -32,7 +32,6 @@ import { setProfile } from '@/lib/profileSlice' import { startAuthentication } from '@simplewebauthn/browser' import { useDispatch } from 'react-redux' import { useForm } from 'react-hook-form' -import { useRouter } from 'next/navigation' import { zodResolver } from '@hookform/resolvers/zod' const signInSchema = z.object({ @@ -67,6 +66,53 @@ export default function SignInViewPage(): React.JSX.Element { }, }) + const searchParams = useSearchParams() + + const redirectTo = searchParams?.get('redirectTo') + const clientAlias = searchParams?.get('clientAlias') + + const signUpUrl = + redirectTo && clientAlias + ? `/auth/sign-up?redirectTo=${encodeURIComponent(redirectTo)}&clientAlias=${clientAlias}` + : '/auth/sign-up' + + const handleSignIn = async (values: { + email: string + password?: string + }): Promise => { + try { + const entityData = { + email: values.email, + password: await passwordEncryption(values.password || ''), + isPassword: isPasswordTab, + } + + const redirectTo = searchParams?.get('redirectTo') + + const response = await signIn('credentials', { + ...entityData, + redirect: false, + callbackUrl: redirectTo ? redirectTo : '/dashboard', + }) + + if (response?.ok && response?.url) { + route.push(response.url) + } else { + const errorMsg = response?.error + ? response.error === 'CredentialsSignin' + ? 'Invalid Credentials' + : response.error + : 'Sign in failed. Please try again.' + setAlert(errorMsg) + + console.error('Sign in failed:', response?.error) + } + } catch (error) { + setAlert('Something went wrong during sign in. Please try again.') + console.error('Sign in error:', error) + } + } + const getUserDetails = async ( // eslint-disable-next-line camelcase access_token: string, @@ -124,45 +170,6 @@ export default function SignInViewPage(): React.JSX.Element { } } - const handleSignIn = async (values: { - email: string - password?: string - }): Promise => { - try { - const entityData: IUserSignInData = isPasswordTab - ? { - email: values.email, - password: await passwordEncryption(values.password || ''), - isPasskey: false, - isPassword: isPasswordTab, - } - : { - email: values.email, - isPasskey: true, - } - - const response = await signIn('credentials', { - ...entityData, - redirect: false, - callbackUrl: '/dashboard', - }) - - if (response?.ok && typeof response.url === 'string') { - route.push(response.url) - } else { - const errorMsg = response?.error - ? response.error === 'CredentialsSignin' - ? 'Invalid Credentials' - : response.error - : 'Sign in failed. Please try again.' - setAlert(errorMsg) - } - } catch (error) { - setAlert('Something went wrong during sign in. Please try again.') - console.error('Sign in error:', error) - } - } - const authenticateWithPasskey = async (email: string): Promise => { try { setLoading(true) @@ -448,7 +455,7 @@ export default function SignInViewPage(): React.JSX.Element { Don’t have an account?{' '} Create one diff --git a/nextjs/src/features/dashboard/components/dashboard.tsx b/nextjs/src/features/dashboard/components/dashboard.tsx index cc888bda1..deccec966 100644 --- a/nextjs/src/features/dashboard/components/dashboard.tsx +++ b/nextjs/src/features/dashboard/components/dashboard.tsx @@ -40,7 +40,7 @@ export default function Dashboard(): React.JSX.Element { const [ecoMessage, setEcoMessage] = useState('') const [walletExists, setWalletExists] = useState(false) - const orgId = useAppSelector((state) => state.organization.orgId) + const orgId = useAppSelector((state) => state?.organization.orgId) const [, setUserOrg] = useState(null) const dispatch = useAppDispatch() diff --git a/nextjs/src/features/organization/components/CreateOrganizationModal.tsx b/nextjs/src/features/organization/components/CreateOrganizationModal.tsx index 407f14352..e50e03ddf 100644 --- a/nextjs/src/features/organization/components/CreateOrganizationModal.tsx +++ b/nextjs/src/features/organization/components/CreateOrganizationModal.tsx @@ -83,6 +83,8 @@ export default function OrganizationOnboarding(): React.JSX.Element { const searchParams = useSearchParams() const router = useRouter() const orgId = searchParams.get('orgId') + const redirectTo = searchParams.get('redirectTo') + const clientAlias = searchParams.get('clientAlias') const getCountries = async (): Promise => { const response = await getAllCountries() @@ -298,7 +300,11 @@ export default function OrganizationOnboarding(): React.JSX.Element { setSuccess(data.message as string) setTimeout(() => { - router.push(`/organizations/agent-config?orgId=${orgId}`) + const redirectUrl = + redirectTo && clientAlias + ? `/organizations/agent-config?orgId=${orgId}&redirectTo=${encodeURIComponent(redirectTo)}&clientAlias=${clientAlias}` + : `/organizations/agent-config?orgId=${orgId}` + router.push(redirectUrl) }, 3000) } else { setFailure(resCreateOrg as string) diff --git a/nextjs/src/features/organization/components/OrganizationDashboard.tsx b/nextjs/src/features/organization/components/OrganizationDashboard.tsx index 03e726cb9..fac92d668 100644 --- a/nextjs/src/features/organization/components/OrganizationDashboard.tsx +++ b/nextjs/src/features/organization/components/OrganizationDashboard.tsx @@ -11,6 +11,7 @@ import { TooltipTrigger, } from '@/components/ui/tooltip' import { getOrgDashboard, getOrganizationById } from '@/app/api/organization' +import { useRouter, useSearchParams } from 'next/navigation' import { AxiosResponse } from 'axios' import { Button } from '@/components/ui/button' @@ -21,7 +22,6 @@ import OrganizationDetails from './OrganizationDetails' import PageContainer from '@/components/layout/page-container' import { apiStatusCodes } from '@/config/CommonConstant' import { useAppSelector } from '@/lib/hooks' -import { useRouter } from 'next/navigation' type OrganizationDashboardProps = { orgId: string @@ -45,6 +45,14 @@ export const OrganizationDashboard = ({ const activeOrgId = selecteDropdownOrgId ?? orgId const orgIdOfDashboard = orgId + const searchParams = useSearchParams() + const redirectTo = searchParams?.get('redirectTo') + const clientAlias = searchParams?.get('clientAlias') + const redirectUrl = + redirectTo && clientAlias + ? `/organizations/agent-config?orgId=${orgIdOfDashboard}&redirectTo=${encodeURIComponent(redirectTo)}&clientAlias=${clientAlias}` + : `/organizations/agent-config?orgId=${orgIdOfDashboard}` + const fetchOrganizationDetails = async (): Promise => { if (!orgId) { return @@ -278,13 +286,7 @@ export const OrganizationDashboard = ({ ) : ( <> {showSetupButton && ( - )} diff --git a/nextjs/src/features/organization/connectionIssuance/components/FieldArray.tsx b/nextjs/src/features/organization/connectionIssuance/components/FieldArray.tsx index c3bb4e5f9..47795cd6f 100644 --- a/nextjs/src/features/organization/connectionIssuance/components/FieldArray.tsx +++ b/nextjs/src/features/organization/connectionIssuance/components/FieldArray.tsx @@ -137,7 +137,7 @@ const FieldArrayData = ({ >
diff --git a/nextjs/src/features/verification/components/ConnectionList.tsx b/nextjs/src/features/verification/components/ConnectionList.tsx index 4a886d2ab..cb13482ff 100644 --- a/nextjs/src/features/verification/components/ConnectionList.tsx +++ b/nextjs/src/features/verification/components/ConnectionList.tsx @@ -335,4 +335,4 @@ const ConnectionList = (props: { ) } -export default ConnectionList +export default ConnectionList \ No newline at end of file diff --git a/nextjs/src/features/wallet/WalletSpinupComponent.tsx b/nextjs/src/features/wallet/WalletSpinupComponent.tsx index 5bb1c4569..766cd52c4 100644 --- a/nextjs/src/features/wallet/WalletSpinupComponent.tsx +++ b/nextjs/src/features/wallet/WalletSpinupComponent.tsx @@ -51,6 +51,9 @@ const WalletSpinup = (): React.JSX.Element => { const searchParams = useSearchParams() const orgId = searchParams.get('orgId') + const redirectTo = searchParams.get('redirectTo') + const clientAlias = searchParams.get('clientAlias') + useEffect(() => { if (orgId) { setCurrentOrgId(orgId) @@ -319,7 +322,11 @@ const WalletSpinup = (): React.JSX.Element => { setWalletSpinStep(6) setWalletSpinupStatus() }, 1000) - router.push(`/organizations/${orgId}`) + + const redirectUrl = + redirectTo && clientAlias ? redirectTo : `/organizations/${orgId}` + + router.push(redirectUrl) // eslint-disable-next-line no-console console.log('invitation-url-creation-success', JSON.stringify(data)) }) diff --git a/nextjs/src/lib/authSlice.ts b/nextjs/src/lib/authSlice.ts index 752c82e0b..f7e51534c 100644 --- a/nextjs/src/lib/authSlice.ts +++ b/nextjs/src/lib/authSlice.ts @@ -33,5 +33,6 @@ const authSlice = createSlice({ }, }) -export const { setToken, setAuthToken, setRefreshToken, logout } = authSlice.actions +export const { setToken, setAuthToken, setRefreshToken, logout } = + authSlice.actions export default authSlice.reducer diff --git a/nextjs/src/utils/authOptions.ts b/nextjs/src/utils/authOptions.ts index 60587f160..8f17e368a 100644 --- a/nextjs/src/utils/authOptions.ts +++ b/nextjs/src/utils/authOptions.ts @@ -22,10 +22,29 @@ interface MyAuthOptions { session: Session token: JWT }): Promise + redirect({ + url, + baseUrl, + }: { + url: string + baseUrl: string + }): Promise } secret?: string session?: Partial | undefined pages?: { signIn: string } + cookies?: { + sessionToken: { + name: string + options: { + domain?: string + path: string + sameSite: 'lax' | 'strict' | 'none' + secure: boolean + httpOnly: boolean + } + } + } } interface jwtDataPayload extends JwtPayload { @@ -171,6 +190,25 @@ export const authOptions: MyAuthOptions = { session.expiresAt = token.expiresAt return session }, + + async redirect({ url, baseUrl }) { + try { + const redirectUrl = new URL(url) + + if ( + ['localhost', '127.0.0.1'].includes(redirectUrl.hostname) && + (redirectUrl.protocol === 'http:' || + redirectUrl.protocol === 'https:') + ) { + return redirectUrl.toString() + } + } catch (err) { + // If not a full URL, treat it as relative path + return new URL(url, baseUrl).toString() + } + + return baseUrl + }, }, secret: process.env.NEXTAUTH_SECRET, @@ -182,4 +220,17 @@ export const authOptions: MyAuthOptions = { pages: { signIn: '/auth/sign-in', }, + + cookies: { + sessionToken: { + name: 'next-auth.session-token', + options: { + httpOnly: true, + sameSite: 'lax', + path: '/', + secure: false, + domain: '.localhost', + }, + }, + }, }