From 1949aefdf0e117a436b17eb2035b12590279dc4a Mon Sep 17 00:00:00 2001 From: sbilketay Date: Mon, 17 Nov 2025 19:29:33 +0300 Subject: [PATCH] =?UTF-8?q?API=20g=C3=BCvenli=C4=9Fi=20sa=C4=9Fland=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + server/index.js | 32 +++++++++++++++++++++++++++++--- src/utils/epubUtils.js | 16 +++++++++++++++- src/utils/translationUtils.js | 16 +++++++++++++++- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 62ab9ab..dfd98be 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,7 @@ SUPABASE_URL="" SUPABASE_SERVICE_ROLE_KEY="" SUPABASE_USERS_TABLE="users" JWT_SECRET="change-me" +# Allowed frontend origins (comma-separated, include scheme) CLIENT_ORIGIN="http://localhost:5173" # GLM / Anthropic çeviri servisi diff --git a/server/index.js b/server/index.js index d228a35..f58bfc5 100644 --- a/server/index.js +++ b/server/index.js @@ -27,7 +27,32 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const app = express(); const PORT = process.env.PORT || 4000; const ORIGIN = process.env.CLIENT_ORIGIN || 'http://localhost:5173'; -const allowedOrigins = ORIGIN.split(',').map((origin) => origin.trim()); +const allowedOrigins = ORIGIN.split(',').map((origin) => origin.trim()).filter(Boolean); + +const normalizeOrigin = (value = '') => { + try { + return new URL(value).origin; + } catch (error) { + return value.replace(/\/$/, ''); + } +}; + +const trustedOrigins = allowedOrigins.map(normalizeOrigin).filter(Boolean); + +const enforceClientOrigin = (req, res, next) => { + if (!trustedOrigins.length) { + return next(); + } + const header = req.get('origin') || req.get('referer'); + if (!header) { + return res.status(403).json({ message: 'Bu istemciye izin verilmiyor.' }); + } + const requestOrigin = normalizeOrigin(header); + if (!requestOrigin || !trustedOrigins.includes(requestOrigin)) { + return res.status(403).json({ message: 'Bu istemciye izin verilmiyor.' }); + } + return next(); +}; const USERS_TABLE = process.env.SUPABASE_USERS_TABLE || 'users'; const JWT_SECRET = process.env.JWT_SECRET; @@ -85,6 +110,7 @@ const authMiddleware = (req, res, next) => { }; const authRouter = express.Router(); +authRouter.use(enforceClientOrigin); authRouter.post('/register', async (req, res) => { const { name, email, password } = req.body || {}; @@ -256,7 +282,7 @@ authRouter.post('/forgot-password', async (req, res) => { app.use('/auth', authRouter); -app.post('/translate', async (req, res) => { +app.post('/translate', authMiddleware, async (req, res) => { const { text } = req.body || {}; if (!text || !text.trim()) { return res.status(400).json({ message: 'Çevrilecek metin bulunamadı.' }); @@ -273,7 +299,7 @@ app.post('/translate', async (req, res) => { } }); -app.post('/generate-epub', async (req, res) => { +app.post('/generate-epub', authMiddleware, async (req, res) => { const { text, meta, cover } = req.body || {}; if (!text || !text.trim()) { return res.status(400).json({ message: 'text is required' }); diff --git a/src/utils/epubUtils.js b/src/utils/epubUtils.js index 42c24e7..06d73c9 100644 --- a/src/utils/epubUtils.js +++ b/src/utils/epubUtils.js @@ -1,3 +1,5 @@ +import { useAppStore } from '../store/useAppStore'; + const base64ToBlob = (base64, mimeType) => { const binary = atob(base64); const len = binary.length; @@ -26,6 +28,14 @@ const blobToBase64 = (blob) => const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:4000'; +const getAuthToken = () => { + const { authToken } = useAppStore.getState(); + if (!authToken) { + throw new Error('EPUB oluşturmak için giriş yapmalısın.'); + } + return authToken; +}; + const slugify = (value = '') => value .toLowerCase() @@ -63,9 +73,13 @@ export const createEpubFromOcr = async (text, coverImage, meta = {}) => { filename: resolvedFilename, }; + const token = getAuthToken(); const response = await fetch(`${API_BASE}/generate-epub`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, body: JSON.stringify({ text, meta: { diff --git a/src/utils/translationUtils.js b/src/utils/translationUtils.js index c484651..f953355 100644 --- a/src/utils/translationUtils.js +++ b/src/utils/translationUtils.js @@ -1,13 +1,27 @@ +import { useAppStore } from '../store/useAppStore'; + const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:4000'; +const getAuthToken = () => { + const { authToken } = useAppStore.getState(); + if (!authToken) { + throw new Error('Çeviri için giriş yapmalısın.'); + } + return authToken; +}; + export const translateChunkToTurkish = async (text) => { if (!text?.trim()) { throw new Error('Çevrilecek metin bulunamadı.'); } + const token = getAuthToken(); const response = await fetch(`${API_BASE}/translate`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, body: JSON.stringify({ text }), });