From b938b6b3aacf7bcb4f2cdd9ca22efda408c636a5 Mon Sep 17 00:00:00 2001 From: Tristan Yang Date: Mon, 20 Jun 2022 17:41:58 +0800 Subject: [PATCH] refactor: magic link auth --- src/app/services/auth.js | 37 +++++++-- src/app/services/base.query.js | 5 +- src/app/services/contact.js | 11 +-- .../component}/GithubLoginButton.js | 18 +++- src/common/component/GoogleLoginButton.js | 58 +++++++++++++ src/routes/index.js | 4 +- src/routes/invite/index.js | 6 +- src/routes/login/GoogleLoginButton.js | 33 -------- src/routes/login/SignUpLink.js | 2 +- src/routes/login/index.js | 18 ++-- src/routes/reg/EmailNextStepTip.js | 33 ++++++++ src/routes/reg/RegWithUsername.js | 51 ++++++++---- src/routes/reg/Register.js | 82 ++++++++++++++----- src/routes/reg/SignInLink.js | 34 ++++++++ src/routes/reg/index.js | 39 ++++++++- src/routes/reg/styled.js | 19 +++++ src/routes/sendMagicLink/index.js | 10 +-- 17 files changed, 349 insertions(+), 111 deletions(-) rename src/{routes/login => common/component}/GithubLoginButton.js (64%) create mode 100644 src/common/component/GoogleLoginButton.js delete mode 100644 src/routes/login/GoogleLoginButton.js create mode 100644 src/routes/reg/EmailNextStepTip.js create mode 100644 src/routes/reg/SignInLink.js diff --git a/src/app/services/auth.js b/src/app/services/auth.js index 648c8512..f372f5d7 100644 --- a/src/app/services/auth.js +++ b/src/app/services/auth.js @@ -45,6 +45,13 @@ export const authApi = createApi({ } } }), + register: builder.mutation({ + query: (data) => ({ + url: `user/register`, + method: "POST", + body: data + }) + }), // 更新token renew: builder.mutation({ query: ({ token, refreshToken }) => ({ @@ -87,9 +94,9 @@ export const authApi = createApi({ }) }), - checkInviteTokenValid: builder.mutation({ + checkMagicTokenValid: builder.mutation({ query: (token) => ({ - url: "user/check_invite_magic_token", + url: "user/check_magic_token", method: "POST", body: { magic_token: token } }) @@ -101,11 +108,23 @@ export const authApi = createApi({ body: { old_password, new_password } }) }), - sendMagicLink: builder.mutation({ + sendLoginMagicLink: builder.mutation({ query: (email) => ({ - url: "token/send_magic_link", + headers: { + // "content-type": "text/plain", + accept: "text/plain" + }, + url: `user/send_login_magic_link?email=${encodeURIComponent(email)}`, method: "POST", - body: { email } + responseHandler: (response) => response.text() + // body: { email } + }) + }), + sendRegMagicLink: builder.mutation({ + query: (data) => ({ + url: `user/send_reg_magic_link`, + method: "POST", + body: data }) }), getMetamaskNonce: builder.query({ @@ -147,7 +166,8 @@ export const authApi = createApi({ export const { useGetInitializedQuery, - useSendMagicLinkMutation, + useSendLoginMagicLinkMutation, + useSendRegMagicLinkMutation, useGetCredentialsQuery, useUpdateDeviceTokenMutation, useGetOpenidMutation, @@ -155,6 +175,7 @@ export const { useLazyGetMetamaskNonceQuery, useLoginMutation, useLazyLogoutQuery, - useCheckInviteTokenValidMutation, - useUpdatePasswordMutation + useCheckMagicTokenValidMutation, + useUpdatePasswordMutation, + useRegisterMutation } = authApi; diff --git a/src/app/services/base.query.js b/src/app/services/base.query.js index b416d323..aeadb4a8 100644 --- a/src/app/services/base.query.js +++ b/src/app/services/base.query.js @@ -6,8 +6,9 @@ import BASE_URL, { tokenHeader } from "../config"; const whiteList = [ "login", "register", - "sendMagicLink", - "checkInviteTokenValid", + "sendLoginMagicLink", + "sendRegMagicLink", + "checkMagicTokenValid", "getGoogleAuthConfig", "getGithubAuthConfig", "getSMTPStatus", diff --git a/src/app/services/contact.js b/src/app/services/contact.js index 21d1c2d7..b2712913 100644 --- a/src/app/services/contact.js +++ b/src/app/services/contact.js @@ -89,13 +89,7 @@ export const contactApi = createApi({ body: data }) }), - register: builder.mutation({ - query: (data) => ({ - url: `user/register`, - method: "POST", - body: data - }) - }), + sendMsg: builder.mutation({ query: ({ id, content, type = "text", properties = "" }) => ({ headers: { @@ -139,6 +133,5 @@ export const { useUpdateAvatarMutation, useGetContactsQuery, useLazyGetContactsQuery, - useSendMsgMutation, - useRegisterMutation + useSendMsgMutation } = contactApi; diff --git a/src/routes/login/GithubLoginButton.js b/src/common/component/GithubLoginButton.js similarity index 64% rename from src/routes/login/GithubLoginButton.js rename to src/common/component/GithubLoginButton.js index cfade17f..a0288a3e 100644 --- a/src/routes/login/GithubLoginButton.js +++ b/src/common/component/GithubLoginButton.js @@ -1,7 +1,23 @@ // import { useState, useEffect } from "react"; // import { useGoogleLogin } from "react-google-login"; import IconGithub from "../../assets/icons/github.svg"; -import { StyledSocialButton } from "./styled"; +import styled from "styled-components"; +import Button from "./styled/Button"; +const StyledSocialButton = styled(Button)` + width: 100%; + margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + color: #344054; + border: 1px solid #d0d5dd; + background: none !important; + .icon { + width: 24px; + height: 24px; + } +`; export default function GithubLoginButton({ config = {} }) { const { client_id } = config; const handleGithubLogin = () => { diff --git a/src/common/component/GoogleLoginButton.js b/src/common/component/GoogleLoginButton.js new file mode 100644 index 00000000..90c1ba62 --- /dev/null +++ b/src/common/component/GoogleLoginButton.js @@ -0,0 +1,58 @@ +import { useEffect } from "react"; +import { useGoogleLogin } from "react-google-login"; + +import googleSvg from "../../assets/icons/google.svg?url"; +import styled from "styled-components"; +import Button from "./styled/Button"; +import { useLoginMutation } from "../../app/services/auth"; +import toast from "react-hot-toast"; +const StyledSocialButton = styled(Button)` + width: 100%; + margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + color: #344054; + border: 1px solid #d0d5dd; + background: none !important; + .icon { + width: 24px; + height: 24px; + } +`; +export default function GoogleLoginButton({ clientId }) { + const [login, { isSuccess, isLoading }] = useLoginMutation(); + const { signIn, loaded } = useGoogleLogin({ + onScriptLoadFailure: (wtf) => { + console.error("google login script load failure", wtf); + }, + clientId, + onSuccess: ({ tokenId, ...rest }) => { + console.info("google oauth success", tokenId, rest); + login({ + id_token: tokenId, + type: "google" + }); + }, + onFailure: (wtf) => { + console.error("google login failure", wtf); + } + }); + useEffect(() => { + if (isSuccess) { + toast.success("Login Successfully"); + // navigateTo("/"); + } + }, [isSuccess]); + const handleGoogleLogin = () => { + signIn(); + }; + // console.log("google login ", loaded); + return ( + + google icon + {loaded ? `Sign in with Google` : `Initailizing`} + + ); +} diff --git a/src/routes/index.js b/src/routes/index.js index ea568c97..4538511c 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -75,9 +75,9 @@ const PageRoutes = () => { } > } /> - + } /> - } /> + } /> { // console.log(search); const query = new URLSearchParams(location.search); diff --git a/src/routes/login/GoogleLoginButton.js b/src/routes/login/GoogleLoginButton.js deleted file mode 100644 index 99ddcc26..00000000 --- a/src/routes/login/GoogleLoginButton.js +++ /dev/null @@ -1,33 +0,0 @@ -// import { useState, useEffect } from "react"; -import { useGoogleLogin } from "react-google-login"; - -import googleSvg from "../../assets/icons/google.svg?url"; -import { StyledSocialButton } from "./styled"; -export default function GoogleLoginButton({ login, clientId }) { - const { signIn, loaded } = useGoogleLogin({ - onScriptLoadFailure: (wtf) => { - console.error("google login script load failure", wtf); - }, - clientId, - onSuccess: ({ tokenId, ...rest }) => { - console.info("google oauth success", tokenId, rest); - login({ - id_token: tokenId, - type: "google" - }); - }, - onFailure: (wtf) => { - console.error("google login failure", wtf); - } - }); - const handleGoogleLogin = () => { - signIn(); - }; - // console.log("google login ", loaded); - return ( - - google icon - {loaded ? `Sign in with Google` : `Initailizing`} - - ); -} diff --git a/src/routes/login/SignUpLink.js b/src/routes/login/SignUpLink.js index 1326bd6a..9400df8b 100644 --- a/src/routes/login/SignUpLink.js +++ b/src/routes/login/SignUpLink.js @@ -24,7 +24,7 @@ const StyledSignUpLink = styled.p` export default function MagicLinkLogin() { const navigate = useNavigate(); const handleSignUp = () => { - navigate("/"); + navigate("/register"); }; return ( diff --git a/src/routes/login/index.js b/src/routes/login/index.js index 776a63ad..459aa63a 100644 --- a/src/routes/login/index.js +++ b/src/routes/login/index.js @@ -8,14 +8,14 @@ import MetamaskLoginButton from "./MetamaskLoginButton"; import OidcLoginButton from "./OidcLoginButton"; import Input from "../../common/component/styled/Input"; import Button from "../../common/component/styled/Button"; -import GoogleLoginButton from "./GoogleLoginButton"; import MagicLinkLogin from "./MagicLinkLogin"; import SignUpLink from "./SignUpLink"; import { useLoginMutation } from "../../app/services/auth"; import { useGetLoginConfigQuery, useGetSMTPStatusQuery } from "../../app/services/server"; import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig"; -import GithubLoginButton from "./GithubLoginButton"; import useGithubAuthConfig from "../../common/hook/useGithubAuthConfig"; +import GoogleLoginButton from "../../common/component/GoogleLoginButton"; +import GithubLoginButton from "../../common/component/GithubLoginButton"; export default function LoginPage() { const { data: enableSMTP } = useGetSMTPStatusQuery(); const [login, { isSuccess, isLoading, error }] = useLoginMutation(); @@ -32,7 +32,7 @@ export default function LoginPage() { const oauth = query.get("oauth"); const code = query.get("code"); const state = query.get("state"); - const token = query.get("token"); + const magic_token = query.get("magic_token"); const exists = query.get("exists"); if (oauth) { switch (oauth) { @@ -58,18 +58,18 @@ export default function LoginPage() { } } // magic link - if (token && typeof exists !== "undefined") { - console.log("tokken", token, exists); + if (magic_token && typeof exists !== "undefined") { + // console.log("tokken", token, exists); const isLogin = exists == "true"; if (isLogin) { // login login({ - token, + magic_token, type: "magiclink" }); } else { - // reg - location.href = `/#/reg/magiclink/${token}`; + // reg with magic link and set name only + location.href = `?magic_token=${magic_token}#/register/set_name/login`; } } }, []); @@ -167,7 +167,7 @@ export default function LoginPage() { {hasDivider &&
} {enableMagicLink && } - {googleLogin && } + {googleLogin && } {enableGithubLogin && } {enableMetamaskLogin && } {oidc.length > 0 && } diff --git a/src/routes/reg/EmailNextStepTip.js b/src/routes/reg/EmailNextStepTip.js new file mode 100644 index 00000000..02143d6a --- /dev/null +++ b/src/routes/reg/EmailNextStepTip.js @@ -0,0 +1,33 @@ +// import React from "react"; +import styled from "styled-components"; +const Styled = styled.div` + display: flex; + flex-direction: column; + align-items: center; + .title { + font-weight: 600; + font-size: 30px; + line-height: 38px; + color: #101828; + margin-bottom: 12px; + } + .desc { + text-align: center; + font-weight: 400; + font-size: 16px; + line-height: 24px; + color: #667085; + &:not(:last-child) { + margin-bottom: 24px; + } + } +`; +export default function EmailNextTip() { + return ( + +
Magic link Sent
+

