forked from InsurNiffy/niff-Stellar-shurance
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenv.validation.ts
More file actions
129 lines (128 loc) · 4.53 KB
/
Copy pathenv.validation.ts
File metadata and controls
129 lines (128 loc) · 4.53 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
import * as Joi from "joi";
export const validationSchema = Joi.object({
NODE_ENV: Joi.string()
.valid("development", "production", "test")
.default("development"),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string()
.required()
.description("PostgreSQL connection URL"),
REDIS_URL: Joi.string().required().description("Redis connection URL"),
SOROBAN_RPC_URL: Joi.string().required().description("Soroban RPC endpoint"),
STELLAR_NETWORK: Joi.string()
.default("testnet")
.description("Logical network id for indexer cursor isolation (e.g. testnet, public)"),
INDEXER_GAP_ALERT_THRESHOLD_LEDGERS: Joi.number()
.integer()
.min(1)
.default(100)
.description("Alert when chain head minus last_processed exceeds this"),
INDEXER_GAP_ALERT_COOLDOWN_MS: Joi.number()
.integer()
.min(60_000)
.default(3_600_000)
.description("Minimum milliseconds between gap alerts per network"),
// IPFS Configuration
IPFS_PROVIDER: Joi.string()
.valid("mock", "pinata")
.default("mock")
.description("IPFS provider to use"),
PINATA_API_KEY: Joi.string().allow("").description("Pinata API key"),
PINATA_API_SECRET: Joi.string().allow("").description("Pinata API secret"),
PINATA_GATEWAY_URL: Joi.string()
.default("https://gateway.pinata.cloud/ipfs")
.description("Pinata gateway URL"),
IPFS_MAX_FILE_SIZE: Joi.number()
.default(52428800)
.description("Maximum file size in bytes (default: 50MB)"),
IPFS_MIN_FILE_SIZE: Joi.number()
.default(1)
.description("Minimum file size in bytes"),
IPFS_STRIP_EXIF: Joi.boolean()
.default(true)
.description("Strip EXIF metadata from images"),
// Legacy IPFS config (kept for compatibility)
IPFS_GATEWAY: Joi.string().default("https://ipfs.io"),
IPFS_PROJECT_ID: Joi.string().allow(""),
IPFS_PROJECT_SECRET: Joi.string().allow(""),
// Auth
JWT_SECRET: Joi.string().min(32).required(),
ADMIN_TOKEN: Joi.string().required(),
// CORS
// CORS_ORIGINS is deprecated — use FRONTEND_ORIGINS instead
FRONTEND_ORIGINS: Joi.string()
.required()
.description("Comma-separated public frontend CORS origins")
.custom((value: string, helpers) => {
const nodeEnv =
(helpers.state.ancestors[0] as Record<string, string>)?.NODE_ENV ??
"development";
if (nodeEnv !== "production") {
// development / test: any non-empty string is accepted
return value;
}
// production: every entry must start with https:// and none may equal '*'
const entries = value
.split(",")
.map((s) => s.trim())
.filter(Boolean);
for (const entry of entries) {
if (entry === "*") {
return helpers.error("any.invalid", {
message: 'FRONTEND_ORIGINS must not contain "*" in production',
});
}
if (!entry.startsWith("https://")) {
return helpers.error("any.invalid", {
message: `FRONTEND_ORIGINS entry "${entry}" must start with https:// in production`,
});
}
}
return value;
}),
ADMIN_CORS_ORIGINS: Joi.string()
.allow("")
.default("")
.description("Comma-separated admin UI CORS origins"),
// Logging
LOG_LEVEL: Joi.string()
.default("info")
.valid("error", "warn", "log", "verbose", "debug"),
// Cache
CACHE_TTL_SECONDS: Joi.number()
.default(60)
.description("Cache TTL in seconds"),
QUOTE_SIMULATION_CACHE_ENABLED: Joi.string()
.valid("true", "false", "1", "0")
.default("true")
.description("Redis cache for successful Soroban quote simulations"),
QUOTE_SIMULATION_CACHE_TTL_SECONDS: Joi.number()
.integer()
.min(1)
.max(600)
.default(30)
.description("TTL for quote simulation cache entries (seconds)"),
// CAPTCHA (Turnstile or hCaptcha)
CAPTCHA_PROVIDER: Joi.string()
.valid("turnstile", "hcaptcha")
.default("turnstile"),
CAPTCHA_SECRET_KEY: Joi.string()
.allow("")
.default("dev-skip")
.description("Server-side CAPTCHA secret"),
CAPTCHA_SITE_KEY: Joi.string()
.allow("")
.description("Client-side CAPTCHA site key (exposed to frontend)"),
// Support
IP_HASH_SALT: Joi.string()
.allow("")
.default("niff-salt")
.description("Salt for IP hashing"),
// Multi-tenancy
TENANT_RESOLUTION_ENABLED: Joi.boolean()
.default(false)
.description("Enable tenant resolution from subdomain / x-tenant-id header"),
TENANT_BASE_DOMAIN: Joi.string()
.default("niffyinsur.com")
.description("Base domain for subdomain-based tenant resolution"),
});