You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Sails JS 2018用户管理最佳实践:多权限与子域名配置问询

Hey there! Since you're working with Sails.js v0.12.13 and need multi-tenant subdomain support plus layered role-based access control without Passport, here's a practical, battle-tested approach tailored to your setup:

Best Practices for Multi-Tenant & Role-Based Access in Sails.js v0.12.13

1. Multi-Tenant Subdomain Handling

First, you’ll need to map subdomains like customer1.app.com to individual customer records. Here’s how to implement this seamlessly:

  • Subdomain Parsing Middleware: Add custom middleware in config/http.js to extract the subdomain and attach the corresponding customer to the request:
    // config/http.js
    module.exports.http = {
      middleware: {
        order: [
          'startRequestTimer',
          'parseSubdomain', // Insert this before other auth-related middleware
          'cookieParser',
          // ... rest of your middleware order
        ],
        parseSubdomain: function(req, res, next) {
          const host = req.hostname;
          const parts = host.split('.');
          
          // Adjust this logic to match your base domain structure
          if (parts.length >= 3 && parts.slice(-2).join('.') === 'app.com') {
            req.subdomain = parts[0];
            // Fetch customer from DB using the subdomain
            Customer.findOne({ subdomain: req.subdomain }).exec((err, customer) => {
              if (err) return next(err);
              if (!customer) return res.notFound('Customer not found');
              req.customer = customer;
              next();
            });
          } else {
            // Root domain (e.g., app.com) for global admin access
            req.subdomain = null;
            next();
          }
        }
      }
    };
    
  • Customer Model: Create a Customer model with a unique subdomain field to link subdomains to customer data:
    // api/models/Customer.js
    module.exports = {
      attributes: {
        subdomain: { type: 'string', required: true, unique: true },
        name: { type: 'string', required: true },
        // Add other customer-specific fields as needed
      }
    };
    
  • Hosting Setup: Ensure your DNS has a wildcard record (*.app.com) pointing to your server. If using a reverse proxy like Nginx, configure it to pass the full host header to Sails.

2. Role-Based Access Control (RBAC) with Policies

Sails’ built-in policies are perfect for enforcing granular permissions. Here’s how to structure them:

  • Enhance User Model: Update your User model to include role and customer association:
    // api/models/User.js
    module.exports = {
      attributes: {
        email: { type: 'string', required: true, unique: true },
        password: { type: 'string', required: true },
        role: { 
          type: 'string', 
          enum: ['global-admin', 'global-user', 'customer-admin', 'customer-user'], 
          required: true 
        },
        customer: { model: 'Customer', allowNull: true } // Null for global roles
      }
    };
    
  • Create Core Policies: Build reusable policies in api/policies/ to check authentication and roles:
    • isAuthenticated.js: Verify the user is logged in via session
      module.exports = function(req, res, next) {
        if (req.session.userId) {
          User.findOne(req.session.userId).exec((err, user) => {
            if (err) return next(err);
            if (!user) return res.forbidden('Please log in to access this resource');
            req.user = user;
            next();
          });
        } else {
          return res.forbidden('Please log in to access this resource');
        }
      };
      
    • isGlobalAdmin.js: Restrict access to global admins only
      module.exports = function(req, res, next) {
        if (req.user.role === 'global-admin') return next();
        return res.forbidden('Only global admins can access this resource');
      };
      
    • isCustomerAdmin.js: Ensure the user is the admin of the current subdomain’s customer
      module.exports = function(req, res, next) {
        if (!req.customer) return res.forbidden('This resource requires a customer context');
        if (req.user.role === 'customer-admin' && req.user.customer === req.customer.id) {
          return next();
        }
        return res.forbidden('Only this customer\'s admin can access this resource');
      };
      
  • Apply Policies to Routes: Assign policies in config/routes.js to protect your endpoints:
    // config/routes.js
    module.exports.routes = {
      // Global admin routes
      'GET /admin/dashboard': { 
        controller: 'AdminController', 
        action: 'dashboard', 
        policies: ['isAuthenticated', 'isGlobalAdmin'] 
      },
      // Customer admin routes
      'GET /customer/settings': { 
        controller: 'CustomerController', 
        action: 'settings', 
        policies: ['isAuthenticated', 'isCustomerAdmin'] 
      },
      // Customer user routes (create a combined policy for users/admins if needed)
      'GET /customer/dashboard': { 
        controller: 'CustomerController', 
        action: 'dashboard', 
        policies: ['isAuthenticated', 'isCustomerUserOrAdmin'] 
      },
    };
    

3. Custom Authentication (No Passport)

Since you don’t need social login, a simple session-based auth system works perfectly:

  • Password Hashing: Use bcrypt to hash passwords before saving users. Add a lifecycle hook to the User model:
    // api/models/User.js
    const bcrypt = require('bcrypt');
    
    module.exports = {
      attributes: { /* ... */ },
      beforeCreate: function(values, next) {
        // Use bcrypt v0.8.7 (compatible with Sails 0.12's Node.js version)
        bcrypt.hash(values.password, 10, (err, hash) => {
          if (err) return next(err);
          values.password = hash;
          next();
        });
      }
    };
    
    Install the compatible bcrypt version: npm install bcrypt@^0.8.7
  • Login/Logout Actions: Create an AuthController to handle user authentication:
    // api/controllers/AuthController.js
    const bcrypt = require('bcrypt');
    
    module.exports = {
      login: function(req, res) {
        const { email, password } = req.body;
        User.findOne({ email }).exec((err, user) => {
          if (err) return res.serverError(err);
          if (!user) return res.badRequest('Invalid email or password');
          bcrypt.compare(password, user.password, (err, match) => {
            if (err) return res.serverError(err);
            if (!match) return res.badRequest('Invalid email or password');
            // Store user ID in session
            req.session.userId = user.id;
            return res.ok('Logged in successfully');
          });
        });
      },
      logout: function(req, res) {
        req.session.destroy();
        return res.ok('Logged out successfully');
      }
    };
    
  • Session Configuration: For production, switch from the default memory session store to a persistent option like Redis (configure in config/session.js).

4. Future-Proofing Tips

  • Role Abstraction: Instead of hardcoding roles, consider a Role model with linked permissions. This lets you add new roles later without rewriting policies.
  • Tenant Isolation: Add lifecycle hooks to customer-specific models to automatically filter queries by customer: req.customer.id, preventing cross-tenant data leaks.
  • Testing: Write unit tests for your policies and authentication flows to ensure permissions work as expected when adding new roles or features.

内容的提问来源于stack exchange,提问作者StS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:18:01