356 lines
12 KiB
TypeScript
356 lines
12 KiB
TypeScript
|
|
import { FailedSignInResponse } from '@/proxy/auth/models'
|
|||
|
|
import ActionLink from '@/components/shared/ActionLink'
|
|||
|
|
import Captcha from '@/components/shared/Captcha'
|
|||
|
|
import PasswordInput from '@/components/shared/PasswordInput'
|
|||
|
|
import Alert from '@/components/ui/Alert'
|
|||
|
|
import Button from '@/components/ui/Button'
|
|||
|
|
import Checkbox from '@/components/ui/Checkbox'
|
|||
|
|
import { FormContainer, FormItem } from '@/components/ui/Form'
|
|||
|
|
import Input from '@/components/ui/Input'
|
|||
|
|
import PlatformLoginResultType from '@/constants/login.result.enum'
|
|||
|
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
|||
|
|
import { getTenantByNameDetail } from '@/services/tenant.service'
|
|||
|
|
import { useStoreActions, useStoreState } from '@/store'
|
|||
|
|
import useAuth from '@/utils/hooks/useAuth'
|
|||
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|||
|
|
import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
|
|||
|
|
import { TurnstileInstance } from '@marsidev/react-turnstile'
|
|||
|
|
import { Field, Form, Formik } from 'formik'
|
|||
|
|
import { motion } from 'framer-motion'
|
|||
|
|
import { useEffect, useRef, useState } from 'react'
|
|||
|
|
import { useNavigate } from 'react-router-dom'
|
|||
|
|
import * as Yup from 'yup'
|
|||
|
|
import { defaultDomain, getSubdomain } from '@/utils/subdomain'
|
|||
|
|
import { Helmet } from 'react-helmet'
|
|||
|
|
import { APP_NAME } from '@/constants/app.constant'
|
|||
|
|
|
|||
|
|
type SignInFormSchema = {
|
|||
|
|
userName: string
|
|||
|
|
password: string
|
|||
|
|
rememberMe: boolean
|
|||
|
|
twoFactorCode: string
|
|||
|
|
captchaResponse: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validationSchema = Yup.object().shape({
|
|||
|
|
userName: Yup.string().required('Please enter your user name'),
|
|||
|
|
password: Yup.string().required('Please enter your password'),
|
|||
|
|
rememberMe: Yup.bool(),
|
|||
|
|
twoFactor: Yup.boolean(),
|
|||
|
|
twoFactorCode: Yup.string().when('twoFactor', {
|
|||
|
|
is: true,
|
|||
|
|
then: (schema) => schema.required('Mail adresinize gönderilen doğrulama kodunu giriniz'),
|
|||
|
|
otherwise: (schema) => schema.notRequired(),
|
|||
|
|
}),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const Login = () => {
|
|||
|
|
const navigate = useNavigate()
|
|||
|
|
const isMultiTenant = useStoreState((a) => a.abpConfig.config?.multiTenancy.isEnabled)
|
|||
|
|
const { setTenant } = useStoreActions((a) => a.auth.tenant)
|
|||
|
|
|
|||
|
|
const UiVersion = useStoreState((state) => state.locale.currentUiVersion)
|
|||
|
|
const { setUiVersion } = useStoreActions((a) => a.locale)
|
|||
|
|
|
|||
|
|
const tenantName = useStoreState((state) => state.locale.currentTenantName)
|
|||
|
|
const { setTenantName } = useStoreActions((actions) => actions.locale)
|
|||
|
|
|
|||
|
|
const [message, setMessage] = useState('')
|
|||
|
|
const [error, setError] = useTimeOutMessage(300000)
|
|||
|
|
const [twoFactor, setTwoFactor] = useState(false)
|
|||
|
|
const [showCaptcha, setShowCaptcha] = useState(false)
|
|||
|
|
|
|||
|
|
const captchaRef = useRef<TurnstileInstance>(null)
|
|||
|
|
const { setWarning } = useStoreActions((actions) => actions.base.messages)
|
|||
|
|
const setWarningTimeout = (message: string) => {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
setWarning(message)
|
|||
|
|
}, 100)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const { signIn } = useAuth()
|
|||
|
|
const { translate } = useLocalization()
|
|||
|
|
|
|||
|
|
const onSignIn = async (
|
|||
|
|
values: SignInFormSchema,
|
|||
|
|
{ setSubmitting, isSubmitting, setFieldValue, setFieldTouched }: any,
|
|||
|
|
) => {
|
|||
|
|
if (isSubmitting) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setSubmitting(true)
|
|||
|
|
const { userName, password, twoFactorCode, captchaResponse } = values
|
|||
|
|
|
|||
|
|
const result = await signIn({
|
|||
|
|
userName,
|
|||
|
|
password,
|
|||
|
|
twoFactorCode,
|
|||
|
|
captchaResponse,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (result.status === 'error') {
|
|||
|
|
setError(result.message)
|
|||
|
|
} else {
|
|||
|
|
setError('')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (result.status === 'failed') {
|
|||
|
|
const data = result.data as FailedSignInResponse
|
|||
|
|
setError(data.description)
|
|||
|
|
|
|||
|
|
if (data.pResult === PlatformLoginResultType.NotAllowed) {
|
|||
|
|
setWarningTimeout(data.description)
|
|||
|
|
navigate(ROUTES_ENUM.authenticated.sendConfirmationCode)
|
|||
|
|
} else {
|
|||
|
|
setWarning('')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (
|
|||
|
|
data.pResult === PlatformLoginResultType.RequiresTwoFactor ||
|
|||
|
|
data.pResult === PlatformLoginResultType.WrongTwoFactorCode
|
|||
|
|
) {
|
|||
|
|
if (data.pResult === PlatformLoginResultType.RequiresTwoFactor) {
|
|||
|
|
setError(undefined)
|
|||
|
|
setMessage('Mail adresinize gönderilen doğrulama kodunu giriniz')
|
|||
|
|
} else {
|
|||
|
|
setMessage('')
|
|||
|
|
}
|
|||
|
|
setTwoFactor(true)
|
|||
|
|
setFieldValue('twoFactor', true)
|
|||
|
|
setFieldTouched('twoFactorCode', false)
|
|||
|
|
} else {
|
|||
|
|
setMessage('')
|
|||
|
|
setTwoFactor(false)
|
|||
|
|
setFieldValue('twoFactor', false)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setShowCaptcha(data.pResult === PlatformLoginResultType.ShowCaptcha)
|
|||
|
|
if (data.pResult === PlatformLoginResultType.ShowCaptcha) {
|
|||
|
|
captchaRef.current?.reset()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (
|
|||
|
|
data.pResult === PlatformLoginResultType.ShouldChangePasswordOnNextLogin ||
|
|||
|
|
data.pResult === PlatformLoginResultType.ShouldChangePasswordPeriodic
|
|||
|
|
) {
|
|||
|
|
setWarningTimeout(data.description)
|
|||
|
|
navigate(ROUTES_ENUM.authenticated.forgotPassword)
|
|||
|
|
} else {
|
|||
|
|
setWarning('')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (data.pResult === PlatformLoginResultType.LoginEndDateDue) {
|
|||
|
|
setWarningTimeout(data.description)
|
|||
|
|
navigate(ROUTES_ENUM.authenticated.sendExtendLogin)
|
|||
|
|
} else {
|
|||
|
|
setWarning('')
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Temizlik
|
|||
|
|
setTwoFactor(false)
|
|||
|
|
setFieldValue('twoFactorCode', '')
|
|||
|
|
setFieldValue('twoFactor', false)
|
|||
|
|
setShowCaptcha(false)
|
|||
|
|
setError('')
|
|||
|
|
setMessage('')
|
|||
|
|
|
|||
|
|
//Tenant belirlenmişse
|
|||
|
|
fetchDataByName(tenantName || '')
|
|||
|
|
|
|||
|
|
// Versiyon kontrolü
|
|||
|
|
findUiVersion()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setSubmitting(false)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const fetchDataByName = async (name: string) => {
|
|||
|
|
if (name) {
|
|||
|
|
const response = await getTenantByNameDetail(name)
|
|||
|
|
|
|||
|
|
if (response.data) {
|
|||
|
|
setTenant({ tenantId: response.data.id, tenantName: response.data.name, menuGroup: response.data.menuGroup });
|
|||
|
|
} else {
|
|||
|
|
setTenant(undefined)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
setTenant(undefined)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const subDomainName = getSubdomain()
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (subDomainName) {
|
|||
|
|
setTenantName(subDomainName)
|
|||
|
|
fetchDataByName(subDomainName)
|
|||
|
|
}
|
|||
|
|
}, [subDomainName])
|
|||
|
|
|
|||
|
|
const tenantStyle: React.CSSProperties | undefined =
|
|||
|
|
subDomainName && subDomainName !== defaultDomain
|
|||
|
|
? {
|
|||
|
|
opacity: 0,
|
|||
|
|
position: 'absolute',
|
|||
|
|
pointerEvents: 'none',
|
|||
|
|
height: 0,
|
|||
|
|
margin: 0,
|
|||
|
|
padding: 0,
|
|||
|
|
border: 'none',
|
|||
|
|
}
|
|||
|
|
: undefined
|
|||
|
|
|
|||
|
|
const findUiVersion = async () => {
|
|||
|
|
try {
|
|||
|
|
const res = await fetch(`/version.json?ts=${Date.now()}`)
|
|||
|
|
const latest = (await res.json())?.releases?.[0]?.version
|
|||
|
|
if (latest && UiVersion !== latest) {
|
|||
|
|
setUiVersion(latest)
|
|||
|
|
navigate(ROUTES_ENUM.protected.admin.changeLog)
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.warn('Versiyon okunamadı', e)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<Helmet
|
|||
|
|
titleTemplate={`%s | ${APP_NAME}`}
|
|||
|
|
title={translate('AbpAccount::' + 'Login')}
|
|||
|
|
defaultTitle={APP_NAME}
|
|||
|
|
></Helmet>
|
|||
|
|
<motion.div
|
|||
|
|
initial={{ opacity: 0, x: 100 }}
|
|||
|
|
animate={{ opacity: 1, x: 0 }}
|
|||
|
|
transition={{ duration: 0.5, origin: 1 }}
|
|||
|
|
>
|
|||
|
|
<div className="mb-8 relative text-center">
|
|||
|
|
<h3 className="mb-1 m-5">{translate('::Abp.Account.WelcomeBack')}</h3>
|
|||
|
|
<p>{translate('::Abp.Account.WelcomeBack.Message')}</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{isMultiTenant && (
|
|||
|
|
<>
|
|||
|
|
<label className="form-label mb-2" style={tenantStyle}>
|
|||
|
|
{translate('::Organization')}
|
|||
|
|
</label>
|
|||
|
|
<div className="mb-4">
|
|||
|
|
<Input
|
|||
|
|
placeholder={translate('::Organization')}
|
|||
|
|
value={tenantName}
|
|||
|
|
onChange={(e) => setTenantName(e.target.value)}
|
|||
|
|
style={tenantStyle}
|
|||
|
|
aria-hidden={subDomainName && subDomainName !== defaultDomain ? 'true' : 'false'}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
<div>
|
|||
|
|
<Formik
|
|||
|
|
initialValues={{
|
|||
|
|
userName: '',
|
|||
|
|
password: '',
|
|||
|
|
twoFactorCode: '',
|
|||
|
|
rememberMe: true,
|
|||
|
|
captchaResponse: '',
|
|||
|
|
}}
|
|||
|
|
validationSchema={validationSchema}
|
|||
|
|
onSubmit={onSignIn}
|
|||
|
|
>
|
|||
|
|
{({ touched, errors, isSubmitting, setFieldValue }) => (
|
|||
|
|
<Form>
|
|||
|
|
<FormContainer>
|
|||
|
|
{!twoFactor && (
|
|||
|
|
<FormItem
|
|||
|
|
label={translate('::Abp.Account.EmailAddress')}
|
|||
|
|
invalid={errors.userName && touched.userName}
|
|||
|
|
errorMessage={errors.userName}
|
|||
|
|
>
|
|||
|
|
<Field
|
|||
|
|
type="text"
|
|||
|
|
autoComplete="off"
|
|||
|
|
name="userName"
|
|||
|
|
placeholder={translate('::Abp.Account.EmailAddress')}
|
|||
|
|
component={Input}
|
|||
|
|
/>
|
|||
|
|
</FormItem>
|
|||
|
|
)}
|
|||
|
|
{!twoFactor && (
|
|||
|
|
<FormItem
|
|||
|
|
label={translate('::Abp.Identity.Password')}
|
|||
|
|
invalid={errors.password && touched.password}
|
|||
|
|
errorMessage={errors.password}
|
|||
|
|
>
|
|||
|
|
<Field
|
|||
|
|
autoComplete="off"
|
|||
|
|
name="password"
|
|||
|
|
placeholder={translate('::Abp.Identity.Password')}
|
|||
|
|
component={PasswordInput}
|
|||
|
|
/>
|
|||
|
|
</FormItem>
|
|||
|
|
)}
|
|||
|
|
{twoFactor && (
|
|||
|
|
<FormItem
|
|||
|
|
label={translate('::Abp.Account.2FACode')}
|
|||
|
|
invalid={errors.twoFactorCode && touched.twoFactorCode}
|
|||
|
|
errorMessage={errors.twoFactorCode}
|
|||
|
|
>
|
|||
|
|
<Field
|
|||
|
|
autoComplete="off"
|
|||
|
|
name="twoFactorCode"
|
|||
|
|
placeholder={translate('::Abp.Account.2FACode')}
|
|||
|
|
component={Input}
|
|||
|
|
/>
|
|||
|
|
</FormItem>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<div className="flex justify-between mb-6">
|
|||
|
|
<Field className="mb-0" name="rememberMe" component={Checkbox}>
|
|||
|
|
{translate('::Abp.Account.RememberMe')}
|
|||
|
|
</Field>
|
|||
|
|
<ActionLink to={ROUTES_ENUM.authenticated.forgotPassword}>
|
|||
|
|
{translate('::Abp.Account.ForgotPassword')}
|
|||
|
|
</ActionLink>
|
|||
|
|
</div>
|
|||
|
|
{showCaptcha && (
|
|||
|
|
<Captcha
|
|||
|
|
ref={captchaRef}
|
|||
|
|
onError={() => setFieldValue('captchaResponse', '')}
|
|||
|
|
onExpire={() => setFieldValue('captchaResponse', '')}
|
|||
|
|
onSuccess={(token: string) => setFieldValue('captchaResponse', token)}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{message && (
|
|||
|
|
<Alert showIcon className="mb-4" type="success">
|
|||
|
|
{message}
|
|||
|
|
</Alert>
|
|||
|
|
)}
|
|||
|
|
{error && (
|
|||
|
|
<Alert showIcon className="mb-4" type="danger">
|
|||
|
|
{error}
|
|||
|
|
</Alert>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<Button block loading={isSubmitting} variant="solid" type="submit">
|
|||
|
|
{isSubmitting ? 'Signing in...' : 'Sign In'}
|
|||
|
|
</Button>
|
|||
|
|
<div className="mt-4 text-center">
|
|||
|
|
<span>{translate('::Abp.Account.SignUp.Message')} </span>
|
|||
|
|
<ActionLink to={ROUTES_ENUM.authenticated.register}>
|
|||
|
|
{translate('::Abp.Account.Register')}
|
|||
|
|
</ActionLink>
|
|||
|
|
</div>
|
|||
|
|
</FormContainer>
|
|||
|
|
</Form>
|
|||
|
|
)}
|
|||
|
|
</Formik>
|
|||
|
|
</div>
|
|||
|
|
</motion.div>
|
|||
|
|
</>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default Login
|