Introduced MANAGEMENT_DOMAIN env var to fix hostname security issue

This commit is contained in:
Karl Ludwig Weise
2026-06-16 21:08:13 +02:00
parent df5432776a
commit 9b253bd04f
10 changed files with 33 additions and 2 deletions
+1
View File
@@ -21,5 +21,6 @@ NODE_ENV=development
APP_PORT=5173
JWT_SECRET=devsecretforjwtencryptionchangeforproduction
MANAGEMENT_DOMAIN=management.example.com
DATABASE_URL="postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:$POSTGRES_PORT/$POSTGRES_DB"
+3
View File
@@ -42,6 +42,7 @@ jobs:
SMTP_FROM_NAME: "Open Reception"
SMTP_FROM_EMAIL: "noreply@example.com"
JWT_SECRET: "test"
MANAGEMENT_DOMAIN: "example.com"
- name: Build application
run: npm run build
@@ -55,6 +56,7 @@ jobs:
SMTP_FROM_NAME: "Open Reception"
SMTP_FROM_EMAIL: "noreply@example.com"
JWT_SECRET: "test"
MANAGEMENT_DOMAIN: "example.com"
# - name: Install Playwright browsers
# run: npx playwright install --with-deps
@@ -70,3 +72,4 @@ jobs:
# SMTP_FROM_NAME: "Open Reception"
# SMTP_FROM_EMAIL: "noreply@example.com"
# JWT_SECRET: "test"
# MANAGEMENT_DOMAIN: "example.com"
+2
View File
@@ -70,6 +70,7 @@ jobs:
SMTP_FROM_NAME: "Open Reception"
SMTP_FROM_EMAIL: "noreply@example.com"
JWT_SECRET: "test"
MANAGEMENT_DOMAIN: "example.com"
build:
runs-on: ubuntu-latest
@@ -99,3 +100,4 @@ jobs:
SMTP_FROM_NAME: "Open Reception"
SMTP_FROM_EMAIL: "noreply@example.com"
JWT_SECRET: "test"
MANAGEMENT_DOMAIN: "example.com"
+2
View File
@@ -50,12 +50,14 @@ jobs:
SMTP_FROM_NAME: "Open Reception"
SMTP_FROM_EMAIL: "noreply@example.com"
JWT_SECRET: "test"
MANAGEMENT_DOMAIN: "example.com"
- name: Build application
run: npm run build
env:
DATABASE_URL: "postgresql://test:test@localhost:5432/test"
JWT_SECRET: "build"
MANAGEMENT_DOMAIN: "example.com"
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
+2
View File
@@ -61,6 +61,7 @@ services:
- smtp_from_name
- smtp_from_email
- jwt_secret
- management_domain
depends_on:
postgres:
condition: service_healthy
@@ -93,6 +94,7 @@ services:
export SMTP_FROM_NAME=$$(cat /run/secrets/smtp_from_name)
export SMTP_FROM_EMAIL=$$(cat /run/secrets/smtp_from_email)
export JWT_SECRET=$$(cat /run/secrets/jwt_secret)
export MANAGEMENT_DOMAIN=$$(cat /run/secrets/management_domain)
exec node build/index.js
"
read_only: true
@@ -3,6 +3,7 @@ import { describe, it, expect, vi } from "vitest";
vi.mock("$env/dynamic/private", () => ({
env: {
JWT_SECRET: "test-jwt-secret-for-registration-bootstrap-unit-tests-minimum-32-chars",
MANAGEMENT_DOMAIN: "example.com",
},
}));
@@ -14,6 +14,10 @@ export type BookingAccessScope =
| typeof EXISTING_CLIENT_BOOKING_SCOPE
| typeof NEW_CLIENT_BOOTSTRAP_SCOPE;
if (!process.env.BUILDING && !env.MANAGEMENT_DOMAIN) {
throw new Error("Mandatory ENV variable MANAGEMENT_DOMAIN is missing!");
}
if (!process.env.BUILDING && !env.JWT_SECRET) {
throw new Error("Mandatory ENV variable JWT_SECRET is missing!");
}
+16 -2
View File
@@ -13,6 +13,7 @@ import {
hashPassphrase,
validatePassphraseStrength,
} from "../utils/passphrase";
import { env } from "$env/dynamic/private";
import { sendConfirmationEmail } from "../email/email-service";
import type { SelectTenant } from "../db/central-schema";
import { TenantAdminService } from "./tenant-admin-service";
@@ -182,16 +183,23 @@ export class UserService {
hasRecoveryPassphrase: !!insertedUser.recoveryPassphrase,
});
if (!env.MANAGEMENT_DOMAIN) {
throw new InternalError(`MANAGEMENT_DOMAIN is missing`);
}
// Send confirmation email to user (token is used as confirmation code)
try {
if (insertedUser?.email && inviteResult?.inviteCode) {
const tenant = await getTenantForUser(insertedUser);
const linkUrl = dev
? requestUrl
: new URL(`https://${tenant.id === "system" ? env.MANAGEMENT_DOMAIN : tenant.domain}`);
await sendConfirmationEmail(
insertedUser,
tenant,
inviteResult.inviteCode,
10, // 10 minutes expiration to match tokenValidUntil
requestUrl,
linkUrl,
);
log.debug("Confirmation email sent successfully", {
userId: insertedUser.id,
@@ -239,10 +247,16 @@ export class UserService {
throw new NotFoundError(`Could not resend confirmation mail for unknown user ${email}`);
}
if (!env.MANAGEMENT_DOMAIN) {
throw new InternalError(`MANAGEMENT_DOMAIN is missing`);
}
// Send confirmation email with new token - use tenant-specific branding if available
try {
const tenant = await getTenantForUser(user);
const linkUrl = dev ? requestUrl : new URL(`https://${tenant.domain}`);
const linkUrl = dev
? requestUrl
: new URL(`https://${tenant.id === "system" ? env.MANAGEMENT_DOMAIN : tenant.domain}`);
await sendConfirmationEmail(
user,
tenant,
@@ -6,6 +6,7 @@ vi.mock("$env/dynamic/private", () => ({
env: {
JWT_SECRET: "test-jwt-secret-for-registration-bootstrap-unit-tests-minimum-32-chars",
NODE_ENV: "test",
MANAGEMENT_DOMAIN: "example.com",
},
}));
@@ -6,6 +6,7 @@ vi.mock("$env/dynamic/private", () => ({
env: {
JWT_SECRET: "test-jwt-secret-for-registration-bootstrap-unit-tests-minimum-32-chars",
NODE_ENV: "test",
MANAGEMENT_DOMAIN: "example.com",
},
}));