fix(portal): add invitation links and correct customer login routing
This commit is contained in:
33
backend/src/utils/loginTenant.ts
Normal file
33
backend/src/utils/loginTenant.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { FastifyInstance } from "fastify"
|
||||
import { and, eq } from "drizzle-orm"
|
||||
import { authProfiles, authTenantUsers } from "../../db/schema"
|
||||
|
||||
export async function resolveLoginTenant(server: FastifyInstance, userId: string, domainTenantId: number | null) {
|
||||
const memberships = await server.db
|
||||
.selectDistinct({
|
||||
tenantId: authTenantUsers.tenant_id,
|
||||
portalCustomerId: authProfiles.customer_for_portal,
|
||||
})
|
||||
.from(authTenantUsers)
|
||||
.leftJoin(authProfiles, and(
|
||||
eq(authProfiles.user_id, authTenantUsers.user_id),
|
||||
eq(authProfiles.tenant_id, authTenantUsers.tenant_id),
|
||||
eq(authProfiles.active, true)
|
||||
))
|
||||
.where(eq(authTenantUsers.user_id, userId))
|
||||
|
||||
if (domainTenantId) {
|
||||
if (!memberships.some((membership) => membership.tenantId === domainTenantId)) {
|
||||
throw Object.assign(new Error("Not a member of this tenant"), { statusCode: 403 })
|
||||
}
|
||||
return domainTenantId
|
||||
}
|
||||
|
||||
// Portal invitations assign one customer account. Load its profile immediately,
|
||||
// even when the customer signs in through the shared FEDEO address.
|
||||
if (memberships.length === 1 && memberships[0].portalCustomerId) {
|
||||
return memberships[0].tenantId
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
29
backend/src/utils/portalUrl.ts
Normal file
29
backend/src/utils/portalUrl.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
type PortalUrlSettings = {
|
||||
portalDomain?: string | null
|
||||
publicUrl?: string
|
||||
apiBaseUrl?: string
|
||||
}
|
||||
|
||||
/** Use deployment configuration, never request headers, for links sent by email. */
|
||||
export function getPortalLoginUrl({ portalDomain, publicUrl, apiBaseUrl }: PortalUrlSettings) {
|
||||
const domain = portalDomain?.trim()
|
||||
const candidates = [
|
||||
{ value: domain ? (/^https?:\/\//i.test(domain) ? domain : `https://${domain}`) : undefined },
|
||||
{ value: publicUrl?.trim() },
|
||||
{ value: apiBaseUrl?.trim(), originOnly: true },
|
||||
]
|
||||
|
||||
for (const { value, originOnly } of candidates) {
|
||||
if (!value) continue
|
||||
try {
|
||||
const url = new URL(value)
|
||||
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) continue
|
||||
url.pathname = `${originOnly ? "" : url.pathname.replace(/\/+$/, "")}/login`
|
||||
url.search = ""
|
||||
url.hash = ""
|
||||
return url.toString()
|
||||
} catch { /* Try the next configured address. */ }
|
||||
}
|
||||
|
||||
throw new Error("Für die Portal-Einladung fehlt eine gültige öffentliche FEDEO-Adresse.")
|
||||
}
|
||||
Reference in New Issue
Block a user