HotTRDealsBackend/services/auth.service.js
2026-01-25 17:50:56 +00:00

188 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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) + DBde 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,
}