feat: add email/password signup

This commit is contained in:
sam 2024-02-17 03:31:39 +01:00
parent 1cfe28cd59
commit dfea953381
No known key found for this signature in database
GPG key ID: B4EF20DDE721CAA1
9 changed files with 232 additions and 6 deletions

View file

@ -1,7 +1,6 @@
package auth package auth
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
@ -73,7 +72,6 @@ func (s *Server) postEmailSignupConfirm(w http.ResponseWriter, r *http.Request)
} }
var email string var email string
fmt.Println(emailSignupTicketKey(req.Ticket))
err = s.DB.Redis.Do(ctx, radix.Cmd(&email, "GET", emailSignupTicketKey(req.Ticket))) err = s.DB.Redis.Do(ctx, radix.Cmd(&email, "GET", emailSignupTicketKey(req.Ticket)))
if err != nil { if err != nil {
return errors.Wrap(err, "getting email signup key") return errors.Wrap(err, "getting email signup key")

View file

@ -15,6 +15,7 @@ func (s *Server) SendEmail(to, title, template string, data map[string]any) {
e := email.NewEmail() e := email.NewEmail()
e.From = s.emailAddress e.From = s.emailAddress
e.To = []string{to} e.To = []string{to}
e.Subject = title
text, html, err := s.Template(template, data) text, html, err := s.Template(template, data)
if err != nil { if err != nil {

View file

@ -11,6 +11,6 @@
<body> <body>
<p>Please continue creating a new pronouns.cc account by using the following link:</p> <p>Please continue creating a new pronouns.cc account by using the following link:</p>
<p><a href="{{.BaseURL}}/auth/email/signup/{{.Ticket}}">Confirm your email address</a></p> <p><a href="{{.BaseURL}}/auth/signup/confirm/{{.Ticket}}">Confirm your email address</a></p>
</body> </body>
</html> </html>

View file

@ -1,4 +1,4 @@
Please continue creating a new pronouns.cc account by using the following link: Please continue creating a new pronouns.cc account by using the following link:
{{.BaseURL}}/auth/email/signup/{{.Ticket}} {{.BaseURL}}/auth/signup/confirm/{{.Ticket}}
If you didn't mean to create a new account, feel free to ignore this email. If you didn't mean to create a new account, feel free to ignore this email.

View file

@ -81,10 +81,10 @@
<form method="POST" use:enhance> <form method="POST" use:enhance>
<FormGroup class="m-1" floating label="Email"> <FormGroup class="m-1" floating label="Email">
<Input name="email" type="email" /> <Input name="email" type="email" required />
</FormGroup> </FormGroup>
<FormGroup class="m-1" floating label="Password"> <FormGroup class="m-1" floating label="Password">
<Input name="password" type="password" /> <Input name="password" type="password" required />
</FormGroup> </FormGroup>
<p class="m-1"> <p class="m-1">
<Button color="primary" type="submit">Log in</Button> <Button color="primary" type="submit">Log in</Button>

View file

@ -0,0 +1,57 @@
import { PUBLIC_BASE_URL } from "$env/static/public";
import { ErrorCode, type APIError } from "$lib/api/entities";
import { apiFetch, fastFetch } from "$lib/api/fetch";
import type { UrlsResponse } from "$lib/api/responses";
import { error, redirect } from "@sveltejs/kit";
export const load = async ({ parent }) => {
const data = await parent();
if (!data.email_signup) {
redirect(303, "/auth/login");
}
const resp = await apiFetch<UrlsResponse>("/auth/urls", {
method: "POST",
body: {
callback_domain: PUBLIC_BASE_URL,
},
});
return resp;
};
export const actions = {
signup: async ({ request }) => {
const data = await request.formData();
const email = data.get("email");
try {
await fastFetch("/auth/email/signup", {
method: "POST",
version: 2,
body: { email },
});
return { status: "ok", email };
} catch (e) {
return { status: "error", error: e as APIError };
}
},
fedi: async ({ request }) => {
const data = await request.formData();
const instance = data.get("instance");
if (!instance) error(400, { code: ErrorCode.BadRequest, message: "Empty instance domain" });
let resp: { url: string };
try {
resp = await apiFetch<{ url: string }>(
`/auth/urls/fediverse?instance=${encodeURIComponent(instance as string)}`,
{},
);
} catch (e) {
return { status: "error", error: e as APIError };
}
redirect(303, resp.url);
},
};

View file

@ -0,0 +1,83 @@
<script lang="ts">
import { enhance } from "$app/forms";
import { Alert, Button, FormGroup, Input, InputGroup } from "@sveltestrap/sveltestrap";
import type { ActionData, PageData } from "./$types";
import { onMount } from "svelte";
import { userStore } from "$lib/store";
import { addToast } from "$lib/toast";
import { goto } from "$app/navigation";
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
export let data: PageData;
export let form: ActionData;
$: ok = form?.status === "ok";
onMount(() => {
if ($userStore) {
addToast({ header: "Error", body: "You are already logged in." });
goto("/");
}
});
</script>
<svelte:head>
<title>Sign up - pronouns.ccc</title>
</svelte:head>
<h1>Sign up</h1>
<p>
Enter your email address to get started. After confirming it, you can choose a username and
password.
</p>
<form method="POST" use:enhance action="?/signup">
{#if form?.error}
<ErrorAlert error={form.error} />
{/if}
{#if ok}
<Alert class="mx-1">
<h4 class="alert-heading">Check your email</h4>
Check your email to continue signing up!
</Alert>
{/if}
<FormGroup class="m-1" floating label="Email">
{#if !ok}
<Input name="email" type="email" required />
{:else}
<Input name="email" type="email" disabled value={form?.email || undefined} />
{/if}
</FormGroup>
<p class="mt-3 mx-1">
<Button color="primary" type="submit" disabled={ok}>Sign up</Button>
</p>
</form>
<hr />
<p>Or, if you would rather sign up using another provider:</p>
<div class="d-flex flex-column flex-lg-row">
<div class="d-flex flex-row my-1">
{#if data.discord}
<Button class="mx-1" href={data.discord}>Log in with Discord</Button>
{/if}
{#if data.tumblr}
<Button class="mx-1" href={data.tumblr}>Log in with Tumblr</Button>
{/if}
{#if data.google}
<Button class="mx-1" href={data.google}>Log in with Google</Button>
{/if}
</div>
<div class="m-1">
<form method="POST" use:enhance action="?/fedi">
<InputGroup>
<Input style="max-width: 200px" name="instance" placeholder="Server" required />
<Button type="submit">Log in with the fediverse</Button>
</InputGroup>
</form>
</div>
</div>

View file

@ -0,0 +1,39 @@
import { ErrorCode, type APIError, type MeUser } from "$lib/api/entities";
import { apiFetch } from "$lib/api/fetch";
export const load = async ({ params }) => {
return { ticket: params.ticket };
};
interface SignupConfirmData {
user: MeUser;
token: string;
}
export const actions = {
default: async ({ request, params }) => {
const data = await request.formData();
const username = data.get("username");
const password = data.get("password");
const password2 = data.get("password2");
if (password !== password2) {
return {
status: "error",
error: { code: ErrorCode.BadRequest, message: "Passwords do not match" } as APIError,
};
}
try {
const resp = await apiFetch<SignupConfirmData>("/auth/email/signup/confirm", {
method: "POST",
version: 2,
body: { username, password, ticket: params.ticket },
});
return { status: "ok", data: resp };
} catch (e) {
return { status: "error", error: e as APIError };
}
},
};

View file

@ -0,0 +1,48 @@
<script lang="ts">
import { enhance } from "$app/forms";
import { Button, FormGroup, Input } from "@sveltestrap/sveltestrap";
import type { ActionData, PageData } from "./$types";
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
import { userStore } from "$lib/store";
import { addToast } from "$lib/toast";
import { goto } from "$app/navigation";
export let data: PageData;
export let form: ActionData;
$: signupCallback(form);
const signupCallback = (form: ActionData) => {
if (!form?.data?.user) return;
localStorage.setItem("pronouns-token", form.data.token);
localStorage.setItem("pronouns-user", JSON.stringify(form.data.user));
userStore.set(form.data.user);
addToast({ header: "Signed up", body: "Successfully created account!" });
goto(`/@${form.data.user.name}`);
};
</script>
<svelte:head>
<title>Sign up - pronouns.cc</title>
</svelte:head>
<h1>Sign up</h1>
<form method="POST" use:enhance>
{#if form?.error}
<ErrorAlert error={form.error} />
{/if}
<FormGroup class="m-1" floating label="Username">
<Input name="username" type="text" required />
</FormGroup>
<FormGroup class="m-1" floating label="Password">
<Input name="password" type="password" required />
</FormGroup>
<FormGroup class="m-1" floating label="Password (again)">
<Input name="password2" type="password" required />
</FormGroup>
<p class="m-1">
<Button color="primary" type="submit">Sign up</Button>
</p>
</form>