Skip to content

Commit

Permalink
feat: add an admin form for sending emails.
Browse files Browse the repository at this point in the history
  • Loading branch information
zicklag committed Dec 12, 2024
1 parent a4134f3 commit 498b30b
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 1 deletion.
9 changes: 8 additions & 1 deletion .env.local
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ BACKEND_URL="http://127.0.0.1:7431"
BACKEND_SECRET="temporarydevelopmentkey"
INSTANCE_SUBSPACE_SECRET=gqvkucapoouch6xhhnn3pnqu7zpwpd3a4nctz2vkm2qrsnfbf6ha
RAUTHY_URL="http://localhost:8921"

# SMTP for rauthy
SMTP_HOST="localhost"
SMTP_PORT="2525"
SMTP_FROM="[email protected]"
SMTP_SECURE="false"
SMTP_USER="dummy"
SMTP_PASS="dummy"

# SMTP for Weird
SMTP_URL="smtp://dummy:dummy@localhost:2525/?pool=true"

# SMTP for Weird & Rauthy
SMTP_FROM="[email protected]"

DISCORD_CLIENT_ID=
DISCORD_TOKEN=

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"linktree-parser": "^1.5.0",
"marked": "^14.1.4",
"namedrop-js": "^0.5.0",
"nodemailer": "^6.9.16",
"oauth-pkce": "^0.0.7",
"postcss": "^8.4.49",
"prettier": "^3.4.1",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/lib/email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { env } from '$env/dynamic/private';
import nodemailer from 'nodemailer';

export const emailer = nodemailer.createTransport(
{
url: env.SMTP_URL
},
{ from: env.SMTP_FROM }
);
12 changes: 12 additions & 0 deletions src/lib/rauthy/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,15 @@ export async function getUserInfo(

return { userInfo };
}

export async function listRauthyUsers(
fetch: typeof window.fetch,
request: Request
): Promise<{ email: string; id: string; created_at: number; last_login: number }[]> {
const resp = await fetch(`/auth/v1/users`, {
headers: cleanHeaders(request)
});
await checkResponse(resp);

return resp.json();
}
1 change: 1 addition & 0 deletions src/routes/(internal)/__internal__/admin/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
<li><a href="/__internal__/admin/dns/resolve">DNS Resolver</a></li>
<li><a href="/__internal__/admin/usernames">Username Manager</a></li>
<li><a href="/__internal__/admin/billing">Billing Manager</a></li>
<li><a href="/__internal__/admin/send-emails">Send Emails</a></li>
<li><a href="/__internal__/admin/migration-508f757">Migration ( 508f757 )</a></li>
</ul>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { usernames } from '$lib/usernames/index';
import type { ServerLoad } from '@sveltejs/kit';
import type { Actions } from './$types';
import { marked } from 'marked';
import { emailer } from '$lib/email';
import { listRauthyUsers } from '$lib/rauthy/server';

export const load: ServerLoad = async ({}) => {};

export const actions = {
sendEmail: async ({ request, fetch }) => {
const formData = await request.formData();
const rawRecipient = formData.get('recipient')?.toString();
const subject = formData.get('subject')?.toString();
const bodyMarkdown = formData.get('bodyMarkdown')?.toString();
if (!(subject && bodyMarkdown && rawRecipient)) return { error: 'You must fill in all fields' };

const bodyHtml = await marked.parse(bodyMarkdown);

let recipients = [];
if (rawRecipient == '___everyone___') {
// TODO: should we send to all rauthy-registered users, not just ones that have registered a username?
//
// We need to double-check the consequences of this, though, because a user that deletes their
// profile probably doesn't want to be emailed by us, and if they delete their profile, their
// rauthy account still exists.
for (const user of await listRauthyUsers(fetch, request)) {
const username = await usernames.getByRauthyId(user.id);
if (username) {
recipients.push(user.email);
}
}
} else {
recipients.push(rawRecipient);
}

for (const recipient of recipients) {
await emailer.sendMail({
to: recipient,
subject,
text: bodyMarkdown,
html: bodyHtml
});
}

return { success: `Email sent!` };
}
} satisfies Actions;
28 changes: 28 additions & 0 deletions src/routes/(internal)/__internal__/admin/send-emails/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
import type { ActionData } from './$types';
const { form }: { form: ActionData } = $props();
</script>

{#if form?.error}
<article class="pico-background-red-550">
{form.error}
</article>
{:else if form?.success}
<article class="pico-background-green-550">
{form.success}
</article>
{/if}

<h2>Send Email From Weird</h2>

<p>
Set recipient to <code>___everyone___</code> to send an email to all users with a username set.
</p>

<form method="post" action="?/sendEmail">
<input name="recipient" placeholder="recipient" />
<input name="subject" placeholder="subject" />
<textarea name="bodyMarkdown" placeholder="Markdown body"></textarea>
<button>Send</button>
</form>

0 comments on commit 498b30b

Please sign in to comment.