mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
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
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:
parent
7a5a66172e
commit
ddbf902687
@ -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();
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user