reels
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
const mongoose = require('mongoose')
|
||||
const crypto = require('crypto')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const DeviceLinkSessionModel = require('../../../models/DeviceLinkSessionModel')
|
||||
|
||||
@@ -154,34 +154,26 @@ const pollDeviceLink = async (req, res, next) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* رمزِ خودکارِ per-account برای رمزنگاری/بازگشاییِ خودکارِ بکآپِ کلیدِ E2EE — بهجای
|
||||
* PIN دستیِ کاربر. با یه درخواستِ احرازهویتشده به همین اکانت (از هر دستگاهی) قابل
|
||||
* دریافته، پس بازیابیِ هویت روی دستگاهِ جدید بدون هیچ دخالتِ کاربری انجام میشه.
|
||||
* اولینبار که خواسته بشه lazily ساخته میشه؛ با یه آپدیتِ شرطی (فقط اگه هنوز null
|
||||
* باشه) از race بینِ دو دستگاهی که همزمان اولینبار درخواستش میکنن جلوگیری میشه.
|
||||
* کلیدِ E2EE حالا از رمزِ عبورِ خودِ کاربر مشتق میشه (نه یه رمزِ خودکارِ ساختهشدهی
|
||||
* سرور) — این اندپوینت فقط رمزِ واردشده رو با bcrypt در برابرِ هشِ ذخیرهشده تایید
|
||||
* میکنه (عینِ loginWithUserName.js)، تا کلاینت قبل از اجرای PBKDF2ِ ۲۵۰هزار-تکراریِ
|
||||
* محلی (برای decryptPrivateKeyFromBackup) یه فیدبکِ سریع برای «رمزِ اشتباه» داشته
|
||||
* باشه؛ خودِ مشتقسازیِ کلید کاملاً سمتِ کلاینت انجام میشه، سرور هیچوقت کلید رو نمیبینه
|
||||
*/
|
||||
const getOrCreateAutoBackupSecret = async (req, res, next) => {
|
||||
const verifyPassword = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
const existing = await UserModel.findById(userId).select('e2ee_auto_backup_secret').lean()
|
||||
if (existing?.e2ee_auto_backup_secret) {
|
||||
return res.json({ secret: existing.e2ee_auto_backup_secret })
|
||||
const { password } = req.body
|
||||
if (!password || typeof password !== 'string') {
|
||||
return res.status(422).json({ error: 'password is required' })
|
||||
}
|
||||
|
||||
const candidate = crypto.randomBytes(32).toString('base64')
|
||||
const updated = await UserModel.findOneAndUpdate(
|
||||
{ _id: userId, e2ee_auto_backup_secret: null },
|
||||
{ $set: { e2ee_auto_backup_secret: candidate } },
|
||||
{ new: true }
|
||||
).select('e2ee_auto_backup_secret').lean()
|
||||
const user = await UserModel.findById(req.user._id).select('password').lean()
|
||||
if (!user?.password) {
|
||||
return res.json({ valid: false })
|
||||
}
|
||||
|
||||
// اگه updated خالی برگشت یعنی یه درخواستِ همزمانِ دیگه (دستگاهِ دیگه) بین
|
||||
// خوندنِ بالا و همین آپدیت، خودش رمز رو ساخته بود — بخونش تا هر دو دستگاه دقیقاً
|
||||
// یه رمزِ یکسان بگیرن، نه دوتا رمزِ متفاوت
|
||||
const secret = updated?.e2ee_auto_backup_secret ||
|
||||
(await UserModel.findById(userId).select('e2ee_auto_backup_secret').lean())?.e2ee_auto_backup_secret
|
||||
|
||||
res.json({ secret })
|
||||
const valid = await bcrypt.compare(password, user.password)
|
||||
res.json({ valid })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
@@ -243,7 +235,7 @@ module.exports = {
|
||||
getDeviceLinkSession,
|
||||
approveDeviceLink,
|
||||
pollDeviceLink,
|
||||
getOrCreateAutoBackupSecret,
|
||||
verifyPassword,
|
||||
putKeyBackup,
|
||||
getKeyBackup,
|
||||
deleteKeyBackup
|
||||
|
||||
129
controllers/application/account/registerMobileController.js
Normal file
129
controllers/application/account/registerMobileController.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { OTP_VALID_MS, isOtpExpired } = require('../../../utils/otpExpiry')
|
||||
const { sendSmsIrVerify, getSmsIrTemplateId } = require('../../../utils/smsIr')
|
||||
|
||||
function normalizeMobile (value) {
|
||||
return String(value || '').replace(/\D/g, '')
|
||||
}
|
||||
|
||||
function generateOTP () {
|
||||
return Math.floor(100000 + Math.random() * 900000)
|
||||
}
|
||||
|
||||
// کاربرانی که با گوگل/اینستاگرام ثبتنام کردن و شماره موبایل ندارن، از همینجا
|
||||
// (نه فلوی لاگین) میتونن یه بار برای همیشه شمارهشون رو ثبت کنن. بعد از ثبت،
|
||||
// دیگه هیچ مسیرِ ویرایشی براش نیست — طبق خواستهی کاربر.
|
||||
const requestMobileOtp = async (req, res, next) => {
|
||||
try {
|
||||
if (req.user.mobile) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شماره موبایل شما قبلاً ثبت شده و قابل تغییر نیست'
|
||||
})
|
||||
}
|
||||
|
||||
const mobile = normalizeMobile(req.body.mobile)
|
||||
if (!mobile || !/^09[0-9]{9}$/.test(mobile)) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'فرمت شماره موبایل صحیح نیست'
|
||||
})
|
||||
}
|
||||
|
||||
const existing = await UserModel.findOne({ mobile })
|
||||
if (existing) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'این شماره موبایل قبلاً توسط کاربر دیگری ثبت شده است'
|
||||
})
|
||||
}
|
||||
|
||||
const otp = generateOTP()
|
||||
const otpSentAt = new Date()
|
||||
|
||||
await UserModel.updateOne(
|
||||
{ _id: req.user._id },
|
||||
{ $set: { pending_mobile: mobile, otp: String(otp), otpSentAt } }
|
||||
)
|
||||
|
||||
setTimeout(() => {
|
||||
UserModel.updateOne(
|
||||
{ _id: req.user._id, otp: String(otp) },
|
||||
{ $set: { otp: null, otpSentAt: null, pending_mobile: null } }
|
||||
).catch((error) => console.error('[requestMobileOtp] clear OTP failed:', error))
|
||||
}, OTP_VALID_MS)
|
||||
|
||||
const smsResult = await sendSmsIrVerify({
|
||||
mobile,
|
||||
otp,
|
||||
templateId: getSmsIrTemplateId('930719')
|
||||
})
|
||||
|
||||
if (!smsResult.ok) {
|
||||
return res.status(502).json({
|
||||
error: true,
|
||||
message: 'ارسال پیامک ناموفق بود. لطفاً کمی بعد دوباره تلاش کنید.'
|
||||
})
|
||||
}
|
||||
|
||||
return res.json({ success: true, message: 'کد تایید ارسال شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const verifyMobileOtp = async (req, res, next) => {
|
||||
try {
|
||||
const { otp } = req.body
|
||||
if (!otp) {
|
||||
return res.status(422).json({ error: true, message: 'کد تایید الزامی است' })
|
||||
}
|
||||
|
||||
const user = await UserModel.findById(req.user._id)
|
||||
if (!user.pending_mobile || !user.otp) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'درخواستی برای ثبت شماره موبایل یافت نشد. دوباره تلاش کنید'
|
||||
})
|
||||
}
|
||||
|
||||
if (isOtpExpired(user)) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'کد تایید منقضی شده است. لطفاً دوباره درخواست ارسال کد دهید'
|
||||
})
|
||||
}
|
||||
|
||||
if (String(user.otp) !== String(otp)) {
|
||||
return res.status(422).json({ error: true, message: 'کد را اشتباه وارد کردید' })
|
||||
}
|
||||
|
||||
// بینِ درخواستِ کد و تاییدش، ممکنه یه کاربرِ دیگه دقیقاً همین شماره رو گرفته باشه
|
||||
const conflict = await UserModel.findOne({ mobile: user.pending_mobile })
|
||||
if (conflict) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'این شماره موبایل قبلاً توسط کاربر دیگری ثبت شده است'
|
||||
})
|
||||
}
|
||||
|
||||
user.mobile = user.pending_mobile
|
||||
user.pending_mobile = null
|
||||
user.otp = null
|
||||
user.otpSentAt = null
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: 'شماره موبایل با موفقیت ثبت شد',
|
||||
mobile: user.mobile
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requestMobileOtp,
|
||||
verifyMobileOtp
|
||||
}
|
||||
Reference in New Issue
Block a user