/* * Authentication and Authorization utilities for SaaS VOC Cloud Functions */ /** * Safely serialize a value, removing sensitive fields */ function safeValue(value, depth) { if (depth > 5 || value === null || value === undefined) return value; if (value instanceof Date) return value; if (Array.isArray(value)) return value.slice(0, 100).map((item) => safeValue(item, depth + 1)); if (typeof value !== 'object') return value; const output = {}; Object.keys(value).slice(0, 100).forEach((key) => { if (/token|secret|password|credential|authorization|master/i.test(key)) return; output[key] = safeValue(value[key], depth + 1); }); return output; } // Roles that can perform write operations const WRITE_ROLES = new Set(['owner', 'admin', 'editor']); const ADMIN_ROLES = new Set(['owner', 'admin']); let activeRequest = null; let activeResponse = null; /** * Set the current request/response context */ function setContext(request, response) { activeRequest = request; activeResponse = response; } /** * Clear the current context */ function clearContext() { activeRequest = null; activeResponse = null; } /** * Get request ID from headers or generate one */ function requestId() { return activeRequest?.headers && ( activeRequest.headers['x-request-id'] || activeRequest.headers['X-Request-Id'] ) || 'cloud-' + Date.now().toString(36); } /** * Get the current user from the request */ function getUser() { return activeRequest?.user || null; } /** * Get the current workspace ID */ function getWorkspaceId() { return activeRequest?.workspaceId || ''; } /** * Respond with an error */ function fail(status, code, message) { activeResponse?.status(status).json({ success: false, code, message, requestId: requestId(), }); } /** * Respond with success */ function success(data, statusCode = 200) { const response = statusCode === 202 ? activeResponse?.status(202).json({ success: true, data, requestId: requestId() }) : activeResponse?.json({ success: true, data, requestId: requestId() }); return response; } /** * Check if user is authenticated */ function requireAuth() { if (!activeRequest?.user) { throw { status: 401, code: 'unauthenticated', message: '需要登录' }; } } /** * Get the active workspace member for the current user */ async function activeMember(workspaceId) { requireAuth(); const user = getUser(); const query = new Parse.Query('VocWorkspaceMember'); query.equalTo('workspaceId', workspaceId); query.equalTo('userId', user.id); query.equalTo('status', 'active'); return query.first({ useMasterKey: true }); } /** * Get all workspaces accessible by the current user */ async function accessibleWorkspaces() { requireAuth(); const user = getUser(); const memberQuery = new Parse.Query('VocWorkspaceMember'); memberQuery.equalTo('userId', user.id); memberQuery.equalTo('status', 'active'); memberQuery.limit(100); const members = await memberQuery.find({ useMasterKey: true }); const ids = [...new Set( members .map((member) => String(member.get('workspaceId') || '')) .filter(Boolean) )]; if (!ids.length) return []; const workspaceQuery = new Parse.Query('VocWorkspace'); workspaceQuery.containedIn('publicId', ids); workspaceQuery.equalTo('status', 'active'); workspaceQuery.limit(100); const workspaces = await workspaceQuery.find({ useMasterKey: true }); const roleByWorkspace = new Map( members.map((member) => [ String(member.get('workspaceId') || ''), String(member.get('role') || 'viewer'), ]) ); return workspaces.map((workspace) => ({ ...safeValue(workspace.toJSON(), 0), role: roleByWorkspace.get(String(workspace.get('publicId') || '')) || 'viewer', })); } /** * Check if an action is a write action */ function isWriteAction(action) { return ( /\.create$|\.update$|\.upsert$|\.delete$|\.enqueue$|\.retry$|\.cancel$|\.adopt$|\.run$/.test(action) || action === 'ai.chat' || action === 'ai.test' || action === 'competitor.refresh' ); } /** * Get safe value utility (lazy loaded to avoid circular dependency) */ function safeValue(value, depth) { if (depth > 5 || value === null || value === undefined) return value; if (value instanceof Date) return value; if (Array.isArray(value)) return value.slice(0, 100).map((item) => safeValue(item, depth + 1)); if (typeof value !== 'object') return value; const output = {}; Object.keys(value).slice(0, 100).forEach((key) => { if (/token|secret|password|credential|authorization|master/i.test(key)) return; output[key] = safeValue(value[key], depth + 1); }); return output; } /** * Authorize an action for the current user in a workspace * @param {string} action - The action being performed * @param {string} workspaceId - The workspace ID * @returns {Object} Authorization result with member, role, and productIds */ async function authorize(action, workspaceId) { requireAuth(); const member = await activeMember(workspaceId); if (!member) { throw { status: 403, code: 'workspace_access_denied', message: '无权访问该 workspace' }; } const role = String(member.get('role') || 'viewer'); // Check admin-only actions if (action === 'workspace.members.list' && !ADMIN_ROLES.has(role)) { throw { status: 403, code: 'forbidden', message: '权限不足' }; } // Check write permission if (isWriteAction(action) && !WRITE_ROLES.has(role)) { throw { status: 403, code: 'viewer_write_forbidden', message: 'Viewer 不能执行写入操作' }; } // Get product scope const configuredProductIds = member.get('productIds'); const productIds = Array.isArray(configuredProductIds) ? [...new Set(configuredProductIds.filter((value) => typeof value === 'string' && value.length <= 200))] : (ADMIN_ROLES.has(role) ? null : []); return { member, role, productIds }; } /** * Resolve workspace ID, using membership if not provided */ async function resolveWorkspaceId(workspaceId) { if (workspaceId && workspaceId.trim()) return workspaceId.trim(); requireAuth(); const user = getUser(); const membershipQuery = new Parse.Query('VocWorkspaceMember'); membershipQuery.equalTo('userId', user.id); membershipQuery.equalTo('status', 'active'); const membership = await membershipQuery.first({ useMasterKey: true }); return membership ? String(membership.get('workspaceId') || '') : ''; } /* // module.exports = { // setContext, // clearContext, // requestId, // getUser, // getWorkspaceId, // fail, // success, // requireAuth, // activeMember, // accessibleWorkspaces, // isWriteAction, // safeValue, // authorize, // resolveWorkspaceId, // }; */