user.service.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /**
  2. * 用户相关云函数
  3. * 采用 Parse 标准安全实践:
  4. * Parse Server 默认使用 bcrypt 对密码进行安全哈希存储。
  5. * 客户端/服务端传输应使用 HTTPS 保证安全。
  6. */
  7. import crypto from 'crypto';
  8. console.log('Loading user.service.js...');
  9. // RSA 私钥,用于解密前端发来的数据
  10. // 警告:在生产环境中,请不要硬编码私钥,建议通过环境变量注入
  11. const RSA_PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
  12. MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDo3GIkDFqMjRmx
  13. Dt0LnM/HhSIKNMdBWOXUBqALY811q0RDWAoiAX/4L5BKntJumopMhIAJYjIsZTFz
  14. h8f3/afrejeUiV7DyXYD5ZKTD/ZyKyxcrFgEdLOqx3zovxuWExnQXrBnnUNruPMB
  15. eQvQgL3Cng5hq0gLrqdEqtz6qAfImtKhp2Kzwbj+LzQPkiABEU/4RLShB5m7rLgu
  16. gLrFYKEFRIkz/CwFtbQh8clJuwzoVtxtFC9E3c2F6T3vnoBzCqq5dXKfPm6laZJO
  17. 3SkBBC8CmYTi+DyQnaDiGOIvP4/6jZzzvpz2jq+mltkRcXnHzCMEd0encDdCQj8B
  18. sfHgbIDxAgMBAAECggEACxH4FfHi56HpHeRgoJXTwG7dOYo9C0eiRHhMqUH1QXHz
  19. ELcJo0h6OJatR8jhZOnyp3TYn+mTzE3CwjTrLihYUG7q38RAlbL5CfIi69wjSZXx
  20. GdZFKHASXwz1DJ0SPwoZLPFldkWREBjWr6QdKkxGiS8prJ2/kvOaht6PGxj8Jr6T
  21. SJv8aAdy/oMB+maME1s1JSern+AINjD6f6yOwajxhE9KK8F9rHnyt7+tlGS/ATIv
  22. 9FkEahKhz/K/ety8ns56ckOVIw7a740aq8eJ/JnOw4ISLRvMexJdCZKtLr4PpFnC
  23. TDL/VzrJ57eVAFj/YA3kXMBdKzzOPoIFpxETMc2dCQKBgQD8w7ctRjSo3o/fhLIA
  24. 9LVjAgoWBFlATrXR/ncoPjHjCCo2G8X6IeMcL1RZTSs3NuX+xQZcqPcHmQRLceBJ
  25. PqvroKE8e/z8WgtPQn0kGKxwU0G9dKHbB57QB5GO5gHc9I0pVEYBNpq37VR7tQ1R
  26. BDJB2mP+l7yApmQFhffXhPt2yQKBgQDr13IPsRVNq2COTYP72Q2726xtk0+83AxW
  27. 2y+CGPCW1gnM6bZzykeFZImBcOnlyC9rCqVWM5ZXcic1zApIX7HOaVGlkyLhi5ac
  28. IFrnKX9Ejz/6B+NccPGT+9b+3d7WWh4aANWwgk6l2sOAkJPMKH1nyusPqOIL5Hia
  29. KlporABE6QKBgQC8Fue4SQXmo7dsHNJY48AWbO4UdCf2MpwaJ8GDsMRurSS8Hu6z
  30. s4PM9WtPCJ2f+LotW2/gg9tPTA5mFddQqdJFBML2v00Ce/7ufAujULkA3POOh7vp
  31. wUHsi7VFIONCCLkCiDOPs5JuS60SYSgHoZfEaT7/tOoQ0cCcW5SSS+LUAQKBgQCi
  32. Y/jwARkQH/8ePRWyJmSkg8AYuyRegI4miEoQJKgU1FBvPkYZOKFColVkQaFsTQkJ
  33. hY0O6/qLP9jr3BEAgJPn8vD7N3L/JqSP4F93aEVvqct9mEzfbA2Lvg7t80vrAKZV
  34. dVmubitFXHrdXa/HFeIzw69TWxA67Bh23s7d4AFXcQKBgDMnq/jtZwyBoJhmwLKX
  35. nx/pG+G7X2Jt4BW9dbttBn5QeUw5ZwjOpOEU4NqYoM7Td7qpeTTQ/WS0OFsOfXcd
  36. baH3/NOVr6ntip6y38iYN1tbyaT4fCCHuesj6NdoicekM7564JrNhBAr1nqoc+9B
  37. q6YYTf819dRCh272VeKIFzjt
  38. -----END PRIVATE KEY-----`;
  39. /**
  40. * 解密 RSA 加密的数据
  41. * @param {string} encryptedData Base64 编码的加密字符串
  42. * @returns {string} 解密后的明文字符串
  43. */
  44. function decryptRSA(encryptedData) {
  45. try {
  46. const buffer = Buffer.from(encryptedData, 'base64');
  47. const decrypted = crypto.privateDecrypt(
  48. {
  49. key: RSA_PRIVATE_KEY,
  50. // 根据前端使用的 padding 方式进行调整,常见的是 crypto.constants.RSA_PKCS1_PADDING 或 RSA_PKCS1_OAEP_PADDING
  51. padding: crypto.constants.RSA_PKCS1_PADDING,
  52. },
  53. buffer
  54. );
  55. return decrypted.toString('utf8');
  56. } catch (error) {
  57. console.error('RSA Decryption failed:', error.message);
  58. throw new Error('解密失败');
  59. }
  60. }
  61. /**
  62. * 用户登录云函数
  63. * 调用方式: Parse.Cloud.run('user_login', { username, password })
  64. * 此时传入的 username 和 password 为 RSA 加密并经过 Base64 编码的字符串
  65. */
  66. Parse.Cloud.define('user_login', async request => {
  67. console.log('Cloud Function called: user_login', request.params);
  68. const { username: encryptedUsername, password: encryptedPassword } = request.params;
  69. // 参数校验
  70. if (!encryptedUsername || !encryptedPassword) {
  71. throw new Parse.Error(Parse.Error.VALIDATION_ERROR, '用户名和密码不能为空');
  72. }
  73. let username, password;
  74. try {
  75. // 解密账号和密码
  76. username = decryptRSA(encryptedUsername);
  77. password = decryptRSA(encryptedPassword);
  78. } catch (error) {
  79. throw new Parse.Error(Parse.Error.VALIDATION_ERROR, '账号或密码解密失败,请检查加密格式');
  80. }
  81. try {
  82. // 使用 Parse 原生登录,自动验证密码哈希
  83. // logIn 返回的用户对象已包含 sessionToken
  84. const user = await Parse.User.logIn(username, password);
  85. return {
  86. success: true,
  87. user: user.toJSON(),
  88. sessionToken: user.getSessionToken()
  89. };
  90. } catch (error) {
  91. console.error('Login error:', error);
  92. throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, '用户名或密码错误');
  93. }
  94. });
  95. /**
  96. * 用户注册云函数
  97. * 调用方式: Parse.Cloud.run('user_signup', { username, password, email, ...attributes })
  98. */
  99. Parse.Cloud.define('user_signup', async request => {
  100. console.log('Cloud Function called: user_signup');
  101. const { username, password, email, ...attributes } = request.params;
  102. // 参数校验
  103. if (!username || !password) {
  104. throw new Parse.Error(Parse.Error.VALIDATION_ERROR, '用户名和密码不能为空');
  105. }
  106. const user = new Parse.User();
  107. user.set('username', username);
  108. user.set('password', password); // Parse 会自动进行 bcrypt 加密存储
  109. if (email) {
  110. user.set('email', email);
  111. }
  112. // 动态设置其他属性
  113. Object.keys(attributes).forEach(key => {
  114. user.set(key, attributes[key]);
  115. });
  116. try {
  117. await user.signUp();
  118. return {
  119. success: true,
  120. userId: user.id,
  121. sessionToken: user.getSessionToken(),
  122. user: user.toJSON()
  123. };
  124. } catch (error) {
  125. console.error('Signup error:', error);
  126. // 转换错误信息(如用户名已存在)
  127. throw new Parse.Error(Parse.Error.USERNAME_TAKEN, error.message);
  128. }
  129. });
  130. export default {};