diff --git a/apps/trust/src/pages/auth/VerifyMagicLinkPage.tsx b/apps/trust/src/pages/auth/VerifyMagicLinkPage.tsx index 09b94452d..c6faaaf59 100644 --- a/apps/trust/src/pages/auth/VerifyMagicLinkPage.tsx +++ b/apps/trust/src/pages/auth/VerifyMagicLinkPage.tsx @@ -46,9 +46,16 @@ export default function VerifyMagicLinkPagePageMutation() { return; } } + + const hasExpiredToken = errors.some( + (err) => err.message === "token has expired", + ); + toast({ title: __("Error"), - description: formatError(__("Failed to connect"), errors), + description: hasExpiredToken + ? __("This magic link has expired. Please request a new one.") + : formatError(__("Failed to connect"), errors), variant: "error", }); return; diff --git a/pkg/iam/auth_service.go b/pkg/iam/auth_service.go index 81c3002c0..da841833c 100644 --- a/pkg/iam/auth_service.go +++ b/pkg/iam/auth_service.go @@ -634,6 +634,11 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques func (s AuthService) GetMagicLinkEmail(ctx context.Context, tokenString string) (mail.Addr, error) { payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString) if err != nil { + var errExpired *statelesstoken.ErrExpiredToken + if errors.As(err, &errExpired) { + return mail.Nil, NewExpiredTokenError() + } + return mail.Nil, NewInvalidTokenError() } @@ -649,6 +654,11 @@ func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString s payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString) if err != nil { + var errExpired *statelesstoken.ErrExpiredToken + if errors.As(err, &errExpired) { + return nil, nil, nil, NewExpiredTokenError() + } + return nil, nil, nil, NewInvalidTokenError() } diff --git a/pkg/iam/errors.go b/pkg/iam/errors.go index 4f28f2fb0..a797e9eee 100644 --- a/pkg/iam/errors.go +++ b/pkg/iam/errors.go @@ -31,6 +31,16 @@ func (e ErrInvalidToken) Error() string { return e.message } +type ErrExpiredToken struct{ message string } + +func NewExpiredTokenError() error { + return &ErrExpiredToken{"token has expired"} +} + +func (e ErrExpiredToken) Error() string { + return e.message +} + type ErrInvitationAlreadyAccepted struct{ InvitationID gid.GID } func NewInvitationAlreadyAcceptedError(invitationID gid.GID) error { diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go index 3c7bb81b1..254c89448 100644 --- a/pkg/server/api/trust/v1/v1_resolver.go +++ b/pkg/server/api/trust/v1/v1_resolver.go @@ -225,6 +225,11 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri email, err := r.iam.AuthService.GetMagicLinkEmail(ctx, input.Token) if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + var errInvalidToken *iam.ErrInvalidToken if errors.As(err, &errInvalidToken) { return nil, gqlutils.Invalid(ctx, err) @@ -241,6 +246,11 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri var err error identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + var errInvalidToken *iam.ErrInvalidToken if errors.As(err, &errInvalidToken) { return nil, gqlutils.Invalid(ctx, err) @@ -258,6 +268,11 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri var err error identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + var errInvalidToken *iam.ErrInvalidToken if errors.As(err, &errInvalidToken) { return nil, gqlutils.Invalid(ctx, err)