From 9acd4b4ea17cf7c2785c9196249738a611fc7724 Mon Sep 17 00:00:00 2001 From: Howard Braham Date: Tue, 20 Jun 2023 11:27:10 -0700 Subject: [PATCH] feat(srp): add a quiz to the SRP reveal (#19283) * feat(srp): add a quiz to the SRP reveal * fixed the popover header centering * lint fixes * converted from `ui/components/ui/popover` to `ui/components/component-library/modal` * responded to @darkwing review * added unit tests * renamed the folder to 'srp-quiz-modal' * responded to Monte's review * using i18n-helper in the test suite * small improvement to JSXDict comments * wrote a new webdriver.holdMouseDownOnElement() to assist with testing the "Hold to reveal SRP" button * Updating layout and some storybook naming and migrating to tsx * Apply suggestions from @georgewrmarshall Co-authored-by: George Marshall * Unit test searches by data-testid instead of by text * new layout and copy for the Settings->Security page * now with 100% test coverage for /ui/pages/settings/security-tab fixes #16871 fixes #18140 * e2e tests to reveal SRP after quiz * e2e- Fix lint, remove unneeded extras * @coreyjanssen and @georgewrmarshall compromise Co-authored-by: George Marshall Co-authored-by: Corey Janssen * trying isRequired again * transparent background on PNG * [e2e] moving functions to helpers and adding testid for SRP reveal quiz (#19481) * moving functions to helpers and adding testid * fix lint error * took out the IPFS gateway fixes * lint fix * translations of SRP Reveal Quiz * new Spanish translation from Guto * Update describe for e2e tests * Apply suggestion from @georgewrmarshall Co-authored-by: George Marshall * fixed the Tab key problem --------- Co-authored-by: georgewrmarshall Co-authored-by: Plasma Corral <32695229+plasmacorral@users.noreply.github.com> Co-authored-by: Corey Janssen --- app/_locales/de/messages.json | 59 +++- app/_locales/el/messages.json | 59 +++- app/_locales/en/messages.json | 56 +++- app/_locales/es/messages.json | 59 +++- app/_locales/fr/messages.json | 59 +++- app/_locales/hi/messages.json | 59 +++- app/_locales/id/messages.json | 59 +++- app/_locales/ja/messages.json | 59 +++- app/_locales/ko/messages.json | 59 +++- app/_locales/pt/messages.json | 59 +++- app/_locales/ru/messages.json | 59 +++- app/_locales/tl/messages.json | 59 +++- app/_locales/tr/messages.json | 59 +++- app/_locales/vi/messages.json | 59 +++- app/_locales/zh_CN/messages.json | 59 +++- app/images/reveal-srp.png | Bin 0 -> 17049 bytes development/verify-locale-strings.js | 2 + package.json | 1 + test/e2e/helpers.js | 55 ++++ .../settings-security-reveal-srp.spec.js | 135 ++++++++ test/e2e/webdriver/driver.js | 12 + test/lib/i18n-helpers.js | 6 + .../hold-to-reveal-button.js | 2 +- ui/components/app/modals/modal.js | 22 +- .../QuizContent/QuizContent.tsx | 73 +++++ .../app/srp-quiz-modal/QuizContent/index.ts | 1 + .../SRPQuiz/SRPQuiz.stories.tsx | 35 ++ .../srp-quiz-modal/SRPQuiz/SRPQuiz.test.js | 95 ++++++ .../app/srp-quiz-modal/SRPQuiz/SRPQuiz.tsx | 298 ++++++++++++++++++ .../app/srp-quiz-modal/SRPQuiz/index.ts | 1 + ui/components/app/srp-quiz-modal/index.ts | 1 + ui/components/app/srp-quiz-modal/types.ts | 40 +++ .../export-text-container.component.js | 1 + ui/helpers/constants/zendesk-url.js | 4 +- ui/helpers/utils/ipfs.js | 3 - ui/pages/keychains/reveal-seed.js | 5 +- .../create-password/create-password.js | 2 +- .../privacy-settings/privacy-settings.js | 65 ++-- .../__snapshots__/security-tab.test.js.snap | 63 ++-- .../security-tab/security-tab.component.js | 155 +++++---- .../security-tab/security-tab.container.js | 8 +- .../security-tab/security-tab.test.js | 158 ++++------ yarn.lock | 29 ++ 43 files changed, 1885 insertions(+), 269 deletions(-) create mode 100644 app/images/reveal-srp.png create mode 100644 test/e2e/tests/settings-security-reveal-srp.spec.js create mode 100644 test/lib/i18n-helpers.js create mode 100644 ui/components/app/srp-quiz-modal/QuizContent/QuizContent.tsx create mode 100644 ui/components/app/srp-quiz-modal/QuizContent/index.ts create mode 100644 ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.stories.tsx create mode 100644 ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.test.js create mode 100644 ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.tsx create mode 100644 ui/components/app/srp-quiz-modal/SRPQuiz/index.ts create mode 100644 ui/components/app/srp-quiz-modal/index.ts create mode 100644 ui/components/app/srp-quiz-modal/types.ts delete mode 100644 ui/helpers/utils/ipfs.js diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 52d391c4a..1525a731a 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -1562,6 +1562,12 @@ "message": "Betrüger aber schon.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "Halten, um GWP anzuzeigen" + }, + "holdToRevealSRPTitle": { + "message": "Bewahren Sie Ihre GWP sicher auf" + }, "ignoreAll": { "message": "Alle ignorieren" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "Das Einfügen schlug fehl, weil sie mehr als 24 Wörter enthielt. Eine geheime Wiederherstellungsphrase darf maximal 24 Wörter enthalten.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Sie können Ihre gesamte geheime Wiederherstellungsphrase in ein beliebiges Feld einfügen", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Anfangen" + }, + "srpSecurityQuizIntroduction": { + "message": "Zur Enthüllung Ihrer geheimen Wiederherstellungsphrase, müssen Sie zwei Fragen beantworten" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Wenn Sie Ihre geheime Wiederherstellungsphrase verlieren, kann MetaMask ..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Ihnen nicht helfen" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Schreiben Sie sie auf, gravieren Sie sie in Metall ein oder bewahren Sie sie an mehreren geheimen Orten auf, damit Sie sie niemals verlieren. Sollten Sie sie verlieren, ist sie für immer weg." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Richtig! Niemand kann Ihnen dabei helfen, Ihre geheime Wiederherstellungsphrase zurückzubekommen" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "diese für Sie zurückzubekommen" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Wenn Sie Ihre geheime Wiederherstellungsphrase verlieren, ist diese für immer verloren. Niemand kann Ihnen dabei helfen, sie zurückzubekommen, egal, was behauptet wird." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Falsch! Niemand kann Ihnen dabei helfen, Ihre geheime Wiederherstellungsphrase zurückzubekommen" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Sollte jemand, selbst ein Support-Mitarbeiter, nach Ihrer geheimen Wiederherstellungsphrase fragen ..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Werden Sie betrogen" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Jeder, der behauptet, Ihre gemeine Wiederherstellungsphrase zu benötigen, lügt Sie an. Wenn Sie diese mit solchen Personen teilen, werden diese Ihre Vermögenswerte stehlen." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Richtig! Es ist nie eine gute Idee, Ihre geheime Wiederherstellungsphrase mit anderen zu teilen" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Sie sollten sie dieser Person geben" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Jeder, der behauptet, Ihre gemeine Wiederherstellungsphrase zu benötigen, lügt Sie an. Wenn Sie diese mit einer solchen Person teilen, wird sie Ihre Vermögenswerte stehlen." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Nein! Teilen Sie Ihre geheime Wiederherstellungsphrase mit niemandem, niemals" + }, + "srpSecurityQuizTitle": { + "message": "Sicherheits-Quiz" + }, "srpToggleShow": { "message": "Dieses Wort der geheimen Wiederherstellungsphrase anzeigen/ausblenden", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 7403c072b..c70d365c9 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -1562,6 +1562,12 @@ "message": "αλλά οι απατεώνες μπορεί να το κάνουν.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "Κρατήστε το πατημένο για να αποκαλυφθεί το ΜΦΑ" + }, + "holdToRevealSRPTitle": { + "message": "Κρατήστε το ΜΦΑ σας ασφαλές" + }, "ignoreAll": { "message": "Αγνόηση όλων" }, @@ -3330,12 +3336,63 @@ }, "srpPasteFailedTooManyWords": { "message": "Η επικόλληση απέτυχε επειδή περιείχε περισσότερες από 24 λέξεις. Μια μυστική φράση ανάκτησης μπορεί να αποτελείται από το πολύ 24 λέξεις.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Μπορείτε να επικολλήσετε ολόκληρη τη μυστική φράση ανάκτησής σας σε οποιοδήποτε πεδίο", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Ξεκινήστε" + }, + "srpSecurityQuizIntroduction": { + "message": "Για να σας αποκαλύψουμε τη Μυστική Φράση Ανάκτησης, πρέπει να απαντήσετε σωστά σε δύο ερωτήσεις" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Αν χάσετε τη Μυστική Φράση Ανάκτησης, το MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Δεν μπορεί να σας βοηθήσει" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Γράψτε την κάπου, χαράξτε την πάνω σε μέταλλο ή κρατήστε την σε πολλά μυστικά σημεία για να μην την χάσετε ποτέ. Αν την χάσετε, θα χαθεί για πάντα." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Σωστά! Κανείς δεν μπορεί να σας βοηθήσει να επαναφέρετε τη Μυστική Φράση Ανάκτησης" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Μπορεί να την επαναφέρει για εσάς" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Αν χάσετε τη Μυστική Φράση Ανάκτησης, θα την χάσετε για πάντα. Κανείς δεν μπορεί να σας βοηθήσει να την επαναφέρετε, ό,τι κι αν σας πει." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Λάθος! Κανείς δεν μπορεί να σας βοηθήσει να επαναφέρετε τη Μυστική Φράση Ανάκτησης" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Αν κάποιος, ακόμα και ένας τεχνικός υποστήριξης, σας ζητήσει τη Μυστική Φράση Ανάκτησης..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Σας έχουν εξαπατήσει" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Όποιος ισχυριστεί ότι χρειάζεται τη Μυστική Φράση Ανάκτησης, σας λέει ψέματα. Αν την μοιραστείτε μαζί του, θα κλέψει τα περιουσιακά σας στοιχεία." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Σωστά! Το να μοιράζεστε τη Μυστική Φράση Ανάκτησης δεν είναι καλή ιδέα" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Πρέπει να τους την δώσετε" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Όποιος ισχυριστεί ότι χρειάζεται τη Μυστική Φράση Ανάκτησης, σας λέει ψέματα. Αν την μοιραστείτε μαζί του, θα κλέψει τα περιουσιακά σας στοιχεία." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Όχι! Ποτέ μα ποτέ μην μοιραστείτε με κανέναν τη Μυστική Φράση Ανάκτησης" + }, + "srpSecurityQuizTitle": { + "message": "Κουίζ Ασφαλείας" + }, "srpToggleShow": { "message": "Εμφάνιση/Απόκρυψη αυτής της λέξης από τη μυστική φράση ανάκτησης", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 023c14ace..71a426f5a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4024,12 +4024,66 @@ }, "srpPasteFailedTooManyWords": { "message": "Paste failed because it contained over 24 words. A secret recovery phrase can have a maximum of 24 words.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "You can paste your entire secret recovery phrase into any field", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Get started" + }, + "srpSecurityQuizImgAlt": { + "message": "An eye with a keyhole in the center, and three floating password fields" + }, + "srpSecurityQuizIntroduction": { + "message": "To reveal your Secret Recovery Phrase, you need to correctly answer two questions" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "If you lose your Secret Recovery Phrase, MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Can’t help you" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Write it down, engrave it on metal, or keep it in multiple secret spots so you never lose it. If you lose it, it’s gone forever." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Right! No one can help get your Secret Recovery Phrase back" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Can get it back for you" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "If you lose your Secret Recovery Phrase, it’s gone forever. No one can help you get it back, no matter what they might say." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Wrong! No one can help get your Secret Recovery Phrase back" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "If anyone, even a support agent, asks for your Secret Recovery Phrase..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "You’re being scammed" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Anyone claiming to need your Secret Recovery Phrase is lying to you. If you share it with them, they will steal your assets." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Correct! Sharing your Secret Recovery Phrase is never a good idea" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "You should give it to them" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Anyone claiming to need your Secret Recovery Phrase is lying to you. If you share it with them, they will steal your assets." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Nope! Never share your Secret Recovery Phrase with anyone, ever" + }, + "srpSecurityQuizTitle": { + "message": "Security quiz" + }, "srpToggleShow": { "message": "Show/Hide this word of the secret recovery phrase", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index d290bc5a3..474b8997b 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1562,6 +1562,12 @@ "message": "pero los defraudadores sí.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "Mantén presionado para revelar su SRP" + }, + "holdToRevealSRPTitle": { + "message": "Proteja su SRP" + }, "ignoreAll": { "message": "Ignorar todo" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "Pegar falló porque contenía más de 24 palabras. Una frase de recuperación secreta puede tener un máximo de 24 palabras.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Puede pegar toda su frase secreta de recuperación en cualquier campo", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Iniciar" + }, + "srpSecurityQuizIntroduction": { + "message": "Para revelar su frase secreta de recuperación, debe responder correctamente dos preguntas" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Si extravía su frase secreta de recuperación, MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "No puede ayudarlo" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Anótela, grábela en metal o guárdela en múltiples lugares secretos para que nunca la pierda. Si la extravía, se ha ido para siempre." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "¡Correcto! Nadie puede ayudarlo a recuperar su frase secreta de recuperación" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Puede recuperarla para usted" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Si pierde su frase secreta de recuperación, ésta desaparecerá para siempre. Nadie puede ayudarle a recuperarla, sin importar lo que digan." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "¡Incorrecto! Nadie puede ayudarlo a recuperar su frase secreta de recuperación" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Si alguien, incluso un agente de soporte, le pide su frase secreta de recuperación..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Lo están estafando" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Cualquiera que afirme necesitar su frase secreta de recuperación le está mintiendo. Si la comparte, le robarán sus activos." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "¡Correcto! Compartir su frase secreta de recuperación nunca es una buena idea" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Debiera brindársela" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Cualquiera que afirme necesitar su frase secreta de recuperación le está mintiendo. Si la comparte, le robarán sus activos." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "¡No! Nunca comparta su frase secreta de recuperación con nadie, nunca" + }, + "srpSecurityQuizTitle": { + "message": "Cuestionario de seguridad" + }, "srpToggleShow": { "message": "Mostrar/Ocultar esta palabra de la frase secreta de recuperación", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 39fb2dc33..7b179078f 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -1562,6 +1562,12 @@ "message": "mais les hameçonneurs pourraient le faire.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "Appuyez longuement pour révéler PSR" + }, + "holdToRevealSRPTitle": { + "message": "Protégez votre PSR" + }, "ignoreAll": { "message": "Ignorer tout" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "Le collage a échoué parce que la phrase contenait plus de 24 mots. Une phrase secrète de récupération peut contenir un maximum de 24 mots.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Vous pouvez coller toute votre phrase de récupération secrète dans n’importe quel champ", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Commencer" + }, + "srpSecurityQuizIntroduction": { + "message": "Pour révéler votre Phrase secrète de récupération, vous devez répondre correctement à deux questions" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Si vous perdez votre Phrase secrète de récupération, MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Ne pourra pas vous aider" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Gravez-la sur une plaque en métal ou inscrivez-la sur plusieurs bouts de papier et cachez-les dans différents endroits secrets pour ne jamais la perdre. Si vous la perdez, il n'y a aucun moyen de la récupérer." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "En effet ! Personne ne peut vous aider à récupérer votre Phrase secrète de récupération." + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Pourra la récupérer pour vous" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Personne ne peut vous aider à récupérer votre phrase secrète de récupération si jamais vous la perdez." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "C'est faux ! Personne ne peut vous aider à récupérer votre Phrase secrète de récupération." + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Si un membre du service d'assistance ou toute autre personne vous demande votre Phrase secrète de récupération..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Ne la lui fournissez pas, car cette personne essaie de vous arnaquer." + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Toute personne qui vous demande votre phrase secrète de récupération, que ce soit pour des raisons de sécurité ou autre, essaie de vous arnaquer." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "C'est exact ! Vous ne devez jamais partager votre Phrase secrète de récupération avec qui que ce soit." + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Vous devez la lui fournir" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Toute personne qui vous demande votre phrase secrète de récupération, que ce soit pour des raisons de sécurité ou autre, essaie de vous arnaquer." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "C'est faux ! Vous ne devez jamais partager votre Phrase secrète de récupération avec qui que ce soit." + }, + "srpSecurityQuizTitle": { + "message": "Quiz sur la sécurité" + }, "srpToggleShow": { "message": "Afficher / Masquer ce mot de la phrase de récupération secrète", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 87eb9b33a..69f1099a3 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -1562,6 +1562,12 @@ "message": "लेकिन फिशर कर सकते हैं।", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "SRP दिखाने के लिए होल्ड करें" + }, + "holdToRevealSRPTitle": { + "message": "अपना SRP सुरक्षित रखें" + }, "ignoreAll": { "message": "सभी को अनदेखा करें" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "पेस्ट विफल हुआ क्योंकि उसमें 24 से ज़्यादा शब्द हैं। सीक्रेट रिकवरी फ़्रेज़ में अधिकतम 24 शब्द हो सकते हैं।", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "आप अपना पूरा सीक्रेट रिकवरी फ़्रेज किसी भी फ़ील्ड में पेस्ट कर सकते हैं", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "शुरू करें" + }, + "srpSecurityQuizIntroduction": { + "message": "अपना सीक्रेट रिकवरी फ्रेज़ प्रकट करने के लिए, आपको दो प्रश्नों का सही उत्तर देना होगा" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "यदि आप अपना सीक्रेट रिकवरी फ्रेज़ खो देते हैं, तो MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "आपकी मदद नहीं कर सकता" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "इसे लिख लें, इसे किसी धातु पर उकेर दें, या इसे कई गुप्त स्थानों पर रखें ताकि आप इसे कभी न खोएं। यदि आप इसे खो देते हैं, तो यह हमेशा के लिए चला जाता है।" + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "सही! आपके सीक्रेट रिकवरी फ्रेज़ को वापस पाने में कोई भी सहायता नहीं कर सकता" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "आपके लिए इसे वापस ला सकते हैं" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "यदि आप अपना सीक्रेट रिकवरी फ्रेज़ खो देते हैं, तो यह हमेशा के लिए चला जाता है। इसे वापस पाने में कोई भी आपकी मदद नहीं कर सकता, चाहे वे कुछ भी कहें।" + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "गलत! आपके सीक्रेट रिकवरी फ्रेज़ को वापस पाने में कोई भी सहायता नहीं कर सकता" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "यदि कोई, यहां तक कि एक सहायक एजेंट भी, आपका सीक्रेट रिकवरी फ्रेज़ मांगता है..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "तो आपके साथ धोखा किया जा रहा है" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "आपके सीक्रेट रिकवरी फ्रेज़ की आवश्यकता का दावा करने वाला कोई भी व्यक्ति आपसे झूठ बोल रहा है। यदि आप इसे उनके साथ साझा करते हैं, तो वे आपकी संपत्ति चुरा लेंगे।" + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "सही! अपना सीक्रेट रिकवरी फ्रेज़ साझा करना कभी भी अच्छा विचार नहीं है" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "आपको उन्हें यह देना चाहिए" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "आपके सीक्रेट रिकवरी फ्रेज़ की आवश्यकता का दावा करने वाला कोई भी व्यक्ति आपसे झूठ बोल रहा है। यदि आप इसे उनके साथ साझा करते हैं, तो वे आपकी संपत्तियां चुरा लेंगे।" + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "नहीं! अपने सीक्रेट रिकवरी फ्रेज़ को कभी भी किसी के साथ साझा न करें" + }, + "srpSecurityQuizTitle": { + "message": "सुरक्षा प्रश्नोत्तरी" + }, "srpToggleShow": { "message": "सीक्रेट रिकवरी फ़्रेज का ये शब्द दिखाएं/छुपाएं", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 30e982f40..23fe1f555 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -1562,6 +1562,12 @@ "message": "tetapi penipu akan mencoba memintanya.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "Tahan untuk mengungkap FPR" + }, + "holdToRevealSRPTitle": { + "message": "Jaga keamanan FPR Anda" + }, "ignoreAll": { "message": "Abaikan semua" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "Gagal ditempel karena memuat lebih dari 24 kata. Frasa pemulihan rahasia dapat memuat maksimum 24 kata.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Anda bisa menempelkan seluruh frasa pemulihan rahasia ke bagian mana pun", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Mulai" + }, + "srpSecurityQuizIntroduction": { + "message": "Untuk mengungkapkan Frasa Pemulihan Rahasia, Anda perlu menjawab dua pertanyaan dengan benar" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Jika Anda kehilangan Frasa Pemulihan Rahasia, MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Tidak dapat membantu Anda" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Catat, ukir pada logam, atau simpan di beberapa tempat rahasia agar Anda tidak pernah kehilangan. Jika Anda kehilangan, maka akan hilang selamanya." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Benar! Tidak ada yang dapat membantu mengembalikan Frasa Pemulihan Rahasia Anda" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Dapat mengembalikannya untuk Anda" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Jika Anda kehilangan Frasa Pemulihan Rahasia, maka akan hilang selamanya. Tidak ada yang dapat membantu Anda mengembalikannya, apa pun yang mereka katakan." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Salah! Tidak ada yang dapat membantu mengembalikan Frasa Pemulihan Rahasia Anda" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Jika ada yang menanyakan Frasa Pemulihan Rahasia Anda, bahkan agen pendukung,..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Anda ditipu" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Siapa pun yang mengaku membutuhkan Frasa Pemulihan Rahasia, mereka berbohong kepada Anda. Jika membaginya, mereka akan mencuri aset Anda." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Benar! Membagikan Frasa Pemulihan Rahasia bukanlah ide yang baik" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Anda harus memberikannya kepada mereka" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Siapa pun yang mengaku membutuhkan Frasa Pemulihan Rahasia, mereka berbohong kepada Anda. Jika membaginya, mereka akan mencuri aset Anda." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Tidak! Jangan pernah membagikan Frasa Pemulihan Rahasia kepada siapa pun" + }, + "srpSecurityQuizTitle": { + "message": "Kuis keamanan" + }, "srpToggleShow": { "message": "Tampilkan/Sembunyikan kata dari frasa pemulihan rahasia ini", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index ea053b713..74e387c46 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1562,6 +1562,12 @@ "message": "もし尋ねられた場合はフィッシング詐欺の可能性があります。", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "長押ししてSRPを表示" + }, + "holdToRevealSRPTitle": { + "message": "SRPは安全に保管してください" + }, "ignoreAll": { "message": "すべて無視" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "24 を超える単語が含まれていたため、貼り付けに失敗しました。秘密のリカバリーフレーズは 24 語までです。", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "秘密のリカバリーフレーズ全体をいずれかのフィールドに張り付けできます。", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "開始" + }, + "srpSecurityQuizIntroduction": { + "message": "秘密のリカバリーフレーズを表示するには、2 つの質問に正しく答える必要があります。" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "秘密のリカバリーフレーズをなくした場合、MetaMask は..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "どうすることもできません" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "書き留めたり金属に掘ったり、いくつかの秘密の場所に保管したりして、絶対になくさないようにしてください。なくした場合、一生戻ってきません。" + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "正解です!秘密のリカバリーフレーズは誰にも取り戻すことができません" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "それを取り戻すことができます" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "秘密のリカバリーフレーズをなくした場合、一生戻ってきません。誰が何と言おうと、誰にも取り戻すことはできません。" + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "不正解!秘密のリカバリーフレーズは誰にも取り戻せません" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "誰かに秘密のリカバリーフレーズを尋ねられたら、それがサポート担当者であっても..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "あなたは騙されようとしています" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "秘密のリカバリーフレーズが必要だと言われたら、それは嘘です。教えてしまったら資産を盗まれます。" + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "正解です!秘密のリカバリーフレーズは決して誰にも教えてはいけません" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "教えるべきです" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "秘密のリカバリーフレーズが必要だと言われたら、それは嘘です。教えてしまったら資産を盗まれます。" + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "不正解!秘密のリカバリーフレーズは決して誰にも教えないでください" + }, + "srpSecurityQuizTitle": { + "message": "セキュリティの質問" + }, "srpToggleShow": { "message": "秘密のリカバリーフレーズのこの単語を表示・非表示", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 440e0512a..c6a89341b 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -1562,6 +1562,12 @@ "message": "오히려 피싱 사기꾼들이 요구할 수 있으니 주의가 필요합니다.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "눌러서 SRP 정보를 확인하세요" + }, + "holdToRevealSRPTitle": { + "message": "SRP 정보를 안전하게 보관하세요" + }, "ignoreAll": { "message": "모두 무시" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "단어가 24개를 초과하여 붙여넣기에 실패했습니다. 비밀 복구 구문은 24개 이하의 단어로 이루어집니다.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "비밀 복구 구문 전체를 아무 입력란에 붙여넣을 수 있습니다", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "시작하기" + }, + "srpSecurityQuizIntroduction": { + "message": "비밀 복구 구문을 찾으려면 두 가지 질문에 올바르게 답해야 합니다" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "비밀 복구 구문을 분실하시면 MetaMask가..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "도와드릴 수 없습니다" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "따라서 이를 적거나, 금속 등에 새기거나, 여러 비밀 장소에 보관하여 절대로 잃어버리지 않도록 하세요. 한 번 잃어버리면 영원히 찾을 수 없습니다." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "맞습니다! 아무도 본인의 비밀 복구 구문을 복구할 수 없습니다" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "찾아드릴 수 있습니다" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "비밀 복구 구문은 한 번 잃어버리면 영원히 찾을 수 없습니다. 누가 뭐라고 해도 아무도 이를 찾아드리지 못합니다." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "아닙니다! 아무도 본인의 비밀 복구 구문을 복구할 수 없습니다" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "누군가, 심지어 고객 센터 직원이라고 해도 여러분의 비밀 복구 구문을 물어본다면..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "이는 반드시 사기입니다" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "비밀 복구 구문이 필요하다고 하는 사람은 모두 거짓말쟁이입니다. 그런 자들과 비밀 복구 구문을 공유하면 자산을 도둑맞게 됩니다." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "맞습니다! 비밀 복구 구문은 아무와도 공유하면 안 됩니다" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "주어야 합니다" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "비밀 복구 구문이 필요하다고 하는 사람은 모두 거짓말쟁이입니다. 그런 자들과 비밀 복구 구문을 공유하면 자산을 도둑맞게 됩니다." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "맞습니다! 비밀 복구 구문은 절대로 아무와도 공유하면 안 됩니다" + }, + "srpSecurityQuizTitle": { + "message": "보안 퀴즈" + }, "srpToggleShow": { "message": "비밀 복구 구문 중에서 이 단어 공개하기/숨기기", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 6b46b4c39..58665feaf 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -1562,6 +1562,12 @@ "message": "mas os phishers talvez solicitem.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "Mantenha pressionado para revelar FRS" + }, + "holdToRevealSRPTitle": { + "message": "Mantenha sua FRS protegida" + }, "ignoreAll": { "message": "Ignorar tudo" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "A função colar falhou porque continha mais de 24 palavras. Uma frase secreta de recuperação pode ter no máximo 24 palavras.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Você pode colar a sua frase secreta de recuperação inteira em qualquer campo", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Começar" + }, + "srpSecurityQuizIntroduction": { + "message": "Para revelar sua Frase de Recuperação Secreta, você precisa responder corretamente duas perguntas" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Se você perder sua Frase de Recuperação Secreta, a MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Não poderá ajudar" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Anote-a, grave em metal ou guarde-a em diversos lugares secretos para que nunca a perca. Se perdê-la, é para sempre." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Certo! Ninguém pode ajudar a recuperar sua Frase de Recuperação Secreta" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Poderá recuperá-la para você" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Se você perder sua Frase de Recuperação Secreta, é para sempre. Ninguém consegue ajudar a recuperá-la, não importa o que digam." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Errado! Ninguém consegue recuperar sua Frase de Recuperação Secreta" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Se alguém, até mesmo um atendente do suporte, pedir sua Frase de Recuperação Secreta..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Você estará sendo vítima de um golpe" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Qualquer pessoa que afirme precisar da sua Frase de Recuperação Secreta está mentindo. Se você compartilhar com ela, seus ativos serão roubados." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Correto! Compartilhar sua Frase de Recuperação Secreta nunca é uma boa ideia" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Você deverá revelar" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Qualquer pessoa que afirme precisar da sua Frase de Recuperação Secreta está mentindo. Se você compartilhar com ela, seus ativos serão roubados." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Não! Não compartilhe sua Frase de Recuperação Secreta com ninguém, nunca" + }, + "srpSecurityQuizTitle": { + "message": "Quiz de segurança" + }, "srpToggleShow": { "message": "Mostrar/Ocultar esta palavra da frase secreta de recuperação", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 0383594a4..b236dde70 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1562,6 +1562,12 @@ "message": "но злоумышленники-фишеры могут.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "Удерживайте, чтобы показать СФВ" + }, + "holdToRevealSRPTitle": { + "message": "Храните СФВ в безопасности" + }, "ignoreAll": { "message": "Игнорировать все" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "Не удалось вставить, так как он содержит более 24 слов. Секретная фраза для восстановления может содержать не более 24 слов.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Вы можете вставить всю свою секретную фразу для восстановления в любое поле", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Начать" + }, + "srpSecurityQuizIntroduction": { + "message": "Чтобы увидеть свою секретную фразу для восстановления, вам нужно правильно ответить на два вопроса" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Если вы потеряете свою секретную фразу для восстановления, MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Не сможет вам помочь" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Запишите ее, выгравируйте ее на металле или храните в нескольких потайных местах, чтобы никогда не потерять. Если вы потеряете ее, она пропадет навсегда." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Правильно! Никто не может помочь вернуть вашу секретную фразу для восстановления" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Не сможет вернуть ее вам" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Если вы потеряете свою секретную фразу для восстановления, она пропадет навсегда. Никто не может помочь вам вернуть ее, что бы кто ни говорил." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Неправильно! Никто не может помочь вернуть вашу секретную фразу для восстановления" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Если кто-нибудь, даже представитель службы поддержки, попросит вашу секретную фразу для восстановления..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Вас обманывают" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Любой, кто утверждает, что ему нужна ваша секретная фраза для восстановления, лжет вам. Если вы сообщите эту фразу ему (ей), он(-а) украдет ваши активы." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Правильно! Сообщать кому-либо своей секретную фразу для восстановления — это всегда плохая идея" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Вы должны сообщите фразу ему (ей)" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Любой, кто утверждает, что ему нужна ваша секретная фраза для восстановления, лжет вам. Если вы сообщите эту фразу ему (ей), он(-а) украдет ваши активы." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Нет! Никогда никому не сообщайте никому свою секретную фразу для восстановления" + }, + "srpSecurityQuizTitle": { + "message": "Тест по безопасности" + }, "srpToggleShow": { "message": "Показать/скрыть это слово секретной фразы для восстановления", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index ef244c616..2bac2eae2 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -1562,6 +1562,12 @@ "message": "ngunit maaring hingin ng mga phisher.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "I-hold para ipakita ang SRP" + }, + "holdToRevealSRPTitle": { + "message": "Panatilihing ligtas ang iyong SRP" + }, "ignoreAll": { "message": "Huwag pansinin ang lahat" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "Nabigong i-paste dahil naglalaman ito ng higit sa 24 na salita. Ang secret recovery phrase ay mayroong hanggang 24 na salita lamang.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Maaari mong i-paste ang iyong buong secret recovery phrase sa alinmang patlang", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Magsimula" + }, + "srpSecurityQuizIntroduction": { + "message": "Upang ipakita ang iyong Secret Recovery Phrase, kailangan mong sagutin nang tama ang dalawang tanong" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Kung mawala mo ang iyong Secret Recovery Phrase, ang MetaMask ay..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Hindi ka matutulungan" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Isulat ito, iukit sa metal, o itago ito sa maraming lihim na lugar upang hindi ito mawala. Kung nawala mo ito, wala na ito ng tuluyan." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Tama! Walang makakatulong na maibalik ang iyong Secret Recovery Phrase" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Maaari itong ibalik para sa iyo" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Kung nawala mo ang iyong Secret Recovery Phrase mawawala na ito nang tuluyan. Walang makakatulong sa iyo na maibalik ito, anuman ang maaaring sabihin nila." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Mali! Walang makakatulong na maibalik ang iyong Secret Recovery Phrase" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Kung sinuman, kahit isang ahente ng suporta, ay humingi ng iyong Secret Recovery Phrase..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Niloloko ka" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Sinumang nagsasabing nangangailangan ng iyong Secret Recovery Phrase ay nagsisinungaling sa iyo. Kung ibabahagi mo ito sa kanila, nanakawin nila ng iyong mga ari-arian." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Tama! Ang pagbabahagi ng iyong Secret Recovery Phrase ay hindi kailanman isang magandang ideya" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Dapat mong ibigay sa kanila" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Sinumang nagsasabing nangangailangan ng iyong Secret Recovery Phrase ay nagsisinungaling sa iyo. Kung ibabahagi mo ito sa kanila, nanakawin nila ng iyong mga ari-arian." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Hindi! Huwag kailanman ibahagi ang iyong Secret Recovery Phrase sa sinuman, kailanman" + }, + "srpSecurityQuizTitle": { + "message": "Pagsusulit sa seguridad" + }, "srpToggleShow": { "message": "Ipakita/Itago ang salitang ito ng secret recovery phrase", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 3ed1752b6..fdaaedffc 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -1562,6 +1562,12 @@ "message": "ancak dolandırıcılar talep edilebilir.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "GKİ bilgisinin gösterilmesi için tut" + }, + "holdToRevealSRPTitle": { + "message": "GKİ bilgini güvende tut" + }, "ignoreAll": { "message": "Tümünü yoksay" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "24'ten fazla sözcük içerdiği için yapıştırma başarısız oldu. Gizli bir kurtarma ifadesi en fazla 24 sözcükten oluşabilir.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Gizli kurtarma ifadenin tamamını herhangi bir alana yapıştırabilirsin", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Başla" + }, + "srpSecurityQuizIntroduction": { + "message": "Gizli Kurtarma İfadenizi görmek için iki soruyu doğru cevaplamanız gerekmektedir" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Gizli Kurtarma İfadenizi kaybederseniz MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Size yardımcı olamaz" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Bir yere yazın, bir metalin üzerine kazıyın veya asla kaybetmemeniz için birden fazla noktada saklayın. Kaybederseniz sonsuza dek kaybolur." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Doğru! Hiç kimse Gizli Kurtarma İfadenizi geri almanıza yardımcı olamaz" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Size onu tekrar verebilir" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Gizli Kurtarma İfadenizi kaybederseniz sonsuza dek kaybolur. Söylediklerinin ne olduğuna bakılmaksızın hiç kimse onu geri almanıza yardımcı olamaz." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Yanlış! Hiç kimse Gizli Kurtarma İfadenizi geri almanıza yardımcı olamaz" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Herhangi birisi, bir destek temsilcisi bile sizden Gizli Kurtarma İfadenizi isterse..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Dolandırılıyorsunuzdur" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Gizli Kurtarma İfadenizi isteyen kişi size yalan söylüyordur. Kendisi ile paylaşırsanız varlıklarınızı çalacaktır." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Doğru! Gizli Kurtarma İfadenizi paylaşmak asla iyi bir fikir değildir" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Kendisine vermelisiniz" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Gizli Kurtarma İfadenizi isteyen kişi size yalan söylüyordur. Kendisi ile paylaşırsanız varlıklarınızı çalacaktır." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Hayır! Gizli Kurtarma İfadenizi asla hiç kimse ile paylaşmayın, asla" + }, + "srpSecurityQuizTitle": { + "message": "Güvenlik testi" + }, "srpToggleShow": { "message": "Gizli kurtarma ifadesinin bu sözcüğünü göster/gizle", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 50ff318e1..a1bc5ca94 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1562,6 +1562,12 @@ "message": "nhưng những kẻ lừa đảo qua mạng thì có.", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "Giữ để hiển thị SRP" + }, + "holdToRevealSRPTitle": { + "message": "Đảm bảo an toàn cho SRP của bạn" + }, "ignoreAll": { "message": "Bỏ qua tất cả" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "Dán không thành công vì cụm từ có nhiều hơn 24 từ. Cụm từ khôi phục bí mật chỉ có tối đa 24 từ.", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "Bạn có thể dán toàn bộ cụm từ khôi phục bí mật vào bất kỳ trường nào", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "Bắt đầu" + }, + "srpSecurityQuizIntroduction": { + "message": "Để hiển thị Cụm từ khôi phục bí mật, bạn cần trả lời đúng hai câu hỏi" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "Nếu bạn làm mất Cụm từ khôi phục bí mật, MetaMask..." + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "Không thể giúp bạn" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "Hãy viết ra, khắc lên kim loại hoặc cất giữ ở nhiều nơi bí mật để bạn không bao giờ làm mất nó. Nếu bạn làm mất, nó sẽ bị mất vĩnh viễn." + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "Đúng! Không ai có thể giúp bạn lấy lại Cụm từ khôi phục bí mật" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "Có thể lấy lại cho bạn" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "Nếu bạn làm mất Cụm từ khôi phục bí mật, nó sẽ bị mất vĩnh viễn. Dù mọi người có nói gì, thì cũng không ai có thể giúp bạn lấy lại." + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "Sai! Không ai có thể giúp bạn lấy lại Cụm từ khôi phục bí mật" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "Nếu có bất kỳ ai, kể cả nhân viên hỗ trợ, hỏi về Cụm từ khôi phục bí mật của bạn..." + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "Bạn đang bị lừa đảo" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "Bất kỳ ai nói rằng họ cần Cụm từ khôi phục bí mật của bạn thì đều đang nói dối bạn. Nếu bạn chia sẻ với họ thì họ sẽ đánh cắp tài sản của bạn." + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "Chính xác! Chia sẻ Cụm từ khôi phục bí mật chưa bao giờ là một ý hay" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "Bạn nên đưa nó cho họ" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "Bất kỳ ai nói rằng họ cần Cụm từ khôi phục bí mật của bạn thì đều đang nói dối bạn. Nếu bạn chia sẻ với họ thì họ sẽ đánh cắp tài sản của bạn." + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "Không! Tuyệt đối không bao giờ chia sẻ Cụm từ khôi phục bí mật của bạn với bất kỳ ai" + }, + "srpSecurityQuizTitle": { + "message": "Câu hỏi bảo mật" + }, "srpToggleShow": { "message": "Hiện/Ẩn từ này của cụm từ khôi phục bí mật", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index de0b5e697..60ccc7859 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1562,6 +1562,12 @@ "message": "但网络钓鱼者可能会。", "description": "The text link in 'holdToRevealContent3'" }, + "holdToRevealSRP": { + "message": "按住以显示 助记词" + }, + "holdToRevealSRPTitle": { + "message": "保护您的 助记词 安全" + }, "ignoreAll": { "message": "忽略所有" }, @@ -3333,12 +3339,63 @@ }, "srpPasteFailedTooManyWords": { "message": "粘贴失败,因为它包含超过24个单词。一个助记词最多可包含24个单词。", - "description": "Description of SRP paste erorr when the pasted content has too many words" + "description": "Description of SRP paste error when the pasted content has too many words" }, "srpPasteTip": { "message": "您可以将整个助记词粘贴到任何字段中", "description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly." }, + "srpSecurityQuizGetStarted": { + "message": "开始" + }, + "srpSecurityQuizIntroduction": { + "message": "要查看助记词,您需要答对两个问题" + }, + "srpSecurityQuizQuestionOneQuestion": { + "message": "如果您丢失了助记词,MetaMask......" + }, + "srpSecurityQuizQuestionOneRightAnswer": { + "message": "无法帮助您" + }, + "srpSecurityQuizQuestionOneRightAnswerDescription": { + "message": "将它写下来、刻在金属上,或保存在多个秘密位置,这样您就不会丢失它。如果丢失了,它就会永远消失。" + }, + "srpSecurityQuizQuestionOneRightAnswerTitle": { + "message": "答对了!没有人能够帮您找回您的助记词" + }, + "srpSecurityQuizQuestionOneWrongAnswer": { + "message": "可以为您找回来" + }, + "srpSecurityQuizQuestionOneWrongAnswerDescription": { + "message": "一旦遗失助记词,它将永远消失。无论他人如何保证,无人能够帮您找回。" + }, + "srpSecurityQuizQuestionOneWrongAnswerTitle": { + "message": "答错了!没有人能够帮您找回您的助记词" + }, + "srpSecurityQuizQuestionTwoQuestion": { + "message": "如果有人(即使是技术支持人员)查问您的助记词......" + }, + "srpSecurityQuizQuestionTwoRightAnswer": { + "message": "就是在对您进行诈骗" + }, + "srpSecurityQuizQuestionTwoRightAnswerDescription": { + "message": "任何声称需要您的助记词的人都在对您进行欺诈。如果您与他们分享助记词,他们就会偷窃您的资产。" + }, + "srpSecurityQuizQuestionTwoRightAnswerTitle": { + "message": "答对了!分享您的助记词绝对不是个好主意" + }, + "srpSecurityQuizQuestionTwoWrongAnswer": { + "message": "您应该交给他们" + }, + "srpSecurityQuizQuestionTwoWrongAnswerDescription": { + "message": "任何声称需要您的助记词的人都在对您进行欺诈。如果您与他们分享助记词,他们就会偷窃您的资产。" + }, + "srpSecurityQuizQuestionTwoWrongAnswerTitle": { + "message": "不!永远不要与任何人分享您的助记词" + }, + "srpSecurityQuizTitle": { + "message": "安全问答" + }, "srpToggleShow": { "message": "显示/隐藏助记词中的这个单词", "description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase" diff --git a/app/images/reveal-srp.png b/app/images/reveal-srp.png new file mode 100644 index 0000000000000000000000000000000000000000..80feea7f6dabed6b8fe54a98882ab3fd675f95a0 GIT binary patch literal 17049 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 IM6N<$f_rdBaR2}S literal 0 HcmV?d00001 diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js index 40649cceb..1bdc4339f 100755 --- a/development/verify-locale-strings.js +++ b/development/verify-locale-strings.js @@ -183,8 +183,10 @@ async function verifyEnglishLocale() { [ 'ui/**/*.js', 'ui/**/*.ts', + 'ui/**/*.tsx', 'shared/**/*.js', 'shared/**/*.ts', + 'shared/**/*.tsx', 'app/scripts/constants/**/*.js', 'app/scripts/constants/**/*.ts', 'app/scripts/platforms/**/*.js', diff --git a/package.json b/package.json index 069a3c161..c7ed2b232 100644 --- a/package.json +++ b/package.json @@ -425,6 +425,7 @@ "@types/react": "^16.9.53", "@types/react-dom": "^17.0.11", "@types/react-redux": "^7.1.25", + "@types/react-router-dom": "^5.3.3", "@types/remote-redux-devtools": "^0.5.5", "@types/sass": "^1.43.1", "@types/sinon": "^10.0.13", diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index e66f5fbda..040ed2166 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -4,6 +4,7 @@ const { promises: fs } = require('fs'); const BigNumber = require('bignumber.js'); const mockttp = require('mockttp'); const createStaticServer = require('../../development/create-static-server'); +const { tEn } = require('../lib/i18n-helpers'); const { setupMocking } = require('./mock-e2e'); const Ganache = require('./ganache'); const FixtureServer = require('./fixture-server'); @@ -384,6 +385,56 @@ const testSRPDropdownIterations = async (options, driver, iterations) => { } }; +const passwordUnlockOpenSRPRevealQuiz = async (driver) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // navigate settings to reveal SRP + await driver.clickElement('[data-testid="account-options-menu-button"]'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.clickElement({ text: 'Security & privacy', tag: 'div' }); + await driver.clickElement('[data-testid="reveal-seed-words"]'); +}; + +const completeSRPRevealQuiz = async (driver) => { + // start quiz + await driver.clickElement('[data-testid="srp-quiz-get-started"]'); + + // tap correct answer 1 + await driver.clickElement('[data-testid="srp-quiz-right-answer"]'); + + // tap Continue 1 + await driver.clickElement('[data-testid="srp-quiz-continue"]'); + + // tap correct answer 2 + await driver.clickElement('[data-testid="srp-quiz-right-answer"]'); + + // tap Continue 2 + await driver.clickElement('[data-testid="srp-quiz-continue"]'); +}; + +const tapAndHoldToRevealSRP = async (driver) => { + await driver.holdMouseDownOnElement( + { + text: tEn('holdToRevealSRP'), + tag: 'span', + }, + 2000, + ); +}; + +const closeSRPReveal = async (driver) => { + await driver.clickElement({ + text: tEn('close'), + tag: 'button', + }); + await driver.findVisibleElement({ + text: tEn('tokens'), + tag: 'button', + }); +}; + const DAPP_URL = 'http://127.0.0.1:8080'; const DAPP_ONE_URL = 'http://127.0.0.1:8081'; @@ -641,6 +692,10 @@ module.exports = { completeImportSRPOnboardingFlow, completeImportSRPOnboardingFlowWordByWord, completeCreateNewWalletOnboardingFlow, + passwordUnlockOpenSRPRevealQuiz, + completeSRPRevealQuiz, + closeSRPReveal, + tapAndHoldToRevealSRP, createDownloadFolder, importWrongSRPOnboardingFlow, testSRPDropdownIterations, diff --git a/test/e2e/tests/settings-security-reveal-srp.spec.js b/test/e2e/tests/settings-security-reveal-srp.spec.js new file mode 100644 index 000000000..1a7cc14c0 --- /dev/null +++ b/test/e2e/tests/settings-security-reveal-srp.spec.js @@ -0,0 +1,135 @@ +const { strict: assert } = require('assert'); +const { + withFixtures, + passwordUnlockOpenSRPRevealQuiz, + completeSRPRevealQuiz, + tapAndHoldToRevealSRP, + closeSRPReveal, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); +const { tEn } = require('../../lib/i18n-helpers'); + +describe('Reveal SRP through settings', function () { + const testPassword = 'correct horse battery staple'; + const wrongTestPassword = 'test test test test'; + const seedPhraseWords = + 'spread raise short crane omit tent fringe mandate neglect detail suspect cradle'; + + it('should not reveal SRP text with incorrect password', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test.title, + failOnConsoleError: false, + }, + async ({ driver }) => { + await passwordUnlockOpenSRPRevealQuiz(driver); + await completeSRPRevealQuiz(driver); + await driver.fill('#password-box', wrongTestPassword); + await driver.press('#password-box', driver.Key.ENTER); + await driver.isElementPresent( + { + css: '.mm-help-text', + text: 'Incorrect password', + }, + true, + ); + }, + ); + }); + + it('completes quiz and reveals SRP text', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test.title, + }, + async ({ driver }) => { + await passwordUnlockOpenSRPRevealQuiz(driver); + await completeSRPRevealQuiz(driver); + + // enter password + await driver.fill('#password-box', testPassword); + await driver.press('#password-box', driver.Key.ENTER); + + await tapAndHoldToRevealSRP(driver); + + // confirm SRP text matches expected + const displayedSRP = await driver.findVisibleElement( + '[data-testid="srp_text"]', + ); + assert.equal(await displayedSRP.getText(), seedPhraseWords); + + // copy SRP text to clipboard + await driver.clickElement({ + text: tEn('copyToClipboard'), + tag: 'button', + }); + await driver.findVisibleElement({ + text: tEn('copiedExclamation'), + tag: 'button', + }); + + // confirm that CTA returns user to wallet view + await closeSRPReveal(driver); + }, + ); + }); + + it('completes quiz and reveals SRP QR after wrong answers', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test.title, + }, + async ({ driver }) => { + await passwordUnlockOpenSRPRevealQuiz(driver); + + // start quiz + await driver.clickElement('[data-testid="srp-quiz-get-started"]'); + + // tap incorrect answer 1 + await driver.clickElement('[data-testid="srp-quiz-wrong-answer"]'); + + // try again + await driver.clickElement('[data-testid="srp-quiz-try-again"]'); + + // tap correct answer 1 + await driver.clickElement('[data-testid="srp-quiz-right-answer"]'); + + // tap Continue 1 + await driver.clickElement('[data-testid="srp-quiz-continue"]'); + + // tap incorrect answer 2 + await driver.clickElement('[data-testid="srp-quiz-wrong-answer"]'); + + // try again + await driver.clickElement('[data-testid="srp-quiz-try-again"]'); + + // tap correct answer 1 + await driver.clickElement('[data-testid="srp-quiz-right-answer"]'); + + // tap Continue 2 + await driver.clickElement('[data-testid="srp-quiz-continue"]'); + + // enter password + await driver.fill('#password-box', testPassword); + await driver.press('#password-box', driver.Key.ENTER); + + // tap and hold to reveal + await tapAndHoldToRevealSRP(driver); + + // confirm SRP QR is displayed + await driver.clickElement({ + text: 'QR', + tag: 'button', + }); + const qrCode = await driver.findElement('[data-testid="qr-srp"]'); + assert.equal(await qrCode.isDisplayed(), true); + + // confirm that CTA returns user to wallet view + await closeSRPReveal(driver); + }, + ); + }); +}); diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 243337c59..2c222ae95 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -261,6 +261,18 @@ class Driver { .perform(); } + async holdMouseDownOnElement(rawLocator, ms) { + const locator = this.buildLocator(rawLocator); + const element = await this.findClickableElement(locator); + await this.driver + .actions() + .move({ origin: element, x: 1, y: 1 }) + .press() + .pause(ms) + .release() + .perform(); + } + async scrollToElement(element) { await this.driver.executeScript( 'arguments[0].scrollIntoView(true)', diff --git a/test/lib/i18n-helpers.js b/test/lib/i18n-helpers.js new file mode 100644 index 000000000..2c73a5cce --- /dev/null +++ b/test/lib/i18n-helpers.js @@ -0,0 +1,6 @@ +import { getMessage } from '../../ui/helpers/utils/i18n-helper'; +import * as en from '../../app/_locales/en/messages.json'; + +export function tEn(key) { + return getMessage('en', en, key); +} diff --git a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js index b12443ccd..832e8a770 100644 --- a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js +++ b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js @@ -89,7 +89,7 @@ export default function HoldToRevealButton({ buttonText, onLongPressed }) { setHasTriggeredUnlock(true); preventPropogation(e); }, - [onLongPressed], + [onLongPressed, trackEvent], ); /** diff --git a/ui/components/app/modals/modal.js b/ui/components/app/modals/modal.js index 73252848e..55b044a7c 100644 --- a/ui/components/app/modals/modal.js +++ b/ui/components/app/modals/modal.js @@ -2,10 +2,10 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; -import * as actions from '../../../store/actions'; -import isMobileView from '../../../helpers/utils/is-mobile-view'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; +import isMobileView from '../../../helpers/utils/is-mobile-view'; +import * as actions from '../../../store/actions'; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) import { mmiActionsFactory } from '../../../store/institutional/institution-background'; ///: END:ONLY_INCLUDE_IN @@ -13,31 +13,31 @@ import { mmiActionsFactory } from '../../../store/institutional/institution-back // Modal Components import AddNetworkModal from '../../../pages/onboarding-flow/add-network-modal'; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +import ComplianceDetailsModal from '../../institutional/compliance-details'; +import ComplianceModal from '../../institutional/compliance-modal'; import ConfirmRemoveJWT from '../../institutional/confirm-remove-jwt-modal'; -import TransactionFailed from '../../institutional/transaction-failed-modal'; import CustodyConfirmLink from '../../institutional/custody-confirm-link-modal'; import InteractiveReplacementTokenModal from '../../institutional/interactive-replacement-token-modal'; -import ComplianceModal from '../../institutional/compliance-modal'; -import ComplianceDetailsModal from '../../institutional/compliance-details'; +import TransactionFailed from '../../institutional/transaction-failed-modal'; ///: END:ONLY_INCLUDE_IN import AccountDetailsModal from './account-details-modal'; import ExportPrivateKeyModal from './export-private-key-modal'; import HideTokenConfirmationModal from './hide-token-confirmation-modal'; import QRScanner from './qr-scanner'; -import HoldToRevealModal from './hold-to-reveal-modal'; import ConfirmRemoveAccount from './confirm-remove-account'; import ConfirmResetAccount from './confirm-reset-account'; +import HoldToRevealModal from './hold-to-reveal-modal'; import TransactionConfirmed from './transaction-confirmed'; -import FadeModal from './fade-modal'; -import RejectTransactions from './reject-transactions'; import ConfirmDeleteNetwork from './confirm-delete-network'; -import EditApprovalPermission from './edit-approval-permission'; -import NewAccountModal from './new-account-modal'; -import CustomizeNonceModal from './customize-nonce'; import ConvertTokenToNftModal from './convert-token-to-nft-modal/convert-token-to-nft-modal'; +import CustomizeNonceModal from './customize-nonce'; +import EditApprovalPermission from './edit-approval-permission'; import EthSignModal from './eth-sign-modal/eth-sign-modal'; +import FadeModal from './fade-modal'; +import NewAccountModal from './new-account-modal'; +import RejectTransactions from './reject-transactions'; const modalContainerBaseStyle = { transform: 'translate3d(-50%, 0, 0px)', diff --git a/ui/components/app/srp-quiz-modal/QuizContent/QuizContent.tsx b/ui/components/app/srp-quiz-modal/QuizContent/QuizContent.tsx new file mode 100644 index 000000000..67b1f4c06 --- /dev/null +++ b/ui/components/app/srp-quiz-modal/QuizContent/QuizContent.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { + AlignItems, + BlockSize, + Display, + FlexDirection, + JustifyContent, + TextAlign, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { Button, Text, Box } from '../../../component-library'; +import { IQuizInformationProps } from '../types'; + +export default function QuizContent({ + icon, + image, + content, + moreContent, + buttons, +}: IQuizInformationProps) { + const t = useI18nContext(); + + return ( + <> + {icon && ( + + {icon} + + )} + {image && ( + + {t('srpSecurityQuizImgAlt')} + + )} + + {content} + + {moreContent && ( + + {moreContent} + + )} + {buttons.map((btn, idx) => ( + + ))} + + ); +} diff --git a/ui/components/app/srp-quiz-modal/QuizContent/index.ts b/ui/components/app/srp-quiz-modal/QuizContent/index.ts new file mode 100644 index 000000000..537995246 --- /dev/null +++ b/ui/components/app/srp-quiz-modal/QuizContent/index.ts @@ -0,0 +1 @@ +export { default } from './QuizContent'; diff --git a/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.stories.tsx b/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.stories.tsx new file mode 100644 index 000000000..495c4e14a --- /dev/null +++ b/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.stories.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { StoryFn, Meta } from '@storybook/react'; +import { useArgs } from '@storybook/client-api'; +import { Button } from '../../../component-library'; +import SRPQuiz from '.'; + +export default { + title: 'Components/App/SRPQuizModal', + component: SRPQuiz, + argTypes: { + isShowingModal: { + control: 'boolean', + }, + }, +} as Meta; + +export const DefaultStory: StoryFn = () => { + const [{ isShowingModal }, updateArgs] = useArgs(); + + return ( + <> + + {isShowingModal && ( + updateArgs({ isShowingModal: false })} + /> + )} + + ); +}; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.test.js b/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.test.js new file mode 100644 index 000000000..d1e7ae395 --- /dev/null +++ b/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.test.js @@ -0,0 +1,95 @@ +import { fireEvent, screen, waitFor } from '@testing-library/react'; +import React from 'react'; +import mockState from '../../../../../test/data/mock-state.json'; +import { renderWithProvider } from '../../../../../test/jest'; +import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url'; +import configureStore from '../../../../store/store'; +import { QuizStage } from '../types'; +import SRPQuiz from './SRPQuiz'; + +const store = configureStore({ + metamask: { + ...mockState.metamask, + }, +}); + +let openTabSpy; + +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + return { + ...original, + useHistory: () => ({ + push: jest.fn(), + }), + }; +}); + +async function waitForStage(stage) { + return await waitFor(() => { + expect(screen.getByTestId(`srp_stage_${stage}`)).toBeInTheDocument(); + }); +} + +function clickButton(id) { + fireEvent.click(screen.getByTestId(id)); +} + +describe('srp-reveal-quiz', () => { + beforeAll(() => { + global.platform = { openTab: jest.fn() }; + openTabSpy = jest.spyOn(global.platform, 'openTab'); + }); + + it('should go through the full sequence of steps', async () => { + renderWithProvider(, store); + + expect(screen.queryByTestId('srp-quiz-get-started')).toBeInTheDocument(); + + expect( + screen.queryByTestId('srp-quiz-right-answer'), + ).not.toBeInTheDocument(); + + clickButton('srp-quiz-learn-more'); + + await waitFor(() => + expect(openTabSpy).toHaveBeenCalledWith({ + url: expect.stringMatching(ZENDESK_URLS.PASSWORD_AND_SRP_ARTICLE), + }), + ); + + clickButton('srp-quiz-get-started'); + + await waitForStage(QuizStage.questionOne); + + clickButton('srp-quiz-wrong-answer'); + + await waitForStage(QuizStage.wrongAnswerQuestionOne); + + clickButton('srp-quiz-try-again'); + + await waitForStage(QuizStage.questionOne); + + clickButton('srp-quiz-right-answer'); + + await waitForStage(QuizStage.rightAnswerQuestionOne); + + clickButton('srp-quiz-continue'); + + await waitForStage(QuizStage.questionTwo); + + clickButton('srp-quiz-wrong-answer'); + + await waitForStage(QuizStage.wrongAnswerQuestionTwo); + + clickButton('srp-quiz-try-again'); + + await waitForStage(QuizStage.questionTwo); + + clickButton('srp-quiz-right-answer'); + + await waitForStage(QuizStage.rightAnswerQuestionTwo); + + clickButton('srp-quiz-continue'); + }); +}); diff --git a/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.tsx b/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.tsx new file mode 100644 index 000000000..44f3fd29b --- /dev/null +++ b/ui/components/app/srp-quiz-modal/SRPQuiz/SRPQuiz.tsx @@ -0,0 +1,298 @@ +/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports, import/no-commonjs */ +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { + MetaMetricsEventCategory, + MetaMetricsEventKeyType, + MetaMetricsEventName, +} from '../../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../../contexts/metametrics'; +import { + BlockSize, + Display, + FlexDirection, + IconColor, + TextAlign, +} from '../../../../helpers/constants/design-system'; +import { REVEAL_SEED_ROUTE } from '../../../../helpers/constants/routes'; +import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { + BUTTON_SIZES, + BUTTON_VARIANT, + Icon, + IconName, + IconSize, + Modal, + ModalContent, + ModalHeader, + ModalOverlay, +} from '../../../component-library'; +import QuizContent from '../QuizContent'; +import { JSXDict, QuizStage } from '../types'; + +const wrongAnswerIcon = ( + +); + +const rightAnswerIcon = ( + +); + +const openSupportArticle = (): void => { + global.platform.openTab({ + url: ZENDESK_URLS.PASSWORD_AND_SRP_ARTICLE, + }); +}; + +export default function SRPQuiz(props: any) { + const [stage, setStage] = useState(QuizStage.introduction); + + const trackEvent = useContext(MetaMetricsContext); + const history = useHistory(); + const t = useI18nContext(); + + // This should not be a state variable, because it's derivable from the state variable `stage` + // (Making it a state variable forces the component to render twice) + let title = ''; + + // Using a dictionary of JSX elements eliminates the need for a switch statement + const stages: JSXDict = {}; + + stages[QuizStage.introduction] = () => { + title = t('srpSecurityQuizTitle'); + return ( + setStage(QuizStage.questionOne), + variant: BUTTON_VARIANT.PRIMARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-get-started', + }, + { + label: t('learnMoreUpperCase'), + onClick: openSupportArticle, + variant: BUTTON_VARIANT.LINK, + 'data-testid': 'srp-quiz-learn-more', + }, + ]} + /> + ); + }; + + stages[QuizStage.questionOne] = () => { + title = `1 ${t('ofTextNofM')} 2`; + return ( + setStage(QuizStage.wrongAnswerQuestionOne), + variant: BUTTON_VARIANT.SECONDARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-wrong-answer', + }, + { + label: t('srpSecurityQuizQuestionOneRightAnswer'), + onClick: () => setStage(QuizStage.rightAnswerQuestionOne), + variant: BUTTON_VARIANT.SECONDARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-right-answer', + }, + { + label: t('learnMoreUpperCase'), + onClick: openSupportArticle, + variant: BUTTON_VARIANT.LINK, + }, + ]} + /> + ); + }; + + stages[QuizStage.rightAnswerQuestionOne] = () => { + title = `1 ${t('ofTextNofM')} 2`; + return ( + setStage(QuizStage.questionTwo), + variant: BUTTON_VARIANT.PRIMARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-continue', + }, + { + label: t('learnMoreUpperCase'), + onClick: openSupportArticle, + variant: BUTTON_VARIANT.LINK, + }, + ]} + /> + ); + }; + + stages[QuizStage.wrongAnswerQuestionOne] = () => { + title = `1 ${t('ofTextNofM')} 2`; + return ( + setStage(QuizStage.questionOne), + variant: BUTTON_VARIANT.PRIMARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-try-again', + }, + { + label: t('learnMoreUpperCase'), + onClick: openSupportArticle, + variant: BUTTON_VARIANT.LINK, + }, + ]} + /> + ); + }; + + stages[QuizStage.questionTwo] = () => { + title = `2 ${t('ofTextNofM')} 2`; + return ( + setStage(QuizStage.rightAnswerQuestionTwo), + variant: BUTTON_VARIANT.SECONDARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-right-answer', + }, + { + label: t('srpSecurityQuizQuestionTwoWrongAnswer'), + onClick: () => setStage(QuizStage.wrongAnswerQuestionTwo), + variant: BUTTON_VARIANT.SECONDARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-wrong-answer', + }, + { + label: t('learnMoreUpperCase'), + onClick: openSupportArticle, + variant: BUTTON_VARIANT.LINK, + }, + ]} + /> + ); + }; + + stages[QuizStage.rightAnswerQuestionTwo] = () => { + title = `2 ${t('ofTextNofM')} 2`; + return ( + history.push(REVEAL_SEED_ROUTE), + variant: BUTTON_VARIANT.PRIMARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-continue', + }, + { + label: t('learnMoreUpperCase'), + onClick: openSupportArticle, + variant: BUTTON_VARIANT.LINK, + }, + ]} + /> + ); + }; + + stages[QuizStage.wrongAnswerQuestionTwo] = () => { + title = `2 ${t('ofTextNofM')} 2`; + return ( + setStage(QuizStage.questionTwo), + variant: BUTTON_VARIANT.PRIMARY, + size: BUTTON_SIZES.LG, + 'data-testid': 'srp-quiz-try-again', + }, + { + label: t('learnMoreUpperCase'), + onClick: openSupportArticle, + variant: BUTTON_VARIANT.LINK, + }, + ]} + /> + ); + }; + + // trackEvent shortcut specific to the SRP quiz + const trackEventSrp = useCallback((location) => { + trackEvent( + { + category: MetaMetricsEventCategory.Keys, + event: MetaMetricsEventName.KeyExportSelected, + properties: { + key_type: MetaMetricsEventKeyType.Srp, + location, + }, + }, + {}, + ); + }, []); + + useEffect(() => { + trackEventSrp(`stage_${stage}`); // Call MetaMetrics based on the current stage + }, [stage]); // Only call this when the stage changes + + const quizContent = stages[stage](); // Pick the content using the right stage from the JSXDict + + return ( + + + + + {title} + + + {quizContent} + + + ); +} diff --git a/ui/components/app/srp-quiz-modal/SRPQuiz/index.ts b/ui/components/app/srp-quiz-modal/SRPQuiz/index.ts new file mode 100644 index 000000000..8fd4c830a --- /dev/null +++ b/ui/components/app/srp-quiz-modal/SRPQuiz/index.ts @@ -0,0 +1 @@ +export { default } from './SRPQuiz'; diff --git a/ui/components/app/srp-quiz-modal/index.ts b/ui/components/app/srp-quiz-modal/index.ts new file mode 100644 index 000000000..8fd4c830a --- /dev/null +++ b/ui/components/app/srp-quiz-modal/index.ts @@ -0,0 +1 @@ +export { default } from './SRPQuiz'; diff --git a/ui/components/app/srp-quiz-modal/types.ts b/ui/components/app/srp-quiz-modal/types.ts new file mode 100644 index 000000000..7c83dee67 --- /dev/null +++ b/ui/components/app/srp-quiz-modal/types.ts @@ -0,0 +1,40 @@ +export enum QuizStage { + introduction = 'introduction', + questionOne = 'question_one', + wrongAnswerQuestionOne = 'wrong_answer_question_one', + rightAnswerQuestionOne = 'right_answer_question_one', + questionTwo = 'question_two', + wrongAnswerQuestionTwo = 'wrong_answer_question_two', + rightAnswerQuestionTwo = 'right_answer_question_two', +} + +export interface IQuizInformationProps { + /** + * The icon to display in the modal should use component + */ + icon?: any; + /** + * The image to display in the modal + */ + image?: string; + /** + * The text content to go inside of the component + */ + content: string; + /** + * More text content to go inside of the component + */ + moreContent?: string; + /** + * Array of - - - + Reveal Secret Recovery Phrase +