prevent email client from clicking on magic link/email verification button
Some checks failed
Runs E2E API Tests / build (20.x) (push) Has been cancelled
Runs E2E API Tests / build (22.x) (push) Has been cancelled
Lint & build / lint_and_build (20.x) (push) Has been cancelled
Lint & build / lint_and_build (22.x) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled

This commit is contained in:
Zai Shi 2024-11-03 13:44:00 -08:00
parent 7a5a66172e
commit ddbf902687
4 changed files with 2024 additions and 1957 deletions

View File

@ -1,16 +1,13 @@
'use client';
import React from "react";
import { StackClientApp, useStackApp } from "..";
import { StackClientApp, useStackApp, useUser } from "..";
import { MessageCard } from "../components/message-cards/message-card";
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
import { KnownErrors } from "@stackframe/stack-shared";
import { cacheFunction } from "@stackframe/stack-shared/dist/utils/caches";
import { useTranslation } from "../lib/translations";
const cacheVerifyEmail = cacheFunction(async (stackApp: StackClientApp<true>, code: string) => {
return await stackApp.verifyEmail(code);
});
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
export function EmailVerification(props: {
searchParams?: Record<string, string>,
@ -18,6 +15,8 @@ export function EmailVerification(props: {
}) {
const { t } = useTranslation();
const stackApp = useStackApp();
const user = useUser();
const [result, setResult] = React.useState<Awaited<ReturnType<typeof stackApp.verifyEmail>> | null>(null);
const invalidJsx = (
<MessageCard title={t("Invalid Verification Link")} fullPage={!!props.fullPage}>
@ -35,19 +34,40 @@ export function EmailVerification(props: {
return invalidJsx;
}
const result = React.use(cacheVerifyEmail(stackApp, props.searchParams.code));
if (result.status === 'error') {
if (result.error instanceof KnownErrors.VerificationCodeNotFound) {
return invalidJsx;
} else if (result.error instanceof KnownErrors.VerificationCodeExpired) {
return expiredJsx;
} else if (result.error instanceof KnownErrors.VerificationCodeAlreadyUsed) {
// everything fine, continue
} else {
throw result.error;
if (!result) {
return <MessageCard
title={t("Do you want to verify your email?")}
fullPage={!!props.fullPage}
primaryButtonText={t("Verify")}
primaryAction={async () => {
const result = await stackApp.verifyEmail(props.searchParams?.code || throwErr("No verification code provided"));
setResult(result);
}}
secondaryButtonText={t("Cancel")}
secondaryAction={async () => {
await stackApp.redirectToHome();
}}
/>;
} else {
if (result.status === 'error') {
if (result.error instanceof KnownErrors.VerificationCodeNotFound) {
return invalidJsx;
} else if (result.error instanceof KnownErrors.VerificationCodeExpired) {
return expiredJsx;
} else if (result.error instanceof KnownErrors.VerificationCodeAlreadyUsed) {
// everything fine, continue
} else {
throw result.error;
}
}
}
return <PredefinedMessageCard type='emailVerified' fullPage={!!props.fullPage} />;
return <MessageCard
title={t("You email has been verified!")}
fullPage={!!props.fullPage}
primaryButtonText={t("Go to home")}
primaryAction={async () => {
await stackApp.redirectToHome();
}}
/>;
}
}

View File

@ -8,6 +8,7 @@ import { KnownErrors } from "@stackframe/stack-shared";
import { neverResolve } from "@stackframe/stack-shared/dist/utils/promises";
import { cacheFunction } from "@stackframe/stack-shared/dist/utils/caches";
import { useTranslation } from "../lib/translations";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
const cacheSignInWithMagicLink = cacheFunction(async (stackApp: StackClientApp<true>, code: string) => {
return await stackApp.signInWithMagicLink(code);
@ -20,6 +21,7 @@ export function MagicLinkCallback(props: {
const { t } = useTranslation();
const stackApp = useStackApp();
const user = useUser();
const [result, setResult] = React.useState<Awaited<ReturnType<typeof stackApp.signInWithMagicLink>> | null>(null);
if (user) {
return <PredefinedMessageCard type='signedIn' fullPage={!!props.fullPage} />;
@ -47,18 +49,40 @@ export function MagicLinkCallback(props: {
return invalidJsx;
}
const result = React.use(cacheSignInWithMagicLink(stackApp, props.searchParams.code));
if (result.status === 'error') {
if (result.error instanceof KnownErrors.VerificationCodeNotFound) {
return invalidJsx;
} else if (result.error instanceof KnownErrors.VerificationCodeExpired) {
return expiredJsx;
} else if (result.error instanceof KnownErrors.VerificationCodeAlreadyUsed) {
return alreadyUsedJsx;
} else {
throw result.error;
if (!result) {
return <MessageCard
title={t("Do you want to sign in?")}
fullPage={!!props.fullPage}
primaryButtonText={t("Sign in")}
primaryAction={async () => {
const result = await stackApp.signInWithMagicLink(props.searchParams?.code || throwErr("No magic link provided"));
setResult(result);
}}
secondaryButtonText={t("Cancel")}
secondaryAction={async () => {
await stackApp.redirectToHome();
}}
/>;
} else {
if (result.status === 'error') {
if (result.error instanceof KnownErrors.VerificationCodeNotFound) {
return invalidJsx;
} else if (result.error instanceof KnownErrors.VerificationCodeExpired) {
return expiredJsx;
} else if (result.error instanceof KnownErrors.VerificationCodeAlreadyUsed) {
return alreadyUsedJsx;
} else {
throw result.error;
}
}
}
React.use(neverResolve());
return <MessageCard
title={t("Signed in successfully!")}
fullPage={!!props.fullPage}
primaryButtonText={t("Go to home")}
primaryAction={async () => {
await stackApp.redirectToHome();
}}
/>;
}
}

View File

@ -9,11 +9,10 @@ export function PredefinedMessageCard({
type,
fullPage=false,
}: {
type: 'signedIn' | 'signedOut' | 'emailSent' | 'passwordReset' | 'emailVerified' | 'unknownError' | 'signUpDisabled',
type: 'signedIn' | 'signedOut' | 'emailSent' | 'passwordReset' | 'unknownError' | 'signUpDisabled',
fullPage?: boolean,
}) {
const stackApp = useStackApp();
const user = useUser();
const { t } = useTranslation();
let title: string;
@ -60,18 +59,6 @@ export function PredefinedMessageCard({
primaryButton = t("Sign in");
break;
}
case 'emailVerified': {
title = t("Email verified!");
message = t("Your have successfully verified your email.");
if (!user) {
primaryAction = () => stackApp.redirectToSignIn({ noRedirectBack: true });
primaryButton = t("Sign in");
} else {
primaryAction = () => stackApp.redirectToHome();
primaryButton = t("Go to home");
}
break;
}
case 'unknownError': {
title = t("An unknown error occurred");
message = t("Please try again and if the problem persists, contact support.");

File diff suppressed because it is too large Load Diff