fix(org): route admin and tutor invites to app domain (#731)
Team member invite emails and link-invite copy URLs were built with the org tenant host (custom domain or siteName.myclassroomio.com). Admin and tutor invites should always point at the admin dashboard origin (app.classroomio.com in cloud). Student org invites are unchanged.
This commit is contained in:
@@ -28,7 +28,7 @@ import { ROLE } from '@cio/utils/constants';
|
||||
import type { TNewOrganizationInviteAudit } from '@db/types';
|
||||
import crypto from 'node:crypto';
|
||||
import { db, type DbOrTxClient } from '@cio/db/drizzle';
|
||||
import { getDashboardBaseUrl } from '@cio/core/config/dashboard-url';
|
||||
import { getAppBaseUrl } from '@cio/core/config/dashboard-url';
|
||||
import { parseCourseIdsFromInviteMetadata, parseCohortIdsFromInviteMetadata } from '@api/utils/org';
|
||||
import { getProfileById, markUserAndProfileEmailVerified } from '@cio/db/queries/auth/profile';
|
||||
import { enqueueTransactionalEmail } from '@api/services/jobs';
|
||||
@@ -61,11 +61,8 @@ function normalizeEmails(emails: string[]): string[] {
|
||||
return [...new Set(emails.map((email) => email.toLowerCase().trim()).filter(Boolean))];
|
||||
}
|
||||
|
||||
function buildInviteLink(
|
||||
token: string,
|
||||
org?: { siteName?: string | null; customDomain?: string | null; isCustomDomainVerified?: boolean | null }
|
||||
): string {
|
||||
return `${getDashboardBaseUrl(org)}/invite/${encodeURIComponent(token)}`;
|
||||
function buildTeamInviteLink(token: string): string {
|
||||
return `${getAppBaseUrl()}/invite/${encodeURIComponent(token)}`;
|
||||
}
|
||||
|
||||
function getRoleLabel(roleId: number): string {
|
||||
@@ -251,7 +248,7 @@ export async function inviteTeamMembers(orgId: string, emails: string[], roleId:
|
||||
}
|
||||
});
|
||||
|
||||
const inviteLink = buildInviteLink(token, organization);
|
||||
const inviteLink = buildTeamInviteLink(token);
|
||||
|
||||
try {
|
||||
await enqueueTransactionalEmail('inviteTeacher', {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import CopyIcon from '@lucide/svelte/icons/copy';
|
||||
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { isFreePlan, currentOrg } from '$lib/utils/store/org';
|
||||
import { getAppOrigin, isFreePlan, currentOrg } from '$lib/utils/store/org';
|
||||
import { orgApi } from '$features/org/api/org.svelte';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { snackbar } from '$features/ui/snackbar/store';
|
||||
@@ -25,7 +25,7 @@
|
||||
let isRemoving: number | null = $state(null);
|
||||
|
||||
function buildLinkInviteUrl(token: string): string {
|
||||
return `${window.location.origin}/invite/link/${encodeURIComponent(token)}`;
|
||||
return `${getAppOrigin()}/invite/link/${encodeURIComponent(token)}`;
|
||||
}
|
||||
|
||||
async function onCopyInviteLink() {
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { AccountOrg } from '$features/app/types';
|
||||
import type { OrgTeamMember } from '../types/org';
|
||||
import { canUsePublicApi, isOrgOnFreePlan, PLAN } from '@cio/utils/plans';
|
||||
import { PUBLIC_IS_SELFHOSTED } from '$env/static/public';
|
||||
import { ROLE, TENANT_ROOT_DOMAIN } from '@cio/utils/constants';
|
||||
import { BRAND_ROOT_DOMAIN, ROLE, TENANT_ROOT_DOMAIN } from '@cio/utils/constants';
|
||||
import { STEPS } from '../constants/quiz';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
@@ -79,6 +79,18 @@ export const currentOrgPath = derived(currentOrg, ($currentOrg) =>
|
||||
|
||||
type OrgPublicOrigin = Pick<AccountOrg, 'customDomain' | 'isCustomDomainVerified' | 'siteName'>;
|
||||
|
||||
/**
|
||||
* Admin dashboard origin (`app.classroomio.com` in cloud). Use for team invite links
|
||||
* and other admin-only URLs — not student-facing tenant pages.
|
||||
*/
|
||||
export function getAppOrigin(): string {
|
||||
if (PUBLIC_IS_SELFHOSTED === 'true' || dev) {
|
||||
return browser ? window.location.origin : '';
|
||||
}
|
||||
|
||||
return `https://app.${BRAND_ROOT_DOMAIN}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public origin for an org's tenant site (student LMS, public course pages, login-link handoff).
|
||||
* Self-hosted uses the current deployment URL. On cloud, verified custom domains win over the
|
||||
|
||||
@@ -46,3 +46,11 @@ export function getDashboardBaseUrl(org?: DashboardOrg): string {
|
||||
|
||||
return `https://app.${BRAND_ROOT_DOMAIN}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the admin dashboard origin (`app.classroomio.com` in cloud).
|
||||
* Use for team-member invite links (admin/tutor) — not student-facing URLs.
|
||||
*/
|
||||
export function getAppBaseUrl(): string {
|
||||
return getDashboardBaseUrl();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user