Add error for already used token
Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
46
apps/trust/src/pages/auth/MagicLinkAlreadyUsedPage.tsx
Normal file
46
apps/trust/src/pages/auth/MagicLinkAlreadyUsedPage.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button } from "@probo/ui";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
export default function MagicLinkAlreadyUsedPage() {
|
||||
const { __ } = useTranslate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
usePageTitle(__("Link Already Used"));
|
||||
|
||||
return (
|
||||
<div className="space-y-6 w-full max-w-md mx-auto pt-8">
|
||||
<div className="space-y-2 text-center">
|
||||
<h1 className="text-3xl font-bold">{__("Link Already Used")}</h1>
|
||||
<p className="text-txt-tertiary">
|
||||
{__(
|
||||
"This magic link has already been used. Magic links can only be used once. Please request a new one if you need to sign in again.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
className="w-xs h-10"
|
||||
onClick={() => void navigate("/connect")}
|
||||
>
|
||||
{__("Request a new link")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -65,6 +65,11 @@ export default function VerifyMagicLinkPagePageMutation() {
|
||||
void navigate("/magic-link-expired");
|
||||
return;
|
||||
}
|
||||
|
||||
if (err.extensions?.code === "TOKEN_ALREADY_USED") {
|
||||
void navigate("/magic-link-already-used");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
toast({
|
||||
|
||||
@@ -57,6 +57,10 @@ const routes = [
|
||||
path: "/magic-link-expired",
|
||||
Component: lazy(() => import("#/pages/auth/MagicLinkExpiredPage")),
|
||||
},
|
||||
{
|
||||
path: "/magic-link-already-used",
|
||||
Component: lazy(() => import("#/pages/auth/MagicLinkAlreadyUsedPage")),
|
||||
},
|
||||
{
|
||||
path: "/full-name",
|
||||
Component: lazy(() => import("#/pages/auth/FullNamePage")),
|
||||
|
||||
@@ -680,7 +680,7 @@ func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString s
|
||||
|
||||
if err := token.LoadByHashedValueForUpdate(ctx, tx, hashedValue); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return NewInvalidTokenError()
|
||||
return NewTokenAlreadyUsedError()
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load token by hashed value: %w", err)
|
||||
|
||||
@@ -31,6 +31,16 @@ func (e ErrInvalidToken) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
type ErrTokenAlreadyUsed struct{ message string }
|
||||
|
||||
func NewTokenAlreadyUsedError() error {
|
||||
return &ErrTokenAlreadyUsed{"this magic link has already been used"}
|
||||
}
|
||||
|
||||
func (e ErrTokenAlreadyUsed) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
type ErrExpiredToken struct{ message string }
|
||||
|
||||
func NewExpiredTokenError() error {
|
||||
|
||||
@@ -83,6 +83,10 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri
|
||||
return nil, gqlutils.TokenExpired(ctx, err)
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*iam.ErrTokenAlreadyUsed](err); ok {
|
||||
return nil, gqlutils.TokenAlreadyUsed(ctx, err)
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
@@ -105,6 +109,10 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri
|
||||
return nil, gqlutils.TokenExpired(ctx, err)
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*iam.ErrTokenAlreadyUsed](err); ok {
|
||||
return nil, gqlutils.TokenAlreadyUsed(ctx, err)
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
|
||||
@@ -150,6 +150,14 @@ func TokenExpired(ctx context.Context, err error) *gqlerror.Error {
|
||||
}
|
||||
}
|
||||
|
||||
func TokenAlreadyUsed(ctx context.Context, err error) *gqlerror.Error {
|
||||
return &gqlerror.Error{
|
||||
Message: err.Error(),
|
||||
Path: graphql.GetPath(ctx),
|
||||
Extensions: map[string]any{"code": "TOKEN_ALREADY_USED"},
|
||||
}
|
||||
}
|
||||
|
||||
func Invalid(ctx context.Context, err error) *gqlerror.Error {
|
||||
var details map[string]any
|
||||
|
||||
|
||||
Reference in New Issue
Block a user