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