Files
modstagram-back/utils/googleUserAuth.js
payacom 3f416a7a60 full
2026-08-31 20:45:41 +03:30

183 lines
5.0 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.
const UserModel = require('../models/UserModel')
function escapeRegex (value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
function normalizeEmail (email) {
return String(email || '').toLowerCase().trim()
}
async function findUserByGoogleId (googleId) {
if (!googleId) return null
return UserModel.findOne({ google_id: String(googleId) })
}
async function findUserByEmail (email) {
if (!email) return null
const normalized = normalizeEmail(email)
return UserModel.findOne({
$or: [
{ email: normalized },
{ email: new RegExp(`^${escapeRegex(normalized)}$`, 'i') },
],
})
}
async function findExistingGoogleUser ({ googleId, email }) {
const byGoogle = await findUserByGoogleId(googleId)
if (byGoogle) return byGoogle
return findUserByEmail(email)
}
async function patchGoogleUser (userId, fields) {
// فقط فیلدهای واقعی؛ هرگز uniqueهای sparse را null نکن
const $set = {}
for (const [key, value] of Object.entries(fields || {})) {
if (value === undefined || value === null || value === '') continue
$set[key] = value
}
if (Object.keys($set).length === 0) {
return UserModel.findById(userId)
}
await UserModel.collection.updateOne(
{ _id: userId },
{ $set }
)
return UserModel.findById(userId)
}
function getDuplicateKeyValue (err) {
return err?.keyValue && typeof err.keyValue === 'object' ? err.keyValue : null
}
/**
* ساخت کاربر گوگل بدون اعمال defaultهای null روی فیلدهای unique/sparse
*/
async function createGoogleUser ({ googleId, email }) {
const now = new Date()
const doc = {
google_id: String(googleId),
email: normalizeEmail(email),
email_verified: true,
auth_provider: 'google',
createdAt: now,
updatedAt: now,
}
try {
const result = await UserModel.collection.insertOne(doc)
const created = await UserModel.findById(result.insertedId)
if (created) return created
return findExistingGoogleUser({ googleId, email })
} catch (err) {
if (err?.code !== 11000) throw err
const existing = await findExistingGoogleUser({ googleId, email })
if (existing) return existing
const keyValue = getDuplicateKeyValue(err)
// ایندکس غیر-sparse روی mobile:null — nullها را پاک کن و دوباره بساز
if (keyValue && Object.prototype.hasOwnProperty.call(keyValue, 'mobile')) {
try {
await UserModel.collection.updateMany(
{ mobile: null },
{ $unset: { mobile: '' } }
)
const retry = await UserModel.collection.insertOne(doc)
return UserModel.findById(retry.insertedId)
} catch (retryErr) {
if (retryErr?.code === 11000) {
const again = await findExistingGoogleUser({ googleId, email })
if (again) return again
}
throw retryErr
}
}
if (keyValue?.email) {
const byEmail = await findUserByEmail(keyValue.email)
if (byEmail) {
return (
(await patchGoogleUser(byEmail._id, {
google_id: String(googleId),
email: normalizeEmail(byEmail.email || email),
email_verified: true,
})) || byEmail
)
}
}
if (keyValue?.google_id) {
const byGoogle = await findUserByGoogleId(keyValue.google_id)
if (byGoogle) return byGoogle
}
throw err
}
}
/**
* Login/register via Google:
* - existing google_id → login
* - existing email without google / same google → link + login
* - existing email with different google_id → conflict
* - otherwise create new user
*/
async function findOrCreateGoogleUser ({ googleId, email }) {
const normalizedEmail = normalizeEmail(email)
const normalizedGoogleId = String(googleId || '').trim()
if (!normalizedGoogleId || !normalizedEmail) {
const err = new Error('MISSING_GOOGLE_IDENTITY')
err.code = 'MISSING_GOOGLE_IDENTITY'
throw err
}
let user = await findUserByGoogleId(normalizedGoogleId)
if (user) {
const patch = {}
if (!user.email) patch.email = normalizedEmail
if (!user.email_verified) patch.email_verified = true
if (Object.keys(patch).length > 0) {
return (await patchGoogleUser(user._id, patch)) || user
}
return user
}
user = await findUserByEmail(normalizedEmail)
if (user) {
const existingGoogleId = user.google_id ? String(user.google_id).trim() : ''
if (existingGoogleId && existingGoogleId !== normalizedGoogleId) {
const conflict = new Error('EMAIL_LINKED_TO_OTHER_GOOGLE')
conflict.code = 'EMAIL_LINKED_TO_OTHER_GOOGLE'
throw conflict
}
return (
(await patchGoogleUser(user._id, {
google_id: normalizedGoogleId,
email: normalizeEmail(user.email || normalizedEmail),
email_verified: true,
})) || user
)
}
return createGoogleUser({
googleId: normalizedGoogleId,
email: normalizedEmail,
})
}
module.exports = {
findOrCreateGoogleUser,
findExistingGoogleUser,
}