Merge pull request #2806 from jbelke/fix-invite-email-encoding

Fix invite email encoding
This commit is contained in:
Owen Schwartz
2026-04-11 16:37:49 -07:00
committed by GitHub
2 changed files with 26 additions and 14 deletions

View File

@@ -1,7 +1,14 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { orgs, roles, userInviteRoles, userInvites, userOrgs, users } from "@server/db"; import {
orgs,
roles,
userInviteRoles,
userInvites,
userOrgs,
users
} from "@server/db";
import { and, eq, inArray } from "drizzle-orm"; import { and, eq, inArray } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
@@ -37,8 +44,7 @@ const inviteUserBodySchema = z
regenerate: z.boolean().optional() regenerate: z.boolean().optional()
}) })
.refine( .refine(
(d) => (d) => (d.roleIds != null && d.roleIds.length > 0) || d.roleId != null,
(d.roleIds != null && d.roleIds.length > 0) || d.roleId != null,
{ message: "roleIds or roleId is required", path: ["roleIds"] } { message: "roleIds or roleId is required", path: ["roleIds"] }
) )
.transform((data) => ({ .transform((data) => ({
@@ -265,7 +271,7 @@ export async function inviteUser(
) )
); );
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`; const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${email}`;
if (doEmail) { if (doEmail) {
await sendEmail( await sendEmail(
@@ -314,12 +320,12 @@ export async function inviteUser(
expiresAt, expiresAt,
tokenHash tokenHash
}); });
await trx.insert(userInviteRoles).values( await trx
uniqueRoleIds.map((roleId) => ({ inviteId, roleId })) .insert(userInviteRoles)
); .values(uniqueRoleIds.map((roleId) => ({ inviteId, roleId })));
}); });
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`; const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${email}`;
if (doEmail) { if (doEmail) {
await sendEmail( await sendEmail(

View File

@@ -39,7 +39,11 @@ export default function InviteStatusCard({
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [type, setType] = useState< const [type, setType] = useState<
"rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in" | "user_limit_exceeded" | "rejected"
| "wrong_user"
| "user_does_not_exist"
| "not_logged_in"
| "user_limit_exceeded"
>("rejected"); >("rejected");
useEffect(() => { useEffect(() => {
@@ -90,12 +94,12 @@ export default function InviteStatusCard({
if (!user && type === "user_does_not_exist") { if (!user && type === "user_does_not_exist") {
const redirectUrl = email const redirectUrl = email
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}` ? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${email}`
: `/auth/signup?redirect=/invite?token=${tokenParam}`; : `/auth/signup?redirect=/invite?token=${tokenParam}`;
router.push(redirectUrl); router.push(redirectUrl);
} else if (!user && type === "not_logged_in") { } else if (!user && type === "not_logged_in") {
const redirectUrl = email const redirectUrl = email
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}` ? `/auth/login?redirect=/invite?token=${tokenParam}&email=${email}`
: `/auth/login?redirect=/invite?token=${tokenParam}`; : `/auth/login?redirect=/invite?token=${tokenParam}`;
router.push(redirectUrl); router.push(redirectUrl);
} else { } else {
@@ -109,7 +113,7 @@ export default function InviteStatusCard({
async function goToLogin() { async function goToLogin() {
await api.post("/auth/logout", {}); await api.post("/auth/logout", {});
const redirectUrl = email const redirectUrl = email
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}` ? `/auth/login?redirect=/invite?token=${tokenParam}&email=${email}`
: `/auth/login?redirect=/invite?token=${tokenParam}`; : `/auth/login?redirect=/invite?token=${tokenParam}`;
router.push(redirectUrl); router.push(redirectUrl);
} }
@@ -117,7 +121,7 @@ export default function InviteStatusCard({
async function goToSignup() { async function goToSignup() {
await api.post("/auth/logout", {}); await api.post("/auth/logout", {});
const redirectUrl = email const redirectUrl = email
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}` ? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${email}`
: `/auth/signup?redirect=/invite?token=${tokenParam}`; : `/auth/signup?redirect=/invite?token=${tokenParam}`;
router.push(redirectUrl); router.push(redirectUrl);
} }
@@ -157,7 +161,9 @@ export default function InviteStatusCard({
Cannot Accept Invite Cannot Accept Invite
</p> </p>
<p className="text-center text-sm"> <p className="text-center text-sm">
This organization has reached its user limit. Please contact the organization administrator to upgrade their plan before accepting this invite. This organization has reached its user limit. Please
contact the organization administrator to upgrade their
plan before accepting this invite.
</p> </p>
</div> </div>
); );