Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
capJavert authored Jan 16, 2025
2 parents 0305674 + a020659 commit 2a6fead
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/shared/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ module.exports = {
'\\.svg$': '<rootDir>/__mocks__/svgrMock.ts',
'\\.css$': 'identity-obj-proxy',
'react-markdown': '<rootDir>/__mocks__/reactMarkdownMock.tsx',
'react-turnstile': 'identity-obj-proxy',
},
};
1 change: 1 addition & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"dependencies": {
"@growthbook/growthbook": "https://gitpkg.now.sh/dailydotdev/growthbook/packages/sdk-js?b8f31f9e80879fe2bcc42b275087b50e1357f1cb",
"@growthbook/growthbook-react": "^0.17.0",
"@marsidev/react-turnstile": "^1.1.0",
"@paddle/paddle-js": "^1.3.2",
"@tippyjs/react": "^4.2.6",
"check-password-strength": "^2.0.10",
Expand Down
41 changes: 39 additions & 2 deletions packages/shared/src/components/auth/RegistrationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import classNames from 'classnames';
import type { MutableRefObject, ReactElement } from 'react';
import React, { useContext, useEffect, useId, useState } from 'react';
import React, { useContext, useEffect, useId, useRef, useState } from 'react';
import type { TurnstileInstance } from '@marsidev/react-turnstile';
import { Turnstile } from '@marsidev/react-turnstile';
import type {
AuthTriggersType,
RegistrationError,
Expand All @@ -25,6 +27,7 @@ import AuthContainer from './AuthContainer';
import { onValidateHandles } from '../../hooks/useProfileForm';
import ExperienceLevelDropdown from '../profile/ExperienceLevelDropdown';
import { LanguageDropdown } from '../profile/LanguageDropdown';
import Alert, { AlertType } from '../widgets/Alert';

export interface RegistrationFormProps extends AuthFormProps {
email: string;
Expand All @@ -40,7 +43,9 @@ export interface RegistrationFormProps extends AuthFormProps {
export type RegistrationFormValues = Omit<
RegistrationParameters,
'method' | 'provider'
>;
> & {
headers?: Record<string, string>;
};

const RegistrationForm = ({
email,
Expand All @@ -54,10 +59,12 @@ const RegistrationForm = ({
simplified,
}: RegistrationFormProps): ReactElement => {
const { logEvent } = useContext(LogContext);
const [turnstileError, setTurnstileError] = useState<boolean>(false);
const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
const [name, setName] = useState('');
const isAuthorOnboarding = trigger === AuthTriggers.Author;
const { username, setUsername } = useGenerateUsername(name);
const ref = useRef<TurnstileInstance>(null);

useEffect(() => {
logEvent({
Expand All @@ -73,6 +80,9 @@ const RegistrationForm = ({
event_name: AuthEventNames.SubmitSignUpFormError,
extra: JSON.stringify({ error: hints }),
});
if (hints?.csrf_token) {
setTurnstileError(true);
}
}
// @NOTE see https://dailydotdev.atlassian.net/l/cp/dK9h1zoM
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand All @@ -81,6 +91,7 @@ const RegistrationForm = ({
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();

setTurnstileError(false);
logEvent({
event_name: AuthEventNames.SubmitSignUpForm,
});
Expand All @@ -90,6 +101,7 @@ const RegistrationForm = ({
const { optOutMarketing, ...values } = formToJson<RegistrationFormValues>(
formRef?.current ?? form,
);
delete values?.['cf-turnstile-response'];

if (
!values['traits.name']?.length ||
Expand All @@ -112,6 +124,17 @@ const RegistrationForm = ({
return;
}

if (!ref?.current?.getResponse()) {
logEvent({
event_name: AuthEventNames.SubmitSignUpFormError,
extra: JSON.stringify({
error: 'Turnstile not valid',
}),
});
setTurnstileError(true);
return;
}

const error = onValidateHandles(
{},
{
Expand All @@ -138,6 +161,9 @@ const RegistrationForm = ({
onSignup({
...values,
'traits.acceptedMarketing': !optOutMarketing,
headers: {
'True-Client-Ip': ref?.current?.getResponse(),
},
});
};

Expand Down Expand Up @@ -285,6 +311,17 @@ const RegistrationForm = ({
</AuthContainer>
)}
>
<Turnstile
ref={ref}
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_KEY}
className="mx-auto"
/>
{turnstileError ? (
<Alert
type={AlertType.Error}
title="Please complete the security check."
/>
) : undefined}
<Button
form="auth-form"
type="submit"
Expand Down
13 changes: 12 additions & 1 deletion packages/shared/src/hooks/useRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import type {
RegistrationParameters,
ValidateRegistrationParams,
} from '../lib/auth';
import { AuthEventNames, errorsToJson, getNodeValue } from '../lib/auth';
import {
AuthEventNames,
errorsToJson,
getNodeByKey,
getNodeValue,
} from '../lib/auth';
import type {
InitializationData,
SuccessfulRegistrationData,
Expand Down Expand Up @@ -157,9 +162,15 @@ const useRegistration = ({
return displayToast('An error occurred, please refresh the page.');
}

const turnstileError = getNodeByKey('csrf_token', error.ui.nodes);
const emailExists = error?.ui?.messages?.find(
(message) => message.id === EMAIL_EXISTS_ERROR_ID,
);
if (turnstileError?.messages?.length > 0) {
return onInvalidRegistration?.({
csrf_token: 'Turnstile error',
});
}
if (emailExists) {
return onInvalidRegistration?.({
'traits.email': 'Email is already taken!',
Expand Down
7 changes: 5 additions & 2 deletions packages/shared/src/lib/kratos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export interface RequestResponse<TData = unknown, TError = InitializationData> {

export interface AuthPostParams {
csrf_token: string;
headers?: Record<string, string>;
}

export interface KratosFormParams<T extends AuthPostParams> {
Expand Down Expand Up @@ -311,15 +312,17 @@ export const submitKratosFlow = async <
params,
method = 'POST',
}: KratosFormParams<T>): Promise<RequestResponse<R, E>> => {
const { headers, ...rest } = params;
const res = await fetch(action, {
method,
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': params.csrf_token,
'X-CSRF-Token': rest.csrf_token,
Accept: 'application/json',
...headers,
},
body: method === 'GET' ? undefined : JSON.stringify(params),
body: method === 'GET' ? undefined : JSON.stringify(rest),
});

if (res.status === 204) {
Expand Down
4 changes: 3 additions & 1 deletion packages/webapp/.env
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ NEXT_PUBLIC_GB_DEV_MODE=false
NEXT_PUBLIC_SLACK_CLIENT_ID=1137730955072.7361269413077
NEXT_PUBLIC_PADDLE_TOKEN=topsecret

NEXT_PUBLIC_ANDROID_APP="https://play.google.com/store/apps/details?id=dev.daily"
NEXT_PUBLIC_ANDROID_APP="https://play.google.com/store/apps/details?id=dev.daily"

NEXT_PUBLIC_TURNSTILE_KEY=1x00000000000000000000AA
1 change: 1 addition & 0 deletions packages/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@dailydotdev/react-contexify": "^5.0.2",
"@dailydotdev/shared": "workspace:*",
"@marsidev/react-turnstile": "1.1.0",
"@paddle/paddle-js": "^1.3.2",
"@serwist/next": "^9.0.9",
"@tanstack/react-query": "^5.59.16",
Expand Down
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2a6fead

Please sign in to comment.