2022-02-04 20:30:36 +00:00
|
|
|
import classNames from "classnames";
|
2021-11-16 17:12:08 +00:00
|
|
|
import { GetServerSidePropsContext } from "next";
|
2022-01-07 20:23:37 +00:00
|
|
|
import { getCsrfToken, signIn } from "next-auth/react";
|
2021-09-22 19:52:38 +00:00
|
|
|
import Link from "next/link";
|
2021-09-12 08:51:59 +00:00
|
|
|
import { useRouter } from "next/router";
|
2021-09-27 14:47:55 +00:00
|
|
|
import { useState } from "react";
|
2022-11-23 02:55:25 +00:00
|
|
|
import { FormProvider, useForm } from "react-hook-form";
|
2022-10-17 12:44:43 +00:00
|
|
|
import { FaGoogle } from "react-icons/fa";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
2022-12-22 19:06:26 +00:00
|
|
|
import { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
|
2022-10-19 15:50:25 +00:00
|
|
|
import { isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
2022-04-27 14:28:36 +00:00
|
|
|
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
2022-07-27 23:28:21 +00:00
|
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
2022-07-28 19:58:26 +00:00
|
|
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
2022-08-22 19:34:28 +00:00
|
|
|
import prisma from "@calcom/prisma";
|
2023-01-23 23:08:01 +00:00
|
|
|
import { Alert, Button, EmailField, PasswordField } from "@calcom/ui";
|
|
|
|
import { FiArrowLeft } from "@calcom/ui/components/icon";
|
2022-03-16 23:36:43 +00:00
|
|
|
|
2021-09-22 19:52:38 +00:00
|
|
|
import { ErrorCode, getSession } from "@lib/auth";
|
2022-03-26 00:39:38 +00:00
|
|
|
import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants";
|
2022-01-13 20:45:44 +00:00
|
|
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
2021-10-06 14:05:01 +00:00
|
|
|
import AddToHomescreen from "@components/AddToHomescreen";
|
2022-02-04 20:30:36 +00:00
|
|
|
import TwoFactor from "@components/auth/TwoFactor";
|
2022-11-01 13:29:01 +00:00
|
|
|
import AuthContainer from "@components/ui/AuthContainer";
|
2021-09-21 09:29:20 +00:00
|
|
|
|
2022-01-13 20:05:23 +00:00
|
|
|
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
|
2021-11-16 17:12:08 +00:00
|
|
|
import { ssrInit } from "@server/lib/ssr";
|
|
|
|
|
2022-02-04 20:30:36 +00:00
|
|
|
interface LoginValues {
|
|
|
|
email: string;
|
|
|
|
password: string;
|
|
|
|
totpCode: string;
|
|
|
|
csrfToken: string;
|
|
|
|
}
|
|
|
|
|
2022-01-13 20:05:23 +00:00
|
|
|
export default function Login({
|
|
|
|
csrfToken,
|
|
|
|
isGoogleLoginEnabled,
|
|
|
|
isSAMLLoginEnabled,
|
|
|
|
samlTenantID,
|
|
|
|
samlProductID,
|
2022-01-13 20:45:44 +00:00
|
|
|
}: inferSSRProps<typeof getServerSideProps>) {
|
2021-10-14 14:24:21 +00:00
|
|
|
const { t } = useLocale();
|
2021-09-12 08:51:59 +00:00
|
|
|
const router = useRouter();
|
2022-10-19 15:50:25 +00:00
|
|
|
const methods = useForm<LoginValues>();
|
|
|
|
|
|
|
|
const { register, formState } = methods;
|
2022-02-04 20:30:36 +00:00
|
|
|
|
|
|
|
const [twoFactorRequired, setTwoFactorRequired] = useState(false);
|
2021-09-21 09:29:20 +00:00
|
|
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
2022-02-04 20:30:36 +00:00
|
|
|
|
2021-10-14 14:24:21 +00:00
|
|
|
const errorMessages: { [key: string]: string } = {
|
2022-10-17 12:44:43 +00:00
|
|
|
// [ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
|
2021-10-14 14:24:21 +00:00
|
|
|
[ErrorCode.IncorrectPassword]: `${t("incorrect_password")} ${t("please_try_again")}`,
|
|
|
|
[ErrorCode.UserNotFound]: t("no_account_exists"),
|
|
|
|
[ErrorCode.IncorrectTwoFactorCode]: `${t("incorrect_2fa_code")} ${t("please_try_again")}`,
|
|
|
|
[ErrorCode.InternalServerError]: `${t("something_went_wrong")} ${t("please_try_again_and_contact_us")}`,
|
2022-01-13 20:05:23 +00:00
|
|
|
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
|
2021-10-14 14:24:21 +00:00
|
|
|
};
|
2021-09-21 09:29:20 +00:00
|
|
|
|
2022-02-02 18:33:27 +00:00
|
|
|
const telemetry = useTelemetry();
|
|
|
|
|
2022-03-18 17:56:56 +00:00
|
|
|
let callbackUrl = typeof router.query?.callbackUrl === "string" ? router.query.callbackUrl : "";
|
2022-03-09 14:47:02 +00:00
|
|
|
|
2022-03-18 17:56:56 +00:00
|
|
|
if (/"\//.test(callbackUrl)) callbackUrl = callbackUrl.substring(1);
|
2022-04-27 14:28:36 +00:00
|
|
|
|
|
|
|
// If not absolute URL, make it absolute
|
2022-03-09 14:47:02 +00:00
|
|
|
if (!/^https?:\/\//.test(callbackUrl)) {
|
2022-03-26 00:39:38 +00:00
|
|
|
callbackUrl = `${WEBAPP_URL}/${callbackUrl}`;
|
2022-03-09 14:47:02 +00:00
|
|
|
}
|
2021-09-12 08:51:59 +00:00
|
|
|
|
2022-06-30 07:01:07 +00:00
|
|
|
const safeCallbackUrl = getSafeRedirectUrl(callbackUrl);
|
|
|
|
|
|
|
|
callbackUrl = safeCallbackUrl || "";
|
2022-04-27 14:28:36 +00:00
|
|
|
|
2022-02-04 20:30:36 +00:00
|
|
|
const LoginFooter = (
|
2022-10-17 12:44:43 +00:00
|
|
|
<a href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
|
|
|
|
{t("dont_have_an_account")}
|
|
|
|
</a>
|
2022-02-04 20:30:36 +00:00
|
|
|
);
|
2021-09-21 09:29:20 +00:00
|
|
|
|
2022-02-04 20:30:36 +00:00
|
|
|
const TwoFactorFooter = (
|
|
|
|
<Button
|
|
|
|
onClick={() => {
|
|
|
|
setTwoFactorRequired(false);
|
2022-10-19 15:50:25 +00:00
|
|
|
methods.setValue("totpCode", "");
|
2022-02-04 20:30:36 +00:00
|
|
|
}}
|
2023-01-23 23:08:01 +00:00
|
|
|
StartIcon={FiArrowLeft}
|
2022-02-04 20:30:36 +00:00
|
|
|
color="minimal">
|
|
|
|
{t("go_back")}
|
|
|
|
</Button>
|
|
|
|
);
|
2022-01-13 20:05:23 +00:00
|
|
|
|
2022-10-19 15:50:25 +00:00
|
|
|
const onSubmit = async (values: LoginValues) => {
|
|
|
|
setErrorMessage(null);
|
|
|
|
telemetry.event(telemetryEventTypes.login, collectPageParameters());
|
|
|
|
const res = await signIn<"credentials">("credentials", {
|
|
|
|
...values,
|
|
|
|
callbackUrl,
|
|
|
|
redirect: false,
|
|
|
|
});
|
|
|
|
if (!res) setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
|
|
|
|
// we're logged in! let's do a hard refresh to the desired url
|
|
|
|
else if (!res.error) router.push(callbackUrl);
|
|
|
|
// reveal two factor input if required
|
|
|
|
else if (res.error === ErrorCode.SecondFactorRequired) setTwoFactorRequired(true);
|
|
|
|
// fallback if error not found
|
|
|
|
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
|
|
|
|
};
|
|
|
|
|
2021-03-29 21:01:12 +00:00
|
|
|
return (
|
2022-01-27 10:16:20 +00:00
|
|
|
<>
|
|
|
|
<AuthContainer
|
|
|
|
title={t("login")}
|
|
|
|
description={t("login")}
|
|
|
|
showLogo
|
2022-10-17 12:44:43 +00:00
|
|
|
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
|
2023-01-14 00:42:13 +00:00
|
|
|
footerText={
|
|
|
|
twoFactorRequired
|
|
|
|
? TwoFactorFooter
|
|
|
|
: process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== "true"
|
|
|
|
? LoginFooter
|
|
|
|
: null
|
|
|
|
}>
|
2022-10-19 15:50:25 +00:00
|
|
|
<FormProvider {...methods}>
|
|
|
|
<form onSubmit={methods.handleSubmit(onSubmit)} data-testid="login-form">
|
|
|
|
<div>
|
|
|
|
<input defaultValue={csrfToken || undefined} type="hidden" hidden {...register("csrfToken")} />
|
|
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
|
|
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
|
|
|
|
<EmailField
|
|
|
|
id="email"
|
|
|
|
label={t("email_address")}
|
|
|
|
defaultValue={router.query.email as string}
|
|
|
|
placeholder="john.doe@example.com"
|
2022-10-17 12:44:43 +00:00
|
|
|
required
|
2022-10-19 15:50:25 +00:00
|
|
|
{...register("email")}
|
2022-10-17 12:44:43 +00:00
|
|
|
/>
|
2022-10-19 15:50:25 +00:00
|
|
|
<div className="relative">
|
2023-01-04 07:38:45 +00:00
|
|
|
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
|
2023-01-06 12:13:56 +00:00
|
|
|
<Link
|
|
|
|
href="/auth/forgot-password"
|
|
|
|
tabIndex={-1}
|
|
|
|
className="text-sm font-medium text-gray-600">
|
|
|
|
{t("forgot")}
|
2022-10-19 15:50:25 +00:00
|
|
|
</Link>
|
|
|
|
</div>
|
|
|
|
<PasswordField
|
|
|
|
id="password"
|
|
|
|
autoComplete="current-password"
|
|
|
|
required
|
|
|
|
className="mb-0"
|
|
|
|
{...register("password")}
|
|
|
|
/>
|
|
|
|
</div>
|
2022-10-17 12:44:43 +00:00
|
|
|
</div>
|
2022-01-27 10:16:20 +00:00
|
|
|
|
2022-10-19 15:50:25 +00:00
|
|
|
{twoFactorRequired && <TwoFactor center />}
|
|
|
|
|
|
|
|
{errorMessage && <Alert severity="error" title={errorMessage} />}
|
|
|
|
<Button
|
|
|
|
type="submit"
|
|
|
|
color="primary"
|
|
|
|
disabled={formState.isSubmitting}
|
|
|
|
className="w-full justify-center">
|
|
|
|
{twoFactorRequired ? t("submit") : t("sign_in")}
|
|
|
|
</Button>
|
2022-10-17 12:44:43 +00:00
|
|
|
</div>
|
2022-10-19 15:50:25 +00:00
|
|
|
</form>
|
|
|
|
{!twoFactorRequired && (
|
|
|
|
<>
|
|
|
|
{(isGoogleLoginEnabled || isSAMLLoginEnabled) && <hr className="my-8" />}
|
|
|
|
<div className="space-y-3">
|
|
|
|
{isGoogleLoginEnabled && (
|
|
|
|
<Button
|
|
|
|
color="secondary"
|
|
|
|
className="w-full justify-center"
|
|
|
|
data-testid="google"
|
|
|
|
StartIcon={FaGoogle}
|
|
|
|
onClick={async (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
// track Google logins. Without personal data/payload
|
|
|
|
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
|
|
|
|
await signIn("google");
|
|
|
|
}}>
|
|
|
|
{t("signin_with_google")}
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
{isSAMLLoginEnabled && (
|
|
|
|
<SAMLLogin
|
|
|
|
samlTenantID={samlTenantID}
|
|
|
|
samlProductID={samlProductID}
|
|
|
|
setErrorMessage={setErrorMessage}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</FormProvider>
|
2022-01-27 10:16:20 +00:00
|
|
|
</AuthContainer>
|
2021-10-06 14:05:01 +00:00
|
|
|
<AddToHomescreen />
|
2022-01-27 10:16:20 +00:00
|
|
|
</>
|
2021-06-24 15:59:11 +00:00
|
|
|
);
|
2021-03-29 21:01:12 +00:00
|
|
|
}
|
|
|
|
|
2021-11-16 17:12:08 +00:00
|
|
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|
|
|
const { req } = context;
|
2021-08-09 10:35:06 +00:00
|
|
|
const session = await getSession({ req });
|
2021-11-16 17:12:08 +00:00
|
|
|
const ssr = await ssrInit(context);
|
2021-08-09 10:35:06 +00:00
|
|
|
|
|
|
|
if (session) {
|
2021-11-16 17:12:08 +00:00
|
|
|
return {
|
|
|
|
redirect: {
|
|
|
|
destination: "/",
|
|
|
|
permanent: false,
|
|
|
|
},
|
|
|
|
};
|
2021-08-09 10:35:06 +00:00
|
|
|
}
|
|
|
|
|
2022-07-27 23:28:21 +00:00
|
|
|
const userCount = await prisma.user.count();
|
|
|
|
if (userCount === 0) {
|
|
|
|
// Proceed to new onboarding to create first admin user
|
|
|
|
return {
|
|
|
|
redirect: {
|
|
|
|
destination: "/auth/setup",
|
|
|
|
permanent: false,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-03-29 21:01:12 +00:00
|
|
|
return {
|
2021-11-16 17:12:08 +00:00
|
|
|
props: {
|
|
|
|
csrfToken: await getCsrfToken(context),
|
|
|
|
trpcState: ssr.dehydrate(),
|
2022-01-13 20:05:23 +00:00
|
|
|
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
|
|
|
|
isSAMLLoginEnabled,
|
|
|
|
samlTenantID,
|
|
|
|
samlProductID,
|
2021-11-16 17:12:08 +00:00
|
|
|
},
|
2021-06-24 15:59:11 +00:00
|
|
|
};
|
2021-11-16 17:12:08 +00:00
|
|
|
}
|