Show registration unavailable page when signup is disabled
Instead of showing the signup form and returning an internal error on submit, the SignUpPage now queries signUpEnabled upfront and displays a friendly message explaining that registration is not available, with a link back to login. Adds a signUpEnabled GraphQL query field on the connect/v1 API and handles ErrSignupDisabled as a FORBIDDEN error in the SignUp resolver. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -16,14 +16,22 @@ import { formatError } from "@probo/helpers";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Field, useToast } from "@probo/ui";
|
||||
import { useMutation } from "react-relay";
|
||||
import { useEffect } from "react";
|
||||
import { useMutation, usePreloadedQuery, useQueryLoader } from "react-relay";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { SignUpPageMutation } from "#/__generated__/iam/SignUpPageMutation.graphql";
|
||||
import type { SignUpPageQuery } from "#/__generated__/iam/SignUpPageQuery.graphql";
|
||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||
|
||||
const signUpPageQuery = graphql`
|
||||
query SignUpPageQuery {
|
||||
signUpEnabled
|
||||
}
|
||||
`;
|
||||
|
||||
const signUpMutation = graphql`
|
||||
mutation SignUpPageMutation($input: SignUpInput!) {
|
||||
signUp(input: $input) {
|
||||
@@ -42,13 +50,15 @@ const schema = z.object({
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export default function SignUpPage() {
|
||||
function SignUpPageContent(props: { queryRef: NonNullable<ReturnType<typeof useQueryLoader<SignUpPageQuery>>[0]> }) {
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
|
||||
usePageTitle(__("Sign up"));
|
||||
|
||||
const data = usePreloadedQuery<SignUpPageQuery>(signUpPageQuery, props.queryRef);
|
||||
|
||||
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
|
||||
defaultValues: {
|
||||
email: "",
|
||||
@@ -95,6 +105,29 @@ export default function SignUpPage() {
|
||||
});
|
||||
};
|
||||
|
||||
if (!data.signUpEnabled) {
|
||||
return (
|
||||
<div className="space-y-6 w-full max-w-md mx-auto pt-8 text-center">
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-3xl font-bold">{__("Registration unavailable")}</h1>
|
||||
<p className="text-txt-tertiary">
|
||||
{__("New account registration is currently disabled. Please contact your administrator or reach out to Probo for assistance.")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-xs h-10 mx-auto"
|
||||
to="/auth/login"
|
||||
>
|
||||
{__("Back to login")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6 w-full max-w-md mx-auto pt-8">
|
||||
<div className="space-y-2 text-center">
|
||||
@@ -154,3 +187,15 @@ export default function SignUpPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SignUpPage() {
|
||||
const [queryRef, loadQuery] = useQueryLoader<SignUpPageQuery>(signUpPageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({});
|
||||
}, [loadQuery]);
|
||||
|
||||
if (!queryRef) return null;
|
||||
|
||||
return <SignUpPageContent queryRef={queryRef} />;
|
||||
}
|
||||
|
||||
@@ -188,6 +188,10 @@ func NewService(
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func (s *Service) IsSignUpEnabled() bool {
|
||||
return !s.disableSignup
|
||||
}
|
||||
|
||||
func (s *Service) Run(ctx context.Context) error {
|
||||
wg := sync.WaitGroup{}
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
|
||||
@@ -55,6 +55,9 @@ type Query {
|
||||
oidcProviders: [OIDCProviderInfo!]!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: OPTIONAL)
|
||||
signUpEnabled: Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: NONE)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
|
||||
@@ -346,6 +346,11 @@ func (r *mutationResolver) SignUp(ctx context.Context, input types.SignUpInput)
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
|
||||
var errSignupDisabled *iam.ErrSignupDisabled
|
||||
if errors.As(err, &errSignupDisabled) {
|
||||
return nil, gqlutils.Forbidden(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot create identity with password", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
@@ -1777,6 +1782,11 @@ func (r *queryResolver) OidcProviders(ctx context.Context) ([]*types.OIDCProvide
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SignUpEnabled is the resolver for the signUpEnabled field.
|
||||
func (r *queryResolver) SignUpEnabled(ctx context.Context) (bool, error) {
|
||||
return r.iam.IsSignUpEnabled(), nil
|
||||
}
|
||||
|
||||
// TestLoginURL is the resolver for the testLoginUrl field.
|
||||
func (r *sAMLConfigurationResolver) TestLoginURL(ctx context.Context, obj *types.SAMLConfiguration) (string, error) {
|
||||
return r.baseURL.WithPath("/api/connect/v1/saml/2.0/" + obj.ID.String()).MustString(), nil
|
||||
|
||||
Reference in New Issue
Block a user