Skip to content

Commit 41a4ea2

Browse files
fix: remove duplicate turnstile on login page (#608)
Removed the duplicated Turnstile Captcha from login Page. ## Type of change <!-- Please mark the relevant points by using [x] --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Chore (refactoring code, technical debt, workflow improvements) - [ ] Enhancement (small improvements) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ## Checklist <!-- We're starting to get more and more contributions. Please help us making this efficient for all of us and go through this checklist. Please tick off what you did --> ### Required - [ ] Read [Contributing Guide](https://github.com/un/inbox/blob/main/CONTRIBUTING.md) - [ ] Self-reviewed my own code - [ ] Tested my code in a local environment - [ ] Commented on my code in hard-to-understand areas - [ ] Checked for warnings, there are none - [ ] Removed all `console.logs` - [ ] Merged the latest changes from main onto my branch with `git pull origin main` - [ ] My changes don't cause any responsiveness issues ### Appreciated - [ ] If a UI change was made: Added a screen recording or screenshots to this PR - [ ] Updated the UnInbox Docs if changes were necessary
1 parent 6d553dc commit 41a4ea2

File tree

4 files changed

+105
-127
lines changed

4 files changed

+105
-127
lines changed

apps/platform/trpc/routers/authRouter/twoFactorRouter.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ export const twoFactorRouter = router({
5555
})
5656
)
5757
.mutation(async ({ ctx, input }) => {
58-
await new Promise((resolve) => setTimeout(resolve, 4000));
5958
const challengeCookie = getCookie(
6059
ctx.event,
6160
'two-factor-login-challenge'

apps/web/src/app/(login)/_components/passkey-login.tsx

Lines changed: 0 additions & 80 deletions
This file was deleted.

apps/web/src/app/(login)/_components/two-factor-dialog.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@ export function TwoFactorDialog({ open }: { open: boolean }) {
1818
const [code, setCode] = useState('');
1919
const router = useRouter();
2020
const { isSuccess, error, mutateAsync, isPending } =
21-
platform.auth.twoFactorAuthentication.verifyTwoFactorChallenge.useMutation();
21+
platform.auth.twoFactorAuthentication.verifyTwoFactorChallenge.useMutation({
22+
onError: () => setCode('')
23+
});
2224

2325
return (
24-
<AlertDialog open={open}>
26+
<AlertDialog
27+
open={open}
28+
// don't close on any interaction
29+
onOpenChange={() => false}>
2530
<AlertDialogContent className="w-min p-8">
2631
<AlertDialogTitle>Two Factor Authentication</AlertDialogTitle>
2732
<AlertDialogDescription>
28-
Please enter the 6-digit code from your authenticator app
33+
Enter the 6-digit code from your authenticator app
2934
</AlertDialogDescription>
3035
<div className="mx-auto w-fit">
3136
<InputOTP
@@ -43,11 +48,10 @@ export function TwoFactorDialog({ open }: { open: boolean }) {
4348
</InputOTP>
4449
</div>
4550
{error && (
46-
<div className="text-red-9 text-center text-sm">{error.message}</div>
51+
<div className="text-red-9 text-center text-xs">{error.message}</div>
4752
)}
4853
<Button
4954
className="mx-auto w-full"
50-
variant="secondary"
5155
disabled={code.length !== 6 || isPending || isSuccess}
5256
loading={isPending || isSuccess}
5357
onClick={async () => {

apps/web/src/app/(login)/page.tsx

Lines changed: 96 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
import { Button } from '@/src/components/shadcn-ui/button';
1111
import { Separator } from '@/src/components/shadcn-ui/separator';
1212
import { Badge } from '@/src/components/shadcn-ui/badge';
13-
import { PasskeyLoginButton } from './_components/passkey-login';
1413
import { At, Lock } from '@phosphor-icons/react';
1514
import Link from 'next/link';
1615
import Image from 'next/image';
@@ -25,24 +24,27 @@ import {
2524
TurnstileComponent,
2625
turnstileEnabled
2726
} from '@/src/components/turnstile';
28-
import { platform } from '@/src/lib/trpc';
29-
import { useState } from 'react';
3027
import { TwoFactorDialog } from './_components/two-factor-dialog';
28+
import { Fingerprint } from '@phosphor-icons/react';
29+
import { platform } from '@/src/lib/trpc';
30+
import { startAuthentication } from '@simplewebauthn/browser';
31+
import { toast } from 'sonner';
32+
import { useCallback, useState } from 'react';
3133

3234
const loginSchema = z.object({
3335
username: zodSchemas.username(2),
34-
password: z.string().min(8, 'Password must be at least 8 characters'),
35-
turnstileToken: turnstileEnabled
36-
? z.string({
37-
required_error:
38-
'Waiting for Captcha. If you can see the Captcha, complete it manually'
39-
})
40-
: z.undefined()
36+
password: z.string().min(8, 'Password must be at least 8 characters')
4137
});
4238

4339
export default function Page() {
4440
const router = useRouter();
4541
const [twoFactorDialogOpen, setTwoFactorDialogOpen] = useState(false);
42+
const [turnstileToken, setTurnstileToken] = useState<string | undefined>();
43+
const generatePasskey =
44+
platform.useUtils().auth.passkey.generatePasskeyChallenge;
45+
const { mutateAsync: loginPasskey } =
46+
platform.auth.passkey.verifyPasskey.useMutation();
47+
const [loadingPasskey, setLoadingPasskey] = useState(false);
4648

4749
const form = useForm<z.infer<typeof loginSchema>>({
4850
resolver: zodResolver(loginSchema),
@@ -53,29 +55,88 @@ export default function Page() {
5355
});
5456

5557
const {
56-
mutateAsync: login,
58+
mutateAsync: loginPassword,
5759
error,
5860
isPending
5961
} = platform.auth.password.signIn.useMutation();
6062

61-
const handleLogin = async (values: z.infer<typeof loginSchema>) => {
63+
const loginWithPasskey = useCallback(async () => {
64+
if (turnstileEnabled && !turnstileToken) {
65+
toast.error(
66+
'Captcha has not been completed yet, if you can see the Captcha, complete it manually'
67+
);
68+
return;
69+
}
6270
try {
63-
const { status, defaultOrgShortcode } = await login(values);
64-
if (status === 'NO_2FA_SETUP') {
65-
if (!defaultOrgShortcode) {
66-
router.push('/join/org');
71+
setLoadingPasskey(true);
72+
const data = await generatePasskey.fetch({
73+
turnstileToken
74+
});
75+
const response = await startAuthentication(data.options);
76+
const { defaultOrg } = await loginPasskey({
77+
verificationResponseRaw: response
78+
});
79+
80+
if (!defaultOrg) {
81+
toast.error('You are not a member of any organization', {
82+
description: 'Redirecting you to create an organization'
83+
});
84+
router.push('/join/org');
85+
return;
86+
}
87+
88+
toast.success('Sign in successful!', {
89+
description: 'Redirecting you to your conversations'
90+
});
91+
router.push(`/${defaultOrg}/convo`);
92+
} catch (error) {
93+
if (error instanceof Error) {
94+
if (error.name === 'NotAllowedError') {
95+
toast.warning('Passkey login either timed out or was cancelled');
6796
} else {
68-
router.push(
69-
`/${defaultOrgShortcode}/settings/user/security?code=NO_2FA_SETUP`
70-
);
97+
toast.error(error.name, {
98+
description: error.message
99+
});
71100
}
72101
} else {
73-
setTwoFactorDialogOpen(true);
102+
console.error(error);
74103
}
75-
} catch {
76-
/* do nothing */
104+
} finally {
105+
setLoadingPasskey(false);
77106
}
78-
};
107+
}, [generatePasskey, router, turnstileToken, loginPasskey]);
108+
109+
const loginWithPassword = useCallback(
110+
async ({ username, password }: z.infer<typeof loginSchema>) => {
111+
if (turnstileEnabled && !turnstileToken) {
112+
toast.error(
113+
'Captcha has not been completed yet, if you can see the Captcha, complete it manually'
114+
);
115+
return;
116+
}
117+
try {
118+
const { status, defaultOrgShortcode } = await loginPassword({
119+
username,
120+
password,
121+
turnstileToken
122+
});
123+
if (status === 'NO_2FA_SETUP') {
124+
if (!defaultOrgShortcode) {
125+
router.push('/join/org');
126+
} else {
127+
router.push(
128+
`/${defaultOrgShortcode}/settings/user/security?code=NO_2FA_SETUP`
129+
);
130+
}
131+
} else {
132+
setTwoFactorDialogOpen(true);
133+
}
134+
} catch {
135+
/* do nothing */
136+
}
137+
},
138+
[loginPassword, router, turnstileToken]
139+
);
79140

80141
return (
81142
<div className="bg-base-2 flex h-full items-center justify-center">
@@ -90,7 +151,16 @@ export default function Page() {
90151
<h1 className="mb-2 text-2xl font-medium">Login to your UnInbox</h1>
91152
<h2 className="text-base-10 text-sm">Enter your details to login.</h2>
92153
<div className="py-6">
93-
<PasskeyLoginButton />
154+
<div className="flex flex-col gap-2">
155+
<Button
156+
onClick={() => loginWithPasskey()}
157+
loading={loadingPasskey}
158+
disabled={loadingPasskey || (turnstileEnabled && !turnstileToken)}
159+
className="mb-2 w-72 cursor-pointer gap-2 font-semibold">
160+
<Fingerprint size={20} />
161+
<span>Login with my passkey</span>
162+
</Button>
163+
</div>
94164
<div className="flex items-center justify-center gap-2 py-4">
95165
<Separator className="bg-base-5 w-28" />
96166
<Badge
@@ -103,7 +173,7 @@ export default function Page() {
103173
<div className="flex flex-col items-center justify-center gap-4 py-2">
104174
<Form {...form}>
105175
<form
106-
onSubmit={form.handleSubmit(handleLogin)}
176+
onSubmit={form.handleSubmit(loginWithPassword)}
107177
className="flex w-full flex-col items-center justify-center gap-3">
108178
<FormField
109179
control={form.control}
@@ -143,22 +213,6 @@ export default function Page() {
143213
<div className="text-red-10 text-sm">{error.message}</div>
144214
)}
145215

146-
<FormField
147-
control={form.control}
148-
name="turnstileToken"
149-
render={() => (
150-
<FormItem>
151-
<FormControl>
152-
<TurnstileComponent
153-
onSuccess={(value) =>
154-
form.setValue('turnstileToken', value)
155-
}
156-
/>
157-
</FormControl>
158-
<FormMessage />
159-
</FormItem>
160-
)}
161-
/>
162216
<Button
163217
className="w-full"
164218
loading={isPending}
@@ -168,6 +222,7 @@ export default function Page() {
168222
</form>
169223
</Form>
170224
</div>
225+
<TurnstileComponent onSuccess={setTurnstileToken} />
171226
</div>
172227
<div className="text-base-10 flex flex-col gap-2 text-sm">
173228
<Link

0 commit comments

Comments
 (0)