Path: blob/master/src/packages/next/components/auth/sign-in.tsx
5942 views
/*1* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { Button, Input } from "antd";6import { useEffect, useState } from "react";7import {8GoogleReCaptchaProvider,9useGoogleReCaptcha,10} from "react-google-recaptcha-v3";1112import { Icon } from "@cocalc/frontend/components/icon";13import Contact from "components/landing/contact";14import A from "components/misc/A";15import apiPost from "lib/api/post";16import useCustomize from "lib/use-customize";17import AuthPageContainer from "./fragments/auth-page-container";18import SSO, { RequiredSSO, useRequiredSSO } from "./sso";19import { MAX_PASSWORD_LENGTH } from "@cocalc/util/auth";2021interface SignInProps {22minimal?: boolean;23onSuccess?: () => void; // if given, call after sign in *succeeds*.24showSignUp?: boolean;25signUpAction?: () => void; // if given, replaces the default sign-up link behavior.26}2728export default function SignIn(props: SignInProps) {29const { reCaptchaKey } = useCustomize();3031const body = <SignIn0 {...props} />;32if (reCaptchaKey == null) {33return body;34}3536return (37<GoogleReCaptchaProvider reCaptchaKey={reCaptchaKey}>38{body}39</GoogleReCaptchaProvider>40);41}4243function SignIn0(props: SignInProps) {44const { minimal = false, onSuccess, showSignUp, signUpAction } = props;45const { anonymousSignup, reCaptchaKey, siteName, strategies } =46useCustomize();47const [email, setEmail] = useState<string>("");48const [password, setPassword] = useState<string>("");49const [signingIn, setSigningIn] = useState<boolean>(false);50const [error, setError] = useState<string>("");51const [haveSSO, setHaveSSO] = useState<boolean>(false);52const { executeRecaptcha } = useGoogleReCaptcha();5354useEffect(() => {55setHaveSSO(strategies != null && strategies.length > 0);56}, []);5758// based on email: if user has to sign up via SSO, this will tell which strategy to use.59const requiredSSO = useRequiredSSO(strategies, email);6061async function signIn() {62if (signingIn) return;63setError("");64try {65setSigningIn(true);6667let reCaptchaToken: undefined | string;68if (reCaptchaKey) {69if (!executeRecaptcha) {70throw Error("Please wait a few seconds, then try again.");71}72reCaptchaToken = await executeRecaptcha("signin");73}7475await apiPost("/auth/sign-in", {76email,77password,78reCaptchaToken,79});80onSuccess?.();81} catch (err) {82setError(`${err}`);83} finally {84setSigningIn(false);85}86}8788function renderFooter() {89return (90(!minimal || showSignUp) && (91<>92New to CoCalc?{" "}93{signUpAction ? (94<a onClick={signUpAction}>Sign Up</a>95) : (96<A href="/auth/sign-up">Sign Up</A>97)}{" "}98{anonymousSignup ? (99<>100or{" "}101<A href="/auth/try">102{" "}103try {siteName} without creating an account.{" "}104</A>105</>106) : (107"today."108)}109</>110)111);112}113114function renderError() {115return (116error && (117<>118<p>119<b>{error}</b>120</p>121<p>122If you can't remember your password,{" "}123<A href="/auth/password-reset">reset it</A>. If that doesn't work{" "}124<Contact />.125</p>126</>127)128);129}130131return (132<AuthPageContainer133error={renderError()}134footer={renderFooter()}135minimal={minimal}136title={`Sign in to ${siteName}`}137>138<div style={{ margin: "10px 0" }}>139{strategies == null140? "Sign in"141: haveSSO142? requiredSSO != null143? "Sign in using your single sign-on provider"144: "Sign in using your email address or a single sign-on provider."145: "Sign in using your email address."}146</div>147<form>148{haveSSO && (149<div150style={{151textAlign: "center",152margin: "20px 0",153display: requiredSSO == null ? "inherit" : "none",154}}155>156<SSO157size={email ? 24 : undefined}158style={159email ? { textAlign: "right", marginBottom: "20px" } : undefined160}161/>162</div>163)}164<Input165autoFocus166style={{ fontSize: "12pt" }}167placeholder="Email address"168autoComplete="username"169onChange={(e) => setEmail(e.target.value)}170/>171172<RequiredSSO strategy={requiredSSO} />173{/* Don't remove password input, since that messes up autofill. Hide for forced SSO. */}174<div175style={{176marginTop: "30px",177display: requiredSSO == null ? "inherit" : "none",178}}179>180<p>Password </p>181<Input.Password182style={{ fontSize: "12pt" }}183autoComplete="current-password"184placeholder="Password"185maxLength={MAX_PASSWORD_LENGTH}186onChange={(e) => setPassword(e.target.value)}187onPressEnter={(e) => {188e.preventDefault();189signIn();190}}191/>192</div>193{requiredSSO == null && (194<>195<Button196shape="round"197size="large"198type="primary"199style={{ width: "100%", marginTop: "20px" }}200onClick={signIn}201>202{signingIn ? (203<>204<Icon name="spinner" spin /> Signing In...205</>206) : (207"Sign In"208)}209</Button>210<div style={{ marginTop: "18px" }}>211<A href={"/auth/password-reset"}>Forgot your password?</A>212</div>213</>214)}215</form>216</AuthPageContainer>217);218}219220221