Login to your email client, and continue next step

+

You can close this window now.

+
+ ); +} diff --git a/src/routes/reg/RegWithUsername.js b/src/routes/reg/RegWithUsername.js index 14ad4fa7..126bcfa4 100644 --- a/src/routes/reg/RegWithUsername.js +++ b/src/routes/reg/RegWithUsername.js @@ -1,25 +1,30 @@ /* eslint-disable no-undef */ import { useState, useEffect } from "react"; import { useDispatch } from "react-redux"; -// import { useNavigate } from "react-router-dom"; -// import toast from "react-hot-toast"; import { useParams } from "react-router-dom"; import { setAuthData } from "../../app/slices/auth.data"; import Input from "../../common/component/styled/Input"; import Button from "../../common/component/styled/Button"; -import { useLoginMutation, useCheckInviteTokenValidMutation } from "../../app/services/auth"; +import { useLoginMutation, useCheckMagicTokenValidMutation } from "../../app/services/auth"; import toast from "react-hot-toast"; import ExpiredTip from "./ExpiredTip"; +import { useRegisterMutation } from "../../app/services/auth"; export default function RegWithUsername() { - const { token } = useParams(); const [checkTokenInvalid, { data: isTokenValid, isLoading: checkingToken }] = - useCheckInviteTokenValidMutation(); - const [login, { isLoading, error, isSuccess, data }] = useLoginMutation(); + useCheckMagicTokenValidMutation(); + const [login, { isLoading: loginLoading, error, isSuccess: loginSuccess, data: loginData }] = + useLoginMutation(); + const [register, { isLoading: regLoading, isSuccess: regSuccess, data: regData }] = + useRegisterMutation(); // const navigateTo = useNavigate(); + const { from = "reg" } = useParams(); const dispatch = useDispatch(); const [username, setUsername] = useState(""); + const query = new URLSearchParams(location.search); + // const githubCode = query.get("gcode"); + const token = query.get("magic_token"); useEffect(() => { if (token) { checkTokenInvalid(token); @@ -38,32 +43,42 @@ export default function RegWithUsername() { } }, [error]); useEffect(() => { + const isSuccess = loginSuccess || regSuccess; + const data = loginData || regData; if (isSuccess && data) { // 更新本地认证信息 - console.log("login data", data); toast.success("Login Successfully"); dispatch(setAuthData(data)); + // tricky location.href = `/#/`; } - }, [isSuccess, data]); + }, [loginSuccess, regSuccess, loginData, regData]); - const handleLogin = (evt) => { + const handleAuth = (evt) => { evt.preventDefault(); - login({ - token, - username, - type: "magiclink" - }); - // sendMagicLink(email); + if (from == "reg") { + register({ + magic_token: token, + name: username + }); + } else { + login({ + magic_token: token, + extra_name: username, + type: "magiclink" + }); + } }; const handleInput = (evt) => { const { value } = evt.target; setUsername(value); }; - if (!token) return "no token"; - if (checkingToken) return "checking Magic Link..."; + if (!token) return "No Token"; + if (checkingToken) return "Checking Magic Link..."; if (!isTokenValid) return ; + const isLoading = loginLoading || regLoading; + const isSuccess = loginSuccess || regSuccess; return ( <>
@@ -73,7 +88,7 @@ export default function RegWithUsername() { visible to others in spaces you joined.
-
+ { + const query = new URLSearchParams(location.search); + // const githubCode = query.get("gcode"); + const token = query.get("magic_token"); + if (token) { + setMagicToken(token); + } + }, []); + useEffect(() => { + if (isSuccess && data) { + const { new_magic_token, mail_is_sent } = data; + if (!mail_is_sent && new_magic_token) { + // 直接进入set_name流程 + navigateTo(`?magic_token=${new_magic_token}#/register/set_name`); + } + } + }, [isSuccess, data]); - const handleLogin = (evt) => { + const handleReg = (evt) => { evt.preventDefault(); + const { email, password } = input; + sendRegMagicLink({ + magic_token: magicToken, + email, + password + }); // sendMagicLink(email); }; const handleInput = (evt) => { + const { type } = evt.target.dataset; const { value } = evt.target; - console.log(value); - setUsername(value); + // console.log(type, value); + setInput((prev) => { + prev[type] = value; + return { ...prev }; + }); }; + const { email, password } = input; + if (data?.mail_is_sent) return ; return ( - + - + + ); } diff --git a/src/routes/reg/SignInLink.js b/src/routes/reg/SignInLink.js new file mode 100644 index 00000000..c4bd9903 --- /dev/null +++ b/src/routes/reg/SignInLink.js @@ -0,0 +1,34 @@ +import styled from "styled-components"; +import { useNavigate } from "react-router-dom"; + +const StyledSignInLink = styled.p` + text-align: center; + margin: 24px 0 8px; + > span { + font-weight: 400; + font-size: 14px; + line-height: 20px; + color: #667085; + margin-right: 4px; + } + + > a { + font-weight: 500; + font-size: 14px; + line-height: 20px; + color: #22d3ee; + cursor: pointer; + } +`; +export default function SignInLink() { + const navigate = useNavigate(); + const handleSignIn = () => { + navigate("/login"); + }; + return ( + + Have an account? + Sign In + + ); +} diff --git a/src/routes/reg/index.js b/src/routes/reg/index.js index 96d1656a..2e80dcfe 100644 --- a/src/routes/reg/index.js +++ b/src/routes/reg/index.js @@ -1,12 +1,49 @@ -/* eslint-disable no-undef */ import { Outlet } from "react-router-dom"; +import { useMatch } from "react-router-dom"; +import BASE_URL from "../../app/config"; +import SignInLink from "./SignInLink"; +import { useGetLoginConfigQuery } from "../../app/services/server"; +import useGithubAuthConfig from "../../common/hook/useGithubAuthConfig"; +import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig"; +import GoogleLoginButton from "../../common/component/GoogleLoginButton"; +import GithubLoginButton from "../../common/component/GithubLoginButton"; import StyledWrapper from "./styled"; export default function Reg() { + const isRegHome = useMatch(`/register`); + const { clientId } = useGoogleAuthConfig(); + const { config: githubAuthConfig } = useGithubAuthConfig(); + const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery(); + if (!loginConfigSuccess) return null; + const { + github: enableGithubLogin, + google: enableGoogleLogin, + who_can_sign_up: whoCanSignUp + } = loginConfig; + const googleLogin = enableGoogleLogin && clientId; + // 没有开放注册 + if (whoCanSignUp !== "EveryOne") return `Open Register is Closed!`; return (
+ {isRegHome && ( + <> +
+ logo +

Sign Up to Rustchat

+ Please enter your details. +
+ + )} + {isRegHome && ( + <> +
+ {googleLogin && } + {enableGithubLogin && } + + + )}
); diff --git a/src/routes/reg/styled.js b/src/routes/reg/styled.js index e7d03a46..14d3e46b 100644 --- a/src/routes/reg/styled.js +++ b/src/routes/reg/styled.js @@ -45,6 +45,25 @@ const StyledWrapper = styled.div` width: 360px; } } + .or { + border: none; + position: relative; + height: 1px; + background-color: #e4e7ec; + margin: 26px 0; + &:after { + padding: 4px; + background-color: #fff; + content: "OR"; + position: absolute; + left: 50%; + top: 50%; + transform: translate3d(-50%, -50%, 0); + font-size: 14px; + line-height: 20px; + color: #667085; + } + } `; export default StyledWrapper; diff --git a/src/routes/sendMagicLink/index.js b/src/routes/sendMagicLink/index.js index 2f22385c..66676e2a 100644 --- a/src/routes/sendMagicLink/index.js +++ b/src/routes/sendMagicLink/index.js @@ -7,12 +7,12 @@ import BASE_URL from "../../app/config"; import StyledWrapper from "./styled"; import Input from "../../common/component/styled/Input"; import Button from "../../common/component/styled/Button"; -import { useSendMagicLinkMutation } from "../../app/services/auth"; +import { useSendLoginMagicLinkMutation } from "../../app/services/auth"; import SentTip from "./SentTip"; export default function SendMagicLinkPage() { const [sent, setSent] = useState(false); - const [sendMagicLink, { isSuccess, isLoading, error }] = useSendMagicLinkMutation(); + const [sendMagicLink, { isSuccess, isLoading, error }] = useSendLoginMagicLinkMutation(); const navigateTo = useNavigate(); // const dispatch = useDispatch(); @@ -32,13 +32,13 @@ export default function SendMagicLinkPage() { toast.error(error.data); break; case 401: - toast.error("username or password incorrect"); + toast.error("Username or Password Incorrect"); break; case 404: - toast.error("account not exsit"); + toast.error("Account not exsit"); break; default: - toast.error("something error"); + toast.error("Something Error"); break; } return;