Overview

STD Plus uses JWT Bearer Token authentication. All protected endpoints require a valid token in the Authorization header.

Roles & Authorization

STD Plus uses a dynamic, permission-based access control system. Roles are fully customizable — admins can create any role with any combination of permissions.

How It Works

  1. Roles are stored in the database and managed via the /api/roles endpoints
  2. Each role has a permissions array — a list of permission keys like orders:view, panes:scan
  3. Every worker is assigned one role (as an ObjectId reference)
  4. On login, the full role object (with permissions) is returned
  5. Every API endpoint checks the worker’s permissions before allowing access

System Roles (Defaults)

Three system roles are pre-seeded. They cannot be deleted, but their permissions can be modified.
RoleSlugPermissions
Adminadmin* (wildcard — full access to everything)
ManagermanagerAll permissions except workers:manage and roles:manage
WorkerworkerView-only for most resources, plus claims:create, panes:scan, pane_logs:view, production_logs:create, inventory:move, withdrawals:create
You can create additional custom roles (e.g. “หัวหน้าช่าง”, “พนักงานสต๊อก”) via POST /api/roles.

Permission Keys

Permissions follow the resource:action format. Use GET /api/roles/permissions to fetch the full list programmatically.
ResourceAvailable Permissions
Workersworkers:view, workers:manage
Customerscustomers:view, customers:manage
Materialsmaterials:view, materials:manage
Inventoriesinventory:view, inventory:move, inventory:manage
Material Logsmaterial_logs:view, material_logs:manage
Requestsrequests:view, requests:manage
Ordersorders:view, orders:create, orders:manage
Claimsclaims:view, claims:create, claims:approve, claims:manage
Panespanes:view, panes:create, panes:scan, panes:manage
Pane Logspane_logs:view
Production Logsproduction_logs:view, production_logs:create, production_logs:manage
Stationsstations:view, stations:manage
Station Templatesstation_templates:view, station_templates:manage
Sticker Templatessticker_templates:view, sticker_templates:manage
Job Typesjob_types:view, job_types:manage
Pricingpricing:view, pricing:manage
Notificationsnotifications:view, notifications:create, notifications:manage
Withdrawalswithdrawals:view, withdrawals:create, withdrawals:manage
Rolesroles:view, roles:manage
:view grants read access (GET). :create grants creation (POST). :manage grants full CRUD (create, update, delete). A role with orders:view and orders:create can list and create orders, but cannot update or delete them.

Ownership Checks

Some endpoints enforce ownership for users without the :manage permission:
  • Orders — users with orders:view (but not orders:manage) can only update orders assigned to them
  • Claims — users with claims:view (but not claims:manage) can only update claims they reported
  • Notifications — users with notifications:view (but not notifications:manage) can only update their own notifications

Getting a Token

Send a login request with valid credentials:
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "admin123"}'
Response:
{
  "success": true,
  "message": "Login successful",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "worker": {
      "_id": "69a7fe4578410c57fad056f2",
      "name": "Admin",
      "username": "admin",
      "position": "admin",
      "role": {
        "_id": "69a7fe4578410c57fad056e0",
        "name": "Admin",
        "slug": "admin",
        "permissions": ["*"],
        "isSystem": true
      }
    }
  }
}
The role field is a fully populated object. Use role.permissions to check access on the frontend, and role.slug or role.name for display.

Using the Token

Include the token in every protected request:
curl http://localhost:3000/api/workers \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Token Details

PropertyValue
AlgorithmHS256
ExpirationConfigurable via JWT_EXPIRES_IN (default: 7d in src/config/env.js)
Payload{ id: "<worker_id>" }

Error Responses

{
  "success": false,
  "message": "Not authenticated"
}
{
  "success": false,
  "message": "Invalid or expired token"
}
{
  "success": false,
  "message": "Not authorized for this action"
}
{
  "success": false,
  "message": "Invalid username or password"
}

WebSocket Authentication

The same JWT is used for WebSocket connections. Pass it during the Socket.IO handshake:
import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  path: '/api/socket-entry',
  auth: { token: 'your-jwt-token' },
});