feat: add advanced analytics and territory management system
- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
187
reactrebuild0825/functions/lib/claims.js
Normal file
187
reactrebuild0825/functions/lib/claims.js
Normal file
@@ -0,0 +1,187 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getUserClaims = exports.updateUserClaims = void 0;
|
||||
const app_1 = require("firebase-admin/app");
|
||||
const auth_1 = require("firebase-admin/auth");
|
||||
const firestore_1 = require("firebase-admin/firestore");
|
||||
const https_1 = require("firebase-functions/v2/https");
|
||||
const v2_1 = require("firebase-functions/v2");
|
||||
// Initialize Firebase Admin if not already initialized
|
||||
if ((0, app_1.getApps)().length === 0) {
|
||||
(0, app_1.initializeApp)();
|
||||
}
|
||||
(0, v2_1.setGlobalOptions)({
|
||||
region: "us-central1",
|
||||
});
|
||||
const auth = (0, auth_1.getAuth)();
|
||||
const db = (0, firestore_1.getFirestore)();
|
||||
// Helper function to validate authorization
|
||||
async function validateAuthorization(req) {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw new Error('Unauthorized: Missing or invalid authorization header');
|
||||
}
|
||||
const idToken = authHeader.split('Bearer ')[1];
|
||||
const decodedToken = await auth.verifyIdToken(idToken);
|
||||
const { orgId, role, territoryIds } = decodedToken;
|
||||
return {
|
||||
uid: decodedToken.uid,
|
||||
orgId,
|
||||
role,
|
||||
territoryIds: territoryIds || []
|
||||
};
|
||||
}
|
||||
// Helper function to check if user can manage claims for target org
|
||||
function canManageClaims(user, targetOrgId) {
|
||||
// Superadmin can manage any org
|
||||
if (user.role === 'superadmin') {
|
||||
return true;
|
||||
}
|
||||
// OrgAdmin can only manage their own org
|
||||
if (user.role === 'orgAdmin' && user.orgId === targetOrgId) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// POST /api/admin/users/:uid/claims
|
||||
exports.updateUserClaims = (0, https_1.onRequest)({ cors: true }, async (req, res) => {
|
||||
try {
|
||||
// Only allow POST requests
|
||||
if (req.method !== 'POST') {
|
||||
res.status(405).json({ error: 'Method not allowed' });
|
||||
return;
|
||||
}
|
||||
// Validate authorization
|
||||
const authUser = await validateAuthorization(req);
|
||||
// Extract target user ID from path
|
||||
const targetUid = req.params.uid;
|
||||
if (!targetUid) {
|
||||
res.status(400).json({ error: 'Missing user ID in path' });
|
||||
return;
|
||||
}
|
||||
// Parse request body
|
||||
const { orgId, role, territoryIds } = req.body;
|
||||
if (!orgId || !role || !Array.isArray(territoryIds)) {
|
||||
res.status(400).json({
|
||||
error: 'Missing required fields: orgId, role, territoryIds'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Validate role
|
||||
const validRoles = ['superadmin', 'orgAdmin', 'territoryManager', 'staff'];
|
||||
if (!validRoles.includes(role)) {
|
||||
res.status(400).json({
|
||||
error: `Invalid role. Must be one of: ${ validRoles.join(', ')}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Check authorization
|
||||
if (!canManageClaims(authUser, orgId)) {
|
||||
res.status(403).json({
|
||||
error: 'Insufficient permissions to manage claims for this organization'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Validate territories exist in the org
|
||||
if (territoryIds.length > 0) {
|
||||
const territoryChecks = await Promise.all(territoryIds.map(async (territoryId) => {
|
||||
const territoryDoc = await db.collection('territories').doc(territoryId).get();
|
||||
return territoryDoc.exists && territoryDoc.data()?.orgId === orgId;
|
||||
}));
|
||||
if (territoryChecks.some(valid => !valid)) {
|
||||
res.status(400).json({
|
||||
error: 'One or more territory IDs are invalid or not in the specified organization'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Set custom user claims
|
||||
const customClaims = {
|
||||
orgId,
|
||||
role,
|
||||
territoryIds
|
||||
};
|
||||
await auth.setCustomUserClaims(targetUid, customClaims);
|
||||
// Update user document in Firestore for UI consistency
|
||||
await db.collection('users').doc(targetUid).set({
|
||||
orgId,
|
||||
role,
|
||||
territoryIds,
|
||||
updatedAt: new Date().toISOString(),
|
||||
updatedBy: authUser.uid
|
||||
}, { merge: true });
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
claims: customClaims,
|
||||
message: 'User claims updated successfully'
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error updating user claims:', error);
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('Unauthorized')) {
|
||||
res.status(401).json({ error: error.message });
|
||||
}
|
||||
else if (error.message.includes('not found')) {
|
||||
res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
else {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
});
|
||||
// GET /api/admin/users/:uid/claims
|
||||
exports.getUserClaims = (0, https_1.onRequest)({ cors: true }, async (req, res) => {
|
||||
try {
|
||||
// Only allow GET requests
|
||||
if (req.method !== 'GET') {
|
||||
res.status(405).json({ error: 'Method not allowed' });
|
||||
return;
|
||||
}
|
||||
// Validate authorization
|
||||
const authUser = await validateAuthorization(req);
|
||||
// Extract target user ID from path
|
||||
const targetUid = req.params.uid;
|
||||
if (!targetUid) {
|
||||
res.status(400).json({ error: 'Missing user ID in path' });
|
||||
return;
|
||||
}
|
||||
// Get user record
|
||||
const userRecord = await auth.getUser(targetUid);
|
||||
const claims = userRecord.customClaims || {};
|
||||
// Check if user can view these claims
|
||||
if (claims.orgId && !canManageClaims(authUser, claims.orgId)) {
|
||||
res.status(403).json({
|
||||
error: 'Insufficient permissions to view claims for this user'
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.status(200).json({
|
||||
uid: targetUid,
|
||||
email: userRecord.email,
|
||||
claims
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error getting user claims:', error);
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('Unauthorized')) {
|
||||
res.status(401).json({ error: error.message });
|
||||
}
|
||||
else if (error.message.includes('not found')) {
|
||||
res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
else {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
});
|
||||
// # sourceMappingURL=claims.js.map
|
||||
Reference in New Issue
Block a user