From 1554711b86547b5eac74b4f653d6ef529c6e602a Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 19:17:16 +0100 Subject: [PATCH 01/23] fix(styles): change headings to use font.medium --- common/styles.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/common/styles.js b/common/styles.js index a776652d..7cbbd959 100644 --- a/common/styles.js +++ b/common/styles.js @@ -5,7 +5,7 @@ import { css } from "@emotion/react"; /* TYPOGRAPHY */ export const HEADING_01 = css` - font-family: ${Constants.font.text}; + font-family: ${Constants.font.medium}; font-size: 1.953rem; font-weight: medium; line-height: 1.5; @@ -21,25 +21,22 @@ export const HEADING_02 = css` `; export const HEADING_03 = css` - font-family: ${Constants.font.text}; + font-family: ${Constants.font.medium}; font-size: 1.25rem; - font-weight: medium; line-height: 1.5; letter-spacing: -0.02px; `; export const HEADING_04 = css` - font-family: ${Constants.font.text}; + font-family: ${Constants.font.medium}; font-size: 1rem; - font-weight: medium; line-height: 1.5; letter-spacing: -0.01px; `; export const HEADING_05 = css` - font-family: ${Constants.font.text}; + font-family: ${Constants.font.medium}; font-size: 0.875rem; - font-weight: medium; line-height: 1.5; letter-spacing: -0.01px; `; @@ -146,3 +143,14 @@ export const MOBILE_ONLY = css` pointer-events: none; } `; + +export const LINK = (theme) => css` + ${HOVERABLE}; + ${HEADING_05}; + &, + &:link, + &:visited { + color: ${theme.system.blue}; + text-decoration: none; + } +`; From 4b9333efb84f0aee3aa9bb62dec5fb71cdaa0196 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 19:21:06 +0100 Subject: [PATCH 02/23] feat(account-linking): add twitter account linking endpoints --- pages/api/twitter/link-with-verification.js | 108 ++++++++++++++++++++ pages/api/twitter/link.js | 74 ++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 pages/api/twitter/link-with-verification.js create mode 100644 pages/api/twitter/link.js diff --git a/pages/api/twitter/link-with-verification.js b/pages/api/twitter/link-with-verification.js new file mode 100644 index 00000000..8e435863 --- /dev/null +++ b/pages/api/twitter/link-with-verification.js @@ -0,0 +1,108 @@ +import * as Environment from "~/node_common/environment"; +import * as Data from "~/node_common/data"; +import * as Utilities from "~/node_common/utilities"; +import * as Strings from "~/common/strings"; +import * as Validations from "~/common/validations"; +import * as Constants from "~/common/constants"; + +export default async (req, res) => { + if (!Strings.isEmpty(Environment.ALLOWED_HOST) && req.headers.host !== Environment.ALLOWED_HOST) { + return res.status(403).send({ decorator: "SERVER_TWITTER_OAUTH_NOT_ALLOWED", error: true }); + } + + if (!Validations.username(req.body.data.username)) { + return res.status(500).send({ decorator: "SERVER_CREATE_USER_INVALID_USERNAME", error: true }); + } + + if (!Validations.legacyPassword(req.body.data.password)) { + return res.status(500).send({ decorator: "SERVER_CREATE_USER_INVALID_USERNAME", error: true }); + } + + if (Strings.isEmpty(req.body.data.token)) { + return res.status(500).send({ decorator: "SERVER_TWITTER_OAUTH_NO_OAUTH_TOKEN", error: true }); + } + + if (!Validations.verificationPin(req.body.data.pin)) { + return res + .status(500) + .send({ decorator: "SERVER_EMAIL_VERIFICATION_INVALID_PIN", error: true }); + } + + const { token, password, username, pin } = req.body.data; + + const user = await Data.getUserByUsername({ + username: username.toLowerCase(), + }); + + if (!user || user.error) { + return res + .status(!user ? 404 : 500) + .send({ decorator: "SERVER_SIGN_IN_USER_NOT_FOUND", error: true }); + } + + // Note(amine): Twitter users won't have a password, + // we should think in the future how to handle this use case + if ((!user.salt || !user.password) && user.twitterId) { + return res.status(403).send({ decorator: "SERVER_TWITTER_ALREADY_LINKED", error: true }); + } + + const hash = await Utilities.encryptPassword(password, user.salt); + if (hash !== user.password) { + return res.status(403).send({ decorator: "SERVER_TWITTER_WRONG_CREDENTIALS", error: true }); + } + + const verification = await Data.getVerificationBySid({ + sid: token, + }); + + if (!verification) { + return res.status(404).send({ decorator: "SERVER_EMAIL_VERIFICATION_FAILED", error: true }); + } + + if (verification.error) { + return res.status(404).send({ decorator: "SERVER_EMAIL_VERIFICATION_FAILED", error: true }); + } + + const isTokenExpired = + new Date() - new Date(verification.createdAt) > Constants.TOKEN_EXPIRATION_TIME; + if (isTokenExpired) { + return res.status(401).send({ decorator: "SERVER_EMAIL_VERIFICATION_FAILED", error: true }); + } + + if (verification.type !== "email_twitter_verification") { + return res.status(401).send({ decorator: "SERVER_EMAIL_VERIFICATION_FAILED", error: true }); + } + + if (verification.pin !== pin) { + return res + .status(401) + .send({ decorator: "SERVER_EMAIL_VERIFICATION_INVALID_PIN", error: true }); + } + + const twitterUser = await Data.getTwitterToken({ token: verification.twitterToken }); + if (!twitterUser) { + return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + } + + if (!twitterUser) { + return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + } + + const updates = await Data.updateUserById({ + id: user.id, + lastActive: new Date(), + email: verification.email, + twitterId: twitterUser.id_str, + data: { + twitter: { + username: twitterUser.screen_name, + verified: twitterUser.verified, + }, + }, + }); + + if (updates.error) { + return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + } + return res.status(200).send({ decorator: "SERVER_TWITTER_LINKING" }); +}; diff --git a/pages/api/twitter/link.js b/pages/api/twitter/link.js new file mode 100644 index 00000000..d7e99929 --- /dev/null +++ b/pages/api/twitter/link.js @@ -0,0 +1,74 @@ +import * as Environment from "~/node_common/environment"; +import * as Data from "~/node_common/data"; +import * as Utilities from "~/node_common/utilities"; +import * as Strings from "~/common/strings"; +import * as Validations from "~/common/validations"; + +export default async (req, res) => { + if (!Strings.isEmpty(Environment.ALLOWED_HOST) && req.headers.host !== Environment.ALLOWED_HOST) { + return res.status(403).send({ decorator: "SERVER_TWITTER_OAUTH_NOT_ALLOWED", error: true }); + } + + if (Strings.isEmpty(req.body.data.token)) { + return res.status(500).send({ decorator: "SERVER_TWITTER_OAUTH_NO_OAUTH_TOKEN", error: true }); + } + + if (!Validations.username(req.body?.data?.username)) { + return res.status(400).send({ decorator: "SERVER_CREATE_USER_INVALID_USERNAME", error: true }); + } + + if (!Validations.legacyPassword(req.body?.data?.password)) { + return res + .status(400) + .send({ decorator: "SERVER_CREATE_VERIFICATION_INVALID_PASSWORD", error: true }); + } + + const { username, password, token } = req.body.data; + + const user = await Data.getUserByUsername({ + username: username.toLowerCase(), + }); + + if (!user || user.error) { + return res + .status(!user ? 404 : 500) + .send({ decorator: "SERVER_SIGN_IN_USER_NOT_FOUND", error: true }); + } + + // Note(amine): Twitter users won't have a password, + // we should think in the future how to handle this use case + if ((!user.salt || !user.password) && user.twitterId) { + return res.status(403).send({ decorator: "SERVER_TWITTER_ALREADY_LINKED", error: true }); + } + + const hash = await Utilities.encryptPassword(password, user.salt); + if (hash !== user.password) { + return res.status(403).send({ decorator: "SERVER_TWITTER_WRONG_CREDENTIALS", error: true }); + } + + if (!user.email) { + return res.status(200).send({ shouldMigrate: true }); + } + + const twitterUser = await Data.getTwitterToken({ token: token }); + if (!twitterUser) { + return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + } + + const updates = await Data.updateUserById({ + id: user.id, + lastActive: new Date(), + twitterId: twitterUser.id_str, + data: { + twitter: { + username: twitterUser.screen_name, + verified: twitterUser.verified, + }, + }, + }); + + if (updates.error) { + return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + } + return res.status(200).send({ decorator: "SERVER_TWITTER_LINKING" }); +}; From 87a377e3e71859e2d56708af4bc8737b49bf93ee Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 19:21:30 +0100 Subject: [PATCH 03/23] feat(account-linking): add twitter account linking endpoints to actions --- common/actions.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/common/actions.js b/common/actions.js index 1535594a..823c9182 100644 --- a/common/actions.js +++ b/common/actions.js @@ -244,6 +244,20 @@ export const createUserViaTwitterWithVerification = async (data) => { }); }; +export const linkTwitterAccount = async (data) => { + return await returnJSON(`/api/twitter/link`, { + ...DEFAULT_OPTIONS, + body: JSON.stringify({ data }), + }); +}; + +export const linkTwitterAccountWithVerification = async (data) => { + return await returnJSON(`/api/twitter/link-with-verification`, { + ...DEFAULT_OPTIONS, + body: JSON.stringify({ data }), + }); +}; + export const updateViewer = async (data) => { await Websockets.checkWebsocket(); return await returnJSON(`/api/users/update`, { From ed2623249e8bf8741837b0d78f0de935268f2f39 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 19:31:59 +0100 Subject: [PATCH 04/23] feat(account-linking): add TwitterLinking component --- components/core/Auth/TwitterLinking.js | 165 ++++++++++++++++++ .../core/Auth/components/AuthCheckBox.js | 9 +- components/core/Auth/index.js | 1 + 3 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 components/core/Auth/TwitterLinking.js diff --git a/components/core/Auth/TwitterLinking.js b/components/core/Auth/TwitterLinking.js new file mode 100644 index 00000000..dd712abb --- /dev/null +++ b/components/core/Auth/TwitterLinking.js @@ -0,0 +1,165 @@ +/* eslint-disable jsx-a11y/no-autofocus */ +import * as React from "react"; +import * as Validations from "~/common/validations"; +import * as System from "~/components/system"; +import * as Strings from "~/common/strings"; + +import Field from "~/components/core/Field"; + +import { AnimateSharedLayout, motion } from "framer-motion"; +import { useForm } from "~/common/hooks"; + +import { SignUpPopover, Verification, AuthCheckBox } from "~/components/core/Auth/components"; + +const useTwitterLinking = () => { + // NOTE(amine): can be either 'account' | 'email' | 'verificatiom' + const [scene, setScene] = React.useState("account"); + const handlers = React.useMemo( + () => ({ + goToEmailScene: () => setScene("email"), + goToVerificationScene: () => setScene("verification"), + }), + [] + ); + return { ...handlers, scene }; +}; + +const MotionLayout = ({ children, ...props }) => ( + + {children} + +); + +const handleValidation = async ({ username, password, acceptTerms }, errors) => { + if (!Validations.username(username)) errors.username = "Invalid username"; + + if (!Validations.legacyPassword(password)) errors.password = "Incorrect password"; + + if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; + return errors; +}; + +export default function TwitterLinking({ + linkAccount, + linkAccountWithVerification, + createVerification, +}) { + const { scene, goToVerificationScene, goToEmailScene } = useTwitterLinking(); + + const { + getFieldProps, + getFormProps, + values: { username }, + isSubmitting, + } = useForm({ + initialValues: { username: "", password: "", acceptTerms: false }, + validate: handleValidation, + onSubmit: async ({ username, password }) => { + const response = await linkAccount({ username, password }); + if (response.shouldMigrate) { + goToEmailScene(); + } + }, + }); + + const { + getFormProps: getEmailFormProps, + getFieldProps: getEmailFieldProps, + isSubmitting: isEmailFormSubmitting, + } = useForm({ + initialValues: { email: "" }, + validate: ({ email }, errors) => { + if (Strings.isEmpty(email)) { + errors.email = "Please provide an email"; + } else if (!Validations.email(email)) { + errors.email = "Invalid email address"; + } + return errors; + }, + onSubmit: async ({ email }) => { + const response = await createVerification({ email }); + if (!response) return; + goToVerificationScene(); + }, + }); + + if (scene === "verification") { + const handleVerification = async ({ pin }) => { + await linkAccountWithVerification({ pin }); + }; + return ; + } + + if (scene === "email") { + return ( +
+ +
+ + + + + Send verification code + + + + +
+
+ ); + } + + return ( + +
+ + + + + + + + Connect to Twitter + + + + +
+ ); +} diff --git a/components/core/Auth/components/AuthCheckBox.js b/components/core/Auth/components/AuthCheckBox.js index 82a961e5..8cba8be0 100644 --- a/components/core/Auth/components/AuthCheckBox.js +++ b/components/core/Auth/components/AuthCheckBox.js @@ -1,5 +1,6 @@ import * as React from "react"; import * as System from "~/components/system"; + import { css } from "@emotion/react"; const STYLES_CHECKBOX_LABEL = (theme) => css` @@ -28,20 +29,12 @@ const STYLES_CHECKBOX_ERROR = (theme) => css` width: 16px; `; -const STYLES_CHECKBOX_SUCCESS = (theme) => css` - background-color: rgba(242, 242, 247, 0.5); - border: 1px solid ${theme.system.green}; - height: 16px; - width: 16px; -`; - const STYLES_CHECKBOX_WRAPPER = css` align-items: center; `; export default function AuthCheckBox({ touched, error, ...props }) { const showError = touched && error; - const showSuccess = touched && !error; const STYLES_CHECKBOX = React.useMemo(() => { if (showError) return STYLES_CHECKBOX_ERROR; diff --git a/components/core/Auth/index.js b/components/core/Auth/index.js index 436404b6..ae62d806 100644 --- a/components/core/Auth/index.js +++ b/components/core/Auth/index.js @@ -2,4 +2,5 @@ export { default as Signin } from "~/components/core/Auth/Signin"; export { default as Signup } from "~/components/core/Auth/Signup"; export { default as Initial } from "~/components/core/Auth/Initial"; export { default as TwitterSignup } from "~/components/core/Auth/TwitterSignup"; +export { default as TwitterLinking } from "~/components/core/Auth/TwitterLinking"; export { default as ResetPassword } from "~/components/core/Auth/ResetPassword"; From 21e9a2ad63de22b5f100550424fd928166ff73fd Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 19:32:30 +0100 Subject: [PATCH 05/23] feat(account-linking): add TwitterLinking to sceneAuth/index --- components/core/Auth/TwitterSignup.js | 59 +++++++++++-------- scenes/SceneAuth/hooks.js | 83 +++++++++++++++++++++++++-- scenes/SceneAuth/index.js | 42 +++++++++++--- 3 files changed, 147 insertions(+), 37 deletions(-) diff --git a/components/core/Auth/TwitterSignup.js b/components/core/Auth/TwitterSignup.js index eb627777..25633394 100644 --- a/components/core/Auth/TwitterSignup.js +++ b/components/core/Auth/TwitterSignup.js @@ -1,7 +1,9 @@ +/* eslint-disable jsx-a11y/no-autofocus */ import * as React from "react"; import * as System from "~/components/system"; import * as Validations from "~/common/validations"; import * as Actions from "~/common/actions"; +import * as Styles from "~/common/styles"; import Field from "~/components/core/Field"; @@ -12,12 +14,14 @@ import { useForm } from "~/common/hooks"; import { SignUpPopover, Verification, AuthCheckBox } from "~/components/core/Auth/components"; -const STYLES_SMALL = (theme) => css` - font-size: ${theme.typescale.lvlN1}; +const STYLES_LINK = css` + padding: 0; + margin: 0; + max-width: 224px; text-align: center; - color: ${theme.system.textGrayDark}; - max-width: 228px; margin: 0 auto; + background-color: unset; + border: none; `; const useTwitterSignup = () => { @@ -54,22 +58,21 @@ const useCheckUser = () => { }; }; -const createValidations = (validateUsername) => async ( - { username, email, acceptTerms }, - errors -) => { - await validateUsername({ username }, errors); +const createValidations = + (validateUsername) => + async ({ username, email, acceptTerms }, errors) => { + await validateUsername({ username }, errors); - if (!Validations.username(username)) errors.username = "Invalid username"; - // Note(amine): username should not be an email - if (Validations.email(username)) errors.username = "Username shouldn't be an email"; + if (!Validations.username(username)) errors.username = "Invalid username"; + // Note(amine): username should not be an email + if (Validations.email(username)) errors.username = "Username shouldn't be an email"; - if (!Validations.email(email)) errors.email = "Invalid email"; + if (!Validations.email(email)) errors.email = "Invalid email"; - if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; + if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; - return errors; -}; + return errors; + }; const MotionLayout = ({ children, ...props }) => ( @@ -80,6 +83,7 @@ const MotionLayout = ({ children, ...props }) => ( export default function TwitterSignup({ initialEmail, onSignup, + goToTwitterLinkingScene, createVerification, onSignupWithVerification, }) { @@ -90,7 +94,7 @@ export default function TwitterSignup({ const { getFieldProps, getFormProps, - values: { email, username }, + values: { username }, isSubmitting, isValidating, } = useForm({ @@ -141,6 +145,7 @@ export default function TwitterSignup({ } {...getFieldProps("username")} style={{ backgroundColor: "rgba(242,242,247,0.5)" }} + full /> @@ -165,13 +171,18 @@ export default function TwitterSignup({ Create account - {(!initialEmail || initialEmail !== email) && ( - - - You will receive a code to verify your email at this address - - - )} + + +
+ +
+
diff --git a/scenes/SceneAuth/hooks.js b/scenes/SceneAuth/hooks.js index 183ebfd9..6bd2614a 100644 --- a/scenes/SceneAuth/hooks.js +++ b/scenes/SceneAuth/hooks.js @@ -15,7 +15,10 @@ const AUTH_STATE_GRAPH = { RESET_PASSWORD: "password_reset", }, signup: {}, - twitter_signup: {}, + twitter_signup: { + LINK_EXISTING_ACCOUNT: "twitter_linking", + }, + twitter_linking: {}, password_reset: { BACK: "signin" }, }; @@ -40,6 +43,7 @@ export const useAuthFlow = () => { goToTwitterSignupScene: ({ twitterEmail }) => send({ event: "SIGNUP_WITH_TWITTER", context: { twitterEmail } }), goToResetPassword: () => send({ event: "RESET_PASSWORD" }), + goToTwitterLinkingScene: () => send({ event: "LINK_EXISTING_ACCOUNT" }), goBack: () => send({ event: "BACK" }), clearMessages: () => send({ ...state, context: { ...state.context, message: "" } }), }), @@ -206,8 +210,9 @@ export const useSignup = ({ onAuthenticate }) => { return { createVerification, verifyEmail, createUser, resendVerification }; }; -export const useTwitter = ({ onAuthenticate, goToTwitterSignupScene }) => { +export const useTwitter = ({ onAuthenticate, onTwitterAuthenticate, goToTwitterSignupScene }) => { const verificationToken = React.useRef(); + const credentialsRef = React.useRef(); const popupRef = React.useRef(); const [isLoggingIn, setIsLoggingIn] = React.useState(false); @@ -296,7 +301,7 @@ export const useTwitter = ({ onAuthenticate, goToTwitterSignupScene }) => { } if (response.token) { - await onAuthenticate(response); + await onTwitterAuthenticate(response); setIsLoggingIn(false); return; } @@ -310,6 +315,72 @@ export const useTwitter = ({ onAuthenticate, goToTwitterSignupScene }) => { } }; + const linkAccount = async ({ username, password }) => { + const { authToken } = twitterTokens.current; + credentialsRef.current = { username, password }; + + const userVersionResponse = await Actions.getUserVersion({ username }); + if (Events.hasError(userVersionResponse)) return; + + let hashedPassword; + if (userVersionResponse?.data?.version === 2) { + hashedPassword = await Utilities.encryptPasswordClient(password); + } else { + hashedPassword = password; + } + // NOTE(amine): handling client hash if the user is v2 + + const response = await Actions.linkTwitterAccount({ + username, + password: hashedPassword, + token: authToken, + }); + + if (response.shouldMigrate) { + return response; + } + + if (Events.hasError(response)) { + return; + } + + const authResponse = await onAuthenticate({ username, password: hashedPassword }); + if (Events.hasError(authResponse)) { + return; + } + }; + + const linkAccountWithVerification = async ({ pin }) => { + const { username, password } = credentialsRef.current; + + const userVersionResponse = await Actions.getUserVersion({ username }); + if (Events.hasError(userVersionResponse)) return; + + let hashedPassword; + if (userVersionResponse?.data?.version === 2) { + hashedPassword = await Utilities.encryptPasswordClient(password); + } else { + hashedPassword = password; + } + + // NOTE(amine): handling client hash if the user is v2 + const response = await Actions.linkTwitterAccountWithVerification({ + username, + password: hashedPassword, + token: verificationToken.current, + pin, + }); + + if (Events.hasError(response)) { + return; + } + + const authResponse = await onAuthenticate({ username, password: hashedPassword }); + if (Events.hasError(authResponse)) { + return; + } + }; + const signup = async ({ email = "", username = "" }) => { const { authToken } = twitterTokens.current; const response = await Actions.createUserViaTwitter({ @@ -319,7 +390,7 @@ export const useTwitter = ({ onAuthenticate, goToTwitterSignupScene }) => { }); if (Events.hasError(response)) return; if (response.token) { - await onAuthenticate(response); + await onTwitterAuthenticate(response); return; } return response; @@ -334,7 +405,7 @@ export const useTwitter = ({ onAuthenticate, goToTwitterSignupScene }) => { if (Events.hasError(response)) return; if (response.token) { - await onAuthenticate(response); + await onTwitterAuthenticate(response); return; } return response; @@ -365,6 +436,8 @@ export const useTwitter = ({ onAuthenticate, goToTwitterSignupScene }) => { return { isLoggingIn, signin, + linkAccount, + linkAccountWithVerification, signup, signupWithVerification, createVerification, diff --git a/scenes/SceneAuth/index.js b/scenes/SceneAuth/index.js index e65f4356..eb875162 100644 --- a/scenes/SceneAuth/index.js +++ b/scenes/SceneAuth/index.js @@ -3,7 +3,14 @@ import * as Utilities from "common/utilities"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; import { css } from "@emotion/react"; -import { Initial, Signin, Signup, TwitterSignup, ResetPassword } from "~/components/core/Auth"; +import { + Initial, + Signin, + Signup, + TwitterSignup, + TwitterLinking, + ResetPassword, +} from "~/components/core/Auth"; import { useAuthFlow, @@ -57,6 +64,7 @@ const SigninScene = ({ onAuthenticate, onTwitterAuthenticate, page, ...props }) goToSigninScene, goToSignupScene, goToTwitterSignupScene, + goToTwitterLinkingScene, goToResetPassword, clearMessages, goBack, @@ -69,7 +77,8 @@ const SigninScene = ({ onAuthenticate, onTwitterAuthenticate, page, ...props }) onAuthenticate, }); const twitterProvider = useTwitter({ - onAuthenticate: onTwitterAuthenticate, + onTwitterAuthenticate: onTwitterAuthenticate, + onAuthenticate, goToTwitterSignupScene, }); @@ -122,11 +131,21 @@ const SigninScene = ({ onAuthenticate, onTwitterAuthenticate, page, ...props }) initialEmail={context.twitterEmail} createVerification={twitterProvider.createVerification} resendEmailVerification={twitterProvider.resendEmailVerification} + goToTwitterLinkingScene={goToTwitterLinkingScene} onSignupWithVerification={twitterProvider.signupWithVerification} onSignup={twitterProvider.signup} /> ); + if (scene === "twitter_linking") { + return ( + + ); + } // NOTE(amine): if the user goes back, we should prefill the email const initialEmail = prevScene === "signin" && context.emailOrUsername ? context.emailOrUsername : ""; @@ -142,18 +161,25 @@ const SigninScene = ({ onAuthenticate, onTwitterAuthenticate, page, ...props }) /> ); }; - +const BackgroundGenerator = ({ children, ...props }) => { + const background = React.useMemo(() => { + const backgroundIdx = Utilities.getRandomNumberBetween(0, AUTH_BACKGROUNDS.length - 1); + return AUTH_BACKGROUNDS[backgroundIdx]; + }, []); + return ( +
+ {children} +
+ ); +}; const WithCustomWrapper = (Component) => (props) => { - const backgroundIdx = Utilities.getRandomNumberBetween(0, AUTH_BACKGROUNDS.length); - console.log(backgroundIdx); - const background = AUTH_BACKGROUNDS[backgroundIdx]; return ( -
+
-
+
); }; From f255a36df1aacd9d589d7fcb9449e15d013adc33 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 19:47:16 +0100 Subject: [PATCH 06/23] feat(account-linking): add decorators --- common/messages.js | 5 ++++- pages/api/twitter/link-with-verification.js | 14 +++++++++----- pages/api/twitter/link.js | 16 ++++++++++------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/common/messages.js b/common/messages.js index 600d5a0d..f9304b01 100644 --- a/common/messages.js +++ b/common/messages.js @@ -143,7 +143,10 @@ export const error = { SERVER_TWITTER_OAUTH_NOT_ALLOWED: "You can only authenticate via twitter while on slate.host", SERVER_TWITTER_LOGIN_ONLY: "This login is associated with a Twitter account. Please continue with Twitter instead", - + SERVER_TWITTER_LINKING_INVALID_USERNAME: "Please choose a valid username", + SERVER_TWITTER_LINKING_INVALID_PASSWORD: "Please choose a valid password", + SERVER_TWITTER_LINKING_WRONG_CREDENTIALS: "You have entered an invalid username or password", + SERVER_TWITTER_LINKING_FAILED: "SERVER_CREATE_USER_FAILED", // Email Verifications SERVER_EMAIL_VERIFICATION_INVALID_PIN: "Please enter a valid pin", SERVER_EMAIL_VERIFICATION_FAILED: diff --git a/pages/api/twitter/link-with-verification.js b/pages/api/twitter/link-with-verification.js index 8e435863..6ca3d4d0 100644 --- a/pages/api/twitter/link-with-verification.js +++ b/pages/api/twitter/link-with-verification.js @@ -11,11 +11,15 @@ export default async (req, res) => { } if (!Validations.username(req.body.data.username)) { - return res.status(500).send({ decorator: "SERVER_CREATE_USER_INVALID_USERNAME", error: true }); + return res + .status(500) + .send({ decorator: "SERVER_TWITTER_LINKING_INVALID_USERNAME", error: true }); } if (!Validations.legacyPassword(req.body.data.password)) { - return res.status(500).send({ decorator: "SERVER_CREATE_USER_INVALID_USERNAME", error: true }); + return res + .status(500) + .send({ decorator: "SERVER_TWITTER_LINKING_INVALID_PASSWORD", error: true }); } if (Strings.isEmpty(req.body.data.token)) { @@ -81,11 +85,11 @@ export default async (req, res) => { const twitterUser = await Data.getTwitterToken({ token: verification.twitterToken }); if (!twitterUser) { - return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + return res.status(401).send({ decorator: "SERVER_TWITTER_LINKING_FAILED", error: true }); } if (!twitterUser) { - return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + return res.status(401).send({ decorator: "SERVER_TWITTER_LINKING_FAILED", error: true }); } const updates = await Data.updateUserById({ @@ -102,7 +106,7 @@ export default async (req, res) => { }); if (updates.error) { - return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + return res.status(401).send({ decorator: "SERVER_TWITTER_LINKING_FAILED", error: true }); } return res.status(200).send({ decorator: "SERVER_TWITTER_LINKING" }); }; diff --git a/pages/api/twitter/link.js b/pages/api/twitter/link.js index d7e99929..65ba90ef 100644 --- a/pages/api/twitter/link.js +++ b/pages/api/twitter/link.js @@ -14,13 +14,15 @@ export default async (req, res) => { } if (!Validations.username(req.body?.data?.username)) { - return res.status(400).send({ decorator: "SERVER_CREATE_USER_INVALID_USERNAME", error: true }); + return res + .status(400) + .send({ decorator: "SERVER_TWITTER_LINKING_INVALID_USERNAME", error: true }); } if (!Validations.legacyPassword(req.body?.data?.password)) { return res .status(400) - .send({ decorator: "SERVER_CREATE_VERIFICATION_INVALID_PASSWORD", error: true }); + .send({ decorator: "SERVER_TWITTER_LINKING_INVALID_PASSWORD", error: true }); } const { username, password, token } = req.body.data; @@ -38,12 +40,14 @@ export default async (req, res) => { // Note(amine): Twitter users won't have a password, // we should think in the future how to handle this use case if ((!user.salt || !user.password) && user.twitterId) { - return res.status(403).send({ decorator: "SERVER_TWITTER_ALREADY_LINKED", error: true }); + return res.status(403).send({ decorator: "SERVER_CREATE_USER_TWITTER_EXISTS", error: true }); } const hash = await Utilities.encryptPassword(password, user.salt); if (hash !== user.password) { - return res.status(403).send({ decorator: "SERVER_TWITTER_WRONG_CREDENTIALS", error: true }); + return res + .status(403) + .send({ decorator: "SERVER_TWITTER_LINKING_WRONG_CREDENTIALS", error: true }); } if (!user.email) { @@ -52,7 +56,7 @@ export default async (req, res) => { const twitterUser = await Data.getTwitterToken({ token: token }); if (!twitterUser) { - return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + return res.status(401).send({ decorator: "SERVER_TWITTER_LINKING_FAILED", error: true }); } const updates = await Data.updateUserById({ @@ -68,7 +72,7 @@ export default async (req, res) => { }); if (updates.error) { - return res.status(401).send({ decorator: "SERVER_CREATE_USER_FAILED", error: true }); + return res.status(401).send({ decorator: "SERVER_TWITTER_LINKING_FAILED", error: true }); } return res.status(200).send({ decorator: "SERVER_TWITTER_LINKING" }); }; From 8420bfc5440f148fa4e22dd4be299479b7d03858 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 20:46:30 +0100 Subject: [PATCH 07/23] fix(ApplicationLayout): add withPaddings prop to disable paddings --- components/core/Application.js | 1 + components/core/ApplicationLayout.js | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/core/Application.js b/components/core/Application.js index eb307c07..2010e6b2 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -770,6 +770,7 @@ export default class ApplicationPage extends React.Component { return ( { this.prevScrollPos = window.pageYOffset; if (this.props.isMobile) { @@ -169,7 +174,7 @@ export default class ApplicationLayout extends React.Component { } return ( -
+
{this.props.header && (
Date: Tue, 22 Jun 2021 20:47:25 +0100 Subject: [PATCH 08/23] fix(auth): use box-shadow as border --- components/core/Field.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/core/Field.js b/components/core/Field.js index 2d0ae4e9..38ec6c9f 100644 --- a/components/core/Field.js +++ b/components/core/Field.js @@ -49,10 +49,11 @@ const STYLES_INPUT = (theme) => css` } `; const STYLES_INPUT_ERROR = (theme) => css` - border: 1px solid ${theme.system.red}; + box-shadow: 0 0 0 1px ${theme.system.red}; `; + const STYLES_INPUT_SUCCESS = (theme) => css` - border: 1px solid ${theme.system.green}; + box-shadow: 0 0 0 1px ${theme.system.green}; `; const PasswordValidations = ({ validations }) => { From 9f57a1cab9e2a45cc5e172719d59ff6aa277dbdf Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 20:48:00 +0100 Subject: [PATCH 09/23] fix(auth): 100vh on mobile overflowing --- scenes/SceneAuth/index.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scenes/SceneAuth/index.js b/scenes/SceneAuth/index.js index eb875162..f14098da 100644 --- a/scenes/SceneAuth/index.js +++ b/scenes/SceneAuth/index.js @@ -28,7 +28,7 @@ const STYLES_ROOT = css` text-align: center; font-size: 1rem; - min-height: 100vh; + height: 100vh; width: 100vw; position: relative; overflow: hidden; @@ -166,12 +166,23 @@ const BackgroundGenerator = ({ children, ...props }) => { const backgroundIdx = Utilities.getRandomNumberBetween(0, AUTH_BACKGROUNDS.length - 1); return AUTH_BACKGROUNDS[backgroundIdx]; }, []); + + // NOTE(amine): fix for 100vh overflowing in mobile + // https://bugs.webkit.org/show_bug.cgi?id=141832 + const [height, setHeight] = React.useState(); + React.useLayoutEffect(() => { + if (!window) return; + const windowInnerHeight = window.innerHeight; + setHeight(windowInnerHeight); + }, []); + return ( -
+
{children}
); }; + const WithCustomWrapper = (Component) => (props) => { return ( From 745468a2867ee1f27a7f5bbcef4e3834ce411ae5 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 20:48:20 +0100 Subject: [PATCH 10/23] fix(auth): removed button from initial signup screen --- components/core/Auth/Initial.js | 75 +++++++++++++++++---------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/components/core/Auth/Initial.js b/components/core/Auth/Initial.js index cdb49895..deb267de 100644 --- a/components/core/Auth/Initial.js +++ b/components/core/Auth/Initial.js @@ -8,10 +8,12 @@ import * as Strings from "~/common/strings"; import * as Styles from "~/common/styles"; import { css } from "@emotion/react"; -import { motion, AnimateSharedLayout } from "framer-motion"; -import { useForm, useField } from "~/common/hooks"; -import Field from "~/components/core/Field"; +import { AnimateSharedLayout } from "framer-motion"; +import { useField } from "~/common/hooks"; import { Toggle, SignUpPopover, ArrowButton } from "~/components/core/Auth/components"; +import { LoaderSpinner } from "~/components/system/components/Loaders"; + +import Field from "~/components/core/Field"; const STYLES_INITIAL_CONTAINER = css` display: flex; @@ -71,18 +73,14 @@ export default function Initial({ const { TOGGLE_OPTIONS, toggleValue, handleToggleChange } = useToggler(page); // NOTE(amine): Signup view form - const { getFieldProps, getFormProps, isSubmitting: isCheckingEmail } = useForm({ + const { getFieldProps: getSignupFielProps, isSubmitting: isCheckingEmail } = useField({ validateOnBlur: false, - initialValues: { email: initialEmail || "" }, - validate: ({ email }, errors) => { - if (Strings.isEmpty(email)) { - errors.email = "Please provide an email"; - } else if (!Validations.email(email)) { - errors.email = "Invalid email address"; - } - return errors; + initialValues: initialEmail || "", + validate: (email) => { + if (Strings.isEmpty(email)) return "Please provide an email"; + else if (!Validations.email(email)) return "Invalid email address"; }, - onSubmit: async ({ email }) => { + onSubmit: async (email) => { const response = await Actions.checkEmail({ email }); if (response?.data?.twitter) { Events.dispatchMessage({ @@ -183,30 +181,33 @@ export default function Initial({ ) : ( -
- - - - Send verification link - - - + ( + + ) + : ArrowButton + } + // NOTE(amine): the input component internally is using 16px margin top + containerStyle={{ marginTop: "4px" }} + {...getSignupFielProps("email")} + /> +
From 9738bd6dfa2c5a1862bf3180da03aa3f64c837e2 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 22 Jun 2021 21:56:15 +0100 Subject: [PATCH 11/23] fix(field): increase opacity to 0.7 --- components/core/Auth/ResetPassword.js | 2 -- components/core/Auth/Signin.js | 2 -- components/core/Auth/Signup.js | 25 ++++++++----------- components/core/Auth/TwitterLinking.js | 1 - components/core/Auth/TwitterSignup.js | 2 -- .../core/Auth/components/Verification.js | 1 - 6 files changed, 11 insertions(+), 22 deletions(-) diff --git a/components/core/Auth/ResetPassword.js b/components/core/Auth/ResetPassword.js index 06ccf9db..3db33674 100644 --- a/components/core/Auth/ResetPassword.js +++ b/components/core/Auth/ResetPassword.js @@ -123,7 +123,6 @@ export default function ResetPassword({ setPasswordValidations(validations); }, })} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} onClickIcon={() => toggleShowPassword(!showPassword)} icon={showPassword ? SVG.EyeOff : SVG.Eye} /> @@ -161,7 +160,6 @@ export default function ResetPassword({ type="email" full {...getFieldProps("email")} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} /> diff --git a/components/core/Auth/Signin.js b/components/core/Auth/Signin.js index fa3e1af5..b1ed231b 100644 --- a/components/core/Auth/Signin.js +++ b/components/core/Auth/Signin.js @@ -163,7 +163,6 @@ export default function Signin({ type="email" full {...getEmailFieldProps("email")} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} /> @@ -202,7 +201,6 @@ export default function Signin({ onClickIcon={() => toggleShowPassword(!showPassword)} icon={showPassword ? SVG.EyeOff : SVG.Eye} {...getFieldProps("password")} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} /> diff --git a/components/core/Auth/Signup.js b/components/core/Auth/Signup.js index 0e0e1004..61146032 100644 --- a/components/core/Auth/Signup.js +++ b/components/core/Auth/Signup.js @@ -50,21 +50,20 @@ const useCheckUser = () => { }; }; -const createValidations = (validateUsername) => async ( - { username, password, acceptTerms }, - errors -) => { - await validateUsername({ username }, errors); +const createValidations = + (validateUsername) => + async ({ username, password, acceptTerms }, errors) => { + await validateUsername({ username }, errors); - if (!Validations.username(username)) errors.username = "Invalid username"; - // Note(amine): username should not be an email - if (Validations.email(username)) errors.username = "Username shouldn't be an email"; + if (!Validations.username(username)) errors.username = "Invalid username"; + // Note(amine): username should not be an email + if (Validations.email(username)) errors.username = "Username shouldn't be an email"; - if (!Validations.password(password)) errors.password = "Incorrect password"; + if (!Validations.password(password)) errors.password = "Incorrect password"; - if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; - return errors; -}; + if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; + return errors; + }; export default function Signup({ verifyEmail, createUser, resendEmailVerification }) { const [passwordValidations, setPasswordValidations] = React.useState( @@ -119,7 +118,6 @@ export default function Signup({ verifyEmail, createUser, resendEmailVerificatio } full {...getFieldProps("username")} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} /> @@ -136,7 +134,6 @@ export default function Signup({ verifyEmail, createUser, resendEmailVerificatio setPasswordValidations(validations); }, })} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} onClickIcon={() => toggleShowPassword(!showPassword)} icon={showPassword ? SVG.EyeOff : SVG.Eye} /> diff --git a/components/core/Auth/TwitterLinking.js b/components/core/Auth/TwitterLinking.js index dd712abb..c57a3aeb 100644 --- a/components/core/Auth/TwitterLinking.js +++ b/components/core/Auth/TwitterLinking.js @@ -103,7 +103,6 @@ export default function TwitterLinking({ type="email" full {...getEmailFieldProps("email")} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} /> diff --git a/components/core/Auth/TwitterSignup.js b/components/core/Auth/TwitterSignup.js index 25633394..5d0db136 100644 --- a/components/core/Auth/TwitterSignup.js +++ b/components/core/Auth/TwitterSignup.js @@ -144,7 +144,6 @@ export default function TwitterSignup({ : null } {...getFieldProps("username")} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} full /> @@ -157,7 +156,6 @@ export default function TwitterSignup({ type="email" full {...getFieldProps("email")} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} /> diff --git a/components/core/Auth/components/Verification.js b/components/core/Auth/components/Verification.js index 82e06789..915f13f4 100644 --- a/components/core/Auth/components/Verification.js +++ b/components/core/Auth/components/Verification.js @@ -113,7 +113,6 @@ export default function Verification({ onVerify, title = DEFAULT_TITLE, onResend } textStyle={{ width: "100% !important" }} containerStyle={{ marginTop: "28px" }} - style={{ backgroundColor: "rgba(242,242,247,0.5)" }} name="pin" type="pin" {...getFieldProps()} From 27c3ab600e0e52148c35a58d31e7f216437964b1 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 10:19:14 +0100 Subject: [PATCH 12/23] feat(hooks): add useToggle hook --- common/hooks.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/common/hooks.js b/common/hooks.js index 0e713860..02403bdf 100644 --- a/common/hooks.js +++ b/common/hooks.js @@ -39,10 +39,12 @@ export const useForm = ({ }); const _hasError = (obj) => Object.keys(obj).some((name) => obj[name]); - const _mergeEventHandlers = (events = []) => (e) => - events.forEach((event) => { - if (event) event(e); - }); + const _mergeEventHandlers = + (events = []) => + (e) => + events.forEach((event) => { + if (event) event(e); + }); /** ---------- NOTE(amine): Input Handlers ---------- */ const handleFieldChange = (e) => @@ -162,10 +164,12 @@ export const useField = ({ touched: undefined, }); - const _mergeEventHandlers = (events = []) => (e) => - events.forEach((event) => { - if (event) event(e); - }); + const _mergeEventHandlers = + (events = []) => + (e) => + events.forEach((event) => { + if (event) event(e); + }); /** ---------- NOTE(amine): Input Handlers ---------- */ const handleFieldChange = (e) => @@ -176,14 +180,14 @@ export const useField = ({ touched: false, })); - const handleOnBlur = (e) => { + const handleOnBlur = () => { // NOTE(amine): validate the inputs onBlur and touch the current input let error = {}; if (validateOnBlur && validate) error = validate(state.value); setState((prev) => ({ ...prev, touched: validateOnBlur, error })); }; - const handleFormOnSubmit = (e) => { + const handleFormOnSubmit = () => { //NOTE(amine): touch all inputs setState((prev) => ({ ...prev, touched: true })); @@ -219,3 +223,9 @@ export const useField = ({ return { getFieldProps, value: state.value, isSubmitting: state.isSubmitting }; }; + +export const useToggle = (initialState = false) => { + const [state, setState] = React.useState(initialState); + const toggleState = () => setState((prev) => !prev); + return [state, toggleState]; +}; From b8c227f7153885655001a570c9b3c3fba9b49e81 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 10:19:41 +0100 Subject: [PATCH 13/23] feat(field): color variant --- components/core/Field.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/components/core/Field.js b/components/core/Field.js index 38ec6c9f..18b5107f 100644 --- a/components/core/Field.js +++ b/components/core/Field.js @@ -56,9 +56,12 @@ const STYLES_INPUT_SUCCESS = (theme) => css` box-shadow: 0 0 0 1px ${theme.system.green}; `; -const PasswordValidations = ({ validations }) => { +const PasswordValidations = ({ validations, full, color }) => { return ( -
+

Passwords should

@@ -92,6 +95,8 @@ export default function Field({ validations, errorAs, containerAs, + full, + color = "transparent", ...props }) { const showError = touched && error; @@ -107,10 +112,14 @@ export default function Field({ return (
- + {props.name === "password" && validations && ( - + )} {props.name !== "password" && (showError || showSuccess) && ( From c707eed5ab147bd9e130d422b05406b6c4cb03f9 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 10:20:10 +0100 Subject: [PATCH 14/23] feat(field): update change password in settings to use new behavior --- scenes/SceneEditAccount.js | 139 +++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 60 deletions(-) diff --git a/scenes/SceneEditAccount.js b/scenes/SceneEditAccount.js index 8bc88c2c..206be235 100644 --- a/scenes/SceneEditAccount.js +++ b/scenes/SceneEditAccount.js @@ -5,7 +5,7 @@ import * as Strings from "~/common/strings"; import * as Validations from "~/common/validations"; import * as Window from "~/common/window"; import * as Constants from "~/common/constants"; -import * as FileUtilities from "~/common/file-utilities"; +import * as Utilities from "~/common/utilities"; import * as UserBehaviors from "~/common/user-behaviors"; import * as Events from "~/common/custom-events"; import * as SVG from "~/common/svg"; @@ -13,11 +13,13 @@ import * as SVG from "~/common/svg"; import { css } from "@emotion/react"; import { SecondaryTabGroup } from "~/components/core/TabGroup"; import { ConfirmationModal } from "~/components/core/ConfirmationModal"; +import { useForm, useToggle } from "~/common/hooks"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; import ScenePage from "~/components/core/ScenePage"; import ScenePageHeader from "~/components/core/ScenePageHeader"; import Avatar from "~/components/core/Avatar"; +import Field from "~/components/core/Field"; const STYLES_FILE_HIDDEN = css` height: 1px; @@ -41,25 +43,89 @@ const STYLES_HEADER = css` margin-bottom: 16px; `; +const SecuritySection = ({ onUpdateViewer, username }) => { + const [passwordValidations, setPasswordValidations] = React.useState( + Validations.passwordForm("") + ); + + const { getFieldProps, getFormProps, isSubmitting } = useForm({ + initialValues: { password: "" }, + validate: ({ password }, errors) => { + if (!Validations.password(password)) errors.password = "Incorrect password"; + return errors; + }, + onSubmit: async ({ password }) => { + const userVersionResponse = await Actions.getUserVersion({ username }); + if (Events.hasError(userVersionResponse)) return; + + let hashedPassword; + if (userVersionResponse?.data?.version === 2) { + hashedPassword = await Utilities.encryptPasswordClient(password); + } else { + hashedPassword = password; + } + + let response = await onUpdateViewer({ + type: "CHANGE_PASSWORD", + password: hashedPassword, + }); + + if (Events.hasError(response)) { + return; + } + + Events.dispatchMessage({ message: "Password successfully updated!", status: "INFO" }); + }, + }); + + const [showPassword, togglePasswordVisibility] = useToggle(false); + + return ( +
+
Change password
+
Passwords must be a minimum of eight characters.
+
+ { + const validations = Validations.passwordForm(e.target.value); + setPasswordValidations(validations); + }, + })} + type={showPassword ? "text" : "password"} + onClickIcon={togglePasswordVisibility} + icon={showPassword ? SVG.EyeOff : SVG.Eye} + /> +
+ + Change password + +
+ +
+ ); +}; + export default class SceneEditAccount extends React.Component { state = { username: this.props.viewer.username, - password: "", confirm: "", body: this.props.viewer.data.body, photo: this.props.viewer.data.photo, name: this.props.viewer.data.name, deleting: false, - allow_filecoin_directory_listing: this.props.viewer.data.settings - ?.allow_filecoin_directory_listing, + allow_filecoin_directory_listing: + this.props.viewer.data.settings?.allow_filecoin_directory_listing, allow_automatic_data_storage: this.props.viewer.data.settings?.allow_automatic_data_storage, allow_encrypted_data_storage: this.props.viewer.data.settings?.allow_encrypted_data_storage, - changingPassword: false, changingAvatar: false, savingNameBio: false, changingFilecoin: false, modalShow: false, - showPassword: false, }; _handleUpload = async (e) => { @@ -70,7 +136,7 @@ export default class SceneEditAccount extends React.Component { return; } - const cid = file.cid; + const { cid } = file; const url = Strings.getURLfromCID(cid); let updateResponse = await Actions.updateViewer({ data: { @@ -82,7 +148,7 @@ export default class SceneEditAccount extends React.Component { this.setState({ changingAvatar: false, photo: url }); }; - _handleSaveFilecoin = async (e) => { + _handleSaveFilecoin = async () => { this.setState({ changingFilecoin: true }); let response = await Actions.updateViewer({ @@ -100,7 +166,7 @@ export default class SceneEditAccount extends React.Component { this.setState({ changingFilecoin: false }); }; - _handleSave = async (e) => { + _handleSave = async () => { if (!Validations.username(this.state.username)) { Events.dispatchMessage({ message: "Please include only letters and numbers in your username", @@ -129,32 +195,6 @@ export default class SceneEditAccount extends React.Component { this.setState({ [e.target.name]: e.target.value.toLowerCase() }); }; - _handleChangePassword = async (e) => { - if (this.state.password !== this.state.confirm) { - Events.dispatchMessage({ message: "Passwords did not match" }); - return; - } - - if (!Validations.password(this.state.password)) { - Events.dispatchMessage({ message: "Password length must be more than 8 characters" }); - return; - } - - this.setState({ changingPassword: true }); - - let response = await Actions.updateViewer({ - type: "CHANGE_PASSWORD", - password: this.state.password, - }); - - if (Events.hasError(response)) { - this.setState({ changingPassword: false }); - return; - } - - this.setState({ changingPassword: false, password: "", confirm: "" }); - }; - _handleDelete = async (res) => { if (!res) { this.setState({ modalShow: false }); @@ -296,31 +336,10 @@ export default class SceneEditAccount extends React.Component {
) : null} {tab === "security" ? ( -
-
Change password
-
Passwords must be a minimum of eight characters.
- - this.setState({ showPassword: !this.state.showPassword })} - icon={this.state.showPassword ? SVG.EyeOff : SVG.Eye} - /> - -
- - Change password - -
-
+ ) : null} {tab === "account" ? (
From 7d45f048a3d5c78e7e6ae5d721886eaa395d7224 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 11:03:52 +0100 Subject: [PATCH 15/23] fix(field): add white border --- components/core/Field.js | 1 + 1 file changed, 1 insertion(+) diff --git a/components/core/Field.js b/components/core/Field.js index 18b5107f..4e189da6 100644 --- a/components/core/Field.js +++ b/components/core/Field.js @@ -44,6 +44,7 @@ const STYLES_INPUT = (theme) => css` background-color: rgba(242, 242, 247, 0.7); box-shadow: ${theme.shadow.large}; border-radius: 8px; + box-shadow: 0 0 0 1px ${theme.system.white}; &::placeholder { color: ${theme.system.textGrayDark}; } From 6e08cae36fa6327899efa8cff62e40eace63eb85 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 11:08:51 +0100 Subject: [PATCH 16/23] fix(password-validation): increase opacity to 0.7 --- common/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.js b/common/constants.js index fdad8b39..904637ea 100644 --- a/common/constants.js +++ b/common/constants.js @@ -61,7 +61,7 @@ export const system = { active: "#00BB00", blurBlack: "#262626", bgBlurGray: "#403F42", - bgBlurWhiteTRN: "rgba(255,255,255,0.3)", + bgBlurWhiteTRN: "rgba(255,255,255,0.7)", grayLight2: "#AEAEB2", grayLight5: "#E5E5EA", }; From b0b1ca14d083c3e53bca489be07f4bc95ff09119 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 18:45:08 +0100 Subject: [PATCH 17/23] fix(authCheckbox): backgroundColor --- components/core/Auth/components/AuthCheckBox.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/core/Auth/components/AuthCheckBox.js b/components/core/Auth/components/AuthCheckBox.js index 8cba8be0..dfbdca23 100644 --- a/components/core/Auth/components/AuthCheckBox.js +++ b/components/core/Auth/components/AuthCheckBox.js @@ -1,5 +1,6 @@ import * as React from "react"; import * as System from "~/components/system"; +import * as Constants from "~/common/constants"; import { css } from "@emotion/react"; @@ -46,6 +47,7 @@ export default function AuthCheckBox({ touched, error, ...props }) { containerStyles={STYLES_CHECKBOX_WRAPPER} labelStyles={STYLES_CHECKBOX_LABEL} inputStyles={STYLES_CHECKBOX} + boxStyle={{ backgroundColor: Constants.system.bgBlurWhiteTRN }} {...props} > I agree to the Slate{" "} From 5e4d704121e633ccf9fabea27e5f1bd3695e6a0d Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 18:59:09 +0100 Subject: [PATCH 18/23] feat(useForm): add support to format inputs --- common/hooks.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/hooks.js b/common/hooks.js index 02403bdf..e482aaa9 100644 --- a/common/hooks.js +++ b/common/hooks.js @@ -27,6 +27,8 @@ export const useForm = ({ onSubmit, validate, initialValues, + // NOTE(amine): you can format the value of each input before onChange. ex format:{username: formatUsername} + format = {}, validateOnBlur = true, validateOnSubmit = true, }) => { @@ -39,6 +41,13 @@ export const useForm = ({ }); const _hasError = (obj) => Object.keys(obj).some((name) => obj[name]); + + const formatInputValue = (e) => { + if (typeof format !== "object" || !(e.target.name in format)) return e.target.value; + const formatInput = format[e.target.name]; + return formatInput ? formatInput(e.target.value) : e.target.value; + }; + const _mergeEventHandlers = (events = []) => (e) => @@ -50,7 +59,7 @@ export const useForm = ({ const handleFieldChange = (e) => setState((prev) => ({ ...prev, - values: { ...prev.values, [e.target.name]: e.target.value }, + values: { ...prev.values, [e.target.name]: formatInputValue(e) }, errors: { ...prev.errors, [e.target.name]: undefined }, touched: { ...prev.touched, [e.target.name]: false }, })); From bd7605079f8e01b910090d7c84b9dbabd39c7029 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 18:59:55 +0100 Subject: [PATCH 19/23] feat(auth): use Strings.createUsername in Signup component and endpoint --- components/core/Auth/Signup.js | 11 +++++++---- pages/api/users/create.js | 11 +++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/components/core/Auth/Signup.js b/components/core/Auth/Signup.js index 61146032..f3178a75 100644 --- a/components/core/Auth/Signup.js +++ b/components/core/Auth/Signup.js @@ -3,6 +3,7 @@ import * as System from "~/components/system"; import * as Validations from "~/common/validations"; import * as SVG from "~/common/svg"; import * as Actions from "~/common/actions"; +import * as Strings from "~/common/strings"; import Field from "~/components/core/Field"; @@ -29,6 +30,11 @@ const useCheckUser = () => { const usernamesTaken = React.useRef([]); return async ({ username }, errors) => { + if (!Validations.username(username)) { + errors.username = "Invalid username"; + return; + } + if (usernamesAllowed.current.some((value) => value === username)) { return; } @@ -55,10 +61,6 @@ const createValidations = async ({ username, password, acceptTerms }, errors) => { await validateUsername({ username }, errors); - if (!Validations.username(username)) errors.username = "Invalid username"; - // Note(amine): username should not be an email - if (Validations.email(username)) errors.username = "Username shouldn't be an email"; - if (!Validations.password(password)) errors.password = "Incorrect password"; if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; @@ -76,6 +78,7 @@ export default function Signup({ verifyEmail, createUser, resendEmailVerificatio const { getFieldProps, getFormProps, isSubmitting, isValidating } = useForm({ initialValues: { username: "", password: "", acceptTerms: false }, + format: { username: Strings.createUsername }, validate: createValidations(validateUsername), onSubmit: async ({ username, password }) => await createUser({ username, password }), }); diff --git a/pages/api/users/create.js b/pages/api/users/create.js index 34e84d83..30ba0efd 100644 --- a/pages/api/users/create.js +++ b/pages/api/users/create.js @@ -29,6 +29,8 @@ export default async (req, res) => { .send({ decorator: "SERVER_EMAIL_VERIFICATION_INVALID_TOKEN", error: true }); } + const formattedUsername = Strings.createUsername(req.body.data.username); + const verification = await Data.getVerificationBySid({ sid: req.body.data.token, }); @@ -38,7 +40,7 @@ export default async (req, res) => { } const existing = await Data.getUserByUsername({ - username: req.body.data.username.toLowerCase(), + username: formattedUsername, }); if (existing) { return res.status(403).send({ decorator: "SERVER_CREATE_USER_USERNAME_TAKEN", error: true }); @@ -60,12 +62,11 @@ export default async (req, res) => { // TODO(jim): // Don't do this once you refactor. - const newUsername = req.body.data.username.toLowerCase(); const newEmail = verification.email; const { buckets, bucketKey, bucketName } = await Utilities.getBucketAPIFromUserToken({ user: { - username: newUsername, + username: formattedUsername, data: { tokens: { api } }, }, }); @@ -85,7 +86,7 @@ export default async (req, res) => { const user = await Data.createUser({ password: hash, salt, - username: newUsername, + username: formattedUsername, email: newEmail, data: { photo, @@ -122,6 +123,4 @@ export default async (req, res) => { from: slateEmail, templateId: welcomeTemplateId, }); - - Monitor.createUser({ user }); }; From 2aae15c101f7915c6d52b9c30e31e212dd0e4472 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 19:00:56 +0100 Subject: [PATCH 20/23] feat(auth): use Strings.createUsername in TwitterSignup component and endpoints --- components/core/Auth/TwitterSignup.js | 11 +++++++---- pages/api/twitter/signup-with-verification.js | 9 +++++---- pages/api/twitter/signup.js | 9 +++++---- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/components/core/Auth/TwitterSignup.js b/components/core/Auth/TwitterSignup.js index 5d0db136..721bbbd7 100644 --- a/components/core/Auth/TwitterSignup.js +++ b/components/core/Auth/TwitterSignup.js @@ -4,6 +4,7 @@ import * as System from "~/components/system"; import * as Validations from "~/common/validations"; import * as Actions from "~/common/actions"; import * as Styles from "~/common/styles"; +import * as Strings from "~/common/strings"; import Field from "~/components/core/Field"; @@ -37,6 +38,11 @@ const useCheckUser = () => { const usernamesTaken = React.useRef([]); return async ({ username }, errors) => { + if (!Validations.username(username)) { + errors.username = "Invalid username"; + return; + } + if (usernamesAllowed.current.some((value) => value === username)) { return; } @@ -63,10 +69,6 @@ const createValidations = async ({ username, email, acceptTerms }, errors) => { await validateUsername({ username }, errors); - if (!Validations.username(username)) errors.username = "Invalid username"; - // Note(amine): username should not be an email - if (Validations.email(username)) errors.username = "Username shouldn't be an email"; - if (!Validations.email(email)) errors.email = "Invalid email"; if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; @@ -99,6 +101,7 @@ export default function TwitterSignup({ isValidating, } = useForm({ initialValues: { username: "", email: initialEmail, acceptTerms: false }, + format: { username: Strings.createUsername }, validate: createValidations(validateUsername), onSubmit: async ({ username, email }) => { if (email !== initialEmail) { diff --git a/pages/api/twitter/signup-with-verification.js b/pages/api/twitter/signup-with-verification.js index aa6b6798..c4999de6 100644 --- a/pages/api/twitter/signup-with-verification.js +++ b/pages/api/twitter/signup-with-verification.js @@ -31,6 +31,8 @@ export default async (req, res) => { return res.status(500).send({ decorator: "SERVER_CREATE_USER_INVALID_USERNAME", error: true }); } + const formattedUsername = Strings.createUsername(username); + const verification = await Data.getVerificationBySid({ sid: req.body.data.token, }); @@ -70,7 +72,6 @@ export default async (req, res) => { return res.status(201).send({ decorator: "SERVER_CREATE_USER_TWITTER_EXISTS" }); } - const newUsername = username.toLowerCase(); const newEmail = verification.email.toLowerCase(); // NOTE(Amine): If there is an account with the user's twitter email @@ -78,7 +79,7 @@ export default async (req, res) => { if (userByEmail) return res.status(201).send({ decorator: "SERVER_CREATE_USER_EMAIL_TAKEN" }); // NOTE(Amine): If there is an account with the provided username - const userByUsername = await Data.getUserByUsername({ username }); + const userByUsername = await Data.getUserByUsername({ username: formattedUsername }); if (userByUsername) { return res.status(201).send({ decorator: "SERVER_CREATE_USER_USERNAME_TAKEN" }); } @@ -92,7 +93,7 @@ export default async (req, res) => { // Don't do this once you refactor. const { buckets, bucketKey, bucketName } = await Utilities.getBucketAPIFromUserToken({ user: { - username: newUsername, + username: formattedUsername, data: { tokens: { api } }, }, }); @@ -110,7 +111,7 @@ export default async (req, res) => { }); const user = await Data.createUser({ - username: newUsername, + username: formattedUsername, email: newEmail, twitterId: twitterUser.id_str, data: { diff --git a/pages/api/twitter/signup.js b/pages/api/twitter/signup.js index 468fcd83..373893f8 100644 --- a/pages/api/twitter/signup.js +++ b/pages/api/twitter/signup.js @@ -30,6 +30,8 @@ export default async (req, res) => { return res.status(500).send({ decorator: "SERVER_CREATE_USER_INVALID_USERNAME", error: true }); } + const formattedUsername = Strings.createUsername(username); + const storedAuthToken = req.cookies[COOKIE_NAME]; // NOTE(amine): additional security check @@ -59,7 +61,7 @@ export default async (req, res) => { } // NOTE(Amine): If there is an account with the provided username - const userByUsername = await Data.getUserByUsername({ username }); + const userByUsername = await Data.getUserByUsername({ username: formattedUsername }); if (userByUsername) { return res.status(201).send({ decorator: "SERVER_CREATE_USER_USERNAME_TAKEN" }); } @@ -69,12 +71,11 @@ export default async (req, res) => { const identity = await PrivateKey.fromRandom(); const api = identity.toString(); - const newUsername = username.toLowerCase(); const newEmail = email.toLowerCase(); const { buckets, bucketKey, bucketName } = await Utilities.getBucketAPIFromUserToken({ user: { - username: newUsername, + username: formattedUsername, data: { tokens: { api } }, }, }); @@ -92,7 +93,7 @@ export default async (req, res) => { }); const user = await Data.createUser({ - username: newUsername, + username: formattedUsername, email: newEmail, twitterId: twitterUser.id_str, data: { From a62d0dd8b97f8068d82aad7658b23654c006e0b4 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 19:01:07 +0100 Subject: [PATCH 21/23] feat(auth): use Strings.createUsername in app settings --- scenes/SceneEditAccount.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/SceneEditAccount.js b/scenes/SceneEditAccount.js index 206be235..44a00901 100644 --- a/scenes/SceneEditAccount.js +++ b/scenes/SceneEditAccount.js @@ -192,7 +192,7 @@ export default class SceneEditAccount extends React.Component { }; _handleUsernameChange = (e) => { - this.setState({ [e.target.name]: e.target.value.toLowerCase() }); + this.setState({ [e.target.name]: Strings.createUsername(e.target.value) }); }; _handleDelete = async (res) => { From f40a4fab681902a9a4d6c2549efeac07b41e82da Mon Sep 17 00:00:00 2001 From: Aminejv Date: Wed, 23 Jun 2021 19:39:31 +0100 Subject: [PATCH 22/23] fix(auth): username bugs --- common/messages.js | 2 +- components/core/Auth/TwitterLinking.js | 6 +++-- components/core/Auth/TwitterSignup.js | 3 ++- .../core/Auth/components/AuthCheckBox.js | 9 +++++++- pages/api/twitter/link-with-verification.js | 22 +++++++++++++++---- pages/api/twitter/link.js | 22 +++++++++++++++---- scenes/SceneAuth/index.js | 3 ++- 7 files changed, 53 insertions(+), 14 deletions(-) diff --git a/common/messages.js b/common/messages.js index f9304b01..2b8f57f1 100644 --- a/common/messages.js +++ b/common/messages.js @@ -143,7 +143,7 @@ export const error = { SERVER_TWITTER_OAUTH_NOT_ALLOWED: "You can only authenticate via twitter while on slate.host", SERVER_TWITTER_LOGIN_ONLY: "This login is associated with a Twitter account. Please continue with Twitter instead", - SERVER_TWITTER_LINKING_INVALID_USERNAME: "Please choose a valid username", + SERVER_TWITTER_LINKING_INVALID_USERNAME: "Please choose a valid username/email", SERVER_TWITTER_LINKING_INVALID_PASSWORD: "Please choose a valid password", SERVER_TWITTER_LINKING_WRONG_CREDENTIALS: "You have entered an invalid username or password", SERVER_TWITTER_LINKING_FAILED: "SERVER_CREATE_USER_FAILED", diff --git a/components/core/Auth/TwitterLinking.js b/components/core/Auth/TwitterLinking.js index c57a3aeb..ab2ea816 100644 --- a/components/core/Auth/TwitterLinking.js +++ b/components/core/Auth/TwitterLinking.js @@ -31,7 +31,8 @@ const MotionLayout = ({ children, ...props }) => ( ); const handleValidation = async ({ username, password, acceptTerms }, errors) => { - if (!Validations.username(username)) errors.username = "Invalid username"; + if (!Validations.username(username) && !Validations.email(username)) + errors.username = "Invalid username"; if (!Validations.legacyPassword(password)) errors.password = "Incorrect password"; @@ -42,6 +43,7 @@ const handleValidation = async ({ username, password, acceptTerms }, errors) => export default function TwitterLinking({ linkAccount, linkAccountWithVerification, + resendEmailVerification, createVerification, }) { const { scene, goToVerificationScene, goToEmailScene } = useTwitterLinking(); @@ -87,7 +89,7 @@ export default function TwitterLinking({ const handleVerification = async ({ pin }) => { await linkAccountWithVerification({ pin }); }; - return ; + return ; } if (scene === "email") { diff --git a/components/core/Auth/TwitterSignup.js b/components/core/Auth/TwitterSignup.js index 721bbbd7..abc39854 100644 --- a/components/core/Auth/TwitterSignup.js +++ b/components/core/Auth/TwitterSignup.js @@ -86,6 +86,7 @@ export default function TwitterSignup({ initialEmail, onSignup, goToTwitterLinkingScene, + resendEmailVerification, createVerification, onSignupWithVerification, }) { @@ -118,7 +119,7 @@ export default function TwitterSignup({ const handleVerification = async ({ pin }) => { await onSignupWithVerification({ username, pin }); }; - return ; + return ; } return ( diff --git a/components/core/Auth/components/AuthCheckBox.js b/components/core/Auth/components/AuthCheckBox.js index dfbdca23..0f7b3b17 100644 --- a/components/core/Auth/components/AuthCheckBox.js +++ b/components/core/Auth/components/AuthCheckBox.js @@ -47,7 +47,14 @@ export default function AuthCheckBox({ touched, error, ...props }) { containerStyles={STYLES_CHECKBOX_WRAPPER} labelStyles={STYLES_CHECKBOX_LABEL} inputStyles={STYLES_CHECKBOX} - boxStyle={{ backgroundColor: Constants.system.bgBlurWhiteTRN }} + boxStyle={ + props.value + ? { + backgroundColor: Constants.system.brand, + boxShadow: `0 0 0 1px ${Constants.system.brand}`, + } + : { backgroundColor: Constants.system.bgBlurWhiteTRN } + } {...props} > I agree to the Slate{" "} diff --git a/pages/api/twitter/link-with-verification.js b/pages/api/twitter/link-with-verification.js index 6ca3d4d0..69b4ca66 100644 --- a/pages/api/twitter/link-with-verification.js +++ b/pages/api/twitter/link-with-verification.js @@ -4,13 +4,14 @@ import * as Utilities from "~/node_common/utilities"; import * as Strings from "~/common/strings"; import * as Validations from "~/common/validations"; import * as Constants from "~/common/constants"; +import * as Logging from "~/common/logging"; export default async (req, res) => { if (!Strings.isEmpty(Environment.ALLOWED_HOST) && req.headers.host !== Environment.ALLOWED_HOST) { return res.status(403).send({ decorator: "SERVER_TWITTER_OAUTH_NOT_ALLOWED", error: true }); } - if (!Validations.username(req.body.data.username)) { + if (Strings.isEmpty(req.body?.data?.username)) { return res .status(500) .send({ decorator: "SERVER_TWITTER_LINKING_INVALID_USERNAME", error: true }); @@ -34,9 +35,22 @@ export default async (req, res) => { const { token, password, username, pin } = req.body.data; - const user = await Data.getUserByUsername({ - username: username.toLowerCase(), - }); + let user; + if (Validations.email(username)) { + try { + user = await Data.getUserByEmail({ email: username }); + } catch (e) { + Logging.error(e); + } + } else { + try { + user = await Data.getUserByUsername({ + username: req.body.data.username.toLowerCase(), + }); + } catch (e) { + Logging.error(e); + } + } if (!user || user.error) { return res diff --git a/pages/api/twitter/link.js b/pages/api/twitter/link.js index 65ba90ef..dbbfe722 100644 --- a/pages/api/twitter/link.js +++ b/pages/api/twitter/link.js @@ -3,6 +3,7 @@ import * as Data from "~/node_common/data"; import * as Utilities from "~/node_common/utilities"; import * as Strings from "~/common/strings"; import * as Validations from "~/common/validations"; +import * as Logging from "~/common/logging"; export default async (req, res) => { if (!Strings.isEmpty(Environment.ALLOWED_HOST) && req.headers.host !== Environment.ALLOWED_HOST) { @@ -13,7 +14,7 @@ export default async (req, res) => { return res.status(500).send({ decorator: "SERVER_TWITTER_OAUTH_NO_OAUTH_TOKEN", error: true }); } - if (!Validations.username(req.body?.data?.username)) { + if (Strings.isEmpty(req.body?.data?.username)) { return res .status(400) .send({ decorator: "SERVER_TWITTER_LINKING_INVALID_USERNAME", error: true }); @@ -27,9 +28,22 @@ export default async (req, res) => { const { username, password, token } = req.body.data; - const user = await Data.getUserByUsername({ - username: username.toLowerCase(), - }); + let user; + if (Validations.email(username)) { + try { + user = await Data.getUserByEmail({ email: username }); + } catch (e) { + Logging.error(e); + } + } else { + try { + user = await Data.getUserByUsername({ + username: req.body.data.username.toLowerCase(), + }); + } catch (e) { + Logging.error(e); + } + } if (!user || user.error) { return res diff --git a/scenes/SceneAuth/index.js b/scenes/SceneAuth/index.js index f14098da..fd0ba410 100644 --- a/scenes/SceneAuth/index.js +++ b/scenes/SceneAuth/index.js @@ -130,7 +130,7 @@ const SigninScene = ({ onAuthenticate, onTwitterAuthenticate, page, ...props }) ); From a98645c2215a2986a8f5289c15310531e32522b6 Mon Sep 17 00:00:00 2001 From: Martina Date: Wed, 23 Jun 2021 15:23:54 -0700 Subject: [PATCH 23/23] edits to working and word breaks in confirmation modal --- components/core/Auth/Signup.js | 23 ++++++++++++----------- components/core/Auth/TwitterSignup.js | 23 ++++++++++++----------- components/core/ConfirmationModal.js | 3 ++- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/components/core/Auth/Signup.js b/components/core/Auth/Signup.js index f3178a75..078af6f7 100644 --- a/components/core/Auth/Signup.js +++ b/components/core/Auth/Signup.js @@ -24,7 +24,7 @@ const useSignup = () => { }; const useCheckUser = () => { - const MESSAGE = "The username is taken."; + const MESSAGE = "That username is taken"; const usernamesAllowed = React.useRef([]); const usernamesTaken = React.useRef([]); @@ -48,7 +48,7 @@ const useCheckUser = () => { username, }); if (response.data) { - errors.username = "The username is taken."; + errors.username = "That username is taken"; usernamesTaken.current.push(username); return; } @@ -56,16 +56,17 @@ const useCheckUser = () => { }; }; -const createValidations = - (validateUsername) => - async ({ username, password, acceptTerms }, errors) => { - await validateUsername({ username }, errors); +const createValidations = (validateUsername) => async ( + { username, password, acceptTerms }, + errors +) => { + await validateUsername({ username }, errors); - if (!Validations.password(password)) errors.password = "Incorrect password"; + if (!Validations.password(password)) errors.password = "Incorrect password"; - if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; - return errors; - }; + if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; + return errors; +}; export default function Signup({ verifyEmail, createUser, resendEmailVerification }) { const [passwordValidations, setPasswordValidations] = React.useState( @@ -103,7 +104,7 @@ export default function Signup({ verifyEmail, createUser, resendEmailVerificatio placeholder="Username" name="username" type="text" - success="The username is available." + success="That username is available" icon={ isValidating ? () => ( diff --git a/components/core/Auth/TwitterSignup.js b/components/core/Auth/TwitterSignup.js index abc39854..ebcd8779 100644 --- a/components/core/Auth/TwitterSignup.js +++ b/components/core/Auth/TwitterSignup.js @@ -32,7 +32,7 @@ const useTwitterSignup = () => { }; const useCheckUser = () => { - const MESSAGE = "The username is taken."; + const MESSAGE = "That username is taken"; const usernamesAllowed = React.useRef([]); const usernamesTaken = React.useRef([]); @@ -56,7 +56,7 @@ const useCheckUser = () => { username, }); if (response.data) { - errors.username = "The username is taken."; + errors.username = "That username is taken"; usernamesTaken.current.push(username); return; } @@ -64,17 +64,18 @@ const useCheckUser = () => { }; }; -const createValidations = - (validateUsername) => - async ({ username, email, acceptTerms }, errors) => { - await validateUsername({ username }, errors); +const createValidations = (validateUsername) => async ( + { username, email, acceptTerms }, + errors +) => { + await validateUsername({ username }, errors); - if (!Validations.email(email)) errors.email = "Invalid email"; + if (!Validations.email(email)) errors.email = "Invalid email"; - if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; + if (!acceptTerms) errors.acceptTerms = "Must accept terms and conditions"; - return errors; - }; + return errors; +}; const MotionLayout = ({ children, ...props }) => ( @@ -131,7 +132,7 @@ export default function TwitterSignup({ placeholder="Username" name="username" type="text" - success="The username is available." + success="That username is available" icon={ isValidating ? () => ( diff --git a/components/core/ConfirmationModal.js b/components/core/ConfirmationModal.js index fba8bf9b..1e0eb8e1 100644 --- a/components/core/ConfirmationModal.js +++ b/components/core/ConfirmationModal.js @@ -39,7 +39,8 @@ const STYLES_HEADER = css` color: ${Constants.system.black}; font-size: ${Constants.typescale.lvl1}; font-family: ${Constants.font.semiBold}; - word-break: break-all; + word-break: break-word; + white-space: pre-line; `; const STYLES_SUB_HEADER = css`