From 9734cb2074c5cc97a0d9774cb433e71fa1c6ec75 Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Wed, 22 Feb 2023 15:38:46 +0800 Subject: [PATCH] feat: display verification phrase during space registration https://github.com/web3-storage/w3protocol/pull/432 introduces a mechanism for providing "definitive verification of the validity of a [space registration] email request". A full justification for this flow is provided in that PR, and this PR updates w3ui and w3console to show the phrase generated by the access API. --- .../src/components/Authenticator.tsx | 12 +++++++-- packages/keyring-core/src/index.ts | 6 ++++- packages/react-keyring/src/Authenticator.tsx | 25 ++++++++++++++++--- .../react-keyring/src/providers/Keyring.tsx | 7 +++--- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/examples/react/w3console/src/components/Authenticator.tsx b/examples/react/w3console/src/components/Authenticator.tsx index a5499fb6..35773ff6 100644 --- a/examples/react/w3console/src/components/Authenticator.tsx +++ b/examples/react/w3console/src/components/Authenticator.tsx @@ -1,6 +1,7 @@ import React from 'react' import { Authenticator as AuthCore, + SpaceRegistrationPhrase, useAuthenticator } from '@w3ui/react-keyring' @@ -33,8 +34,15 @@ export function AuthenticationSubmitted (): JSX.Element {

Verify your email address!

-

- Click the link in the email we sent to {email} to sign in. +

+ We've just sent an email to {email}. +

+

+ Please verify that the phrase in the email matches the following words: +

+ +

+ If it does, please click "Verify email address" to sign in.

Cancel diff --git a/packages/keyring-core/src/index.ts b/packages/keyring-core/src/index.ts index ba33db3e..e3fab701 100644 --- a/packages/keyring-core/src/index.ts +++ b/packages/keyring-core/src/index.ts @@ -89,6 +89,10 @@ export interface KeyringContextState { agent?: Signer } +export interface RegisterSpaceOptions { + handlePhrase: (phrase: string) => void +} + export interface KeyringContextActions { /** * Load the user agent and all stored data from secure storage. @@ -117,7 +121,7 @@ export interface KeyringContextActions { * storage. Use cancelRegisterSpace to abort. Automatically sets the * newly registered space as the current space. */ - registerSpace: (email: string) => Promise + registerSpace: (email: string, options: RegisterSpaceOptions) => Promise /** * Abort an ongoing account registration. */ diff --git a/packages/react-keyring/src/Authenticator.tsx b/packages/react-keyring/src/Authenticator.tsx index fcad710e..fb47c7de 100644 --- a/packages/react-keyring/src/Authenticator.tsx +++ b/packages/react-keyring/src/Authenticator.tsx @@ -30,7 +30,13 @@ export type AuthenticatorContextState = KeyringContextState & { * A callback that can be passed to an `onSubmit` handler to * register a new space or log in using `email` */ - handleRegisterSubmit?: (e: React.FormEvent) => Promise + handleRegisterSubmit?: (e: React.FormEvent) => Promise, + /** + * A short phrase generated by the access API that should be presented to + * the user on the email verification wait screen. Users should be instructed + * to verify that the phrase in their email matches this phrase. + */ + spaceRegistrationPhrase?: string } export type AuthenticatorContextActions = KeyringContextActions & { @@ -103,6 +109,7 @@ export const AuthenticatorRoot: Component = const { createSpace, registerSpace } = actions const [email, setEmail] = useState('') const [submitted, setSubmitted] = useState(false) + const [spaceRegistrationPhrase, setSpaceRegistrationPhrase] = useState('') const handleRegisterSubmit = useCallback( async (e: React.FormEvent) => { @@ -110,7 +117,7 @@ export const AuthenticatorRoot: Component = setSubmitted(true) try { await createSpace() - await registerSpace(email) + await registerSpace(email, { handlePhrase: (phrase) => { setSpaceRegistrationPhrase(phrase) } }) } catch (error: any) { throw new Error('failed to register', { cause: error }) } finally { @@ -122,10 +129,10 @@ export const AuthenticatorRoot: Component = const value = useMemo( () => [ - { ...state, email, submitted, handleRegisterSubmit }, + { ...state, email, submitted, handleRegisterSubmit, spaceRegistrationPhrase }, { ...actions, setEmail } ], - [state, actions, email, submitted, handleRegisterSubmit] + [state, actions, email, submitted, handleRegisterSubmit, spaceRegistrationPhrase] ) return ( @@ -194,6 +201,16 @@ export const CancelButton: Component = createComponent( } ) +export type SpaceRegistrationPhraseOptions = Options +export type SpaceRegistrationPhraseProps = Props> + +export const SpaceRegistrationPhrase: Component = createComponent( + (props) => { + const [{spaceRegistrationPhrase}] = useAuthenticator() + return createElement('span', {...props, children: <>{spaceRegistrationPhrase}}) + } +) + /** * Use the scoped authenticator context state from a parent `Authenticator`. */ diff --git a/packages/react-keyring/src/providers/Keyring.tsx b/packages/react-keyring/src/providers/Keyring.tsx index 80e8bfd4..fe65d952 100644 --- a/packages/react-keyring/src/providers/Keyring.tsx +++ b/packages/react-keyring/src/providers/Keyring.tsx @@ -9,7 +9,8 @@ import { import type { KeyringContextState, KeyringContextActions, - ServiceConfig + ServiceConfig, + RegisterSpaceOptions } from '@w3ui/keyring-core' import type { Agent } from '@web3-storage/access' import type { Abilities } from '@web3-storage/access/types' @@ -102,13 +103,13 @@ export function KeyringProvider ({ return did } - const registerSpace = async (email: string): Promise => { + const registerSpace = async (email: string, opts: RegisterSpaceOptions): Promise => { const agent = await getAgent() const controller = new AbortController() setRegisterAbortController(controller) try { - await agent.registerSpace(email, { signal: controller.signal }) + await agent.registerSpace(email, {...opts, signal: controller.signal }) setSpace(getCurrentSpace(agent)) setSpaces(getSpaces(agent)) } catch (error) {