feat: Added the ability to remove the login via email button (#1993)

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Nick van Leeuwen
2024-02-14 10:02:08 +01:00
committed by GitHub
parent f899cb0478
commit 3090151c50
13 changed files with 147 additions and 110 deletions

View File

@@ -71,6 +71,9 @@ PASSWORD_RESET_DISABLED=1
# Signup. Disable the ability for new users to create an account.
# SIGNUP_DISABLED=1
# Email login. Disable the ability for users to login with email.
# EMAIL_AUTH_DISABLED=1
# Team Invite. Disable the ability for invited users to create an account.
# INVITE_DISABLED=1

View File

@@ -222,6 +222,7 @@ These variables can be provided at the runtime i.e. in your docker-compose file.
| TERMS_URL | URL for terms of service. | optional | |
| IMPRINT_URL | URL for imprint. | optional | |
| SIGNUP_DISABLED | Disables the ability for new users to create an account if set to `1`. | optional | |
| EMAIL_AUTH_DISABLED | Disables the ability for users to signup or login via email and password if set to `1`. | optional | |
| PASSWORD_RESET_DISABLED | Disables password reset functionality if set to `1`. | optional | |
| EMAIL_VERIFICATION_DISABLED | Disables email verification if set to `1`. | optional | |
| INVITE_DISABLED | Disables the ability for invited users to create an account if set to `1`. | optional | |

View File

@@ -14,10 +14,10 @@ Formbricks v1.2 ships a lot of features targeting our Link Surveys. We have also
### New Environment Variables
| Environment Variable | Required | Recommended Generation | Comments |
| -------------------- | -------- | ------------------------------ | ----------------------------------------------------------- |
| ENCRYPTION_KEY | true | `openssl rand -hex 32` | Needed for 2 Factor Authentication |
| SHORT_URL_BASE | false | `<your-short-base-url>` | Needed if you want to enable shorter links for Link Surveys |
| Environment Variable | Required | Recommended Generation | Comments |
| -------------------- | -------- | ----------------------- | ----------------------------------------------------------- |
| ENCRYPTION_KEY | true | `openssl rand -hex 32` | Needed for 2 Factor Authentication |
| SHORT_URL_BASE | false | `<your-short-base-url>` | Needed if you want to enable shorter links for Link Surveys |
### Deprecated / Removed Environment Variables
@@ -113,6 +113,9 @@ x-environment: &environment
# Uncomment the below and set it to 1 to disable Signups
# SIGNUP_DISABLED:
# Uncomment the below and set it to 1 to disable loging in with email
# EMAIL_AUTH_DISABLED:
# Uncomment the below and set it to 1 to disable Invites
# INVITE_DISABLED:
@@ -139,3 +142,4 @@ x-environment: &environment
</CodeGroup>
</Col>
Did we miss something? Are you still facing issues migrating your app? [Join our Discord!](https://formbricks.com/discord) We'd be happy to help!
```

View File

@@ -24,12 +24,14 @@ type TSigninFormState = {
};
export const SigninForm = ({
emailAuthEnabled,
publicSignUpEnabled,
passwordResetEnabled,
googleOAuthEnabled,
githubOAuthEnabled,
azureOAuthEnabled,
}: {
emailAuthEnabled: boolean;
publicSignUpEnabled: boolean;
passwordResetEnabled: boolean;
googleOAuthEnabled: boolean;
@@ -185,21 +187,23 @@ export const SigninForm = ({
)}
</div>
)}
<Button
onClick={() => {
if (!showLogin) {
setShowLogin(true);
// Add a slight delay before focusing the input field to ensure it's visible
setTimeout(() => emailRef.current?.focus(), 100);
} else if (formRef.current) {
formRef.current.requestSubmit();
}
}}
variant="darkCTA"
className="w-full justify-center"
loading={loggingIn}>
{totpLogin ? "Submit" : "Login with Email"}
</Button>
{emailAuthEnabled && (
<Button
onClick={() => {
if (!showLogin) {
setShowLogin(true);
// Add a slight delay before focusing the input field to ensure it's visible
setTimeout(() => emailRef.current?.focus(), 100);
} else if (formRef.current) {
formRef.current.requestSubmit();
}
}}
variant="darkCTA"
className="w-full justify-center"
loading={loggingIn}>
{totpLogin ? "Submit" : "Login with Email"}
</Button>
)}
</form>
{googleOAuthEnabled && !totpLogin && (

View File

@@ -5,6 +5,7 @@ import { Metadata } from "next";
import {
AZURE_OAUTH_ENABLED,
EMAIL_AUTH_ENABLED,
GITHUB_OAUTH_ENABLED,
GOOGLE_OAUTH_ENABLED,
PASSWORD_RESET_DISABLED,
@@ -25,6 +26,7 @@ export default function SignInPage() {
<div className="col-span-3 flex flex-col items-center justify-center">
<FormWrapper>
<SigninForm
emailAuthEnabled={EMAIL_AUTH_ENABLED}
publicSignUpEnabled={SIGNUP_ENABLED}
passwordResetEnabled={!PASSWORD_RESET_DISABLED}
googleOAuthEnabled={GOOGLE_OAUTH_ENABLED}

View File

@@ -13,25 +13,29 @@ import { useMemo, useRef, useState } from "react";
import { Button } from "@formbricks/ui/Button";
import { PasswordInput } from "@formbricks/ui/PasswordInput";
interface SignupFormProps {
webAppUrl: string;
privacyUrl: string | undefined;
termsUrl: string | undefined;
passwordResetEnabled: boolean;
emailVerificationDisabled: boolean;
emailAuthEnabled: boolean;
googleOAuthEnabled: boolean;
githubOAuthEnabled: boolean;
azureOAuthEnabled: boolean;
}
export const SignupForm = ({
webAppUrl,
privacyUrl,
termsUrl,
passwordResetEnabled,
emailVerificationDisabled,
emailAuthEnabled,
googleOAuthEnabled,
githubOAuthEnabled,
azureOAuthEnabled,
}: {
webAppUrl: string;
privacyUrl: string | undefined;
termsUrl: string | undefined;
passwordResetEnabled: boolean;
emailVerificationDisabled: boolean;
googleOAuthEnabled: boolean;
githubOAuthEnabled: boolean;
azureOAuthEnabled: boolean;
}) => {
}: SignupFormProps) => {
const searchParams = useSearchParams();
const router = useRouter();
const [error, setError] = useState<string>("");
@@ -108,91 +112,92 @@ export const SignupForm = ({
<div className="text-center">
<h1 className="mb-4 text-slate-700">Create your Formbricks account</h1>
<div className="space-y-2">
<form onSubmit={handleSubmit} ref={formRef} className="space-y-2" onChange={checkFormValidity}>
{showLogin && (
<div>
<div className="mb-2 transition-all duration-500 ease-in-out">
<label htmlFor="name" className="sr-only">
Full Name
</label>
<div className="mt-1">
{emailAuthEnabled && (
<form onSubmit={handleSubmit} ref={formRef} className="space-y-2" onChange={checkFormValidity}>
{showLogin && (
<div>
<div className="mb-2 transition-all duration-500 ease-in-out">
<label htmlFor="name" className="sr-only">
Full Name
</label>
<div className="mt-1">
<input
ref={nameRef}
id="name"
name="name"
type="text"
autoComplete="given-name"
placeholder="Full Name"
aria-placeholder="Full Name"
required
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
/>
</div>
</div>
<div className="mb-2 transition-all duration-500 ease-in-out">
<label htmlFor="email" className="sr-only">
Email address
</label>
<input
ref={nameRef}
id="name"
name="name"
type="text"
autoComplete="given-name"
placeholder="Full Name"
aria-placeholder="Full Name"
id="email"
name="email"
type="email"
autoComplete="email"
required
placeholder="work@email.com"
defaultValue={searchParams?.get("email") || ""}
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
/>
</div>
</div>
<div className="mb-2 transition-all duration-500 ease-in-out">
<label htmlFor="email" className="sr-only">
Email address
</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
placeholder="work@email.com"
defaultValue={searchParams?.get("email") || ""}
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
/>
</div>
<div className="transition-all duration-500 ease-in-out">
<label htmlFor="password" className="sr-only">
Password
</label>
<PasswordInput
id="password"
name="password"
value={password ? password : ""}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
placeholder="*******"
aria-placeholder="password"
onFocus={() => setIsPasswordFocused(true)}
required
className="focus:border-brand focus:ring-brand block w-full rounded-md shadow-sm sm:text-sm"
/>
</div>
{passwordResetEnabled && isPasswordFocused && (
<div className="ml-1 text-right transition-all duration-500 ease-in-out">
<Link
href="/auth/forgot-password"
className="hover:text-brand-dark text-xs text-slate-500">
Forgot your password?
</Link>
<div className="transition-all duration-500 ease-in-out">
<label htmlFor="password" className="sr-only">
Password
</label>
<PasswordInput
id="password"
name="password"
value={password ? password : ""}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
placeholder="*******"
aria-placeholder="password"
onFocus={() => setIsPasswordFocused(true)}
required
className="focus:border-brand focus:ring-brand block w-full rounded-md shadow-sm sm:text-sm"
/>
</div>
)}
<IsPasswordValid password={password} setIsValid={setIsValid} />
</div>
)}
<Button
onClick={(e: any) => {
e.preventDefault();
if (!showLogin) {
setShowLogin(true);
setButtonEnabled(false);
// Add a slight delay before focusing the input field to ensure it's visible
setTimeout(() => nameRef.current?.focus(), 100);
} else if (formRef.current) {
formRef.current.requestSubmit();
}
}}
variant="darkCTA"
className="w-full justify-center"
loading={signingUp}
disabled={formRef.current ? !isButtonEnabled || !isValid : !isButtonEnabled}>
Continue with Email
</Button>
</form>
{passwordResetEnabled && isPasswordFocused && (
<div className="ml-1 text-right transition-all duration-500 ease-in-out">
<Link
href="/auth/forgot-password"
className="hover:text-brand-dark text-xs text-slate-500">
Forgot your password?
</Link>
</div>
)}
<IsPasswordValid password={password} setIsValid={setIsValid} />
</div>
)}
<Button
onClick={(e: any) => {
e.preventDefault();
if (!showLogin) {
setShowLogin(true);
setButtonEnabled(false);
// Add a slight delay before focusing the input field to ensure it's visible
setTimeout(() => nameRef.current?.focus(), 100);
} else if (formRef.current) {
formRef.current.requestSubmit();
}
}}
variant="darkCTA"
className="w-full justify-center"
loading={signingUp}
disabled={formRef.current ? !isButtonEnabled || !isValid : !isButtonEnabled}>
Continue with Email
</Button>
</form>
)}
{googleOAuthEnabled && (
<>
<GoogleButton inviteUrl={callbackUrl} />

View File

@@ -5,6 +5,7 @@ import Link from "next/link";
import {
AZURE_OAUTH_ENABLED,
EMAIL_AUTH_ENABLED,
EMAIL_VERIFICATION_DISABLED,
GITHUB_OAUTH_ENABLED,
GOOGLE_OAUTH_ENABLED,
@@ -51,6 +52,7 @@ export default function SignUpPage({
privacyUrl={PRIVACY_URL}
passwordResetEnabled={!PASSWORD_RESET_DISABLED}
emailVerificationDisabled={EMAIL_VERIFICATION_DISABLED}
emailAuthEnabled={EMAIL_AUTH_ENABLED}
googleOAuthEnabled={GOOGLE_OAUTH_ENABLED}
githubOAuthEnabled={GITHUB_OAUTH_ENABLED}
azureOAuthEnabled={AZURE_OAUTH_ENABLED}

View File

@@ -1,7 +1,12 @@
import { NextResponse } from "next/server";
import { prisma } from "@formbricks/database";
import { EMAIL_VERIFICATION_DISABLED, INVITE_DISABLED, SIGNUP_ENABLED } from "@formbricks/lib/constants";
import {
EMAIL_AUTH_ENABLED,
EMAIL_VERIFICATION_DISABLED,
INVITE_DISABLED,
SIGNUP_ENABLED,
} from "@formbricks/lib/constants";
import { sendInviteAcceptedEmail, sendVerificationEmail } from "@formbricks/lib/emails/emails";
import { env } from "@formbricks/lib/env.mjs";
import { deleteInvite } from "@formbricks/lib/invite/service";
@@ -13,7 +18,7 @@ import { createUser, updateUser } from "@formbricks/lib/user/service";
export async function POST(request: Request) {
let { inviteToken, ...user } = await request.json();
if (inviteToken ? INVITE_DISABLED : !SIGNUP_ENABLED) {
if (!EMAIL_AUTH_ENABLED || inviteToken ? INVITE_DISABLED : !SIGNUP_ENABLED) {
return NextResponse.json({ error: "Signup disabled" }, { status: 403 });
}
user = { ...user, ...{ email: user.email.toLowerCase() } };

View File

@@ -40,6 +40,9 @@ x-password-reset-disabled: &password_reset_disabled 1
# Signup. Disable the ability for new users to create an account.
x-signup-disabled: &signup_disabled 0
# Email login. Disable the ability for users to login with email.
x-auth-disabled: &email_auth_disabled 0
# Team Invite. Disable the ability for invited users to create an account.
x-invite-disabled: &invite_disabled 0
@@ -104,6 +107,7 @@ services:
IMPRINT_URL: *imprint_url
EMAIL_VERIFICATION_DISABLED: *email_verification_disabled
PASSWORD_RESET_DISABLED: *password_reset_disabled
EMAIL_AUTH_DISABLED: *email_auth_disabled
SIGNUP_DISABLED: *signup_disabled
INVITE_DISABLED: *invite_disabled
SENTRY_IGNORE_API_RESOLUTION_ERROR: *sentry_ignore_api_resolution_error

View File

@@ -47,6 +47,9 @@ x-environment: &environment
# Uncomment the below and set it to 1 to disable Signups
# SIGNUP_DISABLED:
# Uncomment the below and set it to 1 to disable logging in with email
# EMAIL_AUTH_DISABLED:
# Uncomment the below and set it to 1 to disable Invites
# INVITE_DISABLED:

View File

@@ -44,6 +44,7 @@ export const AZUREAD_CLIENT_SECRET = env.AZUREAD_CLIENT_SECRET;
export const AZUREAD_TENANT_ID = env.AZUREAD_TENANT_ID;
export const SIGNUP_ENABLED = env.SIGNUP_DISABLED !== "1";
export const EMAIL_AUTH_ENABLED = env.EMAIL_AUTH_DISABLED !== "1";
export const INVITE_DISABLED = env.INVITE_DISABLED === "1";
export const GOOGLE_SHEETS_CLIENT_ID = env.GOOGLE_SHEETS_CLIENT_ID;

View File

@@ -32,6 +32,7 @@ export const env = createEnv({
EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
PASSWORD_RESET_DISABLED: z.enum(["1", "0"]).optional(),
SIGNUP_DISABLED: z.enum(["1", "0"]).optional(),
EMAIL_AUTH_DISABLED: z.enum(["1", "0"]).optional(),
PRIVACY_URL: z
.string()
.url()
@@ -119,6 +120,7 @@ export const env = createEnv({
EMAIL_VERIFICATION_DISABLED: process.env.EMAIL_VERIFICATION_DISABLED,
PASSWORD_RESET_DISABLED: process.env.PASSWORD_RESET_DISABLED,
SIGNUP_DISABLED: process.env.SIGNUP_DISABLED,
EMAIL_AUTH_DISABLED: process.env.EMAIL_AUTH_DISABLED,
INVITE_DISABLED: process.env.INVITE_DISABLED,
PRIVACY_URL: process.env.PRIVACY_URL,
TERMS_URL: process.env.TERMS_URL,

View File

@@ -68,6 +68,7 @@
"CUSTOMER_IO_API_KEY",
"CUSTOMER_IO_SITE_ID",
"DEBUG",
"EMAIL_AUTH_DISABLED",
"EMAIL_VERIFICATION_DISABLED",
"ENCRYPTION_KEY",
"ENTERPRISE_LICENSE_KEY",