chore: merge web repo history into monorepo at web/
git-subtree-dir: web git-subtree-mainline:75a0ba593dgit-subtree-split:8f624e7416
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
// رمزنگاری سرتاسری (end-to-end) پیامهای چت با NaCl box (X25519-XSalsa20-Poly1305).
|
||||
// کلید خصوصی هیچوقت از این فایل بیرون نمیره و هیچوقت به سرور فرستاده نمیشه؛
|
||||
// فقط توی localStorage همین مرورگر میمونه. یعنی اگه کاربر localStorage رو پاک
|
||||
// کنه یا از یه مرورگر/دستگاه دیگه وارد بشه، کلید قبلی از بین میره و پیامهای
|
||||
// قبلی دیگه قابل رمزگشایی نیستن — این یه تصمیم آگاهانهست، نه باگ: هیچ نسخهی
|
||||
// پشتیبان یا escrow از کلید خصوصی روی سرور نگه داشته نمیشه.
|
||||
import nacl from "tweetnacl";
|
||||
import util from "tweetnacl-util";
|
||||
import { updatePublicKey } from "./api";
|
||||
|
||||
const SECRET_KEY_STORAGE = "dating_app_e2ee_secret_key";
|
||||
|
||||
function isBrowser() {
|
||||
return typeof window !== "undefined";
|
||||
}
|
||||
|
||||
export interface LocalKeyPair {
|
||||
publicKey: Uint8Array;
|
||||
secretKey: Uint8Array;
|
||||
}
|
||||
|
||||
function readLocalSecretKey(): Uint8Array | null {
|
||||
if (!isBrowser()) return null;
|
||||
const stored = localStorage.getItem(SECRET_KEY_STORAGE);
|
||||
if (!stored) return null;
|
||||
try {
|
||||
return util.decodeBase64(stored);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** آیا این مرورگر از قبل کلید محلی داره؟ (بدون تولید کلید جدید) */
|
||||
export function hasLocalKeyPair(): boolean {
|
||||
return readLocalSecretKey() !== null;
|
||||
}
|
||||
|
||||
/** کلید محلی رو بدون هیچ درخواست شبکهای برمیگردونه؛ اگه هنوز ساخته نشده null. */
|
||||
export function getLocalKeyPair(): LocalKeyPair | null {
|
||||
const secretKey = readLocalSecretKey();
|
||||
if (!secretKey) return null;
|
||||
return nacl.box.keyPair.fromSecretKey(secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* کلید محلی رو برمیگردونه؛ اگه وجود نداشته باشه یه جفتکلید تازه میسازه و
|
||||
* توی localStorage ذخیره میکنه. در هر دو حالت، کلید عمومی رو (دوباره) با
|
||||
* سرور sync میکنه تا اگه ثبت قبلی به هر دلیلی (قطعی شبکه و…) ناموفق بوده،
|
||||
* این بار درست بشه.
|
||||
*/
|
||||
export async function ensureKeyPair(): Promise<LocalKeyPair> {
|
||||
let secretKey = readLocalSecretKey();
|
||||
let keyPair: nacl.BoxKeyPair;
|
||||
|
||||
if (secretKey) {
|
||||
keyPair = nacl.box.keyPair.fromSecretKey(secretKey);
|
||||
} else {
|
||||
keyPair = nacl.box.keyPair();
|
||||
secretKey = keyPair.secretKey;
|
||||
if (isBrowser()) {
|
||||
localStorage.setItem(SECRET_KEY_STORAGE, util.encodeBase64(secretKey));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await updatePublicKey(util.encodeBase64(keyPair.publicKey));
|
||||
} catch {
|
||||
// آفلاین یا خطای موقت سرور؛ کلید محلی معتبره و دفعهی بعد دوباره sync میشه.
|
||||
}
|
||||
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
/** پیام متنی رو با کلید عمومی طرف مقابل رمزنگاری میکنه. */
|
||||
export function encryptMessage(
|
||||
plainText: string,
|
||||
theirPublicKeyB64: string,
|
||||
mySecretKey: Uint8Array
|
||||
): { cipherText: string; nonce: string } {
|
||||
const nonce = nacl.randomBytes(nacl.box.nonceLength);
|
||||
const messageBytes = util.decodeUTF8(plainText);
|
||||
const theirPublicKey = util.decodeBase64(theirPublicKeyB64);
|
||||
const box = nacl.box(messageBytes, nonce, theirPublicKey, mySecretKey);
|
||||
return {
|
||||
cipherText: util.encodeBase64(box),
|
||||
nonce: util.encodeBase64(nonce),
|
||||
};
|
||||
}
|
||||
|
||||
/** پیام رمزنگاریشده رو رمزگشایی میکنه؛ اگه کلید جور نباشه null برمیگردونه. */
|
||||
export function decryptMessage(
|
||||
cipherTextB64: string,
|
||||
nonceB64: string,
|
||||
theirPublicKeyB64: string,
|
||||
mySecretKey: Uint8Array
|
||||
): string | null {
|
||||
try {
|
||||
const cipherText = util.decodeBase64(cipherTextB64);
|
||||
const nonce = util.decodeBase64(nonceB64);
|
||||
const theirPublicKey = util.decodeBase64(theirPublicKeyB64);
|
||||
const opened = nacl.box.open(cipherText, nonce, theirPublicKey, mySecretKey);
|
||||
if (!opened) return null;
|
||||
return util.encodeUTF8(opened);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user