User invite uses external domain option (#118)

* feat: user invite uses external domain option
fixes #117

* fix: inconsistent external url format

* fix: normalize external url more cleanly
This commit is contained in:
Husky
2025-06-30 19:11:26 -04:00
committed by GitHub
parent 12837d44fe
commit 73c27f0984
7 changed files with 32 additions and 26 deletions

View File

@ -2,4 +2,4 @@ DATABASE_URL="postgres://drop:drop@127.0.0.1:5432/drop"
GIANT_BOMB_API_KEY="" GIANT_BOMB_API_KEY=""
EXTERNAL_URL="localhost:3000" EXTERNAL_URL="http://localhost:3000"

View File

@ -38,6 +38,7 @@
"jdenticon": "^3.3.0", "jdenticon": "^3.3.0",
"luxon": "^3.6.1", "luxon": "^3.6.1",
"micromark": "^4.0.1", "micromark": "^4.0.1",
"normalize-url": "^8.0.2",
"nuxt": "^3.17.4", "nuxt": "^3.17.4",
"nuxt-security": "2.2.0", "nuxt-security": "2.2.0",
"prisma": "^6.7.0", "prisma": "^6.7.0",

View File

@ -36,20 +36,14 @@
</div> </div>
<ul role="list" class="divide-y divide-zinc-800"> <ul role="list" class="divide-y divide-zinc-800">
<li <li
v-for="(invitation, invitationIdx) in invitations" v-for="invitation in invitations"
:key="invitation.id" :key="invitation.id"
class="relative flex justify-between gap-x-6 py-5" class="relative flex justify-between gap-x-6 py-5"
> >
<div class="flex min-w-0 gap-x-4"> <div class="flex min-w-0 gap-x-4">
<div class="min-w-0 flex-auto"> <div class="min-w-0 flex-auto">
<div class="text-sm/6 font-semibold text-zinc-100"> <div class="text-sm/6 font-semibold text-zinc-100">
<p v-if="invitationUrls"> <p>{{ invitation.inviteUrl }}</p>
{{ invitationUrls[invitationIdx] }}
</p>
<div
v-else
class="h-4 w-full bg-zinc-800 animate-pulse rounded"
/>
</div> </div>
<p class="mt-1 flex text-xs/5 text-gray-500"> <p class="mt-1 flex text-xs/5 text-gray-500">
@ -415,20 +409,11 @@ useHead({
title: "Simple authentication", title: "Simple authentication",
}); });
const data = await $dropFetch<Array<SerializeObject<Invitation>>>( const data = await $dropFetch<
"/api/v1/admin/auth/invitation", Array<SerializeObject<Invitation & { inviteUrl: string }>>
); >("/api/v1/admin/auth/invitation");
const invitations = ref(data ?? []); const invitations = ref(data ?? []);
const generateInvitationUrl = (id: string) =>
`${window.location.protocol}//${window.location.host}/auth/register?id=${id}`;
const invitationUrls = ref<undefined | Array<string>>();
onMounted(() => {
invitationUrls.value = invitations.value.map((invitation) =>
generateInvitationUrl(invitation.id),
);
});
// Makes username undefined if it's empty // Makes username undefined if it's empty
const _username = ref<undefined | string>(undefined); const _username = ref<undefined | string>(undefined);
const username = computed({ const username = computed({
@ -515,7 +500,6 @@ function invite_wrapper() {
invite() invite()
.then((invitation) => { .then((invitation) => {
invitations.value.push(invitation); invitations.value.push(invitation);
invitationUrls.value?.push(generateInvitationUrl(invitation.id));
}) })
.catch((response) => { .catch((response) => {
const message = response.statusMessage || t("errors.unknown"); const message = response.statusMessage || t("errors.unknown");
@ -536,7 +520,6 @@ async function deleteInvitation(id: string) {
const index = invitations.value.findIndex((e) => e.id === id); const index = invitations.value.findIndex((e) => e.id === id);
invitations.value.splice(index, 1); invitations.value.splice(index, 1);
invitationUrls.value?.splice(index, 1);
} }
const createModalOpen = ref(false); const createModalOpen = ref(false);

View File

@ -1,4 +1,5 @@
import aclManager from "~/server/internal/acls"; import aclManager from "~/server/internal/acls";
import { systemConfig } from "~/server/internal/config/sys-conf";
import prisma from "~/server/internal/db/database"; import prisma from "~/server/internal/db/database";
import taskHandler from "~/server/internal/tasks"; import taskHandler from "~/server/internal/tasks";
@ -10,6 +11,13 @@ export default defineEventHandler(async (h3) => {
await taskHandler.runTaskGroupByName("cleanup:invitations"); await taskHandler.runTaskGroupByName("cleanup:invitations");
const externalUrl = systemConfig.getExternalUrl();
const invitations = await prisma.invitation.findMany({}); const invitations = await prisma.invitation.findMany({});
return invitations;
return invitations.map((invitation) => {
return {
...invitation,
inviteUrl: `${externalUrl}/auth/register?id=${invitation.id}`,
};
});
}); });

View File

@ -2,6 +2,7 @@ import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls"; import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database"; import prisma from "~/server/internal/db/database";
import { SharedRegisterValidator } from "../../../auth/signup/simple.post"; import { SharedRegisterValidator } from "../../../auth/signup/simple.post";
import { systemConfig } from "~/server/internal/config/sys-conf";
const CreateInvite = SharedRegisterValidator.partial() const CreateInvite = SharedRegisterValidator.partial()
.and({ .and({
@ -22,5 +23,8 @@ export default defineEventHandler(async (h3) => {
data: body, data: body,
}); });
return invitation; return {
...invitation,
inviteUrl: `${systemConfig.getExternalUrl()}/auth/register?id=${invitation.id}`,
};
}); });

View File

@ -1,8 +1,13 @@
import normalizeUrl from "normalize-url";
class SystemConfig { class SystemConfig {
private libraryFolder = process.env.LIBRARY ?? "./.data/library"; private libraryFolder = process.env.LIBRARY ?? "./.data/library";
private dataFolder = process.env.DATA ?? "./.data/data"; private dataFolder = process.env.DATA ?? "./.data/data";
private externalUrl = process.env.EXTERNAL_URL ?? "http://localhost:3000"; private externalUrl = normalizeUrl(
process.env.EXTERNAL_URL ?? "http://localhost:3000",
{ stripWWW: false },
);
private dropVersion; private dropVersion;
private gitRef; private gitRef;

View File

@ -6523,6 +6523,11 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
normalize-url@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.2.tgz#3b343a42f837e4dae2b01917c04e8de3782e9170"
integrity sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==
npm-run-path@^5.1.0: npm-run-path@^5.1.0:
version "5.3.0" version "5.3.0"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f"