/** * 云函数共享的 Parse 登录态工具。 * * 浏览器侧为了兼容旧接口仍可能传入 userId,但用户私有数据归属必须由 * Parse sessionToken 在服务端解析得到。不要信任前端传入的 request.userId。 */ const DEFAULT_PARSE_API_HOST = env('PARSE_API_HOST', env('PARSE_BASE_URL', 'https://server.fmode.cn')); const DEFAULT_PARSE_APP_ID = env('PARSE_APP_ID', env('PARSE_APPLICATION_ID', 'ncloudmaster')); async function requireSession(request, _psql) { const sessionToken = clean(pickParam(request, 'sessionToken') || headerValue(request, 'x-parse-session-token')); if (!sessionToken) { const error = new Error('请先登录'); error.status = 401; throw error; } const user = await parseMe(sessionToken); if (!user || !user.objectId) { const error = new Error('登录已失效,请重新登录'); error.status = 401; throw error; } return { sessionToken, user, userId: user.objectId, role: normalizeRole(user), }; } async function optionalSession(request, psql) { try { return await requireSession(request, psql); } catch { return null; } } async function requireAdmin(request, psql) { const session = await requireSession(request, psql); if (session.role !== 'admin') { const error = new Error('需要管理员权限'); error.status = 403; throw error; } return session; } function assertRequestedUserMatchesSession(request, session) { const requestedUserId = clean(pickParam(request, 'userId')); if (requestedUserId && requestedUserId !== session.userId) { const error = new Error('没有访问该账号数据的权限'); error.status = 403; throw error; } } async function parseMe(sessionToken) { const base = String(DEFAULT_PARSE_API_HOST || '').replace(/\/+$/, '').replace(/\/parse$/i, ''); const resp = await fetch(`${base}/parse/users/me?include=company`, { method: 'GET', headers: { 'Accept': 'application/json', 'X-Parse-Application-Id': DEFAULT_PARSE_APP_ID, 'X-Parse-Session-Token': sessionToken, }, }); const data = await resp.json().catch(() => ({})); if (!resp.ok) { const error = new Error(data.error || data.message || '登录已失效,请重新登录'); error.status = resp.status || 401; error.detail = data; throw error; } return data; } function normalizeRole(user) { const raw = String(user?.role || user?.userRole || user?.type || '').toLowerCase(); if (raw === 'admin' || user?.isAdmin === true || user?.admin === true) return 'admin'; return 'user'; } function pickParam(request, ...names) { const sources = [request?.params, request?.body, request?.query, request]; for (const src of sources) { if (!src || typeof src !== 'object') continue; for (const name of names) { const value = src[name]; if (value !== undefined && value !== null && value !== '') return value; } } return null; } function headerValue(request, name) { const headers = request?.headers || {}; const target = String(name || '').toLowerCase(); for (const key of Object.keys(headers)) { if (key.toLowerCase() === target) return headers[key]; } return ''; } function clean(value) { return String(value || '').trim(); } function env(name, fallback = '') { if (typeof process !== 'undefined' && process.env && process.env[name] !== undefined) { return process.env[name]; } return fallback; } module.exports = { requireSession, optionalSession, requireAdmin, assertRequestedUserMatchesSession, pickParam, clean, };