Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nextjs/src/app/api/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface IUserSignInData {
email: string | undefined
isPasskey: boolean
password?: string
isPassword?: boolean
}
export interface IEmailVerifyData {
verificationCode: string
Expand Down
69 changes: 28 additions & 41 deletions nextjs/src/features/auth/components/user-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,17 @@ import {
passwordEncryption,
} from '@/app/api/Auth'
import React, { useState } from 'react'
import {
generateAuthenticationOption,
verifyAuthentication,
} from '@/app/api/Fido'
import { setRefreshToken, setToken } from '@/lib/authSlice'
import { signIn, useSession } from 'next-auth/react'

import { AlertComponent } from '@/components/AlertComponent'
import { AxiosResponse } from 'axios'
import { Button } from '@/components/ui/button'
import { IVerifyRegistrationObj } from '@/components/profile/interfaces'
import { Icons } from '@/config/svgs/Auth'
import { Input } from '@/components/ui/input'
import Link from 'next/link'
import { apiStatusCodes } from '@/config/CommonConstant'
import { generateAuthenticationOption } from '@/app/api/Fido'
import { setProfile } from '@/lib/profileSlice'
import { signIn } from 'next-auth/react'
import { startAuthentication } from '@simplewebauthn/browser'
import { useDispatch } from 'react-redux'
import { useForm } from 'react-hook-form'
Expand Down Expand Up @@ -61,6 +56,7 @@ export default function SignInViewPage(): React.JSX.Element {
const [alert, setAlert] = useState<null | string>(null)
const [success, setSuccess] = useState<null | string>(null)

const { data: session } = useSession()
const dispatch = useDispatch()
const route = useRouter()
const signInForm = useForm<SignInFormValues>({
Expand All @@ -77,7 +73,7 @@ export default function SignInViewPage(): React.JSX.Element {
): Promise<
| {
role: { name: string }
orgId: string
orgId: string | null
}
| undefined
> => {
Expand All @@ -87,10 +83,11 @@ export default function SignInViewPage(): React.JSX.Element {
const { data } = response as AxiosResponse

if (data?.data?.userOrgRoles?.length > 0) {
const role = data?.data?.userOrgRoles.find(
(item: { orgRole: { name: PlatformRoles } }) =>
const platformAdminRole = data?.data?.userOrgRoles.find(
(item: { orgRole: { name: string } }) =>
item.orgRole.name === PlatformRoles.platformAdmin,
)
const selectedRole = platformAdminRole || data?.data?.userOrgRoles[0]

const permissionArray: string[] = []
data?.data?.userOrgRoles?.forEach(
Expand All @@ -114,16 +111,16 @@ export default function SignInViewPage(): React.JSX.Element {
const orgId = orgWithValidId?.orgId ?? null

return {
role: role?.orgRole ?? '',
role: { name: selectedRole.orgRole.name },
orgId,
}
} else {
// eslint-disable-next-line no-console
console.error('No roles found for the user')
return undefined
}
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error fetching user details', error)
return undefined
}
}

Expand All @@ -137,6 +134,7 @@ export default function SignInViewPage(): React.JSX.Element {
email: values.email,
password: await passwordEncryption(values.password || ''),
isPasskey: false,
isPassword: isPasswordTab,
}
: {
email: values.email,
Expand Down Expand Up @@ -165,19 +163,6 @@ export default function SignInViewPage(): React.JSX.Element {
}
}

const verifyAuthenticationMethod = async (
verifyAuthenticationObj: IVerifyRegistrationObj,
userData: { userName: string },
): Promise<string | AxiosResponse> => {
try {
const res = verifyAuthentication(verifyAuthenticationObj, userData)
return await res
} catch (error) {
setFidoLoader(false)
throw error
}
}

const authenticateWithPasskey = async (email: string): Promise<void> => {
try {
setLoading(true)
Expand Down Expand Up @@ -208,21 +193,23 @@ export default function SignInViewPage(): React.JSX.Element {
...attResp,
challangeId: challengeId,
}
const entityData = {
verifyAuthenticationObj: JSON.stringify(verifyAuthenticationObj),
obj: JSON.stringify(obj),
isPasswordTab,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the passkey authentication flow, you're sending 'isPasswordTab' in entityData. Use 'isPassword: isPasswordTab' instead to match the backend's expectation.

Suggested change
isPasswordTab,
isPassword: isPasswordTab,

}

const verificationResp = await verifyAuthenticationMethod(
verifyAuthenticationObj,
obj,
)
const { data } = verificationResp as AxiosResponse

if (data?.statusCode === apiStatusCodes.API_STATUS_SUCCESS) {
const token = data?.data?.access_token
const refreshToken = data?.data?.refresh_token

dispatch(setToken(token))
dispatch(setRefreshToken(refreshToken))
const verificationResp = await signIn('credentials', {
...entityData,
redirect: false,
callbackUrl: '/dashboard',
})

const userRole = await getUserDetails(token)
if (verificationResp?.ok && verificationResp?.status === 200) {
if (!session?.accessToken) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relying on session?.accessToken immediately after signIn might result in stale or missing data. Consider using the returned token from the signIn callback or await a proper session update.

return
}
const userRole = await getUserDetails(session?.accessToken)

if (!userRole?.role?.name) {
setAlert('Invalid user role')
Expand All @@ -234,8 +221,8 @@ export default function SignInViewPage(): React.JSX.Element {
? '/dashboard/settings'
: '/dashboard',
)
} else if (data?.error) {
setFidoUserError(data?.error)
} else if (verificationResp?.error) {
setFidoUserError(verificationResp?.error)
} else {
setFidoUserError('Something went wrong during verification')
}
Expand Down
74 changes: 60 additions & 14 deletions nextjs/src/utils/authOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { Provider } from 'next-auth/providers/index'
import { apiRoutes } from '@/config/apiRoutes'
import { envConfig } from '@/config/envConfig'

type PasskeyUser = {
userName: string
email?: string
}
interface MyAuthOptions {
providers: Provider[]
callbacks?: {
Expand Down Expand Up @@ -41,28 +45,70 @@ export const authOptions: MyAuthOptions = {
},
password: { label: 'Password', type: 'password' },
isPasskey: { label: 'IsPasskey', type: 'boolean' },
isPassword: { label: 'isPassword', type: 'boolean' },
obj: { label: 'obj', type: 'string' },
verifyAuthenticationObj: {
label: 'verifyAuthenticationObj',
type: 'string',
},
},
async authorize(credentials) {
let parsedVerifyAuthObj: Record<string, unknown> = {}
let parsedObj: PasskeyUser = { userName: '' }
try {
const { email, password, isPasskey } = credentials || {}

const sanitizedPayload = {
const {
email,
password,
isPasskey,
isPassword,
verifyAuthenticationObj,
obj,
} = credentials || {}
let sanitizedPayload = {}
if (isPassword) {
sanitizedPayload = {
email,
password,
isPasskey,
}
} else {
try {
parsedVerifyAuthObj = JSON.parse(verifyAuthenticationObj || '{}')
parsedObj = JSON.parse(obj || '{}')
} catch (err) {
console.error('Failed to parse incoming JSON strings:', err)
return null
}
sanitizedPayload = {
...parsedVerifyAuthObj,
}
}
// eslint-disable-next-line init-declarations
let res
if (isPassword) {
res = await fetch(
`${envConfig.NEXT_PUBLIC_BASE_URL}${apiRoutes.auth.sinIn}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sanitizedPayload),
},
)
} else {
if (obj) {
res = await fetch(
`${envConfig.NEXT_PUBLIC_BASE_URL}/${apiRoutes.auth.fidoVerifyAuthentication}${parsedObj.userName}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sanitizedPayload),
},
)
}
}

const res = await fetch(
`${envConfig.NEXT_PUBLIC_BASE_URL}${apiRoutes.auth.sinIn}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sanitizedPayload),
},
)

if (!res.ok) {
console.error('Error fetching user:', res.statusText)
if (!res?.ok) {
console.error('Error fetching user:', res?.statusText)
return null
}

Expand Down