This commit is contained in:
payacom
2026-09-13 21:32:36 +03:30
parent 90c5f94b57
commit c302aea177
49 changed files with 4063 additions and 920 deletions

View File

@@ -1,4 +1,5 @@
const mongoose = require('mongoose')
const crypto = require('crypto')
const UserModel = require('../../../models/UserModel')
const DeviceLinkSessionModel = require('../../../models/DeviceLinkSessionModel')
@@ -152,11 +153,98 @@ const pollDeviceLink = async (req, res, next) => {
}
}
/**
* رمزِ خودکارِ per-account برای رمزنگاری/بازگشاییِ خودکارِ بک‌آپِ کلیدِ E2EE — به‌جای
* PIN دستیِ کاربر. با یه درخواستِ احرازهویت‌شده به همین اکانت (از هر دستگاهی) قابل‌
* دریافته، پس بازیابیِ هویت روی دستگاهِ جدید بدون هیچ دخالتِ کاربری انجام می‌شه.
* اولین‌بار که خواسته بشه lazily ساخته می‌شه؛ با یه آپدیتِ شرطی (فقط اگه هنوز null
* باشه) از race بینِ دو دستگاهی که هم‌زمان اولین‌بار درخواستش می‌کنن جلوگیری می‌شه.
*/
const getOrCreateAutoBackupSecret = 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 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()
// اگه 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 })
} catch (error) {
next(error)
}
}
/**
* ذخیره‌ی بک‌آپِ کلیدِ خصوصیِ رمزنگاری‌شده با PIN/رمزِ انتخابیِ کاربر. سرور فقط
* ciphertext/salt/iv رو می‌بینه — نه خودِ PIN و نه کلیدِ خام. کاربر می‌تونه صدا بزنه
* تا بک‌آپ رو بسازه یا (با یه ciphertext جدید) رمزش رو عوض کنه.
*/
const putKeyBackup = async (req, res, next) => {
try {
const { salt, iv, ciphertext, iterations } = req.body
if (
!salt || typeof salt !== 'string' ||
!iv || typeof iv !== 'string' ||
!ciphertext || typeof ciphertext !== 'string' ||
typeof iterations !== 'number' || iterations < 100000
) {
return res.status(422).json({ error: 'invalid backup payload' })
}
if (ciphertext.length > 8192 || salt.length > 512 || iv.length > 512) {
return res.status(422).json({ error: 'backup payload too large' })
}
await UserModel.findByIdAndUpdate(req.user._id, {
e2ee_key_backup: { salt, iv, ciphertext, iterations }
})
res.json({ ok: true })
} catch (error) {
next(error)
}
}
/** فقط خودِ کاربر می‌تونه بک‌آپِ خودش رو بخونه — برای بازیابی روی یه دستگاه جدید. */
const getKeyBackup = async (req, res, next) => {
try {
const user = await UserModel.findById(req.user._id).select('e2ee_key_backup').lean()
res.json({ backup: user?.e2ee_key_backup || null })
} catch (error) {
next(error)
}
}
const deleteKeyBackup = async (req, res, next) => {
try {
await UserModel.findByIdAndUpdate(req.user._id, { e2ee_key_backup: null })
res.json({ ok: true })
} catch (error) {
next(error)
}
}
module.exports = {
putMyPublicKey,
getPublicKeyByUserId,
startDeviceLink,
getDeviceLinkSession,
approveDeviceLink,
pollDeviceLink
pollDeviceLink,
getOrCreateAutoBackupSecret,
putKeyBackup,
getKeyBackup,
deleteKeyBackup
}