archetecture security fix
This commit is contained in:
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export { PrismaClient } from '../generated'
|
||||
export declare function ensureDatabaseUrl(env?: NodeJS.ProcessEnv): string | undefined
|
||||
@@ -0,0 +1,9 @@
|
||||
const { ensureDatabaseUrl } = require('./runtime-config')
|
||||
const { PrismaClient } = require('../generated')
|
||||
|
||||
ensureDatabaseUrl()
|
||||
|
||||
module.exports = {
|
||||
PrismaClient,
|
||||
ensureDatabaseUrl,
|
||||
}
|
||||
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
export declare function ensureDatabaseUrl(env?: NodeJS.ProcessEnv): string | undefined
|
||||
|
||||
export type AdminRole = 'SUPER_ADMIN' | 'ADMIN' | 'SUPPORT' | 'FINANCE' | 'VIEWER'
|
||||
export type EmployeeRole = 'OWNER' | 'MANAGER' | 'AGENT'
|
||||
export type LicenseStatus = 'PENDING' | 'VALID' | 'EXPIRING' | 'APPROVED' | 'DENIED' | 'EXPIRED'
|
||||
export type NotificationType =
|
||||
| 'ACCOUNT_CREATED'
|
||||
| 'NEW_BOOKING'
|
||||
| 'BOOKING_CANCELLED'
|
||||
| 'PAYMENT_RECEIVED'
|
||||
| 'PAYMENT_FAILED'
|
||||
| 'SUBSCRIPTION_TRIAL_ENDING'
|
||||
| 'SUBSCRIPTION_SUSPENDED'
|
||||
| 'VEHICLE_MAINTENANCE_DUE'
|
||||
| 'OFFER_EXPIRING'
|
||||
| 'NEW_REVIEW_RECEIVED'
|
||||
| 'BOOKING_CONFIRMED'
|
||||
| 'PICKUP_REMINDER_24H'
|
||||
| 'PICKUP_REMINDER_2H'
|
||||
| 'VEHICLE_READY'
|
||||
| 'RETURN_REMINDER'
|
||||
| 'BOOKING_CANCELLED_BY_COMPANY'
|
||||
| 'REFUND_PROCESSED'
|
||||
| 'NEW_OFFER_FROM_SAVED_COMPANY'
|
||||
| 'REVIEW_REQUEST'
|
||||
|
||||
export type NotificationChannel = 'EMAIL' | 'SMS' | 'WHATSAPP' | 'IN_APP' | 'PUSH'
|
||||
|
||||
export interface Company {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
email: string
|
||||
phone: string | null
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface Employee {
|
||||
id: string
|
||||
companyId: string
|
||||
clerkUserId: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
phone: string | null
|
||||
role: EmployeeRole
|
||||
isActive: boolean
|
||||
company?: Company
|
||||
}
|
||||
|
||||
export interface AdminUser {
|
||||
id: string
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
role: AdminRole
|
||||
isActive: boolean
|
||||
totpEnabled?: boolean
|
||||
totpSecret?: string | null
|
||||
}
|
||||
|
||||
export interface InsurancePolicy {
|
||||
id: string
|
||||
companyId: string
|
||||
name: string
|
||||
description?: string | null
|
||||
type: string
|
||||
chargeType: 'PER_DAY' | 'PER_RENTAL' | 'PERCENTAGE_OF_RENTAL'
|
||||
chargeValue: number
|
||||
isRequired: boolean
|
||||
isActive: boolean
|
||||
sortOrder: number
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const { ensureDatabaseUrl } = require('./runtime-config')
|
||||
|
||||
module.exports = {
|
||||
ensureDatabaseUrl,
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
export { ensureDatabaseUrl } from './runtime-config'
|
||||
|
||||
export type AdminRole = 'SUPER_ADMIN' | 'ADMIN' | 'SUPPORT' | 'FINANCE' | 'VIEWER'
|
||||
export type EmployeeRole = 'OWNER' | 'MANAGER' | 'AGENT'
|
||||
export type LicenseStatus = 'PENDING' | 'VALID' | 'EXPIRING' | 'APPROVED' | 'DENIED' | 'EXPIRED'
|
||||
export type NotificationType =
|
||||
| 'ACCOUNT_CREATED'
|
||||
| 'NEW_BOOKING'
|
||||
| 'BOOKING_CANCELLED'
|
||||
| 'PAYMENT_RECEIVED'
|
||||
| 'PAYMENT_FAILED'
|
||||
| 'SUBSCRIPTION_TRIAL_ENDING'
|
||||
| 'SUBSCRIPTION_SUSPENDED'
|
||||
| 'VEHICLE_MAINTENANCE_DUE'
|
||||
| 'OFFER_EXPIRING'
|
||||
| 'NEW_REVIEW_RECEIVED'
|
||||
| 'BOOKING_CONFIRMED'
|
||||
| 'PICKUP_REMINDER_24H'
|
||||
| 'PICKUP_REMINDER_2H'
|
||||
| 'VEHICLE_READY'
|
||||
| 'RETURN_REMINDER'
|
||||
| 'BOOKING_CANCELLED_BY_COMPANY'
|
||||
| 'REFUND_PROCESSED'
|
||||
| 'NEW_OFFER_FROM_SAVED_COMPANY'
|
||||
| 'REVIEW_REQUEST'
|
||||
|
||||
export type NotificationChannel = 'EMAIL' | 'SMS' | 'WHATSAPP' | 'IN_APP' | 'PUSH'
|
||||
|
||||
export interface Company {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
email: string
|
||||
phone: string | null
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface Employee {
|
||||
id: string
|
||||
companyId: string
|
||||
clerkUserId: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
phone: string | null
|
||||
role: EmployeeRole
|
||||
isActive: boolean
|
||||
company?: Company
|
||||
}
|
||||
|
||||
export interface AdminUser {
|
||||
id: string
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
role: AdminRole
|
||||
isActive: boolean
|
||||
totpEnabled?: boolean
|
||||
totpSecret?: string | null
|
||||
}
|
||||
|
||||
export interface InsurancePolicy {
|
||||
id: string
|
||||
companyId: string
|
||||
name: string
|
||||
description?: string | null
|
||||
type: string
|
||||
chargeType: 'PER_DAY' | 'PER_RENTAL' | 'PERCENTAGE_OF_RENTAL'
|
||||
chargeValue: number
|
||||
isRequired: boolean
|
||||
isActive: boolean
|
||||
sortOrder: number
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const ENABLED_VALUES = new Set(['1', 'true', 'yes'])
|
||||
|
||||
function parseEnvFile(content) {
|
||||
const env = {}
|
||||
|
||||
for (const rawLine of content.split(/\r?\n/)) {
|
||||
const line = rawLine.trim()
|
||||
if (!line || line.startsWith('#')) continue
|
||||
|
||||
const normalized = line.startsWith('export ') ? line.slice(7).trim() : line
|
||||
const separatorIndex = normalized.indexOf('=')
|
||||
if (separatorIndex === -1) continue
|
||||
|
||||
const key = normalized.slice(0, separatorIndex).trim()
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
|
||||
|
||||
let value = normalized.slice(separatorIndex + 1)
|
||||
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
const quote = value[0]
|
||||
value = value.slice(1, -1)
|
||||
|
||||
if (quote === '"') {
|
||||
value = value
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\r/g, '\r')
|
||||
.replace(/\\t/g, '\t')
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\\/g, '\\')
|
||||
}
|
||||
}
|
||||
|
||||
env[key] = value
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
function loadEnvFile(filePath, env = process.env, { override = false } = {}) {
|
||||
if (!fs.existsSync(filePath)) return false
|
||||
const parsed = parseEnvFile(fs.readFileSync(filePath, 'utf8'))
|
||||
|
||||
for (const [key, value] of Object.entries(parsed)) {
|
||||
if (override || env[key] === undefined) {
|
||||
env[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function isRunningInContainer() {
|
||||
return fs.existsSync('/.dockerenv')
|
||||
}
|
||||
|
||||
function normalizeDatabaseUrlForExecution(env = process.env) {
|
||||
if (!env.DATABASE_URL) return env.DATABASE_URL
|
||||
|
||||
let parsed
|
||||
try {
|
||||
parsed = new URL(env.DATABASE_URL)
|
||||
} catch {
|
||||
return env.DATABASE_URL
|
||||
}
|
||||
|
||||
if (parsed.hostname !== 'postgres' || isRunningInContainer()) {
|
||||
return env.DATABASE_URL
|
||||
}
|
||||
|
||||
const fallbackHost = env.POSTGRES_HOST_LOCAL ?? 'localhost'
|
||||
parsed.hostname = fallbackHost
|
||||
env.DATABASE_URL = parsed.toString()
|
||||
return env.DATABASE_URL
|
||||
}
|
||||
|
||||
function loadDefaultEnvFiles(baseDir, env = process.env) {
|
||||
const candidates = [
|
||||
path.join(baseDir, '.env'),
|
||||
path.join(baseDir, '.env.local'),
|
||||
path.join(baseDir, '.env.docker.dev'),
|
||||
]
|
||||
|
||||
for (const candidate of candidates) {
|
||||
loadEnvFile(candidate, env)
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDatabaseUrl(env = process.env) {
|
||||
const shouldBuildFromPostgres = ENABLED_VALUES.has(
|
||||
env.DATABASE_URL_FROM_POSTGRES?.trim().toLowerCase() ?? '',
|
||||
)
|
||||
|
||||
if (!shouldBuildFromPostgres) {
|
||||
return env.DATABASE_URL
|
||||
}
|
||||
|
||||
if (!env.POSTGRES_PASSWORD) {
|
||||
throw new Error('DATABASE_URL_FROM_POSTGRES is enabled but POSTGRES_PASSWORD is not set')
|
||||
}
|
||||
|
||||
const user = env.POSTGRES_USER ?? 'postgres'
|
||||
const password = env.POSTGRES_PASSWORD
|
||||
const host = env.POSTGRES_HOST ?? 'postgres'
|
||||
const port = env.POSTGRES_PORT ?? '5432'
|
||||
const database = env.POSTGRES_DB ?? 'rentaldrivego'
|
||||
|
||||
env.DATABASE_URL = `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(database)}`
|
||||
|
||||
return env.DATABASE_URL
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ensureDatabaseUrl,
|
||||
loadDefaultEnvFiles,
|
||||
loadEnvFile,
|
||||
normalizeDatabaseUrlForExecution,
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
const ENABLED_VALUES = new Set(['1', 'true', 'yes'])
|
||||
|
||||
export function ensureDatabaseUrl(env: NodeJS.ProcessEnv = process.env) {
|
||||
const shouldBuildFromPostgres = ENABLED_VALUES.has(
|
||||
env.DATABASE_URL_FROM_POSTGRES?.trim().toLowerCase() ?? '',
|
||||
)
|
||||
|
||||
if (!shouldBuildFromPostgres) {
|
||||
return env.DATABASE_URL
|
||||
}
|
||||
|
||||
if (!env.POSTGRES_PASSWORD) {
|
||||
throw new Error('DATABASE_URL_FROM_POSTGRES is enabled but POSTGRES_PASSWORD is not set')
|
||||
}
|
||||
|
||||
const user = env.POSTGRES_USER ?? 'postgres'
|
||||
const password = env.POSTGRES_PASSWORD
|
||||
const host = env.POSTGRES_HOST ?? 'postgres'
|
||||
const port = env.POSTGRES_PORT ?? '5432'
|
||||
const database = env.POSTGRES_DB ?? 'rentaldrivego'
|
||||
|
||||
env.DATABASE_URL = `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(database)}`
|
||||
|
||||
return env.DATABASE_URL
|
||||
}
|
||||
Reference in New Issue
Block a user