Policies
A password policy describes what a valid password looks like, and how lockout behaves after failed sign-ins. Each policy can be attached to Gatelin roles (roleIds). A user is governed by the policies attached to all of their roles, combined into the strictest rules; users with no role attached to a policy fall back to the first non-archived pwd_policy row (lowest id), which also drives server-side generation when you create a pwd row. The result applies to the passwords users choose in the recovery and expired-password workflows, to the password checklist shown on those pages, and to lockout and expiry.
Keeping these rules in the database rather than in code means you can tighten requirements without redeploying, but see How It Works for what takes effect immediately versus what needs a restart.
How It Works
There is no active flag. The migration seeds three policies (Public User, High Security, Standard), attached to the Gatelin roles guest, super admin/admin and user. Roles that no policy lists, and users unknown to the user service, get the first non-archived row by id.
A user's roles come from the user service (USER_SEARCH_URL, roles field). Gatelin grants the union of a user's roles and does not rank them, so Foxnox does not pick one: it merges the policies attached to the user's roles into the strictest rules.
| Rule | Merged value |
|---|---|
length | the longest |
number, symbol, lowerCase, upperCase, strict | required if any policy requires it |
expiryDays | the shortest, ignoring 0 (never expires) unless every policy has 0 |
maxFailedAttempts | the lowest |
lockoutMinutes | the longest |
Roles are picked in the admin Policies page from the list returned by the Gatelin roles search route (ADMIN_ROLES_SEARCH_URL).
Character-class and expiry rules for user-chosen passwords are read at the moment a password is rotated, not at boot. Generated passwords (POST /foxnox/pwd) follow the policy that was loaded during initPwdGeneration at process start — restart Foxnox after changing generation rules.
expiryDays is used to compute the pwdExpiry stamped on a pwd row when the password is set. Set it to 0 to disable expiry entirely.
maxFailedAttempts and lockoutMinutes are applied on each failed POST /foxnox/pwd/compare. Defaults when a policy omits them (or no policy exists) are 5 attempts and 15 minutes.
Passken generates passwords between 12 and 64 characters. A policy length below 12 is still enforced when users choose a password, but generated passwords are clamped up to 12.
The custom symbols pool is used when validating user-chosen passwords. The generator draws from Passken's built-in symbol list and does not take that pool as an argument.
Search Policies
POST /foxnox/policies/search
Content-Type: application/json
Authorization: Bearer <access_token>
{
"filters": {
"archived": { "value": false, "matchMode": "=" }
}
}Response (200 OK):
{
"rows": [
{
"id": 1,
"name": "Public User",
"description": "Default password policy for regular users",
"length": 10,
"number": true,
"symbol": true,
"lowerCase": true,
"upperCase": true,
"strict": true,
"symbols": "!@#%*_-+=:?><./()",
"expiryDays": 0,
"maxFailedAttempts": 5,
"lockoutMinutes": 15,
"archived": false
}
]
}Get Policy History
GET /foxnox/policies/:id/history
Authorization: Bearer <access_token>Create Policies
POST /foxnox/policies/
Content-Type: application/json
Authorization: Bearer <access_token>
{
"rows": [
{
"name": "strict-2026",
"description": "Tightened requirements",
"length": 16,
"number": true,
"symbol": true,
"lowerCase": true,
"upperCase": true,
"strict": true,
"symbols": "!@#$%^&*()-_=+",
"expiryDays": 60,
"maxFailedAttempts": 5,
"lockoutMinutes": 15
}
]
}Policy fields
| Field | Range | Description |
|---|---|---|
name | 1–50 chars | Policy identifier |
description | 0–100 chars | Human-readable note |
length | 6–64 | Minimum password length |
number | boolean | Require at least one digit |
symbol | boolean | Require at least one symbol |
lowerCase | boolean | Require at least one lower-case letter |
upperCase | boolean | Require at least one upper-case letter |
strict | boolean | When true, every enabled character class must be present. When false, the classes act as a pool to draw from rather than a checklist. |
symbols | 0–50 chars | The set of symbols considered valid for user-chosen passwords |
expiryDays | ≥ 0 | Days until a new password expires; 0 disables expiry |
maxFailedAttempts | ≥ 1 | Failed POST /foxnox/pwd/compare attempts before lockedUntil is set |
lockoutMinutes | ≥ 0 | How long that lock lasts |
roleIds | array of integers | Gatelin role ids this policy applies to (empty = none). Roles are not validated against Gatelin |
At least one of number, symbol, lowerCase, or upperCase must be true (enforced by a database check).
A newly inserted row only becomes the default policy (used for generation and for users without a mapped role) if every lower-id policy is archived, because that lookup is "first non-archived by id".
Update Policies
PUT /foxnox/policies/
Content-Type: application/json
Authorization: Bearer <access_token>
{
"rows": [
{
"id": 1,
"name": "Public User",
"length": 16,
"number": true,
"symbol": true,
"lowerCase": true,
"upperCase": true,
"strict": true,
"symbols": "!@#$%^&*()-_=+",
"expiryDays": 60,
"maxFailedAttempts": 5,
"lockoutMinutes": 15
}
]
}Response (200 OK): the updated rows.
Most fields are required on PUT — send the whole policy rather than a partial patch.
Archive Policies
POST /foxnox/policies/archive
Content-Type: application/json
Authorization: Bearer <access_token>
{
"rows": [
{ "id": 2 }
]
}Response (204 No Content). Do not archive every policy — password generation and validation need one non-archived row to read. Archiving the lowest-id row is how you switch to the next remaining policy.
Get Entity Schema
GET /foxnox/policies/schema
Authorization: Bearer <access_token>