feat(frontend): validate username and member name client-side too

This commit is contained in:
Sam 2023-03-27 01:23:04 +02:00
parent 6532393578
commit 5be0b168c5
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
3 changed files with 37 additions and 13 deletions

View file

@ -75,7 +75,7 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error
if !db.MemberNameValid(cmr.Name) { if !db.MemberNameValid(cmr.Name) {
return server.APIError{ return server.APIError{
Code: server.ErrBadRequest, Code: server.ErrBadRequest,
Details: "Member name cannot contain any of the following: @, \\, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), +, <, =, >, ^, |, ~, `, ,", Details: "Member name cannot contain any of the following: @, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), +, <, =, >, ^, |, ~, `, ,",
} }
} }

View file

@ -58,10 +58,13 @@
memberPage = memberPage + 1; memberPage = memberPage + 1;
}; };
const memberNameRegex = /^[^@\\?!#\/\\\\[\]\"'$%&()+<=>^|~`,]{1,100}$/;
let modalOpen = false; let modalOpen = false;
let toggleModal = () => (modalOpen = !modalOpen); let toggleModal = () => (modalOpen = !modalOpen);
let newMemberName = ""; let newMemberName = "";
let newMemberError: APIError | null = null; let newMemberError: APIError | null = null;
let memberNameValid = true;
$: memberNameValid = memberNameRegex.test(newMemberName);
const createMember = async () => { const createMember = async () => {
try { try {
@ -210,10 +213,8 @@
<p> <p>
You don't have any members yet. You don't have any members yet.
<br /> <br />
Members are sub-profiles that can have their own avatar, Members are sub-profiles that can have their own avatar, names, pronouns, and preferred terms.
names, pronouns, and preferred terms. <span class="text-muted" <span class="text-muted">(only you can see this)</span>
>(only you can see this)</span
>
</p> </p>
</div> </div>
{/if} {/if}
@ -221,12 +222,20 @@
<Modal header="Create member" isOpen={modalOpen} toggle={toggleModal}> <Modal header="Create member" isOpen={modalOpen} toggle={toggleModal}>
<ModalBody> <ModalBody>
<Input bind:value={newMemberName} /> <Input bind:value={newMemberName} />
<p class="text-muted my-2">
<Icon name="info-circle-fill" aria-label="Info" /> Your members must have distinct names. Member
names must be 100 characters long at most, and cannot contain the following characters: @ ?
! # / \ [ ] " ' $ % & ( ) + &lt; = &gt; ^ | ~ ` and ,
</p>
{#if newMemberError} {#if newMemberError}
<ErrorAlert error={newMemberError} /> <ErrorAlert error={newMemberError} />
{/if} {/if}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="primary" on:click={createMember} disabled={newMemberName.length === 0} {#if !memberNameValid && newMemberName.length > 0}
<span class="text-danger-emphasis mb-2">That member name is not valid.</span>
{/if}
<Button color="primary" on:click={createMember} disabled={!memberNameValid}
>Create member</Button >Create member</Button
> >
<Button color="secondary" on:click={toggleModal}>Cancel</Button> <Button color="secondary" on:click={toggleModal}>Cancel</Button>

View file

@ -43,7 +43,11 @@
let deleteCancelled: boolean; let deleteCancelled: boolean;
let deleteError: APIError | null; let deleteError: APIError | null;
const usernameRegex = /^[\w-.]{2,40}$/;
let username: string; let username: string;
let usernameValid = true;
$: usernameValid = usernameRegex.test(username);
let inviteCode: string; let inviteCode: string;
let forceDeleteName = ""; let forceDeleteName = "";
let forceDeleteModalOpen = false; let forceDeleteModalOpen = false;
@ -88,10 +92,10 @@
</script> </script>
<svelte:head> <svelte:head>
<title>Log in with the {authType} - pronouns.cc</title> <title>Log in with {authType} - pronouns.cc</title>
</svelte:head> </svelte:head>
<h1>Log in with the {authType}</h1> <h1>Log in with {authType}</h1>
{#if error} {#if error}
<ErrorAlert {error} /> <ErrorAlert {error} />
@ -122,23 +126,34 @@
<FormGroup floating label="Username"> <FormGroup floating label="Username">
<Input id="username" name="username" bind:value={username} /> <Input id="username" name="username" bind:value={username} />
</FormGroup> </FormGroup>
<div id="username-help" class="text-muted">
<Icon name="info-circle-fill" aria-label="Info" /> Your username must be unique, be at most 40
characters long, and only contain letters from the basic English alphabet, dashes, underscores,
and periods. Your username is used as part of your profile link, you can set a separate display
name.
</div>
</div> </div>
{#if requireInvite} {#if requireInvite}
<div> <div>
<FormGroup floating label="Invite code"> <FormGroup floating label="Invite code">
<Input id="invite" name="invite" aria-describedby="invite-help" bind:value={inviteCode} /> <Input id="invite" name="invite" aria-describedby="invite-help" bind:value={inviteCode} />
</FormGroup> </FormGroup>
<div id="invite-help" class="form-text"> <div id="invite-help" class="text-muted">
<Icon name="info-circle-fill" /> You currently need an invite code to sign up. You can get <Icon name="info-circle-fill" aria-label="Info" /> You currently need an invite code to sign
one from an existing user. up. You can get one from an existing user.
</div> </div>
</div> </div>
{/if} {/if}
<div class="form-text mb-1"> <div class="text-muted my-1">
By signing up, you agree to the <a href="/page/terms">terms of service</a> and the By signing up, you agree to the <a href="/page/terms">terms of service</a> and the
<a href="/page/privacy">privacy policy</a>. <a href="/page/privacy">privacy policy</a>.
</div> </div>
<Button type="submit" color="primary">Sign up</Button> <p>
<Button type="submit" color="primary" disabled={!usernameValid}>Sign up</Button>
{#if !usernameValid && username.length > 0}
<span class="text-danger-emphasis mb-2">That username is not valid.</span>
{/if}
</p>
</form> </form>
{:else if isDeleted && token && selfDelete && deletedAt} {:else if isDeleted && token && selfDelete && deletedAt}
<p> <p>