_session.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. /**
  2. * 云函数共享的 Parse 登录态工具。
  3. *
  4. * 浏览器侧为了兼容旧接口仍可能传入 userId,但用户私有数据归属必须由
  5. * Parse sessionToken 在服务端解析得到。不要信任前端传入的 request.userId。
  6. */
  7. const DEFAULT_PARSE_API_HOST = env('PARSE_API_HOST', env('PARSE_BASE_URL', 'https://server.fmode.cn'));
  8. const DEFAULT_PARSE_APP_ID = env('PARSE_APP_ID', env('PARSE_APPLICATION_ID', 'ncloudmaster'));
  9. async function requireSession(request, _psql) {
  10. const sessionToken = clean(pickParam(request, 'sessionToken') || headerValue(request, 'x-parse-session-token'));
  11. if (!sessionToken) {
  12. const error = new Error('请先登录');
  13. error.status = 401;
  14. throw error;
  15. }
  16. const user = await parseMe(sessionToken);
  17. if (!user || !user.objectId) {
  18. const error = new Error('登录已失效,请重新登录');
  19. error.status = 401;
  20. throw error;
  21. }
  22. return {
  23. sessionToken,
  24. user,
  25. userId: user.objectId,
  26. role: normalizeRole(user),
  27. };
  28. }
  29. async function optionalSession(request, psql) {
  30. try {
  31. return await requireSession(request, psql);
  32. } catch {
  33. return null;
  34. }
  35. }
  36. async function requireAdmin(request, psql) {
  37. const session = await requireSession(request, psql);
  38. if (session.role !== 'admin') {
  39. const error = new Error('需要管理员权限');
  40. error.status = 403;
  41. throw error;
  42. }
  43. return session;
  44. }
  45. function assertRequestedUserMatchesSession(request, session) {
  46. const requestedUserId = clean(pickParam(request, 'userId'));
  47. if (requestedUserId && requestedUserId !== session.userId) {
  48. const error = new Error('没有访问该账号数据的权限');
  49. error.status = 403;
  50. throw error;
  51. }
  52. }
  53. async function parseMe(sessionToken) {
  54. const base = String(DEFAULT_PARSE_API_HOST || '').replace(/\/+$/, '').replace(/\/parse$/i, '');
  55. const resp = await fetch(`${base}/parse/users/me?include=company`, {
  56. method: 'GET',
  57. headers: {
  58. 'Accept': 'application/json',
  59. 'X-Parse-Application-Id': DEFAULT_PARSE_APP_ID,
  60. 'X-Parse-Session-Token': sessionToken,
  61. },
  62. });
  63. const data = await resp.json().catch(() => ({}));
  64. if (!resp.ok) {
  65. const error = new Error(data.error || data.message || '登录已失效,请重新登录');
  66. error.status = resp.status || 401;
  67. error.detail = data;
  68. throw error;
  69. }
  70. return data;
  71. }
  72. function normalizeRole(user) {
  73. const raw = String(user?.role || user?.userRole || user?.type || '').toLowerCase();
  74. if (raw === 'admin' || user?.isAdmin === true || user?.admin === true) return 'admin';
  75. return 'user';
  76. }
  77. function pickParam(request, ...names) {
  78. const sources = [request?.params, request?.body, request?.query, request];
  79. for (const src of sources) {
  80. if (!src || typeof src !== 'object') continue;
  81. for (const name of names) {
  82. const value = src[name];
  83. if (value !== undefined && value !== null && value !== '') return value;
  84. }
  85. }
  86. return null;
  87. }
  88. function headerValue(request, name) {
  89. const headers = request?.headers || {};
  90. const target = String(name || '').toLowerCase();
  91. for (const key of Object.keys(headers)) {
  92. if (key.toLowerCase() === target) return headers[key];
  93. }
  94. return '';
  95. }
  96. function clean(value) {
  97. return String(value || '').trim();
  98. }
  99. function env(name, fallback = '') {
  100. if (typeof process !== 'undefined' && process.env && process.env[name] !== undefined) {
  101. return process.env[name];
  102. }
  103. return fallback;
  104. }
  105. module.exports = {
  106. requireSession,
  107. optionalSession,
  108. requireAdmin,
  109. assertRequestedUserMatchesSession,
  110. pickParam,
  111. clean,
  112. };