feat: add captcha when signing up (closes #53)

This commit is contained in:
Sam 2023-04-24 16:51:55 +02:00
parent bb3d56f548
commit 6f7eb5eeee
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
23 changed files with 316 additions and 61 deletions

View file

@ -4,6 +4,8 @@
import { fastFetch } from "$lib/api/fetch";
import { usernameRegex } from "$lib/api/regex";
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
import { PUBLIC_HCAPTCHA_SITEKEY } from "$env/static/public";
import HCaptcha from "svelte-hcaptcha";
import { userStore } from "$lib/store";
import { addToast } from "$lib/toast";
import { DateTime } from "luxon";
@ -23,6 +25,7 @@
export let remoteName: string | undefined;
export let error: APIError | undefined;
export let requireInvite: boolean | undefined;
export let requireCaptcha: boolean | undefined;
export let isDeleted: boolean | undefined;
export let ticket: string | undefined;
export let token: string | undefined;
@ -54,7 +57,31 @@
let toggleForceDeleteModal = () => (forceDeleteModalOpen = !forceDeleteModalOpen);
export let linkAccount: () => Promise<void>;
export let signupForm: (username: string, inviteCode: string) => Promise<void>;
export let signupForm: (
username: string,
inviteCode: string,
captchaToken: string,
) => Promise<void>;
let captchaToken = "";
let captcha: any;
const captchaSuccess = (token: any) => {
captchaToken = token.detail.token;
};
let canSubmit = false;
$: canSubmit = usernameValid && (!!captchaToken || !requireCaptcha);
const captchaError = () => {
addToast({
header: "Captcha failed",
body: "There was an error verifying the captcha, please try again.",
});
captcha.reset();
};
export const resetCaptcha = (): void => captcha.reset();
const forceDeleteAccount = async () => {
try {
@ -116,7 +143,7 @@
<Button color="secondary" href="/settings/auth">Cancel</Button>
</div>
{:else if ticket}
<form on:submit|preventDefault={() => signupForm(username, inviteCode)}>
<form on:submit|preventDefault={() => signupForm(username, inviteCode, captchaToken)}>
<div>
<FormGroup floating label="{authType} username">
<Input readonly value={remoteName} />
@ -144,12 +171,22 @@
</div>
</div>
{/if}
{#if requireCaptcha}
<div class="mt-2 mx-2 mb-1">
<HCaptcha
bind:this={captcha}
sitekey={PUBLIC_HCAPTCHA_SITEKEY}
on:success={captchaSuccess}
on:error={captchaError}
/>
</div>
{/if}
<div class="text-muted my-1">
By signing up, you agree to the <a href="/page/terms">terms of service</a> and the
<a href="/page/privacy">privacy policy</a>.
</div>
<p>
<Button type="submit" color="primary" disabled={!usernameValid}>Sign up</Button>
<Button type="submit" color="primary" disabled={!canSubmit}>Sign up</Button>
{#if !usernameValid && username.length > 0}
<span class="text-danger-emphasis mb-2">That username is not valid.</span>
{/if}

View file

@ -30,6 +30,7 @@ interface CallbackResponse {
discord?: string;
ticket?: string;
require_invite: boolean;
require_captcha: boolean;
is_deleted: boolean;
deleted_at?: string;

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from "$app/navigation";
import type { APIError, MeUser } from "$lib/api/entities";
import { ErrorCode, type APIError, type MeUser } from "$lib/api/entities";
import { apiFetch, apiFetchClient } from "$lib/api/fetch";
import { userStore } from "$lib/store";
import type { PageData } from "./$types";
@ -10,7 +10,9 @@
export let data: PageData;
const signupForm = async (username: string, invite: string) => {
let callbackPage: any;
const signupForm = async (username: string, invite: string, captchaToken: string) => {
try {
const resp = await apiFetch<SignupResponse>("/auth/discord/signup", {
method: "POST",
@ -18,6 +20,7 @@
ticket: data.ticket,
username: username,
invite_code: invite,
captcha_response: captchaToken,
},
});
@ -27,6 +30,10 @@
addToast({ header: "Welcome!", body: "Signed up successfully!" });
goto(`/@${resp.user.name}`);
} catch (e) {
if ((e as APIError).code === ErrorCode.InvalidCaptcha) {
callbackPage.resetCaptcha();
}
data.error = e as APIError;
}
};
@ -48,10 +55,12 @@
</script>
<CallbackPage
bind:this={callbackPage}
authType="Discord"
remoteName={data.discord}
error={data.error}
requireInvite={data.require_invite}
requireCaptcha={data.require_captcha}
isDeleted={data.is_deleted}
ticket={data.ticket}
token={data.token}

View file

@ -30,6 +30,7 @@ interface CallbackResponse {
google?: string;
ticket?: string;
require_invite: boolean;
require_captcha: boolean;
is_deleted: boolean;
deleted_at?: string;

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from "$app/navigation";
import type { APIError, MeUser } from "$lib/api/entities";
import { ErrorCode, type APIError, type MeUser } from "$lib/api/entities";
import { apiFetch, apiFetchClient } from "$lib/api/fetch";
import { userStore } from "$lib/store";
import type { PageData } from "./$types";
@ -10,7 +10,9 @@
export let data: PageData;
const signupForm = async (username: string, invite: string) => {
let callbackPage: any;
const signupForm = async (username: string, invite: string, captchaToken: string) => {
try {
const resp = await apiFetch<SignupResponse>("/auth/google/signup", {
method: "POST",
@ -18,6 +20,7 @@
ticket: data.ticket,
username: username,
invite_code: invite,
captcha_response: captchaToken,
},
});
@ -27,6 +30,10 @@
addToast({ header: "Welcome!", body: "Signed up successfully!" });
goto(`/@${resp.user.name}`);
} catch (e) {
if ((e as APIError).code === ErrorCode.InvalidCaptcha) {
callbackPage.resetCaptcha();
}
data.error = e as APIError;
}
};
@ -48,10 +55,12 @@
</script>
<CallbackPage
bind:this={callbackPage}
authType="Google"
remoteName={data.google}
error={data.error}
requireInvite={data.require_invite}
requireCaptcha={data.require_captcha}
isDeleted={data.is_deleted}
ticket={data.ticket}
token={data.token}

View file

@ -30,6 +30,7 @@ interface CallbackResponse {
fediverse?: string;
ticket?: string;
require_invite: boolean;
require_captcha: boolean;
is_deleted: boolean;
deleted_at?: string;

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from "$app/navigation";
import type { APIError, MeUser } from "$lib/api/entities";
import { ErrorCode, type APIError, type MeUser } from "$lib/api/entities";
import { apiFetch, apiFetchClient } from "$lib/api/fetch";
import { userStore } from "$lib/store";
import type { PageData } from "./$types";
@ -10,7 +10,9 @@
export let data: PageData;
const signupForm = async (username: string, invite: string) => {
let callbackPage: any;
const signupForm = async (username: string, invite: string, captchaToken: string) => {
try {
const resp = await apiFetch<SignupResponse>("/auth/mastodon/signup", {
method: "POST",
@ -19,6 +21,7 @@
ticket: data.ticket,
username: username,
invite_code: invite,
captcha_response: captchaToken,
},
});
@ -28,6 +31,10 @@
addToast({ header: "Welcome!", body: "Signed up successfully!" });
goto(`/@${resp.user.name}`);
} catch (e) {
if ((e as APIError).code === ErrorCode.InvalidCaptcha) {
callbackPage.resetCaptcha();
}
data.error = e as APIError;
}
};
@ -50,10 +57,12 @@
</script>
<CallbackPage
bind:this={callbackPage}
authType="Fediverse"
remoteName="{data.fediverse}@{data.instance}"
error={data.error}
requireInvite={data.require_invite}
requireCaptcha={data.require_captcha}
isDeleted={data.is_deleted}
ticket={data.ticket}
token={data.token}

View file

@ -29,6 +29,7 @@ interface CallbackResponse {
fediverse?: string;
ticket?: string;
require_invite: boolean;
require_captcha: boolean;
is_deleted: boolean;
deleted_at?: string;

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from "$app/navigation";
import type { APIError, MeUser } from "$lib/api/entities";
import { ErrorCode, type APIError, type MeUser } from "$lib/api/entities";
import { apiFetch, apiFetchClient } from "$lib/api/fetch";
import { userStore } from "$lib/store";
import type { PageData } from "./$types";
@ -10,7 +10,9 @@
export let data: PageData;
const signupForm = async (username: string, invite: string) => {
let callbackPage: any;
const signupForm = async (username: string, invite: string, captchaToken: string) => {
try {
const resp = await apiFetch<SignupResponse>("/auth/misskey/signup", {
method: "POST",
@ -19,6 +21,7 @@
ticket: data.ticket,
username: username,
invite_code: invite,
captcha_response: captchaToken,
},
});
@ -28,6 +31,10 @@
addToast({ header: "Welcome!", body: "Signed up successfully!" });
goto(`/@${resp.user.name}`);
} catch (e) {
if ((e as APIError).code === ErrorCode.InvalidCaptcha) {
callbackPage.resetCaptcha();
}
data.error = e as APIError;
}
};
@ -54,6 +61,7 @@
remoteName="{data.fediverse}@{data.instance}"
error={data.error}
requireInvite={data.require_invite}
requireCaptcha={data.require_captcha}
isDeleted={data.is_deleted}
ticket={data.ticket}
token={data.token}

View file

@ -30,6 +30,7 @@ interface CallbackResponse {
tumblr?: string;
ticket?: string;
require_invite: boolean;
require_captcha: boolean;
is_deleted: boolean;
deleted_at?: string;

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from "$app/navigation";
import type { APIError, MeUser } from "$lib/api/entities";
import { ErrorCode, type APIError, type MeUser } from "$lib/api/entities";
import { apiFetch, apiFetchClient } from "$lib/api/fetch";
import { userStore } from "$lib/store";
import type { PageData } from "./$types";
@ -10,7 +10,9 @@
export let data: PageData;
const signupForm = async (username: string, invite: string) => {
let callbackPage: any;
const signupForm = async (username: string, invite: string, captchaToken: string) => {
try {
const resp = await apiFetch<SignupResponse>("/auth/tumblr/signup", {
method: "POST",
@ -18,6 +20,7 @@
ticket: data.ticket,
username: username,
invite_code: invite,
captcha_response: captchaToken,
},
});
@ -27,6 +30,10 @@
addToast({ header: "Welcome!", body: "Signed up successfully!" });
goto(`/@${resp.user.name}`);
} catch (e) {
if ((e as APIError).code === ErrorCode.InvalidCaptcha) {
callbackPage.resetCaptcha();
}
data.error = e as APIError;
}
};
@ -48,10 +55,12 @@
</script>
<CallbackPage
bind:this={callbackPage}
authType="Tumblr"
remoteName={data.tumblr}
error={data.error}
requireInvite={data.require_invite}
requireCaptcha={data.require_captcha}
isDeleted={data.is_deleted}
ticket={data.ticket}
token={data.token}