feat(auth): Google ile Giriş + landing/login + çalışan çıkış + admin rolü

- server/auth.mjs: ID token'ı oauth2.googleapis.com/tokeninfo ile doğrula (ek bağımlılık yok), HMAC imzalı app token, ADMIN_EMAILS ile admin
- POST /api/auth/google, GET /api/auth/google/config, GET /api/auth/me
- LoginPage: GIS butonlu landing/giriş; giriş yapılmadan uygulama açılmaz
- Header çıkış butonu artık çalışıyor; kullanıcı adı/avatar Google'dan
- AI Model Ayarları sekmesi sadece admin'e görünür
- Env: GOOGLE_CLIENT_ID + ADMIN_EMAILS (Coolify'dan)
This commit is contained in:
2026-08-28 00:43:08 +03:00
parent 1b28ebb603
commit 13f3e9d366
8 changed files with 409 additions and 18 deletions
+38
View File
@@ -16,6 +16,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { readDtTenders } from './dt-store.mjs';
import { loginWithGoogle, userFromAuthHeader } from './auth.mjs';
// ilan.gov.tr sunucusu ara sertifikayı (GeoTrust TLS RSA CA G1) göndermiyor;
// zinciri tamamlamak için PEM'i yerelden ekliyoruz.
@@ -295,9 +296,46 @@ function json(res, code, body) {
res.end(JSON.stringify(body));
}
function readBody(req, limit = 100 * 1024) {
return new Promise((resolve, reject) => {
let data = '';
req.on('data', (c) => {
data += c;
if (data.length > limit) reject(new Error('gövde çok büyük'));
});
req.on('end', () => {
try {
resolve(data ? JSON.parse(data) : {});
} catch {
reject(new Error('geçersiz JSON gövdesi'));
}
});
req.on('error', reject);
});
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://localhost:${PORT}`);
try {
// --- Google ile Giriş ---
if (url.pathname === '/api/auth/google/config') {
return json(res, 200, { clientId: process.env.GOOGLE_CLIENT_ID || null });
}
if (url.pathname === '/api/auth/google' && req.method === 'POST') {
try {
const body = await readBody(req);
const { token, user } = await loginWithGoogle(body.credential);
return json(res, 200, { success: true, token, user });
} catch (e) {
return json(res, 401, { success: false, error: e.message });
}
}
if (url.pathname === '/api/auth/me') {
const user = userFromAuthHeader(req.headers['authorization']);
if (!user) return json(res, 401, { success: false, error: 'oturum yok' });
return json(res, 200, { success: true, user });
}
if (url.pathname === '/api/tenders') {
const force = url.searchParams.get('refresh') === '1';
const c = await getTenders(force);