mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-08-17 21:25:52 +02:00
Introduced MANAGEMENT_DOMAIN env var to fix hostname security issue
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user