@flavoai/fastfold
Version:
Zero-boilerplate backend for React apps with auto-generated CRUD and declarative security
200 lines • 7.3 kB
JavaScript
// ============================================================================
// SECURITY API - Clean One-Liners ✨
// ============================================================================
export class Security {
/**
* 🔓 PUBLIC ACCESS - Anyone can access this table
* Perfect for: blogs, public content, marketing pages
*
* Example: Security.public()
* Can also be used as Express middleware: app.get('/api/endpoint', Security.public(), handler)
*/
static public() {
const rule = {
type: 'public'
};
// Add middleware function
const middleware = (req, res, next) => {
// Public access - always allow
next();
};
return Object.assign(middleware, rule);
}
/**
* 🔐 ADMIN ONLY - Only admin users can access this table
* Perfect for: admin logs, system settings, sensitive data
*
* Example: Security.admin()
* Can also be used as Express middleware: app.post('/api/admin', Security.admin(), handler)
*/
static admin() {
const rule = {
type: 'admin'
};
// Add middleware function
const middleware = (req, res, next) => {
const user = req.user;
if (!user || user.role !== 'admin') {
res.status(403).json({
success: false,
error: 'Admin access required'
});
return;
}
next();
};
return Object.assign(middleware, rule);
}
/**
* 👤 OWNER-BASED - Users can only access their own records
* Perfect for: user profiles, user posts, private data
*
* @param ownerField The field that contains the user ID (default: 'userId')
*
* Example: Security.owner('userId') or Security.owner()
* Can also be used as Express middleware: app.get('/api/posts', Security.owner('authorId'), handler)
*/
static owner(ownerField = 'userId') {
const rule = {
type: 'owner',
ownerField
};
// Add middleware function
const middleware = (req, res, next) => {
const user = req.user;
if (!user) {
res.status(401).json({
success: false,
error: 'Authentication required'
});
return;
}
// For owner-based security, the actual check happens in the SecurityEnforcer
// This middleware just ensures the user is authenticated
next();
};
return Object.assign(middleware, rule);
}
/**
* 🔑 AUTHENTICATED - Any logged-in user can access
* Perfect for: user dashboards, protected content
*
* Example: Security.authenticated()
* Can also be used as Express middleware: app.get('/api/profile', Security.authenticated(), handler)
*/
static authenticated() {
const rule = {
type: 'authenticated' // Adding this type
};
// Add middleware function
const middleware = (req, res, next) => {
const user = req.user;
if (!user) {
res.status(401).json({
success: false,
error: 'Authentication required'
});
return;
}
next();
};
return Object.assign(middleware, rule);
}
/**
* ⚙️ CUSTOM RULE - Define your own security logic
* Perfect for: complex business rules, team-based access
*
* @param rule Custom function that returns true/false for access
*
* Example: Security.custom((ctx) => ctx.user?.teamId === ctx.data?.teamId)
* Can also be used as Express middleware: app.post('/api/data', Security.custom(myRule), handler)
*/
static custom(rule) {
const securityRule = {
type: 'custom',
customRule: rule
};
// Add middleware function
const middleware = (req, res, next) => {
const user = req.user;
// Custom rules typically need authentication, but the actual logic
// is handled by the SecurityEnforcer
if (!user) {
res.status(401).json({
success: false,
error: 'Authentication required'
});
return;
}
next();
};
return Object.assign(middleware, securityRule);
}
// ============================================================================
// CONVENIENCE METHODS - Even more specific use cases
// ============================================================================
/**
* 👥 TEAM-BASED - Users can only access records from their team
*
* @param teamField The field that contains the team ID (default: 'teamId')
*/
static team(teamField = 'teamId') {
return Security.custom((ctx) => {
return ctx.user?.teamId === ctx.data?.[teamField] ||
ctx.user?.teamId === ctx.existingData?.[teamField];
});
}
/**
* 📝 READ-ONLY PUBLIC - Anyone can read, only admins can write
* Perfect for: announcements, company info
*/
static readOnlyPublic() {
return Security.custom((ctx) => {
if (ctx.operation === 'read')
return true;
return ctx.user?.role === 'admin';
});
}
}
// ============================================================================
// SECURITY ENFORCER - Internal implementation
// ============================================================================
export class SecurityEnforcer {
static async checkAccess(rule, context) {
try {
switch (rule.type) {
case 'public':
return true;
case 'admin':
return context.user?.role === 'admin';
case 'owner':
if (!context.user)
return false;
const ownerField = rule.ownerField || 'userId';
// For create operations, check the data being created
if (context.operation === 'create') {
return context.data?.[ownerField] === context.user.id;
}
// For read/update/delete, check existing data
return context.existingData?.[ownerField] === context.user.id;
case 'custom':
if (!rule.customRule)
return false;
const result = await rule.customRule(context);
return Boolean(result);
default:
return false;
}
}
catch (error) {
console.error('Security check failed:', error);
return false; // Fail secure
}
}
static createUnauthorizedError(operation, tableName) {
return new Error(`Unauthorized ${operation} operation on table '${tableName}'`);
}
}
// Export for convenience
export default Security;
//# sourceMappingURL=index.js.map