-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
⭐ feature: Security questions (#2439)
* initial setup * Security Questions -> user profile page (#2416) * Password Reset - security questions - initial setup (#2424) * Add security questions check (#2429) * Combine admin/formBuilder Headers and add Your Account dropdown (#2427) * Revert radix dropdown-menu component to previous minor * Add link to Profile * Move form-builder header to globals * move share dropdown next to your account * replace AdminNav with new globals/Header * Update i18n string * Set min width and fix alignment * load form-builder strings * move string to common * classname order * remove shadow * Fix admin menu test (#2440) * Fix e2e test * fix test * Your Account / Profile tests (#2441) * Add tests around Your Account dropdown and User Profile * Delete redundant test * Security questions edit modal (#2443) * Adds initial modal work * Adds more modal work * Adds localization strings * Update components/admin/Profile/EditSecurityQuestionModal.tsx Co-authored-by: Tim Arney <timarney@users.noreply.github.com> * Fixes from PR comments * Fixes a typo * Updates a test to include updated color --------- Co-authored-by: Tim Arney <timarney@users.noreply.github.com> * feat: added backend layer for security questions feature (#2445) * feat: added backend layer for security questions feature * refactor: remove unused security questions cache lib * refactor: removed useless get security questions for logged in user API path (#2455) * Connect setup security questions to API - Answer questions PT 1 (#2458) * initial api connection setup * fix dependancy * update check for fetch call * lint * Setup security questions (#2460) * Adds fields and localization strings * Moves work from signup to auth * Removing a test for now * Removes redirect to debug outside this PR * fix loop issue * add url * clean --------- Co-authored-by: Tim Arney <tim@line37.com> * Connect profile and questions modal (#2461) * Render questions from session * wire up modal to update questions * Remove old wip * Refresh page after update * Trimp answers * remove whitespace * remove unused' * feat: sanitize security answers (#2468) * Add min / set width for login content (#2466) * add min / set width for login content * Connect setup security questions to api (#2471) * Adds API call * Adds basic api response error handling * Adds question filtering (#2473) * Adds security questions page cypress tests (#2479) * Update answer check (#2480) * Add seeds for SecurityAnswers (#2481) * Update Security questions test (#2482) * Adds unknown error to profile dialog plus loader (#2484) * Adds unknown error to profile dialog plus loader * Fix question output (#2486) * Redirect to profile from setup-security-questions (#2483) --------- Co-authored-by: Dave Samojlenko <dave.samojlenko@cds-snc.ca> * Removes select in favor of customizing Dropdown (#2487) * Update error handling (#2488) Co-authored-by: Daine Trinidad <daine.trinidad@gmail.com> * Toast message for security questions (#2494) --------- Co-authored-by: Anik Brazeau <38330843+anikbrazeau@users.noreply.github.com> * Alert Banner (#2495) --------- Co-authored-by: Dave Samojlenko <dave.samojlenko@cds-snc.ca> * userHasSecurityQuestions property implementation (#2497) * Verify email before showing security questions (#2496) * magic link setup * add code to get token * added new specific errors for password reset lib function * log and return when someone request reset password link with an email address that does not exist --------- Co-authored-by: Clément Janin <clement.janin@cds-snc.ca> --------- Co-authored-by: Tim Arney <tim@line37.com> Co-authored-by: Tim Arney <timarney@users.noreply.github.com> Co-authored-by: Pete <107579368+thiessenp-cds@users.noreply.github.com> Co-authored-by: Clément JANIN <clement.janin@cds-snc.ca> Co-authored-by: Daine Trinidad <daine.trinidad@gmail.com> Co-authored-by: Anik Brazeau <38330843+anikbrazeau@users.noreply.github.com> Co-authored-by: Bryan Robitaille <bryan.robitaille@cds-snc.ca>
- Loading branch information
1 parent
4d75fa7
commit f6f3362
Showing
74 changed files
with
2,937 additions
and
436 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
import React, { useRef, useState, useCallback } from "react"; | ||
import { Label } from "@components/forms"; | ||
import { Button } from "@components/globals"; | ||
import { useTranslation } from "next-i18next"; | ||
import { Dialog, useDialogRef } from "@components/form-builder/app/shared"; | ||
import { logMessage } from "@lib/logger"; | ||
import { Attention, AttentionTypes } from "@components/globals/Attention/Attention"; | ||
import { Question } from "pages/profile"; | ||
import axios from "axios"; | ||
import { getCsrfToken } from "next-auth/react"; | ||
import { useRouter } from "next/router"; | ||
import debounce from "lodash.debounce"; | ||
|
||
const updateSecurityQuestion = async ( | ||
oldQuestionId: string, | ||
newQuestionId: string, | ||
answer: string | undefined | ||
) => { | ||
const csrfToken = await getCsrfToken(); | ||
|
||
if (csrfToken) { | ||
await axios.put( | ||
"/api/account/security-questions", | ||
{ | ||
oldQuestionId: oldQuestionId, | ||
newQuestionId: newQuestionId, | ||
newAnswer: answer, | ||
}, | ||
{ | ||
headers: { | ||
"Content-Type": "application/json", | ||
"X-CSRF-Token": csrfToken, | ||
}, | ||
} | ||
); | ||
} | ||
}; | ||
|
||
export const EditSecurityQuestionModal = ({ | ||
questionNumber, | ||
questionId, | ||
questions, | ||
handleClose, | ||
}: { | ||
questionNumber: number; | ||
questionId: string; | ||
questions: Question[]; | ||
handleClose: () => void; | ||
}) => { | ||
const { t, i18n } = useTranslation(["profile"]); | ||
const dialog = useDialogRef(); | ||
const questionRef = useRef<HTMLSelectElement>(null); | ||
const answerRef = useRef<HTMLInputElement>(null); | ||
const originalQuestionId = questionId; | ||
const router = useRouter(); | ||
|
||
const [isFormError, setIsFormError] = useState(false); | ||
const [isFormWarning, setIsFormWarning] = useState(false); | ||
|
||
const [isAnswerInputError, setIsAnswerInputError] = useState(false); | ||
const isAnswerInputValid = (text: string | undefined): boolean => { | ||
if (text && text.length >= 4) { | ||
return true; | ||
} | ||
return false; | ||
}; | ||
|
||
const langKey = i18n.language === "en" ? "questionEn" : "questionFr"; | ||
|
||
const _debouncedAnswerCheck = debounce( | ||
useCallback(() => { | ||
if (!isAnswerInputValid(answerRef.current?.value)) { | ||
setIsFormWarning(false); | ||
setIsAnswerInputError(true); | ||
return; | ||
} | ||
|
||
setIsAnswerInputError(false); | ||
setIsFormWarning(true); | ||
}, []), | ||
500 | ||
); | ||
|
||
const reset = () => { | ||
setIsFormError(false); | ||
setIsFormWarning(false); | ||
setIsAnswerInputError(false); | ||
}; | ||
|
||
const handleSubmit = async () => { | ||
try { | ||
reset(); | ||
|
||
const questionId = questionRef.current?.value; | ||
const questionAnswer = answerRef.current?.value; | ||
|
||
if (!questionId) { | ||
throw Error("Question Id required for security question API call"); | ||
} | ||
|
||
if (!isAnswerInputValid(questionAnswer)) { | ||
setIsAnswerInputError(true); | ||
return; | ||
} | ||
|
||
await updateSecurityQuestion(originalQuestionId, questionId, questionAnswer); | ||
|
||
dialog.current?.close(); | ||
handleClose(); | ||
router.push({ | ||
pathname: `${i18n.language}/profile`, | ||
}); | ||
} catch (err) { | ||
logMessage.error(err); | ||
setIsFormError(true); | ||
} | ||
}; | ||
|
||
if (!questionNumber || !questionId || questions?.length <= 0) { | ||
return ( | ||
<Dialog handleClose={handleClose} title={t("securityQuestionModal.title")} dialogRef={dialog}> | ||
<Attention | ||
type={AttentionTypes.ERROR} | ||
isAlert={true} | ||
classes="mb-6" | ||
heading={t("securityQuestionModal.errors.unknownError.title")} | ||
> | ||
<p className="text-sm text-[#26374a]"> | ||
{t("securityQuestionModal.errors.unknownError.content")} | ||
</p> | ||
</Attention> | ||
</Dialog> | ||
); | ||
} | ||
|
||
return ( | ||
<Dialog | ||
handleClose={handleClose} | ||
title={t("securityQuestionModal.title")} | ||
dialogRef={dialog} | ||
actions={ | ||
<Button theme="primary" type="submit" onClick={handleSubmit}> | ||
{t("securityQuestionModal.save")} | ||
</Button> | ||
} | ||
> | ||
<> | ||
{/* TODO: probably will not need the error since already selected questions can be removed programmatically */} | ||
{isFormError && ( | ||
<Attention | ||
type={AttentionTypes.ERROR} | ||
isAlert={true} | ||
heading={t("securityQuestionModal.errors.formError.title")} | ||
> | ||
<p className="text-sm text-[#26374a]"> | ||
{t("securityQuestionModal.errors.formError.content")} | ||
</p> | ||
</Attention> | ||
)} | ||
|
||
{isFormWarning && ( | ||
<Attention type={AttentionTypes.WARNING} isAlert={true} isIcon={false} classes="mb-6"> | ||
<p className="text-sm font-bold text-[#26374a]"> | ||
{t("securityQuestionModal.errors.clickSave.title")} | ||
</p> | ||
<p className="text-sm text-[#26374a]"> | ||
{t("securityQuestionModal.errors.clickSave.content")} | ||
</p> | ||
</Attention> | ||
)} | ||
|
||
<p>{t("securityQuestionModal.requirmentsList.title")}</p> | ||
<ul className="mb-6"> | ||
<li>{t("securityQuestionModal.requirmentsList.requirement1")}</li> | ||
<li>{t("securityQuestionModal.requirmentsList.requirement2")}</li> | ||
</ul> | ||
|
||
<div className="mb-10"> | ||
<Label id="questionLabel" htmlFor="question" className="required" required> | ||
{t("securityQuestionModal.questionLabel")} {questionNumber} | ||
</Label> | ||
<select | ||
name="question" | ||
id="questionLabel" | ||
className="gc-dropdown mb-0 w-full rounded" | ||
defaultValue={questionId} | ||
ref={questionRef} | ||
> | ||
{questions.map((q: Question) => ( | ||
<option key={q.id} value={q.id}> | ||
{q[langKey]} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
|
||
<div className="mb-10"> | ||
<Label | ||
id="answerLabel" | ||
htmlFor="answer" | ||
className={`required ${isAnswerInputError ? "text-red" : ""}`} | ||
required | ||
> | ||
{t("securityQuestionModal.answerLabel")} {questionNumber} | ||
</Label> | ||
{isAnswerInputError && ( | ||
<Attention | ||
type={AttentionTypes.ERROR} | ||
isAlert={true} | ||
isIcon={false} | ||
isSmall={true} | ||
isLeftBorder={true} | ||
> | ||
<p className="text-sm font-bold text-[#26374a]"> | ||
{t("securityQuestionModal.errors.invalidInput")} | ||
</p> | ||
</Attention> | ||
)} | ||
<input | ||
className={`gc-input-text w-full rounded ${isAnswerInputError ? "border-red" : ""}`} | ||
id="answer" | ||
name="answer" | ||
type="text" | ||
ref={answerRef} | ||
onChange={_debouncedAnswerCheck} | ||
/> | ||
</div> | ||
</> | ||
</Dialog> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.