Skip to content

Commit 7e091c1

Browse files
authored
fix: turnstile checks and log improvement (#5877)
1 parent 089c031 commit 7e091c1

File tree

4 files changed

+81
-12
lines changed

4 files changed

+81
-12
lines changed

packages/shared/src/components/auth/LoginForm.tsx

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import classNames from 'classnames';
22
import type { Dispatch, FormEvent, ReactElement, SetStateAction } from 'react';
3-
import React, { useState } from 'react';
3+
import React, { useEffect, useRef, useState } from 'react';
4+
import type { TurnstileInstance } from '@marsidev/react-turnstile';
5+
import { Turnstile } from '@marsidev/react-turnstile';
46
import type { LoginPasswordParameters } from '../../lib/auth';
7+
import { AuthEventNames } from '../../lib/auth';
8+
import { Origin } from '../../lib/log';
59
import { formToJson } from '../../lib/form';
610
import { Button, ButtonVariant } from '../buttons/Button';
711
import { ClickableText } from '../buttons/ClickableText';
@@ -12,6 +16,7 @@ import AuthForm from './AuthForm';
1216
import { IconSize } from '../Icon';
1317
import Alert, { AlertParagraph, AlertType } from '../widgets/Alert';
1418
import { labels } from '../../lib';
19+
import { useLogContext } from '../../contexts/LogContext';
1520

1621
export interface LoginFormProps {
1722
onForgotPassword?: (email: string) => unknown;
@@ -34,7 +39,9 @@ export type LoginHintState = [
3439
export type LoginFormParams = Pick<
3540
LoginPasswordParameters,
3641
'identifier' | 'password'
37-
>;
42+
> & {
43+
turnstileToken?: string;
44+
};
3845

3946
function LoginForm({
4047
onForgotPassword,
@@ -48,14 +55,39 @@ function LoginForm({
4855
autoFocus = true,
4956
onSignup,
5057
}: LoginFormProps): ReactElement {
58+
const { logEvent } = useLogContext();
59+
const turnstileRef = useRef<TurnstileInstance>(null);
60+
const [turnstileLoaded, setTurnstileLoaded] = useState(false);
61+
const [turnstileError, setTurnstileError] = useState(false);
62+
const turnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_KEY ?? '';
63+
64+
useEffect(() => {
65+
if (hint) {
66+
turnstileRef.current?.reset();
67+
}
68+
}, [hint]);
69+
5170
const onLogin = async (e: FormEvent<HTMLFormElement>) => {
5271
e.preventDefault();
5372

5473
if (!onPasswordLogin) {
5574
return;
5675
}
5776

77+
if (turnstileSiteKey && !turnstileRef.current?.getResponse()) {
78+
logEvent({
79+
event_name: AuthEventNames.LoginError,
80+
extra: JSON.stringify({
81+
error: 'Turnstile not valid',
82+
origin: Origin.LoginTurnstile,
83+
}),
84+
});
85+
setTurnstileError(true);
86+
return;
87+
}
88+
5889
const form = formToJson<LoginFormParams>(e.currentTarget);
90+
form.turnstileToken = turnstileRef.current?.getResponse() ?? undefined;
5991
onPasswordLogin(form);
6092
};
6193
const [shouldFocus, setShouldFocus] = useState(autoFocus);
@@ -109,6 +141,21 @@ function LoginForm({
109141
saveHintSpace
110142
onChange={() => hint && setHint(null)}
111143
/>
144+
{turnstileSiteKey && (
145+
<Turnstile
146+
ref={turnstileRef}
147+
siteKey={turnstileSiteKey}
148+
options={{ theme: 'dark' }}
149+
className="mx-auto min-h-[4.5rem]"
150+
onWidgetLoad={() => setTurnstileLoaded(true)}
151+
/>
152+
)}
153+
{turnstileError && (
154+
<Alert
155+
type={AlertType.Error}
156+
title="Please complete the security check."
157+
/>
158+
)}
112159
<span className="mt-4 flex w-full flex-row">
113160
{onForgotPassword && (
114161
<ClickableText
@@ -124,7 +171,7 @@ function LoginForm({
124171
variant={ButtonVariant.Primary}
125172
type="submit"
126173
loading={!isReady}
127-
disabled={isLoading}
174+
disabled={isLoading || (!!turnstileSiteKey && !turnstileLoaded)}
128175
>
129176
{loginButton}
130177
</Button>

packages/shared/src/hooks/useLogin.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type { SignBackProvider } from './auth/useSignBack';
2323
import { useSignBack } from './auth/useSignBack';
2424
import type { LoggedUser } from '../lib/user';
2525
import { labels } from '../lib';
26+
import { Origin } from '../lib/log';
2627
import { useEventListener } from './useEventListener';
2728
import { broadcastChannel, webappUrl } from '../lib/constants';
2829
import { isIOSNative } from '../lib/func';
@@ -69,6 +70,7 @@ const useLogin = ({
6970
return betterAuthSignIn({
7071
email: form.identifier,
7172
password: form.password,
73+
turnstileToken: form.turnstileToken,
7274
});
7375
},
7476
onSuccess: async (res) => {
@@ -78,7 +80,8 @@ const useLogin = ({
7880
extra: JSON.stringify({
7981
error: res.error,
8082
displayedError: labels.auth.error.invalidEmailOrPassword,
81-
origin: 'betterauth email login',
83+
origin: Origin.BetterAuthEmailLogin,
84+
userAgent: navigator.userAgent,
8285
}),
8386
});
8487
setHint(labels.auth.error.invalidEmailOrPassword);
@@ -93,7 +96,7 @@ const useLogin = ({
9396
event_name: AuthEventNames.LoginError,
9497
extra: JSON.stringify({
9598
error: 'Missing user after Better Auth email login',
96-
origin: 'betterauth email login boot',
99+
origin: Origin.BetterAuthEmailLoginBoot,
97100
}),
98101
});
99102
setHint(labels.auth.error.generic);
@@ -113,7 +116,7 @@ const useLogin = ({
113116
error,
114117
'Failed to refresh Better Auth login state',
115118
),
116-
origin: 'betterauth email login boot',
119+
origin: Origin.BetterAuthEmailLoginBoot,
117120
}),
118121
});
119122
setHint(labels.auth.error.generic);
@@ -138,7 +141,7 @@ const useLogin = ({
138141
event_name: AuthEventNames.LoginError,
139142
extra: JSON.stringify({
140143
error: result.error,
141-
origin: 'betterauth native id token',
144+
origin: Origin.BetterAuthNativeIdToken,
142145
}),
143146
});
144147
return;
@@ -150,7 +153,7 @@ const useLogin = ({
150153
event_name: AuthEventNames.LoginError,
151154
extra: JSON.stringify({
152155
error: 'Missing user after Better Auth social login',
153-
origin: 'betterauth native id token boot',
156+
origin: Origin.BetterAuthNativeIdTokenBoot,
154157
}),
155158
});
156159
displayToast(labels.auth.error.generic);
@@ -168,7 +171,7 @@ const useLogin = ({
168171
error,
169172
'Failed to refresh Better Auth social login state',
170173
),
171-
origin: 'betterauth native id token boot',
174+
origin: Origin.BetterAuthNativeIdTokenBoot,
172175
}),
173176
});
174177
displayToast(labels.auth.error.generic);
@@ -187,7 +190,7 @@ const useLogin = ({
187190
event_name: AuthEventNames.LoginError,
188191
extra: JSON.stringify({
189192
error: error || 'Failed to get social login URL',
190-
origin: 'betterauth social url',
193+
origin: Origin.BetterAuthSocialUrl,
191194
}),
192195
});
193196
socialPopup?.close();
@@ -206,7 +209,7 @@ const useLogin = ({
206209
event_name: AuthEventNames.LoginError,
207210
extra: JSON.stringify({
208211
error: 'Failed to open social login window',
209-
origin: 'betterauth social popup',
212+
origin: Origin.BetterAuthSocialPopup,
210213
}),
211214
});
212215
displayToast(labels.auth.error.generic);

packages/shared/src/lib/betterAuth.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,22 @@ const betterAuthPost = async <T = Record<string, unknown>>(
9090
export const betterAuthSignIn = async ({
9191
email,
9292
password,
93+
turnstileToken,
9394
}: {
9495
email: string;
9596
password: string;
97+
turnstileToken?: string;
9698
}): Promise<BetterAuthResponse> => {
97-
return betterAuthPost('sign-in/email', { email, password }, 'Sign in failed');
99+
const headers: Record<string, string> = {};
100+
if (turnstileToken) {
101+
headers['x-captcha-response'] = turnstileToken;
102+
}
103+
return betterAuthPost(
104+
'sign-in/email',
105+
{ email, password },
106+
'Sign in failed',
107+
Object.keys(headers).length > 0 ? headers : undefined,
108+
);
98109
};
99110

100111
export const betterAuthSignUp = async ({

packages/shared/src/lib/log.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ export enum Origin {
8585
// Onboarding v2
8686
OnboardingModal = 'onboarding modal',
8787
OnboardingFeedEnd = 'onboarding feed end',
88+
// Auth
89+
BetterAuthEmailLogin = 'betterauth email login',
90+
BetterAuthEmailLoginBoot = 'betterauth email login boot',
91+
BetterAuthNativeIdToken = 'betterauth native id token',
92+
BetterAuthNativeIdTokenBoot = 'betterauth native id token boot',
93+
BetterAuthSocialUrl = 'betterauth social url',
94+
BetterAuthSocialPopup = 'betterauth social popup',
95+
LoginTurnstile = 'login turnstile',
8896
}
8997

9098
export enum LogEvent {

0 commit comments

Comments
 (0)