All skills

Sails.js framework patterns for The Boring JavaScript Stack - actions, helpers, routes, policies, hooks, configuration, security, middleware, file uploads, deployment, and more. Use this skill when building, reviewing, or debugging any server-side code in a Sails.js application.

Use this Skill: https://skilld.dev/gh/sailscastshq/boring-stack/sails

This session only. Nothing lands on disk.

rulespolicies.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Policies

Policies are middleware functions that run before an action. They're used for authentication, authorization, and request validation.

Policy Files

Policies live in api/policies/:

// api/policies/is-logged-in.js
module.exports = async function (req, res, proceed) {
  if (req.session.userId) {
    return proceed()
  }
  return res.redirect('/login')
}

A policy receives (req, res, proceed):

  • Call proceed() to continue to the action
  • Call res.redirect(), res.forbidden(), etc. to stop execution

Policy Configuration

Configure which policies apply to which actions in config/policies.js:

// config/policies.js
module.exports.policies = {
  // Default: all actions require login
  '*': 'is-logged-in',

  // Public pages (no auth required)
  'auth/*': true, // All auth actions are public
  'view-homepage': true, // Homepage is public
  'not-found': true, // 404 page is public

  // Webhooks (no auth, no CSRF)
  'webhooks/*': true,

  // Admin-only actions
  'admin/*': ['is-logged-in', 'is-admin'],

  // Specific action overrides
  'settings/delete-account': ['is-logged-in', 'is-account-owner']
}

Policy Rules

Value Meaning
true Allow all requests (public access)
false Block all requests
'policy-name' Run the named policy
['policy-a', 'policy-b'] Run multiple policies in order (all must pass)

Policies Do NOT Cascade

Each action mapping is independent. If you set '*': 'is-logged-in', then override 'admin/*': 'is-admin', admin actions only run is-admin -- not both is-logged-in and is-admin. To require both:

'admin/*': ['is-logged-in', 'is-admin']

Common Policy Patterns

Authentication (is-logged-in)

// api/policies/is-logged-in.js
module.exports = async function (req, res, proceed) {
  if (req.session.userId) {
    return proceed()
  }

  // For Inertia requests, redirect to login
  if (req.get('X-Inertia')) {
    return res.redirect('/login')
  }

  // For API/non-Inertia requests
  return res.forbidden()
}

Authorization (is-admin)

// api/policies/is-admin.js
module.exports = async function (req, res, proceed) {
  const user = await User.findOne({ id: req.session.userId })
  if (user && user.role === 'admin') {
    return proceed()
  }
  return res.forbidden()
}

Attaching User to Request

A common pattern is to use the custom hook (not a policy) to attach the logged-in user to the request so all actions can access it:

// api/hooks/custom/index.js (in routes.before)
'GET /*': {
  skipAssets: true,
  fn: async function (req, res, next) {
    if (req.session.userId) {
      req.me = await User.findOne({ id: req.session.userId })
    }
    return next()
  }
}

Then in policies:

// api/policies/is-logged-in.js
module.exports = async function (req, res, proceed) {
  if (req.me) {
    return proceed()
  }
  return res.redirect('/login')
}

API Key Authentication

For machine-to-machine endpoints (cloud customers, microservices):

// api/policies/is-cloud-customer.js
module.exports = async function (req, res, proceed) {
  if (req.get('API-KEY') === sails.config.custom.sharedApiSecret) {
    return proceed()
  }
  return res.unauthorized()
}

Feature-Flag Gated Policy

Control access with a config-level feature flag that can be toggled without redeploying:

// api/policies/has-feature-access.js
module.exports = async function (req, res, proceed) {
  // Check global feature flag first
  if (sails.config.custom.enablePublicFeature) {
    return proceed()
  }

  // Fall back to per-user flag
  if (!req.me) {
    return req.wantsJSON ? res.sendStatus(401) : res.redirect('/login')
  }

  if (!req.me.isSuperAdmin && !req.me.canUseFeature) {
    return res.forbidden()
  }

  return proceed()
}

Content Negotiation in Policies

Use req.wantsJSON to handle both browser and API requests:

// api/policies/is-super-admin.js
module.exports = async function (req, res, proceed) {
  if (!req.me) {
    if (req.wantsJSON) {
      return res.sendStatus(401)
    } else {
      return res.redirect('/login?admin')
    }
  }

  if (!req.me.isSuperAdmin) {
    return res.forbidden()
  }

  return proceed()
}

Team Membership Check

// api/policies/is-team-member.js
module.exports = async function (req, res, proceed) {
  const teamId = req.param('id') || req.param('teamId')
  if (!teamId) return res.badRequest()

  const membership = await TeamMember.findOne({
    user: req.session.userId,
    team: teamId
  })

  if (membership) {
    req.teamMembership = membership
    return proceed()
  }

  return res.forbidden()
}

Boring Stack Default Policy Configuration

// config/policies.js
module.exports.policies = {
  '*': 'is-logged-in',

  // Auth pages
  'auth/view-login': true,
  'auth/view-signup': true,
  'auth/login': true,
  'auth/signup': true,
  'auth/view-forgot-password': true,
  'auth/send-password-reset': true,
  'auth/view-reset-password': true,
  'auth/reset-password': true,

  // Public pages
  'view-homepage': true,
  'not-found': true,
  'legal/*': true,

  // Webhooks
  'webhooks/*': true
}

Source: SKILL.md on GitHub

1 alert17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill provides comprehensive documentation and coding patterns for the Sails.js framework as used in The Boring JavaScript Stack. It includes detailed guides on application anatomy, security best practices, and production deployment. No malicious patterns or security vulnerabilities were detected.

  • Socket17d

    2 alerts: gptSecurity

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    13/20 files flagged

Signed by skilld at bf19e10. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 8 months ago
Other metadata
metadata
{
  "author": "sailscastshq",
  "version": "2.1.0",
  "tags": "sails, sailsjs, backend, mvc, actions, helpers, routes, policies, hooks, middleware, deployment, boring-stack"
}

README badge

README badge for sailscastshq/boring-stack/sails