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