forked from mirrors/pronouns.cc
feat: add email/password signup
This commit is contained in:
parent
1cfe28cd59
commit
dfea953381
9 changed files with 232 additions and 6 deletions
|
@ -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")
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
57
frontend/src/routes/auth/signup/+page.server.ts
Normal file
57
frontend/src/routes/auth/signup/+page.server.ts
Normal 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);
|
||||||
|
},
|
||||||
|
};
|
83
frontend/src/routes/auth/signup/+page.svelte
Normal file
83
frontend/src/routes/auth/signup/+page.svelte
Normal 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>
|
|
@ -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 };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -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>
|
Loading…
Reference in a new issue