// services/auth.service.js const bcrypt = require("bcryptjs") const jwt = require("jsonwebtoken") const crypto = require("crypto") const authDb = require("../db/auth.db") const refreshTokenDb = require("../db/refreshToken.db") function httpError(statusCode, message) { const err = new Error(message) err.statusCode = statusCode return err } // Access token: kısa ömür function signAccessToken(user) { const jti = crypto.randomUUID() const payload = { sub: String(user.id), role: user.role, // USER|MOD|ADMIN jti, } const expiresIn = process.env.ACCESS_TOKEN_EXPIRES_IN || "15m" const token = jwt.sign(payload, process.env.JWT_ACCESS_SECRET, { expiresIn }) return { token, jti } } // Refresh token: opaque (JWT değil) + DB’de hash function generateRefreshToken() { // 64 byte -> url-safe base64 return crypto.randomBytes(64).toString("base64url") } function hashToken(token) { return crypto.createHash("sha256").update(token).digest("hex") } function refreshExpiresAt() { const days = Number(process.env.REFRESH_TOKEN_DAYS || 30) return new Date(Date.now() + days * 24 * 60 * 60 * 1000) } function mapUserPublic(user) { return { id: user.id, username: user.username, email: user.email, avatarUrl: user.avatarUrl ?? null, role: user.role, } } async function login({ email, password, meta = {} }) { const user = await authDb.findUserByEmail(email) if (!user) throw httpError(400, "Kullanıcı bulunamadı.") const isMatch = await bcrypt.compare(password, user.passwordHash) if (!isMatch) throw httpError(401, "Şifre hatalı.") const { token: accessToken } = signAccessToken(user) const refreshToken = generateRefreshToken() const tokenHash = hashToken(refreshToken) const familyId = crypto.randomUUID() const jti = crypto.randomUUID() await refreshTokenDb.createRefreshToken(user.id, { tokenHash, familyId, jti, expiresAt: refreshExpiresAt(), createdByIp: meta.ip ?? null, userAgent: meta.userAgent ?? null, }) return { accessToken, refreshToken, user: mapUserPublic(user), } } async function register({ username, email, password, meta = {} }) { const existingUser = await authDb.findUserByEmail(email) if (existingUser) throw httpError(400, "Bu e-posta zaten kayıtlı.") const passwordHash = await bcrypt.hash(password, 10) const user = await authDb.createUser({ username, email, passwordHash }) const { token: accessToken } = signAccessToken(user) const refreshToken = generateRefreshToken() const tokenHash = hashToken(refreshToken) const familyId = crypto.randomUUID() const jti = crypto.randomUUID() await refreshTokenDb.createRefreshToken(user.id, { tokenHash, familyId, jti, expiresAt: refreshExpiresAt(), createdByIp: meta.ip ?? null, userAgent: meta.userAgent ?? null, }) return { accessToken, refreshToken, user: mapUserPublic(user), } } // Refresh: rotate + reuse tespiti async function refresh({ refreshToken, meta = {} }) { if (!refreshToken) throw httpError(401, "Refresh token yok") const tokenHash = hashToken(refreshToken) const existing = await refreshTokenDb.findRefreshTokenByHash(tokenHash, { include: { user: true }, }) if (!existing) throw httpError(401, "Refresh token geçersiz") // süresi geçmiş if (existing.expiresAt && existing.expiresAt.getTime() < Date.now()) { await refreshTokenDb.revokeRefreshTokenById(existing.id) throw httpError(401, "Refresh token süresi dolmuş") } // reuse tespiti: revoke edilmiş token tekrar gelirse -> tüm aileyi kapat if (existing.revokedAt) { await refreshTokenDb.revokeRefreshTokenFamily(existing.familyId) throw httpError(401, "Refresh token reuse tespit edildi") } const user = existing.user const { token: accessToken } = signAccessToken(user) const newRefreshToken = generateRefreshToken() const newTokenHash = hashToken(newRefreshToken) const newJti = crypto.randomUUID() await refreshTokenDb.rotateRefreshToken({ oldId: existing.id, newToken: { userId: user.id, tokenHash: newTokenHash, familyId: existing.familyId, // aynı aile jti: newJti, expiresAt: refreshExpiresAt(), }, meta: { ip: meta.ip ?? null, userAgent: meta.userAgent ?? null }, }) return { accessToken, refreshToken: newRefreshToken, user: mapUserPublic(user), } } async function logout({ refreshToken }) { if (!refreshToken) return const tokenHash = hashToken(refreshToken) // token yoksa sessiz geçmek genelde daha iyi (idempotent logout) try { await refreshTokenDb.revokeRefreshTokenByHash(tokenHash) } catch (_) {} } async function getMe(userId) { const user = await authDb.findUserById(Number(userId), { select: { id: true, username: true, email: true, avatarUrl: true, role: true }, }) if (!user) throw httpError(404, "Kullanıcı bulunamadı") return user } module.exports = { login, register, refresh, logout, getMe, }