Supabase login/register entegrasyonu
This commit is contained in:
162
server/index.js
162
server/index.js
@@ -1,16 +1,37 @@
|
||||
import 'dotenv/config';
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import Epub from 'epub-gen';
|
||||
|
||||
const requiredEnv = ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'JWT_SECRET'];
|
||||
requiredEnv.forEach((key) => {
|
||||
if (!process.env[key]) {
|
||||
console.error(`Missing required environment variable: ${key}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
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());
|
||||
|
||||
app.use(cors({ origin: ORIGIN, credentials: true }));
|
||||
const USERS_TABLE = process.env.SUPABASE_USERS_TABLE || 'users';
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
import { supabase } from './src/services/supabaseClient.js';
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: allowedOrigins,
|
||||
credentials: true,
|
||||
}),
|
||||
);
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
|
||||
const sanitizeHtml = (text = '') =>
|
||||
@@ -22,6 +43,145 @@ const sanitizeHtml = (text = '') =>
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\n/g, '<br/>');
|
||||
|
||||
const createToken = (user) =>
|
||||
jwt.sign(
|
||||
{
|
||||
sub: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
},
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' },
|
||||
);
|
||||
|
||||
const mapUserRecord = (record) => ({
|
||||
id: record.id,
|
||||
email: record.email,
|
||||
name: record.name,
|
||||
username: record.username,
|
||||
});
|
||||
|
||||
const authMiddleware = (req, res, next) => {
|
||||
const header = req.headers.authorization || '';
|
||||
const token = header.startsWith('Bearer ') ? header.slice(7) : null;
|
||||
if (!token) {
|
||||
return res.status(401).json({ message: 'Yetkisiz erişim' });
|
||||
}
|
||||
try {
|
||||
const payload = jwt.verify(token, JWT_SECRET);
|
||||
req.user = payload;
|
||||
return next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({ message: 'Oturum süresi doldu, lütfen tekrar giriş yap.' });
|
||||
}
|
||||
};
|
||||
|
||||
const authRouter = express.Router();
|
||||
|
||||
authRouter.post('/register', async (req, res) => {
|
||||
const { name, email, password } = req.body || {};
|
||||
if (!name || !email || !password) {
|
||||
return res.status(400).json({ message: 'Ad, email ve şifre gereklidir.' });
|
||||
}
|
||||
const normalizedEmail = email.trim().toLowerCase();
|
||||
const username = normalizedEmail.split('@')[0];
|
||||
|
||||
const { data: existingUser, error: existingError } = await supabase
|
||||
.from(USERS_TABLE)
|
||||
.select('id')
|
||||
.eq('email', normalizedEmail)
|
||||
.maybeSingle();
|
||||
|
||||
if (existingError && existingError.code !== 'PGRST116') {
|
||||
return res.status(500).json({ message: 'Kullanıcı kontrolü başarısız.' });
|
||||
}
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(409).json({ message: 'Bu email adresi ile zaten bir hesap mevcut.' });
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
const { data: createdUser, error: insertError } = await supabase
|
||||
.from(USERS_TABLE)
|
||||
.insert({
|
||||
name: name.trim(),
|
||||
email: normalizedEmail,
|
||||
username,
|
||||
password_hash: passwordHash,
|
||||
})
|
||||
.select('id,email,name,username')
|
||||
.single();
|
||||
|
||||
if (insertError || !createdUser) {
|
||||
return res.status(500).json({ message: 'Kullanıcı oluşturulamadı.' });
|
||||
}
|
||||
|
||||
const token = createToken(createdUser);
|
||||
return res.status(201).json({ token, user: mapUserRecord(createdUser) });
|
||||
});
|
||||
|
||||
authRouter.post('/login', async (req, res) => {
|
||||
const { email, password } = req.body || {};
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({ message: 'Email ve şifre gereklidir.' });
|
||||
}
|
||||
|
||||
const normalizedEmail = email.trim().toLowerCase();
|
||||
const { data: userRecord, error: fetchError } = await supabase
|
||||
.from(USERS_TABLE)
|
||||
.select('id,email,name,username,password_hash')
|
||||
.eq('email', normalizedEmail)
|
||||
.maybeSingle();
|
||||
|
||||
if (fetchError && fetchError.code !== 'PGRST116') {
|
||||
return res.status(500).json({ message: 'Giriş işlemi başarısız.' });
|
||||
}
|
||||
if (!userRecord) {
|
||||
return res.status(401).json({ message: 'Email veya şifre hatalı.' });
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(password, userRecord.password_hash);
|
||||
if (!validPassword) {
|
||||
return res.status(401).json({ message: 'Email veya şifre hatalı.' });
|
||||
}
|
||||
|
||||
const token = createToken(userRecord);
|
||||
return res.json({ token, user: mapUserRecord(userRecord) });
|
||||
});
|
||||
|
||||
authRouter.get('/me', authMiddleware, async (req, res) => {
|
||||
const { data: userRecord, error } = await supabase
|
||||
.from(USERS_TABLE)
|
||||
.select('id,email,name,username')
|
||||
.eq('id', req.user.sub)
|
||||
.single();
|
||||
|
||||
if (error || !userRecord) {
|
||||
return res.status(404).json({ message: 'Kullanıcı bulunamadı.' });
|
||||
}
|
||||
|
||||
return res.json({ user: mapUserRecord(userRecord) });
|
||||
});
|
||||
|
||||
authRouter.post('/logout', (_req, res) => {
|
||||
return res.json({ message: 'Çıkış yapıldı.' });
|
||||
});
|
||||
|
||||
authRouter.post('/forgot-password', async (req, res) => {
|
||||
const { email } = req.body || {};
|
||||
if (!email) {
|
||||
return res.status(400).json({ message: 'Email gereklidir.' });
|
||||
}
|
||||
|
||||
// Supabase Auth kullanılmadığı için burada sadece bilgilendirici bir cevap döndürüyoruz.
|
||||
return res.json({
|
||||
message:
|
||||
'Şifre sıfırlama talebin alındı. Bu demo ortamında e-posta gönderimi aktif değildir.',
|
||||
});
|
||||
});
|
||||
|
||||
app.use('/auth', authRouter);
|
||||
|
||||
app.post('/generate-epub', async (req, res) => {
|
||||
const { text, meta, cover } = req.body || {};
|
||||
if (!text || !text.trim()) {
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
"dev": "nodemon index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.45.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"epub-gen": "^0.1.0",
|
||||
"express": "^4.19.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"nodemon": "^3.1.4",
|
||||
"uuid": "^9.0.1"
|
||||
}
|
||||
|
||||
11
server/src/services/supabaseClient.js
Normal file
11
server/src/services/supabaseClient.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const requiredEnv = ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY'];
|
||||
requiredEnv.forEach((key) => {
|
||||
if (!process.env[key]) {
|
||||
console.error(`Missing required Supabase env var: ${key}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
export const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_ROLE_KEY);
|
||||
Reference in New Issue
Block a user