@bloomneo/appkit 1.5.1 โ†’ 1.5.2

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.
Files changed (111) hide show
  1. package/AGENTS.md +195 -0
  2. package/CHANGELOG.md +253 -0
  3. package/README.md +147 -799
  4. package/bin/commands/generate.js +7 -7
  5. package/cookbook/README.md +26 -0
  6. package/cookbook/api-key-service.ts +106 -0
  7. package/cookbook/auth-protected-crud.ts +112 -0
  8. package/cookbook/file-upload-pipeline.ts +113 -0
  9. package/cookbook/multi-tenant-saas.ts +87 -0
  10. package/cookbook/real-time-chat.ts +121 -0
  11. package/dist/auth/auth.d.ts +21 -4
  12. package/dist/auth/auth.d.ts.map +1 -1
  13. package/dist/auth/auth.js +56 -44
  14. package/dist/auth/auth.js.map +1 -1
  15. package/dist/auth/defaults.d.ts +1 -1
  16. package/dist/auth/defaults.js +35 -35
  17. package/dist/cache/cache.d.ts +29 -6
  18. package/dist/cache/cache.d.ts.map +1 -1
  19. package/dist/cache/cache.js +72 -44
  20. package/dist/cache/cache.js.map +1 -1
  21. package/dist/cache/defaults.js +25 -25
  22. package/dist/cache/index.d.ts +19 -10
  23. package/dist/cache/index.d.ts.map +1 -1
  24. package/dist/cache/index.js +21 -18
  25. package/dist/cache/index.js.map +1 -1
  26. package/dist/config/defaults.d.ts +1 -1
  27. package/dist/config/defaults.js +8 -8
  28. package/dist/config/index.d.ts +3 -3
  29. package/dist/config/index.js +4 -4
  30. package/dist/database/adapters/mongoose.js +2 -2
  31. package/dist/database/adapters/prisma.js +2 -2
  32. package/dist/database/defaults.d.ts +1 -1
  33. package/dist/database/defaults.js +4 -4
  34. package/dist/database/index.js +2 -2
  35. package/dist/database/index.js.map +1 -1
  36. package/dist/email/defaults.js +20 -20
  37. package/dist/error/defaults.d.ts +1 -1
  38. package/dist/error/defaults.js +12 -12
  39. package/dist/error/error.d.ts +12 -0
  40. package/dist/error/error.d.ts.map +1 -1
  41. package/dist/error/error.js +19 -0
  42. package/dist/error/error.js.map +1 -1
  43. package/dist/error/index.d.ts +14 -3
  44. package/dist/error/index.d.ts.map +1 -1
  45. package/dist/error/index.js +14 -3
  46. package/dist/error/index.js.map +1 -1
  47. package/dist/event/defaults.js +30 -30
  48. package/dist/logger/defaults.d.ts +1 -1
  49. package/dist/logger/defaults.js +40 -40
  50. package/dist/logger/index.d.ts +1 -0
  51. package/dist/logger/index.d.ts.map +1 -1
  52. package/dist/logger/index.js.map +1 -1
  53. package/dist/logger/logger.d.ts +8 -0
  54. package/dist/logger/logger.d.ts.map +1 -1
  55. package/dist/logger/logger.js +13 -3
  56. package/dist/logger/logger.js.map +1 -1
  57. package/dist/logger/transports/console.js +1 -1
  58. package/dist/logger/transports/http.d.ts +1 -1
  59. package/dist/logger/transports/http.js +1 -1
  60. package/dist/logger/transports/webhook.d.ts +1 -1
  61. package/dist/logger/transports/webhook.js +1 -1
  62. package/dist/queue/defaults.d.ts +2 -2
  63. package/dist/queue/defaults.js +38 -38
  64. package/dist/security/defaults.d.ts +1 -1
  65. package/dist/security/defaults.js +29 -29
  66. package/dist/security/index.d.ts +1 -1
  67. package/dist/security/index.js +3 -3
  68. package/dist/security/security.d.ts +1 -1
  69. package/dist/security/security.js +4 -4
  70. package/dist/storage/defaults.js +19 -19
  71. package/dist/util/defaults.d.ts +1 -1
  72. package/dist/util/defaults.js +34 -34
  73. package/dist/util/env.d.ts +35 -0
  74. package/dist/util/env.d.ts.map +1 -0
  75. package/dist/util/env.js +50 -0
  76. package/dist/util/env.js.map +1 -0
  77. package/dist/util/errors.d.ts +52 -0
  78. package/dist/util/errors.d.ts.map +1 -0
  79. package/dist/util/errors.js +82 -0
  80. package/dist/util/errors.js.map +1 -0
  81. package/examples/.env.example +80 -0
  82. package/examples/README.md +16 -0
  83. package/examples/auth.ts +228 -0
  84. package/examples/cache.ts +36 -0
  85. package/examples/config.ts +45 -0
  86. package/examples/database.ts +69 -0
  87. package/examples/email.ts +53 -0
  88. package/examples/error.ts +50 -0
  89. package/examples/event.ts +42 -0
  90. package/examples/logger.ts +41 -0
  91. package/examples/queue.ts +58 -0
  92. package/examples/security.ts +46 -0
  93. package/examples/storage.ts +44 -0
  94. package/examples/util.ts +47 -0
  95. package/llms.txt +591 -0
  96. package/package.json +19 -10
  97. package/src/auth/README.md +850 -0
  98. package/src/cache/README.md +756 -0
  99. package/src/config/README.md +604 -0
  100. package/src/database/README.md +818 -0
  101. package/src/email/README.md +759 -0
  102. package/src/error/README.md +660 -0
  103. package/src/event/README.md +729 -0
  104. package/src/logger/README.md +435 -0
  105. package/src/queue/README.md +851 -0
  106. package/src/security/README.md +612 -0
  107. package/src/storage/README.md +1008 -0
  108. package/src/util/README.md +955 -0
  109. package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
  110. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
  111. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
