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
+65
View File
@@ -0,0 +1,65 @@
/**
* Google ile Giriş — frontend istemcisi (GIS ID-token akışı).
* Token localStorage'da 'ihaleist_token', kullanıcı 'ihaleist_user' altında saklanır.
*/
const TOKEN_KEY = 'ihaleist_token';
const USER_KEY = 'ihaleist_user';
export function getToken() {
try { return localStorage.getItem(TOKEN_KEY); } catch { return null; }
}
export function getStoredUser() {
try { return JSON.parse(localStorage.getItem(USER_KEY) || 'null'); } catch { return null; }
}
export function setSession(token, user) {
try {
localStorage.setItem(TOKEN_KEY, token);
localStorage.setItem(USER_KEY, JSON.stringify(user));
} catch { /* yoksay */ }
}
export function clearSession() {
try {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
} catch { /* yoksay */ }
}
export async function getGoogleConfig() {
try {
const res = await fetch('/api/auth/google/config');
if (!res.ok) return { clientId: null };
return await res.json();
} catch {
return { clientId: null };
}
}
export async function loginWithGoogle(credential) {
const res = await fetch('/api/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential }),
});
const data = await res.json();
if (!res.ok || !data.success) throw new Error(data.error || 'Giriş başarısız');
setSession(data.token, data.user);
return data.user;
}
// Sayfa açılışında saklı token'ı doğrula (opsiyonel; başarısızsa oturum düşer)
export async function restoreSession() {
const token = getToken();
if (!token) return null;
try {
const res = await fetch('/api/auth/me', { headers: { Authorization: `Bearer ${token}` } });
if (!res.ok) { clearSession(); return null; }
const data = await res.json();
if (data.success) { setSession(token, data.user); return data.user; }
clearSession();
return null;
} catch {
// Ağ hatası: saklı kullanıcıyı koru (offline)
return getStoredUser();
}
}