diff --git a/.env b/.env index 0eab989..acaef7f 100644 --- a/.env +++ b/.env @@ -1,7 +1,8 @@ VITE_GOOGLE_CLIENT_ID=https://272098283932-bft2gvlgjn8edopg0lnqjq1i9ekdmipt.apps.googleusercontent.com VITE_DEFUALT_AUTH_RETURN_URL=/setting/profile +VITE_APP_URL=https://account.business-harmony.com VITE_API_URL=https://accounts.business-harmony.com/api/ VITE_IDENTITY_URL=https://accounts.business-harmony.com/connect/token VITE_IDENTITY_CLIENT_ID=harmony_identity -VITE_IDENTITY_SCOPE=openid profile offline_access harmony_identity +VITE_IDENTITY_SCOPE=openid profile offline_access IMAGE_BASE_URL=https://accounts.business-harmony.com/uploads/ diff --git a/src/features/authentication/api/identityAPI.ts b/src/features/authentication/api/identityAPI.ts index 028cfd6..214891d 100644 --- a/src/features/authentication/api/identityAPI.ts +++ b/src/features/authentication/api/identityAPI.ts @@ -1,6 +1,10 @@ import apiClient from '@/lib/apiClient'; -export interface GenerateTokenWithPassword { +export interface GenerateToken { + client_id: string; +} + +export interface GenerateTokenWithPassword extends GenerateToken { username: string; password: string; } @@ -18,8 +22,11 @@ export const generateTokenWithPassword = ( ) => { const body = new URLSearchParams(); body.set('grant_type', 'password'); - body.set('client_id', import.meta.env.VITE_IDENTITY_CLIENT_ID); - body.set('scope', import.meta.env.VITE_IDENTITY_SCOPE); + body.set('client_id', request.client_id); + body.set( + 'scope', + import.meta.env.VITE_IDENTITY_SCOPE + ' ' + request.client_id, + ); body.set('username', request.username); body.set('password', request.password); @@ -34,7 +41,7 @@ export const generateTokenWithPassword = ( ); }; -export interface GenerateTokenWithOTP { +export interface GenerateTokenWithOTP extends GenerateToken { email?: string; phonenumber?: string; otp: string; @@ -43,8 +50,11 @@ export interface GenerateTokenWithOTP { export const generateTokenWithOtp = (request: GenerateTokenWithOTP) => { const body = new URLSearchParams(); body.set('grant_type', 'otp'); - body.set('client_id', import.meta.env.VITE_IDENTITY_CLIENT_ID); - body.set('scope', import.meta.env.VITE_IDENTITY_SCOPE); + body.set('client_id', request.client_id); + body.set( + 'scope', + import.meta.env.VITE_IDENTITY_SCOPE + ' ' + request.client_id, + ); if (request.email) body.set('email', request.email); if (request.phonenumber) body.set('phonenumber', request.phonenumber); body.set('otp_code', request.otp); @@ -60,15 +70,18 @@ export const generateTokenWithOtp = (request: GenerateTokenWithOTP) => { ); }; -export interface GenerateTokenWithGoogle { +export interface GenerateTokenWithGoogle extends GenerateToken { idToken: string; } export const generateTokenWithGoogle = (request: GenerateTokenWithGoogle) => { const body = new URLSearchParams(); body.set('grant_type', 'google'); - body.set('client_id', import.meta.env.VITE_IDENTITY_CLIENT_ID); - body.set('scope', import.meta.env.VITE_IDENTITY_SCOPE); + body.set('client_id', request.client_id); + body.set( + 'scope', + import.meta.env.VITE_IDENTITY_SCOPE + ' ' + request.client_id, + ); body.set('idtoken', request.idToken); return apiClient.post( diff --git a/src/features/authentication/components/AuthenticationSteps/AuthenticationSteps.tsx b/src/features/authentication/components/AuthenticationSteps/AuthenticationSteps.tsx index 9b57c60..97beeae 100644 --- a/src/features/authentication/components/AuthenticationSteps/AuthenticationSteps.tsx +++ b/src/features/authentication/components/AuthenticationSteps/AuthenticationSteps.tsx @@ -3,7 +3,6 @@ import { LoginRegisterForm } from './LoginRegiserForm'; import type { AuthFactory, AuthMode, - AuthRedirector, AuthStep, AuthType, } from '../../types/authTypes'; @@ -15,7 +14,8 @@ import { UserStatus, type LoginResult } from '../../types/userTypes'; import type { CountryCode } from '@/types/commonTypes'; import { VerifyPhoneNumber } from './VerifyPhoneNumber'; import { useNavigate, useSearchParams } from 'react-router-dom'; -import { REFRESH_TOKEN_KEY } from '@/providers/AuthProvider'; +import type { GenerateTokenResponse } from '../../api/identityAPI'; +import { useAuth } from '@/hooks/useAuth'; export const AuthenticationSteps = (): JSX.Element => { const navigate = useNavigate(); @@ -29,6 +29,8 @@ export const AuthenticationSteps = (): JSX.Element => { useState('+98'); const [addedPhoneNumberValue, setAddedPhoneNumberValue] = useState(''); + const [memoryTokenRes, setMemoryTokenRes] = useState(); + const { login } = useAuth(); const authFactory: AuthFactory = useMemo(() => { const redirectUrl = searchParams.get('redirect_url'); @@ -37,9 +39,13 @@ export const AuthenticationSteps = (): JSX.Element => { if (!clientId) { const defaultFactory: AuthFactory = { clientId: import.meta.env.VITE_IDENTITY_CLIENT_ID, - redirectUrl: 'accounts.', - accountsApplication - setLocalToken: true, + redirectUrl: import.meta.env.VITE_APP_URL, + isCurrentApplication: function () { + return this.clientId === import.meta.env.VITE_IDENTITY_CLIENT_ID; + }, + getFullRedirectUrl: function (token: string) { + return this.redirectUrl + '?token=' + token; + }, }; return defaultFactory; @@ -48,7 +54,9 @@ export const AuthenticationSteps = (): JSX.Element => { const resFactory: AuthFactory = { clientId: clientId, redirectUrl: redirectUrl as string, - setLocalToken: true, + isCurrentApplication: function () { + return this.clientId === import.meta.env.VITE_IDENTITY_CLIENT_ID; + }, getFullRedirectUrl: function (token: string) { return this.redirectUrl + '?token=' + token; }, @@ -82,17 +90,22 @@ export const AuthenticationSteps = (): JSX.Element => { const handleUserLoggedIn = ( loginResult: LoginResult, - refreshToken: string, + tokenResponse: GenerateTokenResponse, ) => { + setMemoryTokenRes(tokenResponse); + if (authFactory.isCurrentApplication()) { + login(tokenResponse); + } + if (loginResult.registeredWithOutPhoneNumber) { setCurrentStep('addPhoneNumber'); return; } if (!loginResult.completedUserInformation) { - if (authFactory.redirectUrl) { + if (!authFactory.isCurrentApplication()) { navigate( - `/signup?returnUrl=${authFactory.getFullRedirectUrl(refreshToken)}`, + `/signup?returnUrl=${authFactory.getFullRedirectUrl(tokenResponse.refresh_token)}`, ); } else { navigate(`/signup`); @@ -100,13 +113,13 @@ export const AuthenticationSteps = (): JSX.Element => { return; } - redirectToReturnUrl(refreshToken); + redirectToReturnUrl(tokenResponse.refresh_token); }; - const handlePhoneNumberVerified = (refreshToken: string) => { - if (authFactory.redirectUrl) { + const handlePhoneNumberVerified = () => { + if (!authFactory.isCurrentApplication()) { navigate( - `/signup?returnUrl=${authFactory.getFullRedirectUrl(refreshToken)}`, + `/signup?returnUrl=${authFactory.getFullRedirectUrl(memoryTokenRes?.refresh_token as string)}`, ); } else { navigate(`/signup`); @@ -114,7 +127,7 @@ export const AuthenticationSteps = (): JSX.Element => { }; const redirectToReturnUrl = (refreshToken: string) => { - if (!authFactory.redirectUrl) { + if (authFactory.isCurrentApplication()) { navigate(import.meta.env.VITE_DEFUALT_AUTH_RETURN_URL); } else { if (authMode === 'register') { @@ -131,7 +144,7 @@ export const AuthenticationSteps = (): JSX.Element => { <> {currentStep === 'emailOrPhone' && ( { {currentStep === 'verify' && ( setCurrentStep('emailOrPhone')} @@ -157,7 +170,7 @@ export const AuthenticationSteps = (): JSX.Element => { {currentStep === 'enterPassword' && ( void; onLoginWithOTP: () => void; - onLoggedIn: (loginResult: LoginResult) => void; + onLoggedIn: ( + loginResult: LoginResult, + tokenResponse: GenerateTokenResponse, + ) => void; emailOrPhone: string; authType: AuthType; loginRegisterValue: string; countryCode: CountryCode; - authReturnUrl: string; + authFactory: AuthFactory; } export const EnterPasswordForm = ({ @@ -43,7 +49,7 @@ export const EnterPasswordForm = ({ authType, loginRegisterValue, countryCode, - authReturnUrl, + authFactory, }: EnterPasswordFormProps) => { const { t } = useTranslation('authentication'); const [passValue, setPassValue] = useState(''); @@ -57,7 +63,6 @@ export const EnterPasswordForm = ({ useApi(sendEmailOtp); const { loading: loginWithPassLoading, execute: loginWithPassCall } = useApi(loginWithPassword); - const auth = useAuth(); const handleBlur = () => { setInputTouched(true); @@ -72,7 +77,7 @@ export const EnterPasswordForm = ({ authType === 'phone' ? countryCode + loginRegisterValue : undefined, email: authType === 'email' ? loginRegisterValue : undefined, password: passValue, - returnUrl: authReturnUrl, + returnUrl: authFactory.redirectUrl, }; const res = await loginWithPassCall(apiRequest); @@ -82,12 +87,10 @@ export const EnterPasswordForm = ({ const tokenRes = await generateTokenWithPassword({ username: apiRequest.email ?? (apiRequest.phoneNumber as string), password: apiRequest.password, - }); - auth.login({ - ...tokenRes.data, + client_id: authFactory.clientId, }); - onLoggedIn(res); + onLoggedIn(res, tokenRes.data); toast({ message: t('verify.youHaveSuccessfullyLoggedIn'), severity: 'success', @@ -134,7 +137,7 @@ export const EnterPasswordForm = ({ endIcon={} onClick={onEditValue} > - {emailOrPhone} + {authType === 'phone' ? countryCode + emailOrPhone : emailOrPhone} diff --git a/src/features/authentication/components/AuthenticationSteps/GoogleAuthentication.tsx b/src/features/authentication/components/AuthenticationSteps/GoogleAuthentication.tsx index eab83b6..7fedd8d 100644 --- a/src/features/authentication/components/AuthenticationSteps/GoogleAuthentication.tsx +++ b/src/features/authentication/components/AuthenticationSteps/GoogleAuthentication.tsx @@ -10,14 +10,20 @@ import { loginOrSignUpWithGoogle } from '../../api/authorizationAPI'; import { Google } from 'iconsax-react'; import { Icon, useToast } from '@rkheftan/harmony-ui'; import { useApi } from '@/hooks/useApi'; -import { generateTokenWithGoogle } from '../../api/identityAPI'; +import { + generateTokenWithGoogle, + type GenerateTokenResponse, +} from '../../api/identityAPI'; import { useAuth } from '@/hooks/useAuth'; import type { AuthFactory } from '../../types/authTypes'; export interface GoogleAuthenticationProps { disabled: boolean; authFactory: AuthFactory; - onGoogleAuthenticated: (loginResult: LoginResult) => void; + onGoogleAuthenticated: ( + loginResult: LoginResult, + tokenResponse: GenerateTokenResponse, + ) => void; } export const GoogleAuthentication = ({ @@ -30,7 +36,6 @@ export const GoogleAuthentication = ({ useApi(loginOrSignUpWithGoogle); const toast = useToast(); const clientRef = useRef(null); - const auth = useAuth(); useEffect(() => { const script = document.createElement('script'); @@ -55,12 +60,12 @@ export const GoogleAuthentication = ({ if (!res) return; if (res.success) { - const tokenRes = await generateTokenWithGoogle(apiRequest); - auth.login({ - ...tokenRes.data, + const tokenRes = await generateTokenWithGoogle({ + ...apiRequest, + client_id: authFactory.clientId, }); - onGoogleAuthenticated(res); + onGoogleAuthenticated(res, tokenRes.data); } else { toast({ message: t('loginForm.googleAuthenticationFailed'), diff --git a/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx b/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx index 53db6d9..dceb8b5 100644 --- a/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx +++ b/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx @@ -13,6 +13,7 @@ import { GoogleAuthentication } from './GoogleAuthentication'; import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber'; import { useToast } from '@rkheftan/harmony-ui'; import { useApi } from '@/hooks/useApi'; +import type { GenerateTokenResponse } from '../../api/identityAPI'; export interface LoginRegisterFormProps { loginRegisterValue: string; @@ -22,7 +23,10 @@ export interface LoginRegisterFormProps { authType: AuthType; setAuthType: Dispatch; onLoginRegisterSubmit: (value: string, userStatus: UserStatus) => void; - onGoogleAuthenticated: (loginResult: LoginResult) => void; + onGoogleAuthenticated: ( + loginResult: LoginResult, + tokenResponse: GenerateTokenResponse, + ) => void; authFactory: AuthFactory; } @@ -156,7 +160,7 @@ export function LoginRegisterForm({ diff --git a/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx b/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx index 62a14f4..319e90f 100644 --- a/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx +++ b/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'; import { Box, Button, Stack, Typography } from '@mui/material'; import { Edit2 } from 'iconsax-react'; import DigitInput from '@/components/DigitsInput'; -import type { AuthMode, AuthType } from '../../types/authTypes'; +import type { AuthFactory, AuthMode, AuthType } from '../../types/authTypes'; import { useEffect, useState } from 'react'; import { AuthenticationCard } from '../AuthenticationCard'; import type { LoginRequest, LoginResult } from '../../types/userTypes'; @@ -14,7 +14,10 @@ import { import type { CountryCode } from '@/types/commonTypes'; import { Icon, useToast } from '@rkheftan/harmony-ui'; import { useApi } from '@/hooks/useApi'; -import { generateTokenWithOtp } from '../../api/identityAPI'; +import { + generateTokenWithOtp, + type GenerateTokenResponse, +} from '../../api/identityAPI'; import { useAuth } from '@/hooks/useAuth'; interface OtpVerifyFormProps { @@ -23,8 +26,11 @@ interface OtpVerifyFormProps { authType: AuthType; authMode: AuthMode; onEditValue: () => void; - onOTPVerified: (loginResult: LoginResult) => void; - authReturnUrl: string; + onOTPVerified: ( + loginResult: LoginResult, + tokenResponse: GenerateTokenResponse, + ) => void; + authFactory: AuthFactory; } export function OtpVerifyForm({ @@ -34,7 +40,7 @@ export function OtpVerifyForm({ authMode, onEditValue, onOTPVerified, - authReturnUrl, + authFactory, }: OtpVerifyFormProps) { const [otpCode, setOtpCode] = useState(''); const [otpDigitInvalid, setOtpDigitInvalid] = useState(false); @@ -49,7 +55,6 @@ export function OtpVerifyForm({ useApi(sendEmailOtp); const { loading: loginSignUpLoading, execute: loginSignUpCall } = useApi(loginOrSignUpWithOtp); - const auth = useAuth(); useEffect(() => { let interval: NodeJS.Timeout; @@ -96,7 +101,7 @@ export function OtpVerifyForm({ otpCode: otpCode, phoneNumber: authType === 'phone' ? countryCode + value : undefined, email: authType === 'email' ? value : undefined, - returnUrl: authReturnUrl, + returnUrl: authFactory.redirectUrl, }; const res = await loginSignUpCall(loginRequest); @@ -111,12 +116,10 @@ export function OtpVerifyForm({ email: loginRequest.email, phonenumber: loginRequest.phoneNumber, otp: loginRequest.otpCode, - }); - auth.login({ - ...tokenRes.data, + client_id: authFactory.clientId, }); - onOTPVerified(res); + onOTPVerified(res, tokenRes.data); toast({ message: diff --git a/src/features/authentication/types/authTypes.ts b/src/features/authentication/types/authTypes.ts index d2ccc48..7effbdf 100644 --- a/src/features/authentication/types/authTypes.ts +++ b/src/features/authentication/types/authTypes.ts @@ -11,7 +11,7 @@ export type AuthStep = export interface AuthFactory { clientId: string; - redirectUrl: string | null; - accountsApplication: boolean; + redirectUrl: string; + isCurrentApplication: () => boolean; getFullRedirectUrl: (token: string) => string; }