@@ -0,0 +1,850 @@
1
+ # @bloomneo/appkit - Authentication Module ๐Ÿ”
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@bloomneo/appkit.svg)](https://www.npmjs.com/package/@bloomneo/appkit)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ > Ultra-simple authentication with JWT tokens, bcrypt passwords, and role-based permissions. Express-only middleware with clear separation between user authentication and API access.
7
+
8
+ **Two token types** for different authentication needs: Login tokens for users, API tokens for external services. Built-in role hierarchy and permission inheritance. Production-ready security.
9
+
10
+ ## ๐Ÿš€ Why Choose This?
11
+
12
+ - **โšก Simple API** - Just `authClass.get()`, everything else is automatic
13
+ - **๐Ÿ”’ Two Token Types** - Login tokens for users, API tokens for services
14
+ - **๐ŸŽฏ Clear Separation** - No confusion between user auth and API auth
15
+ - **๐Ÿ‘ฅ Smart Role Hierarchy** - Built-in role.level inheritance (user.basic โ†’ admin.system)
16
+ - **๐Ÿ”ง Zero Configuration** - Smart defaults for everything
17
+ - **๐Ÿ›ก๏ธ Null-Safe Access** - Safe user extraction with `auth.user(req)`
18
+ - **๐Ÿค– AI-Ready** - Optimized for LLM code generation with clear method names
19
+
20
+ ## ๐Ÿ“ฆ Installation
21
+
22
+ ```bash
23
+ npm install @bloomneo/appkit
24
+ ```
25
+
26
+ ## ๐Ÿƒโ€โ™‚๏ธ Quick Start (30 seconds)
27
+
28
+ **โš ๏ธ AUTH_SECRET Required**: You must generate a secure secret for JWT tokens.
29
+
30
+ ```bash
31
+ # Generate and set your JWT secret (required for startup)
32
+ echo "BLOOM_AUTH_SECRET=your-super-secure-jwt-secret-key-2024-minimum-32-chars" > .env
33
+ ```
34
+
35
+ ```typescript
36
+ import { authClass } from '@bloomneo/appkit/auth';
37
+
38
+ const auth = authClass.get();
39
+
40
+ // User authentication (login tokens)
41
+ const loginToken = auth.generateLoginToken({
42
+ userId: 123,
43
+ role: 'user',
44
+ level: 'basic'
45
+ });
46
+
47
+ // API authentication (API tokens)
48
+ // IMPORTANT: role.level must exist in the configured role hierarchy.
49
+ // The default hierarchy ships with user.basic โ†’ admin.system. To use a
50
+ // custom service role like 'service.webhook', register it via the
51
+ // BLOOM_AUTH_ROLES env var first (see "Custom Role Examples" below).
52
+ const apiToken = auth.generateApiToken({
53
+ keyId: 'webhook_service',
54
+ role: 'admin',
55
+ level: 'system'
56
+ });
57
+
58
+ // Express middleware protection
59
+ app.get('/user/profile', auth.requireLoginToken(), handler);
60
+ app.post('/api/webhook', auth.requireApiToken(), handler);
61
+ app.get('/admin', auth.requireLoginToken(), auth.requireUserRoles(['admin.tenant']), handler);
62
+ ```
63
+
64
+ ## ๐ŸŽฏ Two Token Types - Crystal Clear
65
+
66
+ ### **Login Tokens (User Authentication)**
67
+ For humans logging into your app (mobile/web):
68
+
69
+ ```typescript
70
+ // Generate login token
71
+ const loginToken = auth.generateLoginToken({
72
+ userId: 123,
73
+ role: 'user',
74
+ level: 'basic'
75
+ }, '7d'); // Short-medium expiry
76
+
77
+ // Protect user routes
78
+ app.get('/profile', auth.requireLoginToken(), handler);
79
+ app.get('/admin',
80
+ auth.requireLoginToken(),
81
+ auth.requireUserRoles(['admin.tenant']),
82
+ handler
83
+ );
84
+ ```
85
+
86
+ ### **API Tokens (External Access)**
87
+ For third-party services, webhooks, and integrations:
88
+
89
+ ```typescript
90
+ // Generate API token
91
+ // Uses admin.system from the default role hierarchy. For narrower service
92
+ // scopes, register custom roles via BLOOM_AUTH_ROLES (see Custom Roles below).
93
+ const apiToken = auth.generateApiToken({
94
+ keyId: 'webhook_payment_service',
95
+ role: 'admin',
96
+ level: 'system'
97
+ }, '1y'); // Long expiry
98
+
99
+ // Protect API routes (no user roles/permissions)
100
+ app.post('/webhook/payment', auth.requireApiToken(), handler);
101
+ app.get('/api/public-data', auth.requireApiToken(), handler);
102
+ ```
103
+
104
+ ## ๐Ÿ—๏ธ Role-Level-Permission Architecture
105
+
106
+ **Built-in Role Hierarchy (9 levels):**
107
+
108
+ ```typescript
109
+ 'user.basic' // Level 1 - Basic user
110
+ 'user.pro' // Level 2 - Premium user
111
+ 'user.max' // Level 3 - Max user
112
+ 'moderator.review' // Level 4 - Can review content
113
+ 'moderator.approve'// Level 5 - Can approve content
114
+ 'moderator.manage' // Level 6 - Can manage content
115
+ 'admin.tenant' // Level 7 - Tenant admin
116
+ 'admin.org' // Level 8 - Organization admin
117
+ 'admin.system' // Level 9 - System admin
118
+ ```
119
+
120
+ **Permission System:**
121
+ - **Actions**: `view`, `create`, `edit`, `delete`, `manage`
122
+ - **Scopes**: `own`, `tenant`, `org`, `system`
123
+ - **Format**: `action:scope` (e.g., `manage:tenant`)
124
+
125
+ **Inheritance Examples:**
126
+ ```typescript
127
+ // โœ… These return TRUE (higher includes lower)
128
+ auth.hasRole('admin.org', 'admin.tenant'); // org > tenant
129
+ auth.hasRole('admin.system', 'user.basic'); // system > basic
130
+
131
+ // โŒ These return FALSE (lower cannot access higher)
132
+ auth.hasRole('user.basic', 'admin.tenant'); // basic < tenant
133
+ ```
134
+
135
+ ## ๐Ÿงญ Which case is your app? (Decision tree)
136
+
137
+ The 9-level default hierarchy is **scaffolding, not a requirement**. Most apps
138
+ only use 3 roles. Pick your shape, ignore the rest.
139
+
140
+ **The 3 core roles every app needs:**
141
+
142
+ | Role | Purpose | Example |
143
+ |---|---|---|
144
+ | `user.basic` | Regular end-user | The person using the product |
145
+ | `moderator.manage` | Content / community lead | Reviews flagged content, manages users |
146
+ | `admin.system` | You / your team | Owns the platform |
147
+
148
+ Everything else (`user.pro`, `user.max`, `moderator.review`, `moderator.approve`,
149
+ `admin.tenant`, `admin.org`) is **optional** and only needed if your app has
150
+ **pricing tiers** or **admin-level scoping** (multi-tenancy / multi-org). Skip
151
+ them on day one โ€” add only when product reality demands it.
152
+
153
+ ---
154
+
155
+ ### Case 1 โ€” Admin + users (the ~50% case)
156
+
157
+ > **Examples:** todo apps, single-team SaaS, internal tools, blogs with comments,
158
+ > a course platform, a personal dashboard.
159
+
160
+ **Roles you actually use:** `user.basic`, `moderator.manage`, `admin.system`
161
+ **Roles you ignore:** everything else (5 roles)
162
+
163
+ ```typescript
164
+ // Sign up a regular user
165
+ const token = auth.generateLoginToken({
166
+ userId: user.id,
167
+ role: 'user',
168
+ level: 'basic',
169
+ });
170
+
171
+ // Protect a user route
172
+ app.get('/dashboard', auth.requireLoginToken(), handler);
173
+
174
+ // Protect a moderator route (admin.system also passes โ€” higher includes lower)
175
+ app.post('/flag/:id/review',
176
+ auth.requireLoginToken(),
177
+ auth.requireUserRoles(['moderator.manage']),
178
+ handler,
179
+ );
180
+
181
+ // Protect an admin-only route
182
+ app.delete('/users/:id',
183
+ auth.requireLoginToken(),
184
+ auth.requireUserRoles(['admin.system']),
185
+ handler,
186
+ );
187
+ ```
188
+
189
+ **You don't need:** custom permissions, multi-tenancy, `BLOOM_DB_TENANT`,
190
+ `admin.tenant`, `admin.org`, pricing tiers.
191
+
192
+ **Optional (add later):** `user.pro` / `user.max` if you launch paid plans.
193
+
194
+ ---
195
+
196
+ ### Case 2 โ€” Admin + orgs + users (the ~30% case)
197
+
198
+ > **Examples:** a B2B SaaS where each customer is a company with multiple
199
+ > employees (Slack, Linear, Notion, GitHub teams).
200
+
201
+ **Roles you actually use:** same 3 core roles. Org membership is a **database
202
+ concern**, not an auth concern โ€” store `org_id` on the user row, not in the
203
+ JWT role.
204
+
205
+ **Roles you ignore:** `user.pro`, `user.max`, `moderator.review`,
206
+ `moderator.approve`, `admin.tenant`, `admin.org`.
207
+
208
+ ```typescript
209
+ // Sign up a user that belongs to an org
210
+ const token = auth.generateLoginToken({
211
+ userId: user.id,
212
+ role: 'user',
213
+ level: 'basic',
214
+ });
215
+
216
+ // Routes look identical to Case 1. Org isolation happens at the data layer:
217
+ app.get('/projects', auth.requireLoginToken(), async (req, res) => {
218
+ const u = auth.user(req)!;
219
+ // Query is scoped by org_id from your database, not from the JWT
220
+ const rows = await db.project.findMany({ where: { org_id: u.orgId } });
221
+ res.json(rows);
222
+ });
223
+ ```
224
+
225
+ **Optional (add later):**
226
+ - `admin.org` if you want a per-org admin role distinct from `admin.system`
227
+ - `user.pro` / `user.max` for pricing tiers
228
+ - `BLOOM_DB_TENANT=auto` (in the database module) to **auto-filter** queries
229
+ by `org_id` instead of writing it in every `where` clause
230
+
231
+ ---
232
+
233
+ ### Case 3 โ€” Admin + orgs + tenants (the ~20% case)
234
+
235
+ > **Examples:** a multi-tenant platform where each org owns multiple isolated
236
+ > tenants (a white-label SaaS provider, a CRM that hosts brands per workspace,
237
+ > Shopify-style multi-store).
238
+
239
+ **Roles you actually use:** still the same 3 core roles. The extra layer
240
+ (tenant) is **also a database concern** โ€” store `tenant_id` and `org_id` on
241
+ the user row.
242
+
243
+ **Roles you ignore:** `user.pro`, `user.max`, `moderator.review`,
244
+ `moderator.approve`.
245
+
246
+ ```typescript
247
+ const token = auth.generateLoginToken({
248
+ userId: user.id,
249
+ role: 'user',
250
+ level: 'basic',
251
+ });
252
+
253
+ // Tenant isolation also happens at the data layer.
254
+ // With BLOOM_DB_TENANT=auto, the database module injects WHERE tenant_id=?
255
+ // automatically โ€” you don't write it.
256
+ app.get('/orders', auth.requireLoginToken(), async (req, res) => {
257
+ const rows = await db.order.findMany(); // auto-scoped to req.tenant_id
258
+ res.json(rows);
259
+ });
260
+ ```
261
+
262
+ **Optional (add later):**
263
+ - `admin.tenant` for a per-tenant admin role
264
+ - `admin.org` for a per-org admin role
265
+ - `user.pro` / `user.max` for pricing tiers
266
+
267
+ ---
268
+
269
+ ### Cheat sheet
270
+
271
+ | Question | Answer |
272
+ |---|---|
273
+ | "Which roles do I need on day one?" | `user.basic`, `moderator.manage`, `admin.system` โ€” always these 3 |
274
+ | "When do I add `user.pro` / `user.max`?" | When you launch paid tiers and need different feature gates |
275
+ | "When do I add `admin.tenant` / `admin.org`?" | When you have customers managing their own tenants/orgs and you don't want to give them `admin.system` |
276
+ | "Where does multi-tenancy live?" | The **database** module (`BLOOM_DB_TENANT=auto`), not auth roles |
277
+ | "Can I just delete the roles I don't use?" | No โ€” they're harmless. They only cost something if you reference them |
278
+ | "Why does the moderator role exist in Case 1?" | Almost every app eventually needs *someone* who isn't you to deal with reports and bad actors. Add the role on day one, even if you're the only person using it for the first 6 months |
279
+
280
+ ## ๐Ÿ›ก๏ธ Express Middleware Patterns
281
+
282
+ ### **User Authentication Flow**
283
+ ```typescript
284
+ // Step 1: Authenticate user
285
+ app.get('/user/dashboard', auth.requireLoginToken(), (req, res) => {
286
+ const user = auth.user(req); // Safe access, never null here
287
+ res.json({ userId: user.userId, role: user.role });
288
+ });
289
+
290
+ // Step 2: Require specific roles (user needs ANY of these)
291
+ app.get('/admin/panel',
292
+ auth.requireLoginToken(),
293
+ auth.requireUserRoles(['admin.tenant', 'admin.org']),
294
+ handler
295
+ );
296
+
297
+ // Step 3: Require specific permissions (user needs ALL of these)
298
+ app.post('/admin/users',
299
+ auth.requireLoginToken(),
300
+ auth.requireUserPermissions(['manage:users', 'edit:tenant']),
301
+ handler
302
+ );
303
+ ```
304
+
305
+ ### **API Access Flow**
306
+ ```typescript
307
+ // Simple API protection (no roles/permissions)
308
+ app.post('/api/webhook', auth.requireApiToken(), (req, res) => {
309
+ const token = auth.user(req); // Gets API token info
310
+ console.log('API call from:', token.keyId);
311
+ res.json({ status: 'received' });
312
+ });
313
+ ```
314
+
315
+ ## ๐Ÿค– LLM Quick Reference - Copy These Patterns
316
+
317
+ ### **Token Generation (Copy Exactly)**
318
+
319
+ ```typescript
320
+ // โœ… CORRECT - Login tokens for users
321
+ const loginToken = auth.generateLoginToken({
322
+ userId: 123, // Required: user identifier
323
+ role: 'user', // Required: role name
324
+ level: 'basic', // Required: level within role
325
+ permissions: ['manage:own'] // Optional: custom permissions
326
+ }, '7d');
327
+
328
+ // โœ… CORRECT - API tokens for services
329
+ // role.level must exist in the configured role hierarchy. The default
330
+ // hierarchy ships with user.basic โ†’ admin.system. To use a custom service
331
+ // role like 'service.webhook', register it via BLOOM_AUTH_ROLES first
332
+ // (see "Custom Role Examples" below).
333
+ const apiToken = auth.generateApiToken({
334
+ keyId: 'webhook_service', // Required: service identifier
335
+ role: 'admin', // Required: role name (from configured hierarchy)
336
+ level: 'system', // Required: level within role
337
+ permissions: ['webhook:receive'] // Optional: custom permissions
338
+ }, '1y');
339
+
340
+ // โŒ WRONG - Don't mix these up
341
+ auth.generateLoginToken({ keyId: 'test' }); // keyId is for API tokens
342
+ auth.generateApiToken({ userId: 123 }); // userId is for login tokens
343
+ ```
344
+
345
+ ### **Middleware Patterns (Copy These)**
346
+
347
+ ```typescript
348
+ // โœ… CORRECT - User routes with roles
349
+ app.get('/admin/users',
350
+ auth.requireLoginToken(), // Authenticate user
351
+ auth.requireUserRoles(['admin.tenant']), // Check user role
352
+ handler
353
+ );
354
+
355
+ // โœ… CORRECT - API routes (no roles)
356
+ app.post('/webhook/data',
357
+ auth.requireApiToken(), // Authenticate API token only
358
+ handler
359
+ );
360
+
361
+ // โŒ WRONG - Don't use user roles with API tokens
362
+ app.post('/webhook',
363
+ auth.requireApiToken(),
364
+ auth.requireUserRoles(['admin']), // ERROR: API tokens don't have user roles
365
+ handler
366
+ );
367
+ ```
368
+
369
+ ## โš ๏ธ Common LLM Mistakes - Avoid These
370
+
371
+ ### **Token Type Confusion**
372
+
373
+ ```typescript
374
+ // โŒ Using wrong token type for wrong purpose
375
+ const userToken = auth.generateApiToken({ userId: 123 }); // Wrong: use generateLoginToken
376
+ const apiToken = auth.generateLoginToken({ keyId: 'api' }); // Wrong: use generateApiToken
377
+
378
+ // โœ… Use correct token type for purpose
379
+ const userToken = auth.generateLoginToken({ userId: 123, role: 'user', level: 'basic' });
380
+ const apiToken = auth.generateApiToken({ keyId: 'api_key', role: 'admin', level: 'system' });
381
+ ```
382
+
383
+ ### **Middleware Errors**
384
+
385
+ ```typescript
386
+ // โŒ Trying to use user roles with API tokens
387
+ app.post('/api/data',
388
+ auth.requireApiToken(),
389
+ auth.requireUserRoles(['admin']), // ERROR: API tokens don't have user roles
390
+ handler
391
+ );
392
+
393
+ // โœ… Keep API routes simple
394
+ app.post('/api/data', auth.requireApiToken(), handler);
395
+
396
+ // โœ… Use user roles only with login tokens
397
+ app.get('/admin',
398
+ auth.requireLoginToken(),
399
+ auth.requireUserRoles(['admin.tenant']),
400
+ handler
401
+ );
402
+ ```
403
+
404
+ ### **Role Array Format**
405
+
406
+ ```typescript
407
+ // โŒ Wrong parameter types
408
+ auth.requireUserRoles('admin.tenant'); // String - should be array
409
+ auth.requireUserRoles(['admin', 'tenant']); // Wrong format - should be role.level
410
+
411
+ // โœ… Correct array format
412
+ auth.requireUserRoles(['admin.tenant']);
413
+ auth.requireUserRoles(['admin.tenant', 'admin.org']); // Multiple roles (OR logic)
414
+ auth.requireUserPermissions(['manage:users', 'edit:tenant']); // Multiple permissions (AND logic)
415
+ ```
416
+
417
+ ### **Permissions: Replacement, not Additive**
418
+
419
+ The `permissions` array on a JWT payload **replaces** the role's default
420
+ permissions โ€” it does NOT add to them. This matches AWS IAM, Casbin, OPA,
421
+ and Auth0 RBAC: explicit permissions are the truth, defaults are the fallback.
422
+
423
+ ```typescript
424
+ // โœ… NO explicit permissions โ†’ role defaults apply
425
+ const u1 = auth.generateLoginToken({ userId: 1, role: 'admin', level: 'tenant' });
426
+ // โ†’ user has all of admin.tenant's default permissions (manage:tenant, etc.)
427
+
428
+ // โœ… Explicit permissions โ†’ defaults are IGNORED, only the explicit set applies
429
+ const u2 = auth.generateLoginToken({
430
+ userId: 2,
431
+ role: 'admin',
432
+ level: 'tenant',
433
+ permissions: ['view:own'], // โ† user can ONLY view:own, despite being admin.tenant
434
+ });
435
+ // โ†’ auth.can(u2, 'manage:tenant') === false
436
+ // โ†’ auth.can(u2, 'view:own') === true
437
+
438
+ // โœ… Empty array = ZERO permissions (explicit downgrade)
439
+ const u3 = auth.generateLoginToken({
440
+ userId: 3,
441
+ role: 'admin',
442
+ level: 'tenant',
443
+ permissions: [], // โ† user has no permissions despite admin role
444
+ });
445
+ // โ†’ auth.can(u3, 'view:own') === false
446
+
447
+ // โœ… Action inheritance still works WITHIN the explicit set
448
+ const u4 = auth.generateLoginToken({
449
+ userId: 4,
450
+ role: 'admin',
451
+ level: 'tenant',
452
+ permissions: ['manage:tenant'],
453
+ });
454
+ // โ†’ auth.can(u4, 'edit:tenant') === true (manage inherits all sub-actions)
455
+ // โ†’ auth.can(u4, 'view:tenant') === true
456
+ // โ†’ auth.can(u4, 'manage:org') === false (different scope)
457
+
458
+ // โŒ Common mistake: assuming permissions are ADDITIVE
459
+ // Old (pre-1.5.2) behavior was buggy and additive. If you've seen examples
460
+ // or wrote code assuming `permissions: ['edit:own']` would extend the role
461
+ // defaults, that's no longer how it works. Pass an explicit array only when
462
+ // you want to OVERRIDE the role defaults.
463
+ ```
464
+
465
+ **Mental model:** `permissions` is the user's complete capability list when
466
+ present. To use the role's defaults, omit the field entirely.
467
+
468
+ ## ๐Ÿšจ Error Handling Patterns
469
+
470
+ ### **Token Operations**
471
+
472
+ ```typescript
473
+ try {
474
+ const loginToken = auth.generateLoginToken({ userId, role, level });
475
+ return { token: loginToken };
476
+ } catch (error) {
477
+ // Invalid role.level, missing fields, etc.
478
+ return res.status(500).json({ error: 'Token creation failed' });
479
+ }
480
+
481
+ try {
482
+ const payload = auth.verifyToken(token);
483
+ // Use payload...
484
+ } catch (error) {
485
+ if (error.message === 'Token has expired') {
486
+ return res.status(401).json({ error: 'Session expired' });
487
+ }
488
+ return res.status(401).json({ error: 'Invalid token' });
489
+ }
490
+ ```
491
+
492
+ ### **Middleware Error Handling**
493
+
494
+ ```typescript
495
+ // Errors are handled automatically by middleware
496
+ app.get('/admin',
497
+ auth.requireLoginToken(), // 401 if no/invalid token
498
+ auth.requireUserRoles(['admin.tenant']), // 403 if insufficient role
499
+ (req, res) => {
500
+ // This only runs if all auth succeeds
501
+ const user = auth.user(req); // Safe - never null here
502
+ res.json({ message: 'Welcome admin!' });
503
+ }
504
+ );
505
+ ```
506
+
507
+ ## ๐Ÿš€ Production Deployment Checklist
508
+
509
+ ### **Environment Setup**
510
+
511
+ ```bash
512
+ # โœ… Required - Strong secret (32+ characters)
513
+ BLOOM_AUTH_SECRET=your-cryptographically-secure-secret-key-here
514
+
515
+ # โœ… Recommended - Shorter expiry for security
516
+ BLOOM_AUTH_EXPIRES_IN=2h
517
+
518
+ # โœ… Performance - Higher rounds for better security
519
+ BLOOM_AUTH_BCRYPT_ROUNDS=12
520
+
521
+ # โœ… Optional - Custom role hierarchy (overrides the default user.basic โ†’ admin.system)
522
+ # These names (user.premium, admin.super) are EXAMPLES of custom roles you can
523
+ # define for your app. The default hierarchy uses user.basic, user.pro, user.max,
524
+ # moderator.review, moderator.approve, moderator.manage, admin.tenant, admin.org,
525
+ # admin.system. Set this var only if you need different role names.
526
+ BLOOM_AUTH_ROLES=user.basic:1,user.premium:2,admin.super:10
527
+
528
+ # โœ… Optional - Custom permissions (must reference roles defined above)
529
+ BLOOM_AUTH_PERMISSIONS=user.premium:manage:own,admin.super:manage:system
530
+ ```
531
+
532
+ ### **Security Validation**
533
+
534
+ ```typescript
535
+ // App startup validation
536
+ try {
537
+ const auth = authClass.get();
538
+ console.log('โœ… Auth initialized successfully');
539
+ } catch (error) {
540
+ console.error('โŒ Auth setup failed:', error.message);
541
+ process.exit(1);
542
+ }
543
+ ```
544
+
545
+ ## ๐Ÿ“– Essential Usage Patterns
546
+
547
+ ### **Complete Authentication Flow**
548
+
549
+ ```typescript
550
+ // Registration
551
+ app.post('/register', async (req, res) => {
552
+ const { email, password } = req.body;
553
+
554
+ // Hash password
555
+ const hashedPassword = await auth.hashPassword(password);
556
+
557
+ // Save user to database
558
+ const user = await User.create({ email, password: hashedPassword });
559
+
560
+ // Generate login token
561
+ const token = auth.generateLoginToken({
562
+ userId: user.id,
563
+ role: 'user',
564
+ level: 'basic'
565
+ });
566
+
567
+ res.json({ token, user: { id: user.id, email } });
568
+ });
569
+
570
+ // Login
571
+ app.post('/login', async (req, res) => {
572
+ const { email, password } = req.body;
573
+
574
+ const user = await User.findOne({ email });
575
+ if (!user) return res.status(401).json({ error: 'Invalid credentials' });
576
+
577
+ const isValid = await auth.comparePassword(password, user.password);
578
+ if (!isValid) return res.status(401).json({ error: 'Invalid credentials' });
579
+
580
+ const token = auth.generateLoginToken({
581
+ userId: user.id,
582
+ role: user.role,
583
+ level: user.level
584
+ });
585
+
586
+ res.json({ token });
587
+ });
588
+ ```
589
+
590
+ ### **API Token Management**
591
+
592
+ ```typescript
593
+ // Create API token for external service
594
+ app.post('/admin/api-tokens',
595
+ auth.requireLoginToken(),
596
+ auth.requireUserRoles(['admin.tenant']),
597
+ async (req, res) => {
598
+ const { name, permissions } = req.body;
599
+
600
+ const apiToken = auth.generateApiToken({
601
+ keyId: `api_${Date.now()}`,
602
+ role: 'admin', // role.level must exist in the configured hierarchy
603
+ level: 'system', // (default ships user.basic โ†’ admin.system)
604
+ permissions
605
+ }, '1y');
606
+
607
+ // Store token info in database (store hash, not plain token)
608
+ const hashedToken = await auth.hashPassword(apiToken);
609
+ await ApiToken.create({ name, token: hashedToken });
610
+
611
+ // Return token once (client should save it)
612
+ res.json({ apiToken });
613
+ }
614
+ );
615
+ ```
616
+
617
+ ## ๐ŸŒ Environment Variables
618
+
619
+ ```bash
620
+ # Required
621
+ BLOOM_AUTH_SECRET=your-super-secure-jwt-secret-key-2024-minimum-32-chars
622
+
623
+ # Optional
624
+ BLOOM_AUTH_BCRYPT_ROUNDS=12 # Default: 10
625
+ BLOOM_AUTH_EXPIRES_IN=1h # Default: 7d
626
+ BLOOM_AUTH_DEFAULT_ROLE=user # Default: user
627
+ BLOOM_AUTH_DEFAULT_LEVEL=basic # Default: basic
628
+
629
+ # Custom role hierarchy (optional)
630
+ BLOOM_AUTH_ROLES=user.basic:1,user.pro:2,admin.system:9
631
+
632
+ # Custom permissions (optional)
633
+ BLOOM_AUTH_PERMISSIONS=user.basic:view:own,admin.tenant:manage:tenant
634
+ ```
635
+
636
+ ## ๐Ÿ“– API Reference
637
+
638
+ ### **Core Function**
639
+
640
+ ```typescript
641
+ const auth = authClass.get(); // One function, all methods
642
+ ```
643
+
644
+ ### **Token Generation**
645
+
646
+ ```typescript
647
+ auth.generateLoginToken({ userId, role, level, permissions }, expiresIn); // Create login JWT
648
+ auth.generateApiToken({ keyId, role, level, permissions }, expiresIn); // Create API JWT
649
+ auth.verifyToken(token); // Verify any JWT token
650
+ ```
651
+
652
+ ### **Password Security**
653
+
654
+ ```typescript
655
+ auth.hashPassword(password, rounds); // Hash password with bcrypt
656
+ auth.comparePassword(password, hash); // Verify password
657
+ ```
658
+
659
+ ### **User Access**
660
+
661
+ ```typescript
662
+ auth.user(req); // Safe user extraction (returns null if not authenticated)
663
+ ```
664
+
665
+ ### **Authorization**
666
+
667
+ ```typescript
668
+ auth.hasRole(userRole, requiredRole); // Check role hierarchy
669
+ auth.can(user, permission); // Check permission
670
+ ```
671
+
672
+ ### **Express Middleware**
673
+
674
+ ```typescript
675
+ auth.requireLoginToken(options); // Login token authentication
676
+ auth.requireApiToken(options); // API token authentication
677
+ auth.requireUserRoles(roles); // User role authorization (array of strings)
678
+ auth.requireUserPermissions(permissions); // User permission authorization (array of strings)
679
+ ```
680
+
681
+ ### **Utility Methods**
682
+
683
+ ```typescript
684
+ authClass.getRoles(); // Get role hierarchy
685
+ authClass.getPermissions(); // Get permission config
686
+ authClass.getAllRoles(); // Get all roles sorted by level
687
+ authClass.isValidRole(roleLevel); // Validate role format
688
+ authClass.reset(newConfig); // Reset instance (testing only)
689
+ ```
690
+
691
+ ## ๐Ÿ”ง Custom Role Examples
692
+
693
+ ### **E-commerce Platform**
694
+
695
+ ```bash
696
+ BLOOM_AUTH_ROLES=customer.basic:1,customer.premium:2,vendor.starter:3,vendor.pro:4,staff.support:5,admin.store:6
697
+
698
+ BLOOM_AUTH_PERMISSIONS=customer.basic:view:own,customer.premium:manage:own,vendor.starter:manage:products,admin.store:manage:store
699
+ ```
700
+
701
+ ### **Healthcare System**
702
+
703
+ ```bash
704
+ BLOOM_AUTH_ROLES=patient.basic:1,nurse.junior:2,nurse.senior:3,doctor.resident:4,doctor.attending:5,admin.clinic:6
705
+
706
+ BLOOM_AUTH_PERMISSIONS=patient.basic:view:own,nurse.junior:view:patient,doctor.resident:manage:patient,admin.clinic:manage:clinic
707
+ ```
708
+
709
+ ## ๐Ÿงช Testing
710
+
711
+ ```typescript
712
+ // Reset for clean testing
713
+ const auth = authClass.reset({
714
+ jwt: { secret: 'test-secret-32-characters-long-for-security' },
715
+ roles: {
716
+ 'test.user': { level: 1, inherits: [] },
717
+ 'test.admin': { level: 2, inherits: ['test.user'] },
718
+ },
719
+ });
720
+
721
+ // Test login token
722
+ const loginToken = auth.generateLoginToken({
723
+ userId: 123,
724
+ role: 'test',
725
+ level: 'user'
726
+ });
727
+
728
+ // Test API token
729
+ const apiToken = auth.generateApiToken({
730
+ keyId: 'test_api',
731
+ role: 'test',
732
+ level: 'admin'
733
+ });
734
+
735
+ // Test middleware
736
+ const req = { headers: { authorization: `Bearer ${loginToken}` } };
737
+ const middleware = auth.requireLoginToken();
738
+ // Test with mock req/res objects
739
+ ```
740
+
741
+ ## ๐Ÿ“ˆ Performance
742
+
743
+ - **JWT Operations**: ~1ms per token
744
+ - **Password Hashing**: ~100ms (10 rounds)
745
+ - **Permission Checking**: ~0.1ms per check
746
+ - **Memory Usage**: <1MB overhead
747
+ - **Environment Parsing**: Once per app startup
748
+
749
+ ## ๐Ÿ” TypeScript Support
750
+
751
+ ```typescript
752
+ import type {
753
+ JwtPayload,
754
+ LoginTokenPayload,
755
+ ApiTokenPayload,
756
+ AuthConfig,
757
+ RoleHierarchy,
758
+ ExpressRequest,
759
+ ExpressResponse,
760
+ ExpressMiddleware,
761
+ } from '@bloomneo/appkit/auth';
762
+
763
+ // All methods are fully typed
764
+ const user: JwtPayload | null = auth.user(req);
765
+ const middleware: ExpressMiddleware = auth.requireUserRoles(['admin.tenant']);
766
+ ```
767
+
768
+ ## โ“ FAQ
769
+
770
+ **Q: Can I use both login and API tokens in the same app?**
771
+ A: Yes! Use login tokens for user authentication and API tokens for external services.
772
+
773
+ **Q: Can API tokens have user roles?**
774
+ A: No, API tokens represent services, not users. Use `requireUserRoles()` only with login tokens.
775
+
776
+ **Q: How do I handle token expiration?**
777
+ A: The middleware automatically returns 401 with "Token has expired" message. Handle this in your frontend.
778
+
779
+ **Q: Can I customize the role hierarchy?**
780
+ A: Yes, use environment variables or pass custom config to `authClass.get()`.
781
+
782
+ **Q: What's the difference between roles and permissions?**
783
+ A: Roles are hierarchical (admin.org > admin.tenant), permissions are specific actions (edit:tenant).
784
+
785
+ ## Agent-Dev Friendliness Score
786
+
787
+ **Score: 50/100 โ€” ๐ŸŸ  Usable with caveats** *(uncapped: 83.6/100 ๐ŸŸก Solid)*
788
+ *Scored 2026-04-11 by Claude ยท Rubric [`AGENT_DEV_SCORING_ALGORITHM.md`](../../AGENT_DEV_SCORING_ALGORITHM.md) v1.1*
789
+
790
+ > โš ๏ธ **Cap reason**: 5 cookbook files at the package root still reference
791
+ > hallucinated `auth.requireLogin()` / `auth.requireRole()` from an earlier
792
+ > draft. Anti-pattern "any example file fails to compile" reduces 82.3 โ†’ 50.
793
+ > **Auth module itself is solid; cap is on packaging. Fix is mechanical, deferred until all 12 modules done.**
794
+
795
+ | # | Dimension | Score | Notes |
796
+ |---|---|---:|---|
797
+ | 1 | API correctness | **10** | All 12 methods verified by `auth.test.ts` (55 passing). Zero hallucinated refs in any doc. |
798
+ | 2 | Doc consistency | **9** | Same canonical pattern across README, AGENTS.md, llms.txt, examples, test. |
799
+ | 3 | Runtime verification | **10** | All 12 methods covered + 8 `can()` cases (replacement, downgrade, empty, fallback). 55/55 passing. |
800
+ | 4 | Type safety | **7** | `role`/`level` typed as plain `string` not literal unions. 7 `any` in `.d.ts` are all justified (index signatures, `res.json` data). |
801
+ | 5 | Discoverability | **9** | README hero correct. Pointers to AGENTS.md / llms.txt / examples / cookbook prominent. |
802
+ | 6 | Example completeness | **10** | All 12 methods in `examples/auth.ts`. |
803
+ | 7 | Composability | **3** โš ๏ธ | 5 cookbook recipes fail to compile. **Triggers anti-pattern cap.** |
804
+ | 8 | Educational errors | **9** | Common errors now `[@bloomneo/appkit/auth] message + DOCS_URL#anchor`. Startup `BLOOM_AUTH_SECRET` validation is exemplary. |
805
+ | 9 | Convention enforcement | **9** | One canonical way per task. Chaining rules clearly stated. |
806
+ | 10 | Drift prevention | **5** | `PUBLIC_METHODS` array in test catches runtime drift. No scripted doc-vs-source checker yet. |
807
+ | 11 | Reading order | **10** โฌ† | **Was 9.** "Which case is your app?" decision tree maps the 9-level hierarchy to 3 real-world app shapes โ€” devs no longer have to guess which roles matter. |
808
+ | **12** | **Simplicity** | **7** | 12 methods (>8 ideal). 5 concepts to learn (tokens, role.level, perms, middleware chaining, req.user). The decision tree reframes 9 roles โ†’ 3 core, which softens the perceived surface area. |
809
+ | **13** | **Clarity** | **8** | `user` and `can` are too short โ€” `getUser` / `canPerform` would be better. |
810
+ | **14** | **Unambiguity** | **8** โฌ† | **Was 4. Fixed in earlier round**: `auth.can()` rewritten so explicit `permissions` array REPLACES role defaults instead of supplementing. Matches AWS IAM / Casbin / OPA / Auth0 RBAC. Remaining gaps: `user(req)` null overload (3 conditions), `hasRole(a,b)` arg-order ambiguity, OR-vs-AND in `requireUserRoles` / `requireUserPermissions`, near-identical token methods. |
811
+ | **15** | **Learning curve** | **9** โฌ† | **Was 7.** Decision tree gives a clear 30-second answer to "which roles do I need?" โ€” most devs will identify their case, copy 3 roles, and ship. The 9-level hierarchy is now scaffolding, not a wall. |
812
+
813
+ ### Weighted (v1.1)
814
+
815
+ ```
816
+ (10ร—.12)+(9ร—.08)+(10ร—.09)+(7ร—.06)+(9ร—.06)+(10ร—.08)+(3ร—.06)+(9ร—.05)+(9ร—.05)+(5ร—.04)+(10ร—.03)
817
+ +(7ร—.09)+(8ร—.09)+(8ร—.05)+(9ร—.05) = 8.36 โ†’ 83.6/100
818
+ Anti-pattern cap (D7): 50/100
819
+ ```
820
+
821
+ ### Round-by-round score history
822
+
823
+ | Round | Date | Uncapped | Capped | Key change |
824
+ |---|---|---:|---:|---|
825
+ | v1.0 initial | 2026-04-11 | 79.5 | 50 | First scoring against 11 dimensions |
826
+ | v1.1 (4 new dims) | 2026-04-11 | 79.5 | 50 | +D12 Simplicity, D13 Clarity, D14 Unambiguity, D15 Learning curve |
827
+ | v1.1 + can() fix | 2026-04-11 | 82.3 | 50 | `auth.can()` now correctly REPLACES role defaults instead of supplementing. D14 4 โ†’ 8. |
828
+ | **v1.1 + decision tree** | **2026-04-11** | **83.6** | **50** | **Added "Which case is your app?" mapping the 9-level hierarchy to 3 real-world shapes. D11 9โ†’10, D15 7โ†’9.** |
829
+
830
+ ### Gaps to reach ๐ŸŸข 90+
831
+
832
+ 1. **Fix 5 cookbook files** (mechanical sed) โ†’ lifts cap, +33 points to 83.6
833
+ 2. **D14 โ†’ 9+**: rename `user` โ†’ `getUser`, `can` โ†’ `canPerform`, add explicit "exists" check methods to remove the `null` overload
834
+ 3. **D4 Type safety โ†’ 9**: export `DefaultRoleLevel` literal union for role/level
835
+ 4. **D10 Drift prevention โ†’ 10**: scripted doc-vs-source drift checker
836
+ 5. **D12 Simplicity โ†’ 9**: not really fixable without API redesign
837
+
838
+ **Realistic ceiling:** ~91/100 (with all 5 fixes). Beyond that requires API redesign.
839
+
840
+ ---
841
+
842
+ ## ๐Ÿ“„ License
843
+
844
+ MIT ยฉ [Bloomneo](https://github.com/bloomneo)
845
+
846
+ ---
847
+
848
+ <p align="center">
849
+ Built with โค๏ธ in India by the <a href="https://github.com/orgs/bloomneo/people">Bloomneo Team</a>
850
+ </p>