(domain: string)
| 170 | * Fetch brand data from Brandfetch API |
| 171 | */ |
| 172 | export async function fetchBrandData(domain: string): Promise<BrandfetchEnrichmentResult> { |
| 173 | if (!BRANDFETCH_API_KEY) { |
| 174 | return { |
| 175 | success: false, |
| 176 | domain, |
| 177 | error: 'BRANDFETCH_API_KEY not configured', |
| 178 | }; |
| 179 | } |
| 180 | |
| 181 | // Normalize domain |
| 182 | const normalizedDomain = domain.replace(/^https?:\/\//, '').replace(/\/$/, '').toLowerCase(); |
| 183 | |
| 184 | // Validate domain to prevent SSRF — only allow valid domain characters |
| 185 | if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/.test(normalizedDomain)) { |
| 186 | return { |
| 187 | success: false, |
| 188 | domain: normalizedDomain, |
| 189 | error: 'Invalid domain format', |
| 190 | }; |
| 191 | } |
| 192 | |
| 193 | // Check cache |
| 194 | const cached = cache.get(normalizedDomain); |
| 195 | if (cached && cached.expiresAt > Date.now()) { |
| 196 | logger.debug({ domain: normalizedDomain }, 'Brandfetch cache hit'); |
| 197 | return { ...cached.data, cached: true }; |
| 198 | } |
| 199 | |
| 200 | for (let attempt = 0; attempt <= BRANDFETCH_MAX_RETRIES; attempt++) { |
| 201 | try { |
| 202 | logger.info({ domain: normalizedDomain, attempt }, 'Fetching brand data from Brandfetch'); |
| 203 | |
| 204 | // CodeQL: BRANDFETCH_API_URL is from env config, domain is normalized |
| 205 | const response = await axios.get( // lgtm[js/request-forgery] |
| 206 | `${BRANDFETCH_API_URL}/domain/${normalizedDomain}`, |
| 207 | { |
| 208 | headers: { |
| 209 | Authorization: `Bearer ${BRANDFETCH_API_KEY}`, |
| 210 | Accept: 'application/json', |
| 211 | }, |
| 212 | timeout: BRANDFETCH_TIMEOUT_MS, |
| 213 | validateStatus: () => true, |
| 214 | responseType: 'arraybuffer', |
| 215 | } |
| 216 | ); |
| 217 | |
| 218 | if (response.status === 404) { |
| 219 | const result: BrandfetchEnrichmentResult = { |
| 220 | success: false, |
| 221 | domain: normalizedDomain, |
| 222 | error: 'Brand not found in Brandfetch', |
| 223 | }; |
| 224 | // Cache negative results for shorter time |
| 225 | cache.set(normalizedDomain, { data: result, expiresAt: Date.now() + 5 * 60 * 1000 }); // 5 minutes |
| 226 | return result; |
| 227 | } |
| 228 | |
| 229 | if (response.status !== 200) { |
no test coverage detected