import { useEffect , useRef , useState } from 'react' ; import { c } from 'ttag' ; import { noop } from '@proton/shared/lib/helpers/function' ; import { API_CUSTOM_ERROR_CODES } from '@proton/shared/lib/errors' ; import { APP_NAMES , APPS , BRAND_NAME , REQUIRES_INTERNAL_EMAIL_ADDRESS } from '@proton/shared/lib/constants' ; import { Address as tsAddress } from '@proton/shared/lib/interfaces' ; import { withUIDHeaders } from '@proton/shared/lib/fetch/headers' ; import { queryAddresses } from '@proton/shared/lib/api/addresses' ; import { getHasOnlyExternalAddresses } from '@proton/shared/lib/helpers/address' ; import { revoke } from '@proton/shared/lib/api/auth' ; import { removePersistedSession } from '@proton/shared/lib/authentication/persistedSessionStorage' ; import { getAppName } from '@proton/shared/lib/apps/helper' ; import { queryAvailableDomains } from '@proton/shared/lib/api/domains' ; import { handleCreateInternalAddressAndKey } from '@proton/shared/lib/keys' ; import { getApiErrorMessage } from '@proton/shared/lib/api/helpers/apiErrorHelper' ; import { AbuseModal , FeatureCode , OnLoginCallback , OnLoginCallbackArguments , useFeature , useApi , useErrorHandler , } from '@proton/components' ; import { handleLogin , handleSetupPassword , handleTotp , handleUnlock , } from '@proton/components/containers/login/loginActions' ; import { AuthActionResponse , AuthCacheResult , AuthStep } from '@proton/components/containers/login/interface' ; import BackButton from '../public/BackButton' ; import { getToAppName } from '../public/helper' ; import LoginForm from './LoginForm' ; import Header from '../public/Header' ; import Content from '../public/Content' ; import Footer from '../public/Footer' ; import LoginSupportDropdown from './LoginSupportDropdown' ; import TOTPForm from './TOTPForm' ; import Main from '../public/Main' ; import UnlockForm from './UnlockForm' ; import SetPasswordForm from './SetPasswordForm' ; import GenerateInternalAddressStep , { InternalAddressGeneration } from './GenerateInternalAddressStep' ; interface Props { onLogin : OnLoginCallback ; toApp ? : APP_NAMES ; onBack ? : ( ) => void ; } const LoginContainer = ( { onLogin , onBack , toApp } ) => { const errorHandler = useErrorHandler ( ) ; const keyMigrationFeature = useFeature ( FeatureCode . KeyMigration ) ; const [ abuseModal , setAbuseModal ] = useState < { apiErrorMessage ? : string ; } | undefined > ( undefined ) ; const normalApi = useApi ( ) ; const silentApi = < T > ( config ) => normalApi < T > ( { ... config , silence : true } ) ; const cacheRef = useRef < AuthCacheResult | undefined > ( undefined ) ; const previousUsernameRef = useRef ( '' ) ; const generateInternalAddressRef = useRef < InternalAddressGeneration | undefined > ( undefined ) ; const [ step , setStep ] = useState ( AuthStep . LOGIN ) ; useEffect ( ( ) => { silentApi ( queryAvailableDomains ( 'login' ) ) ; return ( ) => { cacheRef . current = undefined ; generateInternalAddressRef . current = undefined ; } ; } , [ ] ) ; const handleDone = async ( args ) => { const { UID } = args ; const uidApi = < T > ( config ) => silentApi < T > ( withUIDHeaders ( UID , config ) ) ; if ( toApp && REQUIRES_INTERNAL_EMAIL_ADDRESS . includes ( toApp ) ) { const Addresses = args . Addresses || ( await uidApi < { Addresses : tsAddress [ ] ; } > ( queryAddresses ( ) ) . then ( ( { Addresses } ) => Addresses ) ) ; const { keyPassword , LocalID , UID } = args ; if ( Addresses ?. length && keyPassword && getHasOnlyExternalAddresses ( Addresses ) ) { const { Domains = [ ] } = await uidApi < { Domains : string [ ] ; } > ( queryAvailableDomains ( ) ) ; generateInternalAddressRef . current = { externalEmailAddress : Addresses [ 0 ] , availableDomains : Domains , keyPassword , onDone : ( ) => { const { Addresses , ... restAuthSession } = args ; return onLogin ( restAuthSession ) ; } , revoke : ( ) => { uidApi ( revoke ( ) ) . catch ( noop ) ; if ( LocalID !== undefined ) { removePersistedSession ( LocalID , UID ) ; } } , api : uidApi , } ; setStep ( AuthStep . GENERATE_INTERNAL ) ; return ; } } return onLogin ( args ) ; } ; const handleCancel = ( ) => { previousUsernameRef . current = cacheRef . current ?. username ?? '' ; cacheRef . current = undefined ; generateInternalAddressRef . current = undefined ; setStep ( AuthStep . LOGIN ) ; } ; const handleResult = ( result ) => { if ( result . to === AuthStep . DONE ) { return handleDone ( result . session ) ; } cacheRef . current = result . cache ; setStep ( result . to ) ; } ; const handleError = ( e ) => { if ( e . data ?. Code === API_CUSTOM_ERROR_CODES . AUTH_ACCOUNT_DISABLED ) { setAbuseModal ( { apiErrorMessage : getApiErrorMessage ( e ) } ) ; } else { errorHandler ( e ) ; } } ; const toAppName = getToAppName ( toApp ) ; const mailAppName = getAppName ( APPS . PROTONMAIL ) ; const cache = cacheRef . current ; const generateInternalAddress = generateInternalAddressRef . current ; const externalEmailAddress = generateInternalAddress ?. externalEmailAddress ?. Email || '' ; return ( < Main > < AbuseModal message = { abuseModal ?. apiErrorMessage } open = { ! ! abuseModal } onClose = { ( ) => setAbuseModal ( undefined ) } /> { step === AuthStep . LOGIN && ( < > < Header title = { c ( 'Title' ) . t `Sign in` } subTitle = { toAppName ? c ( 'Info' ) . t `to continue to ${ toAppName }` : undefined } left = { onBack && < BackButton onClick = { onBack } />}/ > < Content > < LoginForm defaultUsername = { previousUsernameRef . current } onSubmit = { async ( { username , password , payload , persistent } ) => { const keyMigrationFeatureValue = await keyMigrationFeature . get < number > ( ) . then ( ( { Value } ) => Value ) . catch ( ( ) => 0 ) ; return handleLogin ( { username , password , persistent , api : silentApi , hasGenerateKeys : true , keyMigrationFeatureValue , ignoreUnlock : false , payload , } ) . then ( handleResult ) . catch ( ( e ) => { handleError ( e ) ; handleCancel ( ) ; } ) ; } } /> < /Content> < />)} { step === AuthStep . TOTP && cache && ( < > < Header title = { c ( 'Title' ) . t `Two-factor authentication` } left = { < BackButton onClick = { handleCancel } / > } /> < Content > < TOTPForm onSubmit = { ( totp ) => handleTotp ( { cache , totp , } ) . then ( handleResult ) . catch ( ( e ) => { handleError ( e ) ; if ( e . name !== 'TOTPError' ) { handleCancel ( ) ; } } ) } /> < /Content> < />)} { step === AuthStep . UNLOCK && cache && ( < > < Header title = { c ( 'Title' ) . t `Unlock your mailbox` } left = { < BackButton onClick = { handleCancel } / > } /> < Content > < UnlockForm onSubmit = { ( clearKeyPassword ) => { return handleUnlock ( { cache , clearKeyPassword , isOnePasswordMode : false , } ) . then ( handleResult ) . catch ( ( e ) => { handleError ( e ) ; if ( e . name !== 'PasswordError' ) { handleCancel ( ) ; } } ) ; } } /> < /Content> < />)} { step === AuthStep . NEW_PASSWORD && cache && ( < > < Header title = { c ( 'Title' ) . t `Set new password` } left = { < BackButton onClick = { handleCancel } / > } /> < Content > < div className = "mb1-75" > { c ( 'Info' ) . t `This will replace your temporary password. You will use it to access your ${ BRAND_NAME } Account in the future.` } < /div> < SetPasswordForm onSubmit = { ( newPassword ) => { return handleSetupPassword ( { cache , newPassword , } ) . then ( handleResult ) . catch ( ( e ) => { handleError ( e ) ; handleCancel ( ) ; } ) ; } } /> < /Content> < />)} { step === AuthStep . GENERATE_INTERNAL && generateInternalAddress && ( < GenerateInternalAddressStep api = { silentApi } mailAppName = { mailAppName } toAppName = { toAppName } availableDomains = { generateInternalAddress . availableDomains } externalEmailAddress = { externalEmailAddress } onBack = { ( ) => { generateInternalAddress . revoke ?. ( ) ; handleCancel ( ) ; } } onSubmit = { async ( payload ) => { try { await handleCreateInternalAddressAndKey ( { api : generateInternalAddress . api , keyPassword : generateInternalAddress . keyPassword , domain : payload . domain , username : payload . username , } ) ; await generateInternalAddress . onDone ( ) ; } catch ( e ) { handleError ( e ) ; handleCancel ( ) ; } } } />)} < Footer > < LoginSupportDropdown /> < /Footer> < /Main>); } ; export default LoginContainer ;