Add developer tools page and secure guest scans
This commit is contained in:
@@ -4,6 +4,7 @@ const crypto = require('crypto');
|
||||
const dotenv = require('dotenv');
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
const loadEnvFiles = (filePaths) => {
|
||||
const mergedFileEnv = {};
|
||||
@@ -68,7 +69,7 @@ const {
|
||||
identifyPlant,
|
||||
isConfigured: isOpenAiConfigured,
|
||||
} = require('./lib/openai');
|
||||
const { applyCatalogGrounding, normalizeText } = require('./lib/scanGrounding');
|
||||
const { applyCatalogGrounding, enforceEnglishName, normalizeText } = require('./lib/scanGrounding');
|
||||
const { decideReviewOutcome, reviewAgreesWithPrimary } = require('./lib/scanReview');
|
||||
const { ensureStorageBucketWithRetry, uploadImage, isStorageConfigured } = require('./lib/storage');
|
||||
const { prepareTiktokPhotoUrls } = require('./lib/tiktok-assets');
|
||||
@@ -82,6 +83,9 @@ const {
|
||||
} = require('./lib/tiktok');
|
||||
|
||||
const app = express();
|
||||
// Caddy is the only reverse proxy in front of this service (see
|
||||
// greenlns-landing/Caddyfile), so trust exactly one hop for req.ip / X-Forwarded-For.
|
||||
app.set('trust proxy', 1);
|
||||
const port = Number(process.env.PORT || 3000);
|
||||
const plantsPublicDir = path.join(__dirname, 'public', 'plants');
|
||||
|
||||
@@ -756,7 +760,25 @@ app.post('/v1/billing/sync-revenuecat', async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/v1/scan', async (request, response) => {
|
||||
// The 'Bearer guest' token (see resolveUserId above) grants a free,
|
||||
// pre-auth demo scan with no credit charge. Without a limit here, that
|
||||
// path is an unmetered, unauthenticated OpenAI proxy for anyone who
|
||||
// knows the header — cap it per-IP so it stays a teaser, not a leak.
|
||||
const guestScanLimiter = rateLimit({
|
||||
windowMs: 24 * 60 * 60 * 1000,
|
||||
limit: 3,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
skip: (request) => request.header('authorization') !== 'Bearer guest',
|
||||
message: {
|
||||
error: {
|
||||
code: 'GUEST_SCAN_LIMIT',
|
||||
message: 'Daily free scan limit reached. Sign in or download the app for unlimited scans.',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.post('/v1/scan', guestScanLimiter, async (request, response) => {
|
||||
let userId = 'unknown';
|
||||
let idempotencyKey = null;
|
||||
let creditsCharged = 0;
|
||||
@@ -924,6 +946,8 @@ app.post('/v1/scan', async (request, response) => {
|
||||
modelPath.push('review-skipped-free-plan');
|
||||
}
|
||||
|
||||
result = enforceEnglishName(result, language);
|
||||
|
||||
const payload = {
|
||||
result,
|
||||
lowConfidence: (result.confidence || 0) < LOW_CONFIDENCE_RESULT_THRESHOLD,
|
||||
|
||||
Reference in New Issue
Block a user