/** * 用户相关云函数 * 采用 Parse 标准安全实践: * Parse Server 默认使用 bcrypt 对密码进行安全哈希存储。 * 客户端/服务端传输应使用 HTTPS 保证安全。 */ import crypto from 'crypto'; console.log('Loading user.service.js...'); // RSA 私钥,用于解密前端发来的数据 // 警告:在生产环境中,请不要硬编码私钥,建议通过环境变量注入 const RSA_PRIVATE_KEY = `-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDo3GIkDFqMjRmx Dt0LnM/HhSIKNMdBWOXUBqALY811q0RDWAoiAX/4L5BKntJumopMhIAJYjIsZTFz h8f3/afrejeUiV7DyXYD5ZKTD/ZyKyxcrFgEdLOqx3zovxuWExnQXrBnnUNruPMB eQvQgL3Cng5hq0gLrqdEqtz6qAfImtKhp2Kzwbj+LzQPkiABEU/4RLShB5m7rLgu gLrFYKEFRIkz/CwFtbQh8clJuwzoVtxtFC9E3c2F6T3vnoBzCqq5dXKfPm6laZJO 3SkBBC8CmYTi+DyQnaDiGOIvP4/6jZzzvpz2jq+mltkRcXnHzCMEd0encDdCQj8B sfHgbIDxAgMBAAECggEACxH4FfHi56HpHeRgoJXTwG7dOYo9C0eiRHhMqUH1QXHz ELcJo0h6OJatR8jhZOnyp3TYn+mTzE3CwjTrLihYUG7q38RAlbL5CfIi69wjSZXx GdZFKHASXwz1DJ0SPwoZLPFldkWREBjWr6QdKkxGiS8prJ2/kvOaht6PGxj8Jr6T SJv8aAdy/oMB+maME1s1JSern+AINjD6f6yOwajxhE9KK8F9rHnyt7+tlGS/ATIv 9FkEahKhz/K/ety8ns56ckOVIw7a740aq8eJ/JnOw4ISLRvMexJdCZKtLr4PpFnC TDL/VzrJ57eVAFj/YA3kXMBdKzzOPoIFpxETMc2dCQKBgQD8w7ctRjSo3o/fhLIA 9LVjAgoWBFlATrXR/ncoPjHjCCo2G8X6IeMcL1RZTSs3NuX+xQZcqPcHmQRLceBJ PqvroKE8e/z8WgtPQn0kGKxwU0G9dKHbB57QB5GO5gHc9I0pVEYBNpq37VR7tQ1R BDJB2mP+l7yApmQFhffXhPt2yQKBgQDr13IPsRVNq2COTYP72Q2726xtk0+83AxW 2y+CGPCW1gnM6bZzykeFZImBcOnlyC9rCqVWM5ZXcic1zApIX7HOaVGlkyLhi5ac IFrnKX9Ejz/6B+NccPGT+9b+3d7WWh4aANWwgk6l2sOAkJPMKH1nyusPqOIL5Hia KlporABE6QKBgQC8Fue4SQXmo7dsHNJY48AWbO4UdCf2MpwaJ8GDsMRurSS8Hu6z s4PM9WtPCJ2f+LotW2/gg9tPTA5mFddQqdJFBML2v00Ce/7ufAujULkA3POOh7vp wUHsi7VFIONCCLkCiDOPs5JuS60SYSgHoZfEaT7/tOoQ0cCcW5SSS+LUAQKBgQCi Y/jwARkQH/8ePRWyJmSkg8AYuyRegI4miEoQJKgU1FBvPkYZOKFColVkQaFsTQkJ hY0O6/qLP9jr3BEAgJPn8vD7N3L/JqSP4F93aEVvqct9mEzfbA2Lvg7t80vrAKZV dVmubitFXHrdXa/HFeIzw69TWxA67Bh23s7d4AFXcQKBgDMnq/jtZwyBoJhmwLKX nx/pG+G7X2Jt4BW9dbttBn5QeUw5ZwjOpOEU4NqYoM7Td7qpeTTQ/WS0OFsOfXcd baH3/NOVr6ntip6y38iYN1tbyaT4fCCHuesj6NdoicekM7564JrNhBAr1nqoc+9B q6YYTf819dRCh272VeKIFzjt -----END PRIVATE KEY-----`; /** * 解密 RSA 加密的数据 * @param {string} encryptedData Base64 编码的加密字符串 * @returns {string} 解密后的明文字符串 */ function decryptRSA(encryptedData) { try { const buffer = Buffer.from(encryptedData, 'base64'); const decrypted = crypto.privateDecrypt( { key: RSA_PRIVATE_KEY, // 根据前端使用的 padding 方式进行调整,常见的是 crypto.constants.RSA_PKCS1_PADDING 或 RSA_PKCS1_OAEP_PADDING padding: crypto.constants.RSA_PKCS1_PADDING, }, buffer ); return decrypted.toString('utf8'); } catch (error) { console.error('RSA Decryption failed:', error.message); throw new Error('解密失败'); } } /** * 用户登录云函数 * 调用方式: Parse.Cloud.run('user_login', { username, password }) * 此时传入的 username 和 password 为 RSA 加密并经过 Base64 编码的字符串 */ Parse.Cloud.define('user_login', async request => { console.log('Cloud Function called: user_login', request.params); const { username: encryptedUsername, password: encryptedPassword } = request.params; // 参数校验 if (!encryptedUsername || !encryptedPassword) { throw new Parse.Error(Parse.Error.VALIDATION_ERROR, '用户名和密码不能为空'); } let username, password; try { // 解密账号和密码 username = decryptRSA(encryptedUsername); password = decryptRSA(encryptedPassword); } catch (error) { throw new Parse.Error(Parse.Error.VALIDATION_ERROR, '账号或密码解密失败,请检查加密格式'); } try { // 使用 Parse 原生登录,自动验证密码哈希 // logIn 返回的用户对象已包含 sessionToken const user = await Parse.User.logIn(username, password); return { success: true, user: user.toJSON(), sessionToken: user.getSessionToken() }; } catch (error) { console.error('Login error:', error); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, '用户名或密码错误'); } }); /** * 用户注册云函数 * 调用方式: Parse.Cloud.run('user_signup', { username, password, email, ...attributes }) */ Parse.Cloud.define('user_signup', async request => { console.log('Cloud Function called: user_signup'); const { username, password, email, ...attributes } = request.params; // 参数校验 if (!username || !password) { throw new Parse.Error(Parse.Error.VALIDATION_ERROR, '用户名和密码不能为空'); } const user = new Parse.User(); user.set('username', username); user.set('password', password); // Parse 会自动进行 bcrypt 加密存储 if (email) { user.set('email', email); } // 动态设置其他属性 Object.keys(attributes).forEach(key => { user.set(key, attributes[key]); }); try { await user.signUp(); return { success: true, userId: user.id, sessionToken: user.getSessionToken(), user: user.toJSON() }; } catch (error) { console.error('Signup error:', error); // 转换错误信息(如用户名已存在) throw new Parse.Error(Parse.Error.USERNAME_TAKEN, error.message); } }); export default {};