-
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathenv.ts
More file actions
168 lines (145 loc) · 4.65 KB
/
Copy pathenv.ts
File metadata and controls
168 lines (145 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/**
* Environment variable validation for security-critical configuration
*
* This module ensures that required environment variables are properly set
* and prevents the use of insecure default values in production.
*/
/**
* Get required environment variable or throw error
*/
function getRequiredEnv(name: string, defaultValue?: string): string {
const value = process.env[name];
// In production, require actual values (no defaults)
if (process.env.NODE_ENV === 'production') {
if (!value) {
throw new Error(
`Missing required environment variable: ${name}. ` +
`This must be set in production for security.`
);
}
// Check if using insecure default value
if (defaultValue && value === defaultValue) {
throw new Error(
`Environment variable ${name} is using the default development value. ` +
`This is insecure in production. Please set a unique value.`
);
}
return value;
}
// In development, allow defaults but warn if not set
if (!value && defaultValue) {
console.warn(
`⚠️ Environment variable ${name} not set. Using default value. ` +
`Set this in production for security.`
);
return defaultValue;
}
if (!value) {
throw new Error(
`Missing required environment variable: ${name}. ` +
`Please set this in your .env file.`
);
}
return value;
}
/**
* Validate and load all required environment variables
* Call this at server startup to fail fast if configuration is invalid
*/
export function validateEnvironment(): void {
// Validate all security-critical environment variables
getSessionSecret();
getResetSecret();
getTOTPEncryptionKey();
// Log deployment mode for security audit trail
const selfHosted = process.env.SELF_HOSTED;
const validTruthyValues = ['true', '1', 'yes', 'on'];
const validFalsyValues = ['false', '0', 'no', 'off', ''];
if (
selfHosted &&
!validTruthyValues.includes(selfHosted.toLowerCase()) &&
!validFalsyValues.includes(selfHosted.toLowerCase())
) {
console.warn(`⚠️ [WARNING] Invalid SELF_HOSTED value: "${selfHosted}"`);
console.warn(
' Valid values: true, 1, yes, on (for self-hosted) or false, 0, no, off (for SaaS)'
);
console.warn(
' Defaulting to SaaS mode (false). Set SELF_HOSTED=true explicitly for self-hosted mode.'
);
}
if (
selfHosted === 'true' ||
selfHosted === '1' ||
selfHosted?.toLowerCase() === 'yes' ||
selfHosted?.toLowerCase() === 'on'
) {
console.log(
'⚠️ [SECURITY] Running in SELF_HOSTED mode - all subscription and payment checks bypassed'
);
console.log(' This mode should ONLY be used on deployments you fully control.');
console.log(' Never enable SELF_HOSTED=true on multi-tenant SaaS deployments.');
} else {
console.log('✓ Running in SaaS mode - subscription checks enabled');
}
console.log('✓ Environment variables validated successfully');
}
/**
* Session secret for HMAC signing of session tokens
* MUST be set to a cryptographically secure random value in production
*/
export function getSessionSecret(): string {
return getRequiredEnv(
'SESSION_SECRET',
process.env.NODE_ENV === 'production' ? undefined : 'dev-secret-change-in-production'
);
}
/**
* Password reset secret for HMAC signing of reset tokens
* Falls back to SESSION_SECRET if not set, but separate values are recommended
*/
export function getResetSecret(): string {
const resetSecret = process.env.RESET_SECRET;
if (resetSecret) {
return resetSecret;
}
// Fall back to session secret
const sessionSecret = getSessionSecret();
if (process.env.NODE_ENV !== 'production') {
console.warn(
'⚠️ RESET_SECRET not set. Using SESSION_SECRET as fallback. ' +
'Consider setting a separate RESET_SECRET in production.'
);
}
return sessionSecret;
}
/**
* TOTP encryption key for encrypting authenticator secrets
* MUST be exactly 64 hexadecimal characters (32 bytes for AES-256)
* Generate with: openssl rand -hex 32
*/
export function getTOTPEncryptionKey(): string {
const key = process.env.TOTP_ENCRYPTION_KEY;
if (!key) {
throw new Error(
'TOTP_ENCRYPTION_KEY environment variable is not set. ' +
'Generate with: openssl rand -hex 32'
);
}
// Validate key is exactly 64 hex characters (32 bytes for AES-256)
if (!/^[0-9a-f]{64}$/i.test(key)) {
throw new Error(
'TOTP_ENCRYPTION_KEY must be exactly 64 hexadecimal characters (32 bytes). ' +
'Generate with: openssl rand -hex 32'
);
}
return key;
}
/**
* Generate secure random secret for environment variable
* Use this to generate values for .env file
*/
export function generateSecret(bytes: number = 32): string {
const crypto = require('crypto');
return crypto.randomBytes(bytes).toString('base64');
}