feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
@@ -18,9 +18,8 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import axios from 'axios'
|
||||
import { Loader2, LogIn, KeyRound } from 'lucide-react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
@@ -47,14 +46,18 @@ import { loginFormSchema } from '@/features/auth/constants'
|
||||
import { useAuthRedirect } from '@/features/auth/hooks/use-auth-redirect'
|
||||
import { useTurnstile } from '@/features/auth/hooks/use-turnstile'
|
||||
import { beginPasskeyLogin, finishPasskeyLogin } from '@/features/auth/passkey'
|
||||
import {
|
||||
requestPasskeyAssertion,
|
||||
rememberPasskeyRPID,
|
||||
type PasskeyDomains,
|
||||
} from '@/features/auth/passkey/assertion'
|
||||
import { PasskeyDomainSelector } from '@/features/auth/passkey/components/passkey-domain-selector'
|
||||
import type { AuthFormProps } from '@/features/auth/types'
|
||||
import { useStatus } from '@/hooks/use-status'
|
||||
import {
|
||||
buildAssertionResult,
|
||||
prepareCredentialRequestOptions,
|
||||
isPasskeySupported as detectPasskeySupport,
|
||||
} from '@/lib/passkey'
|
||||
import { getServerErrorMessageKey } from '@/lib/server-error-message'
|
||||
import { handleServerError } from '@/lib/handle-server-error'
|
||||
import { isPasskeySupported as detectPasskeySupport } from '@/lib/passkey'
|
||||
import { AuthOperationError } from '@/lib/secure-verification'
|
||||
import { createServerError } from '@/lib/server-error-message'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function UserAuthForm({
|
||||
@@ -68,6 +71,12 @@ export function UserAuthForm({
|
||||
const [agreedToLegal, setAgreedToLegal] = useState(false)
|
||||
const [passkeySupported, setPasskeySupported] = useState(false)
|
||||
const [isPasskeyLoading, setIsPasskeyLoading] = useState(false)
|
||||
const [passkeyDomains, setPasskeyDomains] = useState<PasskeyDomains | null>(
|
||||
null
|
||||
)
|
||||
const [passkeyRPID, setPasskeyRPID] = useState<string>()
|
||||
const passkeyOperation = useRef<AbortController | null>(null)
|
||||
useEffect(() => () => passkeyOperation.current?.abort(), [])
|
||||
const [isWeChatDialogOpen, setIsWeChatDialogOpen] = useState(false)
|
||||
const [isWeChatSubmitting, setIsWeChatSubmitting] = useState(false)
|
||||
const [turnstileWidgetKey, setTurnstileWidgetKey] = useState(0)
|
||||
@@ -178,10 +187,11 @@ export function UserAuthForm({
|
||||
if (await handleLoginResult(res.data, redirectTo)) {
|
||||
toast.success(t('Welcome back!'))
|
||||
}
|
||||
} else {
|
||||
handleServerError(createServerError(res, loginFailedMessage))
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error)) return
|
||||
toast.error(error instanceof Error ? error.message : loginFailedMessage)
|
||||
handleServerError(AuthOperationError.from(error, loginFailedMessage))
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
@@ -219,12 +229,12 @@ export function UserAuthForm({
|
||||
toast.success(t('Signed in via WeChat'))
|
||||
}
|
||||
} else {
|
||||
if (getServerErrorMessageKey(res)) return
|
||||
toast.error(res?.message || loginFailedMessage)
|
||||
handleServerError(createServerError(res, loginFailedMessage))
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (getServerErrorMessageKey(error)) return
|
||||
toast.error(loginFailedMessage)
|
||||
handleServerError(
|
||||
new AuthOperationError(loginFailedMessage, undefined, { cause: error })
|
||||
)
|
||||
} finally {
|
||||
setIsWeChatSubmitting(false)
|
||||
}
|
||||
@@ -246,55 +256,45 @@ export function UserAuthForm({
|
||||
return
|
||||
}
|
||||
|
||||
if (passkeyOperation.current) return
|
||||
const controller = new AbortController()
|
||||
passkeyOperation.current = controller
|
||||
setIsPasskeyLoading(true)
|
||||
try {
|
||||
const begin = await beginPasskeyLogin()
|
||||
if (!begin.success) {
|
||||
if (getServerErrorMessageKey(begin)) return
|
||||
throw new Error(begin.message || t('Failed to start Passkey login'))
|
||||
}
|
||||
|
||||
const publicKey = prepareCredentialRequestOptions(
|
||||
begin.data?.options ?? begin.data
|
||||
const passkey = await requestPasskeyAssertion(
|
||||
(rpID) => beginPasskeyLogin(rpID, controller.signal),
|
||||
controller.signal,
|
||||
{ rpID: passkeyRPID, onDomains: setPasskeyDomains }
|
||||
)
|
||||
const flowToken = begin.data?.flow_token
|
||||
if (!flowToken) {
|
||||
throw new Error(t('Login flow expired. Please sign in again.'))
|
||||
}
|
||||
|
||||
const credential = (await navigator.credentials.get({
|
||||
publicKey,
|
||||
})) as PublicKeyCredential | null
|
||||
|
||||
if (!credential) {
|
||||
toast.info(t('Passkey login was cancelled'))
|
||||
return
|
||||
}
|
||||
|
||||
const assertion = buildAssertionResult(credential)
|
||||
if (!assertion) {
|
||||
throw new Error(t('Invalid Passkey response'))
|
||||
}
|
||||
|
||||
const finish = await finishPasskeyLogin(flowToken, assertion)
|
||||
const finish = await finishPasskeyLogin(
|
||||
passkey.flowToken,
|
||||
passkey.assertion,
|
||||
controller.signal
|
||||
)
|
||||
controller.signal.throwIfAborted()
|
||||
if (!finish.success) {
|
||||
if (getServerErrorMessageKey(finish)) return
|
||||
throw new Error(finish.message || t('Failed to complete Passkey login'))
|
||||
throw createServerError(finish, t('Failed to complete Passkey login'))
|
||||
}
|
||||
|
||||
rememberPasskeyRPID(passkey.rpID)
|
||||
if (await handleLoginResult(finish.data, redirectTo)) {
|
||||
toast.success(t('Signed in with Passkey'))
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (getServerErrorMessageKey(error)) return
|
||||
if (controller.signal.aborted) return
|
||||
if (error instanceof DOMException && error.name === 'NotAllowedError') {
|
||||
toast.info(t('Passkey login was cancelled or timed out'))
|
||||
} else if (error instanceof Error) {
|
||||
toast.error(error.message)
|
||||
handleServerError(AuthOperationError.from(error))
|
||||
} else {
|
||||
toast.error(t('Passkey login failed'))
|
||||
handleServerError(
|
||||
AuthOperationError.from(error, t('Passkey login failed'))
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
if (passkeyOperation.current === controller) {
|
||||
passkeyOperation.current = null
|
||||
}
|
||||
setIsPasskeyLoading(false)
|
||||
}
|
||||
}
|
||||
@@ -317,6 +317,12 @@ export function UserAuthForm({
|
||||
)}
|
||||
{t('Sign in with Passkey')}
|
||||
</Button>
|
||||
<PasskeyDomainSelector
|
||||
domains={passkeyDomains}
|
||||
value={passkeyRPID}
|
||||
onChange={setPasskeyRPID}
|
||||
disabled={passkeyButtonDisabled}
|
||||
/>
|
||||
{!passkeySupported && (
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Passkey is not supported on this device.')}
|
||||
|
||||
Reference in new issue
Block a user