diff --git a/packages/api/src/Domain/Client/Auth/AuthApiService.ts b/packages/api/src/Domain/Client/Auth/AuthApiService.ts index d4e4a82f6..8a0113523 100644 --- a/packages/api/src/Domain/Client/Auth/AuthApiService.ts +++ b/packages/api/src/Domain/Client/Auth/AuthApiService.ts @@ -72,6 +72,7 @@ export class AuthApiService implements AuthApiServiceInterface { password: string codeVerifier: string recoveryCodes: string + hvmToken?: string }): Promise> { if (this.operationsInProgress.get(AuthApiOperations.SignInWithRecoveryCodes)) { throw new ApiCallError(ErrorMessage.GenericInProgress) @@ -86,6 +87,7 @@ export class AuthApiService implements AuthApiServiceInterface { password: dto.password, recovery_codes: dto.recoveryCodes, username: dto.username, + hvm_token: dto.hvmToken, }) return response diff --git a/packages/api/src/Domain/Client/Auth/AuthApiServiceInterface.ts b/packages/api/src/Domain/Client/Auth/AuthApiServiceInterface.ts index e7026b415..a2bb31cd0 100644 --- a/packages/api/src/Domain/Client/Auth/AuthApiServiceInterface.ts +++ b/packages/api/src/Domain/Client/Auth/AuthApiServiceInterface.ts @@ -17,5 +17,6 @@ export interface AuthApiServiceInterface { password: string codeVerifier: string recoveryCodes: string + hvmToken?: string }): Promise> } diff --git a/packages/api/src/Domain/Client/User/UserApiService.ts b/packages/api/src/Domain/Client/User/UserApiService.ts index 146ad4a17..98b112fb6 100644 --- a/packages/api/src/Domain/Client/User/UserApiService.ts +++ b/packages/api/src/Domain/Client/User/UserApiService.ts @@ -66,6 +66,7 @@ export class UserApiService implements UserApiServiceInterface { async register(registerDTO: { email: string serverPassword: string + hvmToken?: string keyParams: RootKeyParamsInterface ephemeral: boolean }): Promise> { @@ -76,6 +77,7 @@ export class UserApiService implements UserApiServiceInterface { [ApiEndpointParam.ApiVersion]: this.apiVersion, password: registerDTO.serverPassword, email: registerDTO.email, + hvm_token: registerDTO.hvmToken, ephemeral: registerDTO.ephemeral, ...registerDTO.keyParams.getPortableValue(), }) diff --git a/packages/api/src/Domain/Client/User/UserApiServiceInterface.ts b/packages/api/src/Domain/Client/User/UserApiServiceInterface.ts index f10f798a4..a574dd865 100644 --- a/packages/api/src/Domain/Client/User/UserApiServiceInterface.ts +++ b/packages/api/src/Domain/Client/User/UserApiServiceInterface.ts @@ -11,6 +11,7 @@ export interface UserApiServiceInterface { register(registerDTO: { email: string serverPassword: string + hvmToken?: string keyParams: RootKeyParamsInterface ephemeral: boolean }): Promise> diff --git a/packages/api/src/Domain/Request/Recovery/SignInWithRecoveryCodesRequestParams.ts b/packages/api/src/Domain/Request/Recovery/SignInWithRecoveryCodesRequestParams.ts index f4233bab0..0ad4b33e7 100644 --- a/packages/api/src/Domain/Request/Recovery/SignInWithRecoveryCodesRequestParams.ts +++ b/packages/api/src/Domain/Request/Recovery/SignInWithRecoveryCodesRequestParams.ts @@ -4,4 +4,5 @@ export interface SignInWithRecoveryCodesRequestParams { password: string code_verifier: string recovery_codes: string + hvm_token?: string } diff --git a/packages/api/src/Domain/Request/User/UserRegistrationRequestParams.ts b/packages/api/src/Domain/Request/User/UserRegistrationRequestParams.ts index 4169558fa..417a9935a 100644 --- a/packages/api/src/Domain/Request/User/UserRegistrationRequestParams.ts +++ b/packages/api/src/Domain/Request/User/UserRegistrationRequestParams.ts @@ -6,5 +6,6 @@ export type UserRegistrationRequestParams = AnyKeyParamsContent & { [additionalParam: string]: unknown password: string email: string + hvm_token?: string ephemeral: boolean } diff --git a/packages/mobile/fastlane/Fastfile b/packages/mobile/fastlane/Fastfile index cf7a4383a..2d9143219 100755 --- a/packages/mobile/fastlane/Fastfile +++ b/packages/mobile/fastlane/Fastfile @@ -158,7 +158,7 @@ platform :android do desc 'Deploy production app' lane :prod do - version = 3_002_000 + ENV['BUILD_NUMBER'].to_i + version = 3_004_000 + ENV['BUILD_NUMBER'].to_i deploy_android 'prod', version end end diff --git a/packages/responses/src/Domain/Auth/MetaEndpointResponse.ts b/packages/responses/src/Domain/Auth/MetaEndpointResponse.ts new file mode 100644 index 000000000..c0a34425f --- /dev/null +++ b/packages/responses/src/Domain/Auth/MetaEndpointResponse.ts @@ -0,0 +1,3 @@ +export type MetaEndpointResponse = { + captchaUIUrl: string | null +} diff --git a/packages/responses/src/Domain/Http/HttpResponse.ts b/packages/responses/src/Domain/Http/HttpResponse.ts index 8b9294d0e..34c1df8e3 100644 --- a/packages/responses/src/Domain/Http/HttpResponse.ts +++ b/packages/responses/src/Domain/Http/HttpResponse.ts @@ -26,6 +26,30 @@ export function isErrorResponse(response: HttpResponse): response is HttpE return (response.data as HttpErrorResponseBody)?.error != undefined || response.status >= 400 } +export function getCaptchaHeader(response: HttpResponse) { + const captchaHeader = response.headers?.get('x-captcha-required') + if (captchaHeader) { + return captchaHeader + } + return null +} + +export function getErrorMessageFromErrorResponseBody(data: HttpErrorResponseBody, defaultMessage?: string): string { + let errorMessage = defaultMessage || 'Unknown error' + if ( + data && + typeof data === 'object' && + 'error' in data && + data.error && + typeof data.error === 'object' && + 'message' in data.error + ) { + errorMessage = data.error.message as string + } + + return errorMessage +} + export function getErrorFromErrorResponse(response: HttpErrorResponse): HttpError { const embeddedError = response.data.error if (embeddedError) { diff --git a/packages/responses/src/Domain/index.ts b/packages/responses/src/Domain/index.ts index bb3ec6e85..b09a3242b 100644 --- a/packages/responses/src/Domain/index.ts +++ b/packages/responses/src/Domain/index.ts @@ -13,6 +13,7 @@ export * from './Auth/SignInData' export * from './Auth/SignInResponse' export * from './Auth/SignOutResponse' export * from './Auth/User' +export * from './Auth/MetaEndpointResponse' /** Temps are awaiting final publish state on server repo */ export * from './Temp/SharedVaultMoveType' diff --git a/packages/services/src/Domain/Auth/AuthClientInterface.ts b/packages/services/src/Domain/Auth/AuthClientInterface.ts index 6aa5d292b..e471442e8 100644 --- a/packages/services/src/Domain/Auth/AuthClientInterface.ts +++ b/packages/services/src/Domain/Auth/AuthClientInterface.ts @@ -13,8 +13,10 @@ export interface AuthClientInterface { password: string codeVerifier: string recoveryCodes: string + hvmToken?: string }): Promise< | { + success: true keyParams: AnyKeyParamsContent session: SessionBody user: { @@ -23,6 +25,9 @@ export interface AuthClientInterface { protocolVersion: string } } - | false + | { + success: false + captchaURL: string + } > } diff --git a/packages/services/src/Domain/Auth/AuthManager.ts b/packages/services/src/Domain/Auth/AuthManager.ts index 857e77c66..5dcb48bc5 100644 --- a/packages/services/src/Domain/Auth/AuthManager.ts +++ b/packages/services/src/Domain/Auth/AuthManager.ts @@ -1,6 +1,6 @@ import { AuthApiServiceInterface } from '@standardnotes/api' import { AnyKeyParamsContent } from '@standardnotes/common' -import { isErrorResponse, SessionBody } from '@standardnotes/responses' +import { isErrorResponse, getCaptchaHeader } from '@standardnotes/responses' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { AbstractService } from '../Service/AbstractService' @@ -45,37 +45,39 @@ export class AuthManager extends AbstractService implements AuthClientInterface } } - async signInWithRecoveryCodes(dto: { - username: string - password: string - codeVerifier: string - recoveryCodes: string - }): Promise< - | { - keyParams: AnyKeyParamsContent - session: SessionBody - user: { - uuid: string - email: string - protocolVersion: string - } - } - | false - > { + async signInWithRecoveryCodes( + dto: Parameters[0], + ): ReturnType { try { const result = await this.authApiService.signInWithRecoveryCodes(dto) + const captchaURL = getCaptchaHeader(result) + + if (captchaURL) { + return { + success: false, + captchaURL, + } + } + if (isErrorResponse(result)) { - return false + return { + success: false, + captchaURL: '', + } } return { + success: true, keyParams: result.data.key_params as AnyKeyParamsContent, session: result.data.session, user: result.data.user, } } catch (error) { - return false + return { + success: false, + captchaURL: '', + } } } } diff --git a/packages/services/src/Domain/Session/SessionsClientInterface.ts b/packages/services/src/Domain/Session/SessionsClientInterface.ts index 5c1e4e688..457eea35e 100644 --- a/packages/services/src/Domain/Session/SessionsClientInterface.ts +++ b/packages/services/src/Domain/Session/SessionsClientInterface.ts @@ -30,13 +30,14 @@ export interface SessionsClientInterface { revokeAllOtherSessions(): Promise isCurrentSessionReadOnly(): boolean | undefined - register(email: string, password: string, ephemeral: boolean): Promise + register(email: string, password: string, hvmToken: string, ephemeral: boolean): Promise signIn( email: string, password: string, strict: boolean, ephemeral: boolean, minAllowedVersion?: ProtocolVersion, + hvmToken?: string, ): Promise bypassChecksAndSignInWithRootKey( email: string, diff --git a/packages/services/src/Domain/User/UserService.ts b/packages/services/src/Domain/User/UserService.ts index 0151fda94..6d137546a 100644 --- a/packages/services/src/Domain/User/UserService.ts +++ b/packages/services/src/Domain/User/UserService.ts @@ -142,6 +142,7 @@ export class UserService public async register( email: string, password: string, + hvmToken: string, ephemeral = false, mergeLocal = true, ): Promise { @@ -157,7 +158,7 @@ export class UserService try { this.lockSyncing() - const response = await this.sessions.register(email, password, ephemeral) + const response = await this.sessions.register(email, password, hvmToken, ephemeral) await this.notifyEventSync(AccountEvent.SignedInOrRegistered, { payload: { @@ -190,6 +191,7 @@ export class UserService ephemeral = false, mergeLocal = true, awaitSync = false, + hvmToken?: string, ): Promise> { if (this.encryption.hasAccount()) { throw Error('Tried to sign in when an account already exists.') @@ -205,7 +207,7 @@ export class UserService /** Prevent a timed sync from occuring while signing in. */ this.lockSyncing() - const { response } = await this.sessions.signIn(email, password, strict, ephemeral) + const { response } = await this.sessions.signIn(email, password, strict, ephemeral, undefined, hvmToken) if (!isErrorResponse(response)) { const notifyingFunction = awaitSync ? this.notifyEventSync.bind(this) : this.notifyEvent.bind(this) diff --git a/packages/services/src/Domain/User/UserServiceInterface.ts b/packages/services/src/Domain/User/UserServiceInterface.ts index c7d78b93e..2b53768db 100644 --- a/packages/services/src/Domain/User/UserServiceInterface.ts +++ b/packages/services/src/Domain/User/UserServiceInterface.ts @@ -21,6 +21,7 @@ export interface UserServiceInterface extends AbstractService @@ -31,6 +32,7 @@ export interface UserServiceInterface extends AbstractService> deleteAccount(): Promise<{ error: boolean diff --git a/packages/services/src/Domain/index.ts b/packages/services/src/Domain/index.ts index d5eda0a86..4993a91ac 100644 --- a/packages/services/src/Domain/index.ts +++ b/packages/services/src/Domain/index.ts @@ -203,7 +203,6 @@ export * from './User/SignedInOrRegisteredEventPayload' export * from './User/SignedOutEventPayload' export * from './User/UserService' export * from './User/UserServiceInterface' -export * from './User/UserServiceInterface' export * from './UserEvent/NotificationService' export * from './UserEvent/NotificationServiceEvent' export * from './Vault/UseCase/AuthorizeVaultDeletion' diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 500eae0f5..79977ffc9 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -98,6 +98,7 @@ import { SignInResponse, ClientDisplayableError, SessionListEntry, + MetaEndpointResponse, } from '@standardnotes/responses' import { SyncService, @@ -117,7 +118,7 @@ import { LoggerInterface, canBlockDeinit, } from '@standardnotes/utils' -import { UuidString, ApplicationEventPayload } from '../Types' +import { UuidString } from '../Types' import { applicationEventForSyncEvent } from '@Lib/Application/Event' import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files' import { ComputePrivateUsername } from '@standardnotes/encryption' @@ -275,12 +276,12 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli }), ) - const syncEventCallback = async (eventName: SyncEvent) => { + const syncEventCallback = async (eventName: SyncEvent, data?: unknown) => { const appEvent = applicationEventForSyncEvent(eventName) if (appEvent) { await encryptionService.onSyncEvent(eventName) - await this.notifyEvent(appEvent) + await this.notifyEvent(appEvent, data) if (appEvent === ApplicationEvent.CompletedFullSync) { if (!this.handledFullSyncStage) { @@ -535,7 +536,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli return this.addEventObserver(filteredCallback, event) } - private async notifyEvent(event: ApplicationEvent, data?: ApplicationEventPayload) { + private async notifyEvent(event: ApplicationEvent, data?: unknown) { if (event === ApplicationEvent.Started) { this.onStart() } else if (event === ApplicationEvent.Launched) { @@ -768,10 +769,11 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli public async register( email: string, password: string, + hvmToken: string, ephemeral = false, mergeLocal = true, ): Promise { - return this.user.register(email, password, ephemeral, mergeLocal) + return this.user.register(email, password, hvmToken, ephemeral, mergeLocal) } /** @@ -785,8 +787,13 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli ephemeral = false, mergeLocal = true, awaitSync = false, + hvmToken?: string, ): Promise> { - return this.user.signIn(email, password, strict, ephemeral, mergeLocal, awaitSync) + return this.user.signIn(email, password, strict, ephemeral, mergeLocal, awaitSync, hvmToken) + } + + public async getCaptchaUrl(): Promise> { + return this.legacyApi.getCaptchaUrl() } public async changeEmail( diff --git a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.spec.ts b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.spec.ts index e73b3834d..b989f0794 100644 --- a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.spec.ts +++ b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.spec.ts @@ -166,7 +166,9 @@ describe('SignInWithRecoveryCodes', () => { }) it('should fail if the sign in with recovery code fails', async () => { - authManager.signInWithRecoveryCodes = jest.fn().mockReturnValue(false) + authManager.signInWithRecoveryCodes = jest.fn().mockReturnValue({ + success: false, + }) const useCase = createUseCase() const result = await useCase.execute({ diff --git a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts index 3478380bc..e5e66a769 100644 --- a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts +++ b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts @@ -68,9 +68,18 @@ export class SignInWithRecoveryCodes implements UseCaseInterface { recoveryCodes: dto.recoveryCodes, username: dto.username, password: rootKey.serverPassword as string, + hvmToken: dto.hvmToken, }) - if (signInResult === false) { + if (signInResult.success === false) { + if (signInResult.captchaURL) { + return Result.fail( + JSON.stringify({ + captchaURL: signInResult.captchaURL, + }), + ) + } + return Result.fail('Could not sign in with recovery code') } diff --git a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodesDTO.ts b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodesDTO.ts index 01f38b2dc..ed37d6600 100644 --- a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodesDTO.ts +++ b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodesDTO.ts @@ -2,4 +2,5 @@ export interface SignInWithRecoveryCodesDTO { recoveryCodes: string username: string password: string + hvmToken?: string } diff --git a/packages/snjs/lib/Services/Api/ApiService.ts b/packages/snjs/lib/Services/Api/ApiService.ts index d127e68f5..103c8a123 100644 --- a/packages/snjs/lib/Services/Api/ApiService.ts +++ b/packages/snjs/lib/Services/Api/ApiService.ts @@ -68,6 +68,7 @@ import { isErrorResponse, MoveFileResponse, ValetTokenOperation, + MetaEndpointResponse, } from '@standardnotes/responses' import { LegacySession, MapperInterface, Session, SessionToken } from '@standardnotes/domain-core' import { HttpServiceInterface } from '@standardnotes/api' @@ -290,6 +291,7 @@ export class LegacyApiService email: string serverPassword: string ephemeral: boolean + hvmToken?: string }): Promise> { if (this.authenticating) { return this.createErrorResponse(API_MESSAGE_LOGIN_IN_PROGRESS, HttpStatusCode.BadRequest) @@ -301,6 +303,7 @@ export class LegacyApiService password: dto.serverPassword, ephemeral: dto.ephemeral, code_verifier: this.inMemoryStore.getValue(StorageKey.CodeVerifier) as string, + hvm_token: dto.hvmToken, }) const response = await this.request({ @@ -958,4 +961,9 @@ export class LegacyApiService return this.session.accessToken } + + public getCaptchaUrl() { + const response = this.httpService.get(Paths.v1.meta) + return response + } } diff --git a/packages/snjs/lib/Services/Api/Paths.ts b/packages/snjs/lib/Services/Api/Paths.ts index 077b8ee9f..6639ed8dc 100644 --- a/packages/snjs/lib/Services/Api/Paths.ts +++ b/packages/snjs/lib/Services/Api/Paths.ts @@ -70,6 +70,7 @@ export const Paths = { ...SettingsPaths, ...SubscriptionPaths, ...UserPaths, + meta: '/v1/meta', }, v2: { ...UserPathsV2, diff --git a/packages/snjs/lib/Services/Session/SessionManager.ts b/packages/snjs/lib/Services/Session/SessionManager.ts index 920e6a104..00550a86f 100644 --- a/packages/snjs/lib/Services/Session/SessionManager.ts +++ b/packages/snjs/lib/Services/Session/SessionManager.ts @@ -404,7 +404,12 @@ export class SessionManager return undefined } - async register(email: string, password: string, ephemeral: boolean): Promise { + async register( + email: string, + password: string, + hvmToken: string, + ephemeral: boolean, + ): Promise { if (password.length < MINIMUM_PASSWORD_LENGTH) { throw new ApiCallError( ErrorMessage.InsufficientPasswordMessage.replace('%LENGTH%', MINIMUM_PASSWORD_LENGTH.toString()), @@ -429,6 +434,7 @@ export class SessionManager const registerResponse = await this.userApiService.register({ email, serverPassword, + hvmToken, keyParams, ephemeral, }) @@ -503,8 +509,9 @@ export class SessionManager strict = false, ephemeral = false, minAllowedVersion?: Common.ProtocolVersion, + hvmToken?: string, ): Promise { - const result = await this.performSignIn(email, password, strict, ephemeral, minAllowedVersion) + const result = await this.performSignIn(email, password, strict, ephemeral, minAllowedVersion, hvmToken) if ( isErrorResponse(result.response) && getErrorFromErrorResponse(result.response).tag !== ErrorTag.ClientValidationError && @@ -515,7 +522,7 @@ export class SessionManager /** * Try signing in with trimmed + lowercase version of email */ - return this.performSignIn(cleanedEmail, password, strict, ephemeral, minAllowedVersion) + return this.performSignIn(cleanedEmail, password, strict, ephemeral, minAllowedVersion, hvmToken) } else { return result } @@ -530,6 +537,7 @@ export class SessionManager strict = false, ephemeral = false, minAllowedVersion?: Common.ProtocolVersion, + hvmToken?: string, ): Promise { const paramsResult = await this.retrieveKeyParams({ email, @@ -593,7 +601,7 @@ export class SessionManager } } const rootKey = await this.encryptionService.computeRootKey(password, keyParams) - const signInResponse = await this.bypassChecksAndSignInWithRootKey(email, rootKey, ephemeral) + const signInResponse = await this.bypassChecksAndSignInWithRootKey(email, rootKey, ephemeral, hvmToken) return { response: signInResponse, @@ -604,6 +612,7 @@ export class SessionManager email: string, rootKey: SNRootKey, ephemeral = false, + hvmToken?: string, ): Promise> { const { wrappingKey, canceled } = await this.challengeService.getWrappingKeyIfApplicable() @@ -619,6 +628,7 @@ export class SessionManager email, serverPassword: rootKey.serverPassword as string, ephemeral, + hvmToken, }) if (!signInResponse.data || isErrorResponse(signInResponse)) { diff --git a/packages/web/package.json b/packages/web/package.json index b35e07146..ac5f1f15b 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@standardnotes/web", - "version": "3.192.10", + "version": "3.192.12", "license": "AGPL-3.0", "main": "dist/app.js", "author": "Standard Notes", diff --git a/packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx b/packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx index 9d6f03fda..3a20e8217 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx @@ -16,6 +16,8 @@ import DecoratedPasswordInput from '@/Components/Input/DecoratedPasswordInput' import Icon from '@/Components/Icon/Icon' import IconButton from '@/Components/Button/IconButton' import { useApplication } from '../ApplicationProvider' +import { useCaptcha } from '@/Hooks/useCaptcha' +import { isErrorResponse } from '@standardnotes/snjs' type Props = { setMenuPane: (pane: AccountMenuPane) => void @@ -33,6 +35,39 @@ const ConfirmPassword: FunctionComponent = ({ setMenuPane, email, passwor const [shouldMergeLocal, setShouldMergeLocal] = useState(true) const [error, setError] = useState('') + const [hvmToken, setHVMToken] = useState('') + const [captchaURL, setCaptchaURL] = useState('') + + const register = useCallback(() => { + setIsRegistering(true) + application + .register(email, password, hvmToken, isEphemeral, shouldMergeLocal) + .then(() => { + application.accountMenuController.closeAccountMenu() + application.accountMenuController.setCurrentPane(AccountMenuPane.GeneralMenu) + }) + .catch((err) => { + console.error(err) + setError(err.message) + }) + .finally(() => { + setIsRegistering(false) + }) + }, [application, email, hvmToken, isEphemeral, password, shouldMergeLocal]) + + const captchaIframe = useCaptcha(captchaURL, (token) => { + setHVMToken(token) + setCaptchaURL('') + }) + + useEffect(() => { + if (!hvmToken) { + return + } + + register() + }, [hvmToken, register]) + const passwordInputRef = useRef(null) useEffect(() => { @@ -51,6 +86,28 @@ const ConfirmPassword: FunctionComponent = ({ setMenuPane, email, passwor setShouldMergeLocal(!shouldMergeLocal) }, [shouldMergeLocal]) + const checkIfCaptchaRequiredAndRegister = useCallback(() => { + application + .getCaptchaUrl() + .then((response) => { + if (isErrorResponse(response)) { + throw new Error() + } + const { captchaUIUrl } = response.data + if (captchaUIUrl) { + setCaptchaURL(captchaUIUrl) + } else { + setCaptchaURL('') + register() + } + }) + .catch((error) => { + console.error(error) + setCaptchaURL('') + register() + }) + }, [application, register]) + const handleConfirmFormSubmit: FormEventHandler = useCallback( (e) => { e.preventDefault() @@ -60,28 +117,16 @@ const ConfirmPassword: FunctionComponent = ({ setMenuPane, email, passwor return } - if (password === confirmPassword) { - setIsRegistering(true) - application - .register(email, password, isEphemeral, shouldMergeLocal) - .then(() => { - application.accountMenuController.closeAccountMenu() - application.accountMenuController.setCurrentPane(AccountMenuPane.GeneralMenu) - }) - .catch((err) => { - console.error(err) - setError(err.message) - }) - .finally(() => { - setIsRegistering(false) - }) - } else { + if (password !== confirmPassword) { setError(STRING_NON_MATCHING_PASSWORDS) setConfirmPassword('') passwordInputRef.current?.focus() + return } + + checkIfCaptchaRequiredAndRegister() }, - [application, confirmPassword, email, isEphemeral, password, shouldMergeLocal], + [checkIfCaptchaRequiredAndRegister, confirmPassword, password], ) const handleKeyDown: KeyboardEventHandler = useCallback( @@ -100,35 +145,26 @@ const ConfirmPassword: FunctionComponent = ({ setMenuPane, email, passwor setMenuPane(AccountMenuPane.Register) }, [setMenuPane]) - return ( + const confirmPasswordForm = ( <> -
- -
Confirm password
-
Because your notes are encrypted using your password,{' '} Standard Notes does not have a password reset option. If you forget your password, you will permanently lose access to your data.
- ]} - onChange={handlePasswordChange} - onKeyDown={handleKeyDown} - placeholder="Confirm password" - ref={passwordInputRef} - value={confirmPassword} - /> + {!isRegistering && ( + ]} + onChange={handlePasswordChange} + onKeyDown={handleKeyDown} + placeholder="Confirm password" + ref={passwordInputRef} + value={confirmPassword} + /> + )} {error ?
{error}
: null}