@astralibx/staff-engine 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/dist/index.cjs +1087 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +262 -0
- package/dist/index.d.ts +262 -0
- package/dist/index.mjs +1049 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# @astralibx/staff-engine
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@astralibx/staff-engine)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Staff management backend with JWT authentication, role-based token expiry, IP-based rate limiting, runtime-configurable permission groups, and a REST admin API. No hardcoded permissions -- all groups and entries are defined at runtime via API.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @astralibx/staff-engine
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Peer Dependencies
|
|
15
|
+
|
|
16
|
+
| Package | Required |
|
|
17
|
+
|---------|----------|
|
|
18
|
+
| `express` | Yes |
|
|
19
|
+
| `mongoose` | Yes |
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install express mongoose
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createStaffEngine } from '@astralibx/staff-engine';
|
|
29
|
+
import mongoose from 'mongoose';
|
|
30
|
+
import bcrypt from 'bcryptjs';
|
|
31
|
+
import express from 'express';
|
|
32
|
+
|
|
33
|
+
const app = express();
|
|
34
|
+
app.use(express.json());
|
|
35
|
+
|
|
36
|
+
const connection = mongoose.createConnection('mongodb://localhost:27017/myapp');
|
|
37
|
+
|
|
38
|
+
const engine = createStaffEngine({
|
|
39
|
+
db: { connection },
|
|
40
|
+
auth: {
|
|
41
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
42
|
+
},
|
|
43
|
+
adapters: {
|
|
44
|
+
hashPassword: (plain) => bcrypt.hash(plain, 12),
|
|
45
|
+
comparePassword: (plain, hash) => bcrypt.compare(plain, hash),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
app.use('/api/staff', engine.routes);
|
|
50
|
+
app.listen(3000);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
### Authentication
|
|
56
|
+
|
|
57
|
+
- **JWT with role-based expiry** -- login returns a JWT signed with `jwtSecret`. Owners get `ownerTokenExpiry` (default `30d`); staff members get `staffTokenExpiry` (default `24h`). Both are configurable.
|
|
58
|
+
- **Login with IP rate limiting** -- `POST /login` tracks failed attempts per IP using Redis sorted sets or in-memory fallback. Locks out after `maxAttempts` failures within `windowMs` (default: 5 attempts / 15 min). Returns `STAFF_RATE_LIMITED` on lockout.
|
|
59
|
+
- **Setup route auto-locks** -- `POST /setup` creates the initial owner account and then permanently locks itself. Any subsequent call returns `STAFF_SETUP_ALREADY_COMPLETE`. This route is always public and never requires a token.
|
|
60
|
+
- **Token distinguishes expired vs invalid** -- `verifyToken` middleware checks `TokenExpiredError` separately from all other JWT errors and returns `STAFF_TOKEN_EXPIRED` vs `STAFF_TOKEN_INVALID` so clients can show the correct message.
|
|
61
|
+
|
|
62
|
+
### Staff Management
|
|
63
|
+
|
|
64
|
+
- **Create staff with permissions** -- owner creates staff with name, email, password hash (via adapter), optional initial permissions, and optional `externalUserId` for linking to external identity systems.
|
|
65
|
+
- **Email uniqueness** -- duplicate email rejected with `STAFF_EMAIL_EXISTS`. Can be disabled via `requireEmailUniqueness: false`.
|
|
66
|
+
- **Paginated list with filters** -- `GET /` supports `status`, `role`, `page`, and `limit` query params. Returns `data[]` + `pagination` with total counts.
|
|
67
|
+
- **Owner-only access** -- all staff CRUD routes (`GET /`, `POST /`, `PUT /:id`, `PUT /:id/permissions`, `PUT /:id/status`, `PUT /:id/password`) require owner role. `GET /me` and `PUT /me/password` are staff-accessible.
|
|
68
|
+
- **No-delete policy** -- staff records are never hard-deleted. Deactivate to revoke access. Inactive staff tokens are rejected on every authenticated request even before JWT expiry.
|
|
69
|
+
|
|
70
|
+
### Permissions
|
|
71
|
+
|
|
72
|
+
- **Runtime-configurable groups via API** -- `POST /permission-groups` creates a named group with permission entries. Groups have `groupId`, `label`, `sortOrder`, and an array of entries (key, label, type). No redeploy required to define new permissions.
|
|
73
|
+
- **Edit-to-view cascade** -- when granting a permission ending in `.edit`, the corresponding `.view` key is automatically required. The engine validates this on `PUT /:id/permissions`.
|
|
74
|
+
- **Permission cache (Redis or in-memory)** -- each staff member's resolved permission list is cached after the first lookup. TTL is `permissionCacheTtlMs` (default 5 min). Cache is invalidated immediately on `updatePermissions` or `updateStatus`.
|
|
75
|
+
- **Owner bypasses all checks** -- `requirePermission` middleware skips the permission check entirely when `req.user.role === 'owner'`.
|
|
76
|
+
|
|
77
|
+
### Security
|
|
78
|
+
|
|
79
|
+
- **Rate limiting (configurable window/max)** -- `windowMs` and `maxAttempts` are configurable at engine creation time. Uses Redis `ZADD`/`ZREMRANGEBYSCORE` for accurate per-IP tracking across multiple processes.
|
|
80
|
+
- **Last-owner guard** -- `PUT /:id/status` with `status: 'inactive'` checks that at least one other active owner exists before allowing the change. Returns `STAFF_LAST_OWNER_GUARD` if blocked.
|
|
81
|
+
- **Inactive token rejection** -- every call through `verifyToken` re-reads staff status from MongoDB (with optional tenant filter). Inactive or pending accounts are rejected with `STAFF_TOKEN_INVALID` even with an otherwise valid JWT.
|
|
82
|
+
- **Status checks on every request** -- `verifyToken` loads status and role fresh on each request. There is no session store; the database is the source of truth.
|
|
83
|
+
|
|
84
|
+
### Integration
|
|
85
|
+
|
|
86
|
+
- **`resolveStaff` for programmatic token resolution** -- `engine.auth.resolveStaff(token)` returns `{ staffId, role, permissions }` or `null` without sending an HTTP response. Useful for WebSocket authentication or programmatic access in other modules.
|
|
87
|
+
- **`requirePermission` middleware for consumer routes** -- `engine.auth.requirePermission('contacts.view', 'contacts.edit')` returns an Express middleware that checks the current user's permissions. Owners always pass. Non-owners are rejected with `STAFF_INSUFFICIENT_PERMISSIONS` and a list of missing keys.
|
|
88
|
+
- **`requireRole` middleware** -- `engine.auth.requireRole('owner', 'staff')` checks the token's resolved role against the provided list.
|
|
89
|
+
|
|
90
|
+
## Routes
|
|
91
|
+
|
|
92
|
+
| Method | Path | Auth | Description |
|
|
93
|
+
|--------|------|------|-------------|
|
|
94
|
+
| `POST` | `/setup` | Public | Create initial owner account (auto-locks after first use) |
|
|
95
|
+
| `POST` | `/login` | Public | Authenticate and receive JWT token |
|
|
96
|
+
| `GET` | `/me` | Staff | Get current staff profile and resolved permissions |
|
|
97
|
+
| `PUT` | `/me/password` | Staff | Change own password (only when `allowSelfPasswordChange: true`) |
|
|
98
|
+
| `GET` | `/` | Owner | List staff with pagination and status/role filters |
|
|
99
|
+
| `POST` | `/` | Owner | Create a new staff member |
|
|
100
|
+
| `PUT` | `/:staffId` | Owner | Update staff name, email, metadata |
|
|
101
|
+
| `PUT` | `/:staffId/permissions` | Owner | Replace staff permission set |
|
|
102
|
+
| `PUT` | `/:staffId/status` | Owner | Activate or deactivate a staff member |
|
|
103
|
+
| `PUT` | `/:staffId/password` | Owner | Reset a staff member's password |
|
|
104
|
+
| `GET` | `/permission-groups` | Staff | List all permission groups |
|
|
105
|
+
| `POST` | `/permission-groups` | Owner | Create a new permission group |
|
|
106
|
+
| `PUT` | `/permission-groups/:groupId` | Owner | Update a permission group's entries or label |
|
|
107
|
+
| `DELETE` | `/permission-groups/:groupId` | Owner | Delete a permission group |
|
|
108
|
+
|
|
109
|
+
## Architecture
|
|
110
|
+
|
|
111
|
+
The factory function returns a single `StaffEngine` object:
|
|
112
|
+
|
|
113
|
+
| Export | Purpose |
|
|
114
|
+
|--------|---------|
|
|
115
|
+
| `engine.routes` | Express router -- mount at `/api/staff` or similar |
|
|
116
|
+
| `engine.auth.verifyToken` | Middleware to authenticate any route |
|
|
117
|
+
| `engine.auth.requirePermission(...keys)` | Middleware for permission-gated routes |
|
|
118
|
+
| `engine.auth.ownerOnly` | Middleware to restrict to owner role |
|
|
119
|
+
| `engine.auth.resolveStaff(token)` | Programmatic token resolution (no HTTP response) |
|
|
120
|
+
| `engine.staff` | Direct access to `StaffService` for programmatic use |
|
|
121
|
+
| `engine.permissions` | Direct access to `PermissionService` |
|
|
122
|
+
| `engine.models` | Mongoose models (`Staff`, `PermissionGroup`) |
|
|
123
|
+
| `engine.destroy()` | Flush permission cache and clean up resources |
|
|
124
|
+
|
|
125
|
+
## Redis Key Prefix (Required for Multi-Project Deployments)
|
|
126
|
+
|
|
127
|
+
> **WARNING:** If multiple projects share the same Redis server, you MUST set a unique `keyPrefix` per project. Without this, rate limiter state and permission cache entries will collide across projects.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const engine = createStaffEngine({
|
|
131
|
+
redis: {
|
|
132
|
+
connection: redis,
|
|
133
|
+
keyPrefix: 'myproject:staff:', // REQUIRED if sharing Redis
|
|
134
|
+
},
|
|
135
|
+
// ...
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Links
|
|
140
|
+
|
|
141
|
+
- [GitHub](https://github.com/Hariprakash1997/astralib/tree/main/packages/staff/staff-engine)
|
|
142
|
+
- [staff-types](https://github.com/Hariprakash1997/astralib/tree/main/packages/staff/staff-types)
|
|
143
|
+
- [staff-ui](https://github.com/Hariprakash1997/astralib/tree/main/packages/staff/staff-ui)
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|