API güvenliği sağlandı
This commit is contained in:
@@ -8,6 +8,7 @@ SUPABASE_URL=""
|
|||||||
SUPABASE_SERVICE_ROLE_KEY=""
|
SUPABASE_SERVICE_ROLE_KEY=""
|
||||||
SUPABASE_USERS_TABLE="users"
|
SUPABASE_USERS_TABLE="users"
|
||||||
JWT_SECRET="change-me"
|
JWT_SECRET="change-me"
|
||||||
|
# Allowed frontend origins (comma-separated, include scheme)
|
||||||
CLIENT_ORIGIN="http://localhost:5173"
|
CLIENT_ORIGIN="http://localhost:5173"
|
||||||
|
|
||||||
# GLM / Anthropic çeviri servisi
|
# GLM / Anthropic çeviri servisi
|
||||||
|
|||||||
@@ -27,7 +27,32 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 4000;
|
const PORT = process.env.PORT || 4000;
|
||||||
const ORIGIN = process.env.CLIENT_ORIGIN || 'http://localhost:5173';
|
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 USERS_TABLE = process.env.SUPABASE_USERS_TABLE || 'users';
|
||||||
const JWT_SECRET = process.env.JWT_SECRET;
|
const JWT_SECRET = process.env.JWT_SECRET;
|
||||||
@@ -85,6 +110,7 @@ const authMiddleware = (req, res, next) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const authRouter = express.Router();
|
const authRouter = express.Router();
|
||||||
|
authRouter.use(enforceClientOrigin);
|
||||||
|
|
||||||
authRouter.post('/register', async (req, res) => {
|
authRouter.post('/register', async (req, res) => {
|
||||||
const { name, email, password } = req.body || {};
|
const { name, email, password } = req.body || {};
|
||||||
@@ -256,7 +282,7 @@ authRouter.post('/forgot-password', async (req, res) => {
|
|||||||
|
|
||||||
app.use('/auth', authRouter);
|
app.use('/auth', authRouter);
|
||||||
|
|
||||||
app.post('/translate', async (req, res) => {
|
app.post('/translate', authMiddleware, async (req, res) => {
|
||||||
const { text } = req.body || {};
|
const { text } = req.body || {};
|
||||||
if (!text || !text.trim()) {
|
if (!text || !text.trim()) {
|
||||||
return res.status(400).json({ message: 'Çevrilecek metin bulunamadı.' });
|
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 || {};
|
const { text, meta, cover } = req.body || {};
|
||||||
if (!text || !text.trim()) {
|
if (!text || !text.trim()) {
|
||||||
return res.status(400).json({ message: 'text is required' });
|
return res.status(400).json({ message: 'text is required' });
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useAppStore } from '../store/useAppStore';
|
||||||
|
|
||||||
const base64ToBlob = (base64, mimeType) => {
|
const base64ToBlob = (base64, mimeType) => {
|
||||||
const binary = atob(base64);
|
const binary = atob(base64);
|
||||||
const len = binary.length;
|
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 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 = '') =>
|
const slugify = (value = '') =>
|
||||||
value
|
value
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -63,9 +73,13 @@ export const createEpubFromOcr = async (text, coverImage, meta = {}) => {
|
|||||||
filename: resolvedFilename,
|
filename: resolvedFilename,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const token = getAuthToken();
|
||||||
const response = await fetch(`${API_BASE}/generate-epub`, {
|
const response = await fetch(`${API_BASE}/generate-epub`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
text,
|
text,
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
|
import { useAppStore } from '../store/useAppStore';
|
||||||
|
|
||||||
const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:4000';
|
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) => {
|
export const translateChunkToTurkish = async (text) => {
|
||||||
if (!text?.trim()) {
|
if (!text?.trim()) {
|
||||||
throw new Error('Çevrilecek metin bulunamadı.');
|
throw new Error('Çevrilecek metin bulunamadı.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const token = getAuthToken();
|
||||||
const response = await fetch(`${API_BASE}/translate`, {
|
const response = await fetch(`${API_BASE}/translate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
body: JSON.stringify({ text }),
|
body: JSON.stringify({ text }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user