@bloomneo/appkit 1.2.9

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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +902 -0
  3. package/bin/appkit.js +71 -0
  4. package/bin/commands/generate.js +1050 -0
  5. package/bin/templates/backend/README.md.template +39 -0
  6. package/bin/templates/backend/api.http.template +0 -0
  7. package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
  8. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
  9. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
  10. package/bin/templates/backend/package.json.template +34 -0
  11. package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
  12. package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
  13. package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
  14. package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
  15. package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
  16. package/bin/templates/backend/src/api/server.ts.template +188 -0
  17. package/bin/templates/backend/tsconfig.api.json.template +24 -0
  18. package/bin/templates/backend/tsconfig.json.template +40 -0
  19. package/bin/templates/feature/feature.http.template +63 -0
  20. package/bin/templates/feature/feature.route.ts.template +36 -0
  21. package/bin/templates/feature/feature.service.ts.template +81 -0
  22. package/bin/templates/feature/feature.types.ts.template +23 -0
  23. package/bin/templates/feature-db/feature.http.template +63 -0
  24. package/bin/templates/feature-db/feature.model.ts.template +74 -0
  25. package/bin/templates/feature-db/feature.route.ts.template +58 -0
  26. package/bin/templates/feature-db/feature.service.ts.template +231 -0
  27. package/bin/templates/feature-db/feature.types.ts.template +25 -0
  28. package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
  29. package/bin/templates/feature-db/seeding/README.md.template +57 -0
  30. package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
  31. package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
  32. package/bin/templates/feature-user/user.http.template +157 -0
  33. package/bin/templates/feature-user/user.model.ts.template +244 -0
  34. package/bin/templates/feature-user/user.route.ts.template +379 -0
  35. package/bin/templates/feature-user/user.seed.js.template +182 -0
  36. package/bin/templates/feature-user/user.service.ts.template +426 -0
  37. package/bin/templates/feature-user/user.types.ts.template +127 -0
  38. package/dist/auth/auth.d.ts +182 -0
  39. package/dist/auth/auth.d.ts.map +1 -0
  40. package/dist/auth/auth.js +477 -0
  41. package/dist/auth/auth.js.map +1 -0
  42. package/dist/auth/defaults.d.ts +104 -0
  43. package/dist/auth/defaults.d.ts.map +1 -0
  44. package/dist/auth/defaults.js +374 -0
  45. package/dist/auth/defaults.js.map +1 -0
  46. package/dist/auth/index.d.ts +70 -0
  47. package/dist/auth/index.d.ts.map +1 -0
  48. package/dist/auth/index.js +94 -0
  49. package/dist/auth/index.js.map +1 -0
  50. package/dist/cache/cache.d.ts +118 -0
  51. package/dist/cache/cache.d.ts.map +1 -0
  52. package/dist/cache/cache.js +249 -0
  53. package/dist/cache/cache.js.map +1 -0
  54. package/dist/cache/defaults.d.ts +63 -0
  55. package/dist/cache/defaults.d.ts.map +1 -0
  56. package/dist/cache/defaults.js +193 -0
  57. package/dist/cache/defaults.js.map +1 -0
  58. package/dist/cache/index.d.ts +101 -0
  59. package/dist/cache/index.d.ts.map +1 -0
  60. package/dist/cache/index.js +203 -0
  61. package/dist/cache/index.js.map +1 -0
  62. package/dist/cache/strategies/memory.d.ts +138 -0
  63. package/dist/cache/strategies/memory.d.ts.map +1 -0
  64. package/dist/cache/strategies/memory.js +348 -0
  65. package/dist/cache/strategies/memory.js.map +1 -0
  66. package/dist/cache/strategies/redis.d.ts +105 -0
  67. package/dist/cache/strategies/redis.d.ts.map +1 -0
  68. package/dist/cache/strategies/redis.js +318 -0
  69. package/dist/cache/strategies/redis.js.map +1 -0
  70. package/dist/config/config.d.ts +62 -0
  71. package/dist/config/config.d.ts.map +1 -0
  72. package/dist/config/config.js +107 -0
  73. package/dist/config/config.js.map +1 -0
  74. package/dist/config/defaults.d.ts +44 -0
  75. package/dist/config/defaults.d.ts.map +1 -0
  76. package/dist/config/defaults.js +217 -0
  77. package/dist/config/defaults.js.map +1 -0
  78. package/dist/config/index.d.ts +105 -0
  79. package/dist/config/index.d.ts.map +1 -0
  80. package/dist/config/index.js +163 -0
  81. package/dist/config/index.js.map +1 -0
  82. package/dist/database/adapters/mongoose.d.ts +106 -0
  83. package/dist/database/adapters/mongoose.d.ts.map +1 -0
  84. package/dist/database/adapters/mongoose.js +480 -0
  85. package/dist/database/adapters/mongoose.js.map +1 -0
  86. package/dist/database/adapters/prisma.d.ts +106 -0
  87. package/dist/database/adapters/prisma.d.ts.map +1 -0
  88. package/dist/database/adapters/prisma.js +494 -0
  89. package/dist/database/adapters/prisma.js.map +1 -0
  90. package/dist/database/defaults.d.ts +87 -0
  91. package/dist/database/defaults.d.ts.map +1 -0
  92. package/dist/database/defaults.js +271 -0
  93. package/dist/database/defaults.js.map +1 -0
  94. package/dist/database/index.d.ts +137 -0
  95. package/dist/database/index.d.ts.map +1 -0
  96. package/dist/database/index.js +490 -0
  97. package/dist/database/index.js.map +1 -0
  98. package/dist/email/defaults.d.ts +100 -0
  99. package/dist/email/defaults.d.ts.map +1 -0
  100. package/dist/email/defaults.js +400 -0
  101. package/dist/email/defaults.js.map +1 -0
  102. package/dist/email/email.d.ts +139 -0
  103. package/dist/email/email.d.ts.map +1 -0
  104. package/dist/email/email.js +316 -0
  105. package/dist/email/email.js.map +1 -0
  106. package/dist/email/index.d.ts +176 -0
  107. package/dist/email/index.d.ts.map +1 -0
  108. package/dist/email/index.js +251 -0
  109. package/dist/email/index.js.map +1 -0
  110. package/dist/email/strategies/console.d.ts +90 -0
  111. package/dist/email/strategies/console.d.ts.map +1 -0
  112. package/dist/email/strategies/console.js +268 -0
  113. package/dist/email/strategies/console.js.map +1 -0
  114. package/dist/email/strategies/resend.d.ts +84 -0
  115. package/dist/email/strategies/resend.d.ts.map +1 -0
  116. package/dist/email/strategies/resend.js +266 -0
  117. package/dist/email/strategies/resend.js.map +1 -0
  118. package/dist/email/strategies/smtp.d.ts +77 -0
  119. package/dist/email/strategies/smtp.d.ts.map +1 -0
  120. package/dist/email/strategies/smtp.js +286 -0
  121. package/dist/email/strategies/smtp.js.map +1 -0
  122. package/dist/error/defaults.d.ts +40 -0
  123. package/dist/error/defaults.d.ts.map +1 -0
  124. package/dist/error/defaults.js +75 -0
  125. package/dist/error/defaults.js.map +1 -0
  126. package/dist/error/error.d.ts +140 -0
  127. package/dist/error/error.d.ts.map +1 -0
  128. package/dist/error/error.js +200 -0
  129. package/dist/error/error.js.map +1 -0
  130. package/dist/error/index.d.ts +145 -0
  131. package/dist/error/index.d.ts.map +1 -0
  132. package/dist/error/index.js +145 -0
  133. package/dist/error/index.js.map +1 -0
  134. package/dist/event/defaults.d.ts +111 -0
  135. package/dist/event/defaults.d.ts.map +1 -0
  136. package/dist/event/defaults.js +378 -0
  137. package/dist/event/defaults.js.map +1 -0
  138. package/dist/event/event.d.ts +171 -0
  139. package/dist/event/event.d.ts.map +1 -0
  140. package/dist/event/event.js +391 -0
  141. package/dist/event/event.js.map +1 -0
  142. package/dist/event/index.d.ts +173 -0
  143. package/dist/event/index.d.ts.map +1 -0
  144. package/dist/event/index.js +302 -0
  145. package/dist/event/index.js.map +1 -0
  146. package/dist/event/strategies/memory.d.ts +122 -0
  147. package/dist/event/strategies/memory.d.ts.map +1 -0
  148. package/dist/event/strategies/memory.js +331 -0
  149. package/dist/event/strategies/memory.js.map +1 -0
  150. package/dist/event/strategies/redis.d.ts +115 -0
  151. package/dist/event/strategies/redis.d.ts.map +1 -0
  152. package/dist/event/strategies/redis.js +434 -0
  153. package/dist/event/strategies/redis.js.map +1 -0
  154. package/dist/index.d.ts +58 -0
  155. package/dist/index.d.ts.map +1 -0
  156. package/dist/index.js +72 -0
  157. package/dist/index.js.map +1 -0
  158. package/dist/logger/defaults.d.ts +67 -0
  159. package/dist/logger/defaults.d.ts.map +1 -0
  160. package/dist/logger/defaults.js +213 -0
  161. package/dist/logger/defaults.js.map +1 -0
  162. package/dist/logger/index.d.ts +84 -0
  163. package/dist/logger/index.d.ts.map +1 -0
  164. package/dist/logger/index.js +101 -0
  165. package/dist/logger/index.js.map +1 -0
  166. package/dist/logger/logger.d.ts +165 -0
  167. package/dist/logger/logger.d.ts.map +1 -0
  168. package/dist/logger/logger.js +843 -0
  169. package/dist/logger/logger.js.map +1 -0
  170. package/dist/logger/transports/console.d.ts +102 -0
  171. package/dist/logger/transports/console.d.ts.map +1 -0
  172. package/dist/logger/transports/console.js +276 -0
  173. package/dist/logger/transports/console.js.map +1 -0
  174. package/dist/logger/transports/database.d.ts +153 -0
  175. package/dist/logger/transports/database.d.ts.map +1 -0
  176. package/dist/logger/transports/database.js +539 -0
  177. package/dist/logger/transports/database.js.map +1 -0
  178. package/dist/logger/transports/file.d.ts +146 -0
  179. package/dist/logger/transports/file.d.ts.map +1 -0
  180. package/dist/logger/transports/file.js +464 -0
  181. package/dist/logger/transports/file.js.map +1 -0
  182. package/dist/logger/transports/http.d.ts +128 -0
  183. package/dist/logger/transports/http.d.ts.map +1 -0
  184. package/dist/logger/transports/http.js +401 -0
  185. package/dist/logger/transports/http.js.map +1 -0
  186. package/dist/logger/transports/webhook.d.ts +152 -0
  187. package/dist/logger/transports/webhook.d.ts.map +1 -0
  188. package/dist/logger/transports/webhook.js +485 -0
  189. package/dist/logger/transports/webhook.js.map +1 -0
  190. package/dist/queue/defaults.d.ts +66 -0
  191. package/dist/queue/defaults.d.ts.map +1 -0
  192. package/dist/queue/defaults.js +205 -0
  193. package/dist/queue/defaults.js.map +1 -0
  194. package/dist/queue/index.d.ts +124 -0
  195. package/dist/queue/index.d.ts.map +1 -0
  196. package/dist/queue/index.js +116 -0
  197. package/dist/queue/index.js.map +1 -0
  198. package/dist/queue/queue.d.ts +156 -0
  199. package/dist/queue/queue.d.ts.map +1 -0
  200. package/dist/queue/queue.js +387 -0
  201. package/dist/queue/queue.js.map +1 -0
  202. package/dist/queue/transports/database.d.ts +165 -0
  203. package/dist/queue/transports/database.d.ts.map +1 -0
  204. package/dist/queue/transports/database.js +595 -0
  205. package/dist/queue/transports/database.js.map +1 -0
  206. package/dist/queue/transports/memory.d.ts +143 -0
  207. package/dist/queue/transports/memory.d.ts.map +1 -0
  208. package/dist/queue/transports/memory.js +415 -0
  209. package/dist/queue/transports/memory.js.map +1 -0
  210. package/dist/queue/transports/redis.d.ts +203 -0
  211. package/dist/queue/transports/redis.d.ts.map +1 -0
  212. package/dist/queue/transports/redis.js +744 -0
  213. package/dist/queue/transports/redis.js.map +1 -0
  214. package/dist/security/defaults.d.ts +64 -0
  215. package/dist/security/defaults.d.ts.map +1 -0
  216. package/dist/security/defaults.js +159 -0
  217. package/dist/security/defaults.js.map +1 -0
  218. package/dist/security/index.d.ts +110 -0
  219. package/dist/security/index.d.ts.map +1 -0
  220. package/dist/security/index.js +160 -0
  221. package/dist/security/index.js.map +1 -0
  222. package/dist/security/security.d.ts +138 -0
  223. package/dist/security/security.d.ts.map +1 -0
  224. package/dist/security/security.js +419 -0
  225. package/dist/security/security.js.map +1 -0
  226. package/dist/storage/defaults.d.ts +79 -0
  227. package/dist/storage/defaults.d.ts.map +1 -0
  228. package/dist/storage/defaults.js +358 -0
  229. package/dist/storage/defaults.js.map +1 -0
  230. package/dist/storage/index.d.ts +153 -0
  231. package/dist/storage/index.d.ts.map +1 -0
  232. package/dist/storage/index.js +242 -0
  233. package/dist/storage/index.js.map +1 -0
  234. package/dist/storage/storage.d.ts +151 -0
  235. package/dist/storage/storage.d.ts.map +1 -0
  236. package/dist/storage/storage.js +439 -0
  237. package/dist/storage/storage.js.map +1 -0
  238. package/dist/storage/strategies/local.d.ts +117 -0
  239. package/dist/storage/strategies/local.d.ts.map +1 -0
  240. package/dist/storage/strategies/local.js +368 -0
  241. package/dist/storage/strategies/local.js.map +1 -0
  242. package/dist/storage/strategies/r2.d.ts +130 -0
  243. package/dist/storage/strategies/r2.d.ts.map +1 -0
  244. package/dist/storage/strategies/r2.js +470 -0
  245. package/dist/storage/strategies/r2.js.map +1 -0
  246. package/dist/storage/strategies/s3.d.ts +121 -0
  247. package/dist/storage/strategies/s3.d.ts.map +1 -0
  248. package/dist/storage/strategies/s3.js +461 -0
  249. package/dist/storage/strategies/s3.js.map +1 -0
  250. package/dist/util/defaults.d.ts +77 -0
  251. package/dist/util/defaults.d.ts.map +1 -0
  252. package/dist/util/defaults.js +193 -0
  253. package/dist/util/defaults.js.map +1 -0
  254. package/dist/util/index.d.ts +97 -0
  255. package/dist/util/index.d.ts.map +1 -0
  256. package/dist/util/index.js +165 -0
  257. package/dist/util/index.js.map +1 -0
  258. package/dist/util/util.d.ts +145 -0
  259. package/dist/util/util.d.ts.map +1 -0
  260. package/dist/util/util.js +481 -0
  261. package/dist/util/util.js.map +1 -0
  262. package/package.json +234 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Core authentication class with role-level-permission system
3
+ * @module @bloomneo/appkit/auth
4
+ * @file src/auth/authentication.ts
5
+ *
6
+ * @llm-rule WHEN: Building apps that need JWT operations, password hashing, and role-based middleware
7
+ * @llm-rule AVOID: Using directly - always get instance via auth.get()
8
+ * @llm-rule NOTE: Use requireUserRoles() for hierarchy-based access, requireUserPermissions() for action-specific access
9
+ * @llm-rule NOTE: Uses role.level format (user.basic, admin.tenant) with automatic inheritance
10
+ */
11
+ import { type AuthConfig } from './defaults.js';
12
+ export interface JwtPayload {
13
+ userId?: string | number;
14
+ keyId?: string;
15
+ type: 'login' | 'api_key';
16
+ role: string;
17
+ level: string;
18
+ permissions?: string[];
19
+ [key: string]: any;
20
+ iat?: number;
21
+ exp?: number;
22
+ iss?: string;
23
+ aud?: string;
24
+ }
25
+ export interface LoginTokenPayload {
26
+ userId: string | number;
27
+ type: 'login';
28
+ role: string;
29
+ level: string;
30
+ permissions?: string[];
31
+ [key: string]: any;
32
+ }
33
+ export interface ApiTokenPayload {
34
+ keyId: string;
35
+ type: 'api_key';
36
+ role: string;
37
+ level: string;
38
+ permissions?: string[];
39
+ [key: string]: any;
40
+ }
41
+ export interface ExpressRequest {
42
+ headers: {
43
+ [key: string]: string | string[] | undefined;
44
+ };
45
+ cookies?: {
46
+ [key: string]: string;
47
+ };
48
+ query?: {
49
+ [key: string]: any;
50
+ };
51
+ user?: JwtPayload;
52
+ token?: JwtPayload;
53
+ [key: string]: any;
54
+ }
55
+ export interface ExpressResponse {
56
+ status: (code: number) => {
57
+ json: (data: any) => void;
58
+ };
59
+ json: (data: any) => void;
60
+ }
61
+ export interface MiddlewareOptions {
62
+ getToken?: (request: ExpressRequest) => string | null;
63
+ }
64
+ export type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: () => void) => void;
65
+ /**
66
+ * Authentication class with JWT, password, and role-level-permission system
67
+ */
68
+ export declare class AuthenticationClass {
69
+ config: AuthConfig;
70
+ constructor(config: AuthConfig);
71
+ /**
72
+ * Generates a login JWT token for user authentication
73
+ * @llm-rule WHEN: User successfully logs in to your app (mobile/web)
74
+ * @llm-rule AVOID: Using for API access - use generateApiToken instead
75
+ * @llm-rule NOTE: Creates JWT with userId and type: 'login'
76
+ */
77
+ generateLoginToken(payload: Omit<LoginTokenPayload, 'type' | 'iat' | 'exp' | 'iss' | 'aud'>, expiresIn?: string): string;
78
+ /**
79
+ * Generates an API JWT token for external access
80
+ * @llm-rule WHEN: Creating API keys for third-party integrations
81
+ * @llm-rule AVOID: Using for user authentication - use generateLoginToken instead
82
+ * @llm-rule NOTE: Creates JWT with keyId and type: 'api_key'
83
+ */
84
+ generateApiToken(payload: Omit<ApiTokenPayload, 'type' | 'iat' | 'exp' | 'iss' | 'aud'>, expiresIn?: string): string;
85
+ /**
86
+ * Internal method to create and sign JWT tokens
87
+ * @private
88
+ */
89
+ private signToken;
90
+ /**
91
+ * Verifies and decodes a JWT token (both login and API tokens)
92
+ * @llm-rule WHEN: Validating incoming tokens from requests
93
+ * @llm-rule AVOID: Using jwt.verify directly - this handles errors and validates structure
94
+ * @llm-rule NOTE: Handles both login tokens (userId) and API tokens (keyId)
95
+ */
96
+ verifyToken(token: string): JwtPayload;
97
+ /**
98
+ * Hashes a password using bcrypt
99
+ * @llm-rule WHEN: Storing user passwords - always hash before saving to database
100
+ * @llm-rule AVOID: Storing plain text passwords - major security vulnerability
101
+ * @llm-rule NOTE: Takes ~100ms with default 10 rounds - don't call in tight loops
102
+ */
103
+ hashPassword(password: string, rounds?: number): Promise<string>;
104
+ /**
105
+ * Compares a password with its hash
106
+ * @llm-rule WHEN: Validating user login credentials
107
+ * @llm-rule AVOID: Manual string comparison - timing attacks possible
108
+ * @llm-rule NOTE: Always returns boolean, never throws on comparison failure
109
+ */
110
+ comparePassword(password: string, hash: string): Promise<boolean>;
111
+ /**
112
+ * Safely extracts user from request - never crashes
113
+ * @llm-rule WHEN: Need to access user data from authenticated requests
114
+ * @llm-rule AVOID: Accessing req.user directly - may be undefined and cause crashes
115
+ * @llm-rule NOTE: Always returns null for unauthenticated requests - safe to use
116
+ * @llm-rule NOTE: Works with both login authentication (req.user) and API tokens (req.token)
117
+ */
118
+ user(request: ExpressRequest): JwtPayload | null;
119
+ /**
120
+ * Checks if user has specified role with automatic inheritance
121
+ * @llm-rule WHEN: Checking if user can access role-protected resources
122
+ * @llm-rule AVOID: Manual role comparisons - this handles inheritance automatically
123
+ * @llm-rule NOTE: Higher levels inherit lower (admin.org has admin.tenant access)
124
+ * @llm-rule NOTE: INHERITANCE EXAMPLES:
125
+ * @llm-rule NOTE: auth.hasRole('admin.org', 'admin.tenant') → TRUE (org > tenant)
126
+ * @llm-rule NOTE: auth.hasRole('admin.system', 'user.basic') → TRUE (system > basic)
127
+ * @llm-rule NOTE: auth.hasRole('user.basic', 'admin.tenant') → FALSE (basic < tenant)
128
+ * @llm-rule NOTE: Role hierarchy: admin.system > admin.org > admin.tenant > user.max > user.pro > user.basic
129
+ */
130
+ hasRole(userRoleLevel: string, requiredRoleLevel: string): boolean;
131
+ /**
132
+ * Checks if user has specific permission with automatic action inheritance
133
+ * @llm-rule WHEN: Checking fine-grained permissions for specific actions
134
+ * @llm-rule AVOID: Hardcoding permission checks - this handles action inheritance
135
+ * @llm-rule NOTE: 'manage:scope' includes ALL other actions for that scope
136
+ * @llm-rule NOTE: PERMISSION INHERITANCE EXAMPLES:
137
+ * @llm-rule NOTE: If user has 'manage:tenant' → can('edit:tenant') returns TRUE
138
+ * @llm-rule NOTE: If user has 'manage:tenant' → can('view:tenant') returns TRUE
139
+ * @llm-rule NOTE: If user has 'edit:tenant' → can('manage:tenant') returns FALSE
140
+ * @llm-rule NOTE: Actions hierarchy: manage > delete > edit > create > view
141
+ */
142
+ can(user: JwtPayload, permission: string): boolean;
143
+ /**
144
+ * Creates Express authentication middleware for login tokens
145
+ * @llm-rule WHEN: Protecting routes that need authenticated users
146
+ * @llm-rule AVOID: Using for API routes - use requireApiToken instead
147
+ * @llm-rule NOTE: Validates login tokens (type: 'login') and sets req.user
148
+ */
149
+ requireLoginToken(options?: MiddlewareOptions): ExpressMiddleware;
150
+ /**
151
+ * Creates Express role-based authorization middleware for authenticated users
152
+ * @llm-rule WHEN: Protecting routes that require specific user roles
153
+ * @llm-rule AVOID: Using without requireLoginToken - this assumes user is already authenticated
154
+ * @llm-rule AVOID: Using with API tokens - API tokens don't have user roles
155
+ * @llm-rule NOTE: User needs ANY role from the array (OR logic)
156
+ * @llm-rule NOTE: Role inheritance applies - admin.org can access admin.tenant routes
157
+ */
158
+ requireUserRoles(requiredRoles: string[]): ExpressMiddleware;
159
+ /**
160
+ * Creates Express permission-based authorization middleware for authenticated users
161
+ * @llm-rule WHEN: Protecting routes that require specific user permissions
162
+ * @llm-rule AVOID: Using without requireLoginToken - this assumes user is already authenticated
163
+ * @llm-rule AVOID: Using with API tokens - API tokens don't have user permissions
164
+ * @llm-rule NOTE: User needs ALL permissions from the array (AND logic)
165
+ * @llm-rule NOTE: Permission inheritance applies - manage:tenant can access edit:tenant routes
166
+ */
167
+ requireUserPermissions(requiredPermissions: string[]): ExpressMiddleware;
168
+ /**
169
+ * Creates Express API token authentication middleware for external access
170
+ * @llm-rule WHEN: Protecting API routes for third-party integrations
171
+ * @llm-rule AVOID: Using for user routes - use requireLoginToken instead
172
+ * @llm-rule NOTE: Validates API tokens (type: 'api_key') and sets req.token
173
+ */
174
+ requireApiToken(options?: MiddlewareOptions): ExpressMiddleware;
175
+ /**
176
+ * Gets default token extractor that checks headers, cookies, and query params
177
+ * @llm-rule WHEN: Need custom token extraction logic
178
+ * @llm-rule AVOID: Modifying directly - pass custom getToken to middleware options
179
+ */
180
+ private getDefaultTokenExtractor;
181
+ }
182
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/auth/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;KAAE,CAAC;IAC1D,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACpC,KAAK,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;KAAE,CAAC;IACxD,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,MAAM,GAAG,IAAI,CAAC;CACvD;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAEtG;;GAEG;AACH,qBAAa,mBAAmB;IACvB,MAAM,EAAE,UAAU,CAAC;gBAEd,MAAM,EAAE,UAAU;IAI9B;;;;;OAKG;IACH,kBAAkB,CAAC,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IASxH;;;;;OAKG;IACH,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IASpH;;;OAGG;IACH,OAAO,CAAC,SAAS;IA8CjB;;;;;OAKG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU;IA0CtC;;;;;OAKG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAetE;;;;;OAKG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBvE;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,GAAG,IAAI;IAkBhD;;;;;;;;;;OAUG;IACH,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO;IA2BlE;;;;;;;;;;OAUG;IACH,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAuDlD;;;;;OAKG;IACH,iBAAiB,CAAC,OAAO,GAAE,iBAAsB,GAAG,iBAAiB;IA2CrE;;;;;;;OAOG;IACH,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,iBAAiB;IA6C5D;;;;;;;OAOG;IACH,sBAAsB,CAAC,mBAAmB,EAAE,MAAM,EAAE,GAAG,iBAAiB;IA4CxE;;;;;OAKG;IACH,eAAe,CAAC,OAAO,GAAE,iBAAsB,GAAG,iBAAiB;IA2CnE;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;CAwBjC"}
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Core authentication class with role-level-permission system
3
+ * @module @bloomneo/appkit/auth
4
+ * @file src/auth/authentication.ts
5
+ *
6
+ * @llm-rule WHEN: Building apps that need JWT operations, password hashing, and role-based middleware
7
+ * @llm-rule AVOID: Using directly - always get instance via auth.get()
8
+ * @llm-rule NOTE: Use requireUserRoles() for hierarchy-based access, requireUserPermissions() for action-specific access
9
+ * @llm-rule NOTE: Uses role.level format (user.basic, admin.tenant) with automatic inheritance
10
+ */
11
+ import jwt from 'jsonwebtoken';
12
+ import bcrypt from 'bcrypt';
13
+ import { validateRounds, validateRoleLevel, validatePermission, } from './defaults.js';
14
+ /**
15
+ * Authentication class with JWT, password, and role-level-permission system
16
+ */
17
+ export class AuthenticationClass {
18
+ config;
19
+ constructor(config) {
20
+ this.config = config;
21
+ }
22
+ /**
23
+ * Generates a login JWT token for user authentication
24
+ * @llm-rule WHEN: User successfully logs in to your app (mobile/web)
25
+ * @llm-rule AVOID: Using for API access - use generateApiToken instead
26
+ * @llm-rule NOTE: Creates JWT with userId and type: 'login'
27
+ */
28
+ generateLoginToken(payload, expiresIn) {
29
+ const loginPayload = {
30
+ ...payload,
31
+ type: 'login',
32
+ };
33
+ return this.signToken(loginPayload, expiresIn || '7d');
34
+ }
35
+ /**
36
+ * Generates an API JWT token for external access
37
+ * @llm-rule WHEN: Creating API keys for third-party integrations
38
+ * @llm-rule AVOID: Using for user authentication - use generateLoginToken instead
39
+ * @llm-rule NOTE: Creates JWT with keyId and type: 'api_key'
40
+ */
41
+ generateApiToken(payload, expiresIn) {
42
+ const apiPayload = {
43
+ ...payload,
44
+ type: 'api_key',
45
+ };
46
+ return this.signToken(apiPayload, expiresIn || '1y');
47
+ }
48
+ /**
49
+ * Internal method to create and sign JWT tokens
50
+ * @private
51
+ */
52
+ signToken(payload, expiresIn) {
53
+ if (!payload || typeof payload !== 'object') {
54
+ throw new Error('Payload must be an object');
55
+ }
56
+ // Validate based on token type
57
+ if (payload.type === 'login') {
58
+ if (!payload.userId) {
59
+ throw new Error('Login token must include userId');
60
+ }
61
+ }
62
+ else if (payload.type === 'api_key') {
63
+ if (!payload.keyId) {
64
+ throw new Error('API token must include keyId');
65
+ }
66
+ }
67
+ else {
68
+ throw new Error('Token type must be "login" or "api_key"');
69
+ }
70
+ if (!payload.role || !payload.level) {
71
+ throw new Error('Payload must include both role and level');
72
+ }
73
+ // Validate role.level exists
74
+ const roleLevel = `${payload.role}.${payload.level}`;
75
+ if (!validateRoleLevel(roleLevel, this.config.roles)) {
76
+ throw new Error(`Invalid role.level: "${roleLevel}"`);
77
+ }
78
+ const jwtSecret = this.config.jwt.secret;
79
+ if (!jwtSecret) {
80
+ throw new Error('JWT secret required. Set VOILA_AUTH_SECRET environment variable');
81
+ }
82
+ const tokenExpiration = expiresIn || this.config.jwt.expiresIn;
83
+ try {
84
+ return jwt.sign(payload, jwtSecret, {
85
+ expiresIn: tokenExpiration,
86
+ });
87
+ }
88
+ catch (error) {
89
+ throw new Error(`Failed to generate token: ${error.message}`);
90
+ }
91
+ }
92
+ /**
93
+ * Verifies and decodes a JWT token (both login and API tokens)
94
+ * @llm-rule WHEN: Validating incoming tokens from requests
95
+ * @llm-rule AVOID: Using jwt.verify directly - this handles errors and validates structure
96
+ * @llm-rule NOTE: Handles both login tokens (userId) and API tokens (keyId)
97
+ */
98
+ verifyToken(token) {
99
+ if (!token || typeof token !== 'string') {
100
+ throw new Error('Token must be a string');
101
+ }
102
+ const jwtSecret = this.config.jwt.secret;
103
+ if (!jwtSecret) {
104
+ throw new Error('JWT secret required. Set VOILA_AUTH_SECRET environment variable');
105
+ }
106
+ try {
107
+ const decoded = jwt.verify(token, jwtSecret, {
108
+ algorithms: [this.config.jwt.algorithm],
109
+ });
110
+ // Validate decoded token has required structure
111
+ if (!decoded.role || !decoded.level || !decoded.type) {
112
+ throw new Error('Token missing required role, level, or type information');
113
+ }
114
+ // Validate type-specific requirements
115
+ if (decoded.type === 'login' && !decoded.userId) {
116
+ throw new Error('Login token missing userId');
117
+ }
118
+ if (decoded.type === 'api_key' && !decoded.keyId) {
119
+ throw new Error('API token missing keyId');
120
+ }
121
+ return decoded;
122
+ }
123
+ catch (error) {
124
+ if (error.name === 'TokenExpiredError') {
125
+ throw new Error('Token has expired');
126
+ }
127
+ if (error.name === 'JsonWebTokenError') {
128
+ throw new Error('Invalid token');
129
+ }
130
+ throw new Error(`Token verification failed: ${error.message}`);
131
+ }
132
+ }
133
+ /**
134
+ * Hashes a password using bcrypt
135
+ * @llm-rule WHEN: Storing user passwords - always hash before saving to database
136
+ * @llm-rule AVOID: Storing plain text passwords - major security vulnerability
137
+ * @llm-rule NOTE: Takes ~100ms with default 10 rounds - don't call in tight loops
138
+ */
139
+ async hashPassword(password, rounds) {
140
+ if (!password || typeof password !== 'string') {
141
+ throw new Error('Password must be a non-empty string');
142
+ }
143
+ const saltRounds = rounds || this.config.password.saltRounds;
144
+ validateRounds(saltRounds);
145
+ try {
146
+ return await bcrypt.hash(password, saltRounds);
147
+ }
148
+ catch (error) {
149
+ throw new Error(`Password hashing failed: ${error.message}`);
150
+ }
151
+ }
152
+ /**
153
+ * Compares a password with its hash
154
+ * @llm-rule WHEN: Validating user login credentials
155
+ * @llm-rule AVOID: Manual string comparison - timing attacks possible
156
+ * @llm-rule NOTE: Always returns boolean, never throws on comparison failure
157
+ */
158
+ async comparePassword(password, hash) {
159
+ if (!password || typeof password !== 'string') {
160
+ return false;
161
+ }
162
+ if (!hash || typeof hash !== 'string') {
163
+ return false;
164
+ }
165
+ try {
166
+ return await bcrypt.compare(password, hash);
167
+ }
168
+ catch (error) {
169
+ // bcrypt.compare can fail on malformed hashes
170
+ return false;
171
+ }
172
+ }
173
+ /**
174
+ * Safely extracts user from request - never crashes
175
+ * @llm-rule WHEN: Need to access user data from authenticated requests
176
+ * @llm-rule AVOID: Accessing req.user directly - may be undefined and cause crashes
177
+ * @llm-rule NOTE: Always returns null for unauthenticated requests - safe to use
178
+ * @llm-rule NOTE: Works with both login authentication (req.user) and API tokens (req.token)
179
+ */
180
+ user(request) {
181
+ if (!request || typeof request !== 'object') {
182
+ return null;
183
+ }
184
+ // Check for user authentication first (login-based)
185
+ if (request.user && typeof request.user === 'object' && (request.user.userId || request.user.keyId)) {
186
+ return request.user;
187
+ }
188
+ // Check for token authentication (API-based)
189
+ if (request.token && typeof request.token === 'object' && (request.token.userId || request.token.keyId)) {
190
+ return request.token;
191
+ }
192
+ return null;
193
+ }
194
+ /**
195
+ * Checks if user has specified role with automatic inheritance
196
+ * @llm-rule WHEN: Checking if user can access role-protected resources
197
+ * @llm-rule AVOID: Manual role comparisons - this handles inheritance automatically
198
+ * @llm-rule NOTE: Higher levels inherit lower (admin.org has admin.tenant access)
199
+ * @llm-rule NOTE: INHERITANCE EXAMPLES:
200
+ * @llm-rule NOTE: auth.hasRole('admin.org', 'admin.tenant') → TRUE (org > tenant)
201
+ * @llm-rule NOTE: auth.hasRole('admin.system', 'user.basic') → TRUE (system > basic)
202
+ * @llm-rule NOTE: auth.hasRole('user.basic', 'admin.tenant') → FALSE (basic < tenant)
203
+ * @llm-rule NOTE: Role hierarchy: admin.system > admin.org > admin.tenant > user.max > user.pro > user.basic
204
+ */
205
+ hasRole(userRoleLevel, requiredRoleLevel) {
206
+ // INHERITANCE RULE: Higher role levels automatically include lower levels
207
+ // Example: admin.org (level 6) includes admin.tenant (level 5) access
208
+ if (!userRoleLevel || !requiredRoleLevel) {
209
+ return false;
210
+ }
211
+ if (!validateRoleLevel(userRoleLevel, this.config.roles)) {
212
+ return false;
213
+ }
214
+ if (!validateRoleLevel(requiredRoleLevel, this.config.roles)) {
215
+ return false;
216
+ }
217
+ const userLevel = this.config.roles[userRoleLevel]?.level;
218
+ const requiredLevel = this.config.roles[requiredRoleLevel]?.level;
219
+ if (userLevel === undefined || requiredLevel === undefined) {
220
+ return false;
221
+ }
222
+ // Higher numeric levels include lower levels
223
+ return userLevel >= requiredLevel;
224
+ }
225
+ /**
226
+ * Checks if user has specific permission with automatic action inheritance
227
+ * @llm-rule WHEN: Checking fine-grained permissions for specific actions
228
+ * @llm-rule AVOID: Hardcoding permission checks - this handles action inheritance
229
+ * @llm-rule NOTE: 'manage:scope' includes ALL other actions for that scope
230
+ * @llm-rule NOTE: PERMISSION INHERITANCE EXAMPLES:
231
+ * @llm-rule NOTE: If user has 'manage:tenant' → can('edit:tenant') returns TRUE
232
+ * @llm-rule NOTE: If user has 'manage:tenant' → can('view:tenant') returns TRUE
233
+ * @llm-rule NOTE: If user has 'edit:tenant' → can('manage:tenant') returns FALSE
234
+ * @llm-rule NOTE: Actions hierarchy: manage > delete > edit > create > view
235
+ */
236
+ can(user, permission) {
237
+ // PERMISSION INHERITANCE: 'manage:tenant' automatically includes:
238
+ // - view:tenant, create:tenant, edit:tenant, delete:tenant
239
+ // Example: if user has 'manage:tenant', they can do 'edit:tenant'
240
+ if (!user || !permission) {
241
+ return false;
242
+ }
243
+ if (!validatePermission(permission)) {
244
+ throw new Error(`Invalid permission format: "${permission}"`);
245
+ }
246
+ // Check if user has the specific permission
247
+ if (user.permissions && Array.isArray(user.permissions)) {
248
+ if (user.permissions.includes(permission)) {
249
+ return true;
250
+ }
251
+ // Check for manage permission (includes all other actions)
252
+ const [action, scope] = permission.split(':');
253
+ if (action !== 'manage') {
254
+ const managePermission = `manage:${scope}`;
255
+ if (user.permissions.includes(managePermission)) {
256
+ return true;
257
+ }
258
+ }
259
+ }
260
+ // Fallback: check default permissions for user's role.level
261
+ const userRoleLevel = `${user.role}.${user.level}`;
262
+ const defaultPermissions = this.config.permissions.defaults[userRoleLevel];
263
+ if (defaultPermissions && Array.isArray(defaultPermissions)) {
264
+ if (defaultPermissions.includes(permission)) {
265
+ return true;
266
+ }
267
+ // Check for manage permission in defaults
268
+ const [action, scope] = permission.split(':');
269
+ if (action !== 'manage') {
270
+ const managePermission = `manage:${scope}`;
271
+ if (defaultPermissions.includes(managePermission)) {
272
+ return true;
273
+ }
274
+ }
275
+ }
276
+ return false;
277
+ }
278
+ // ====================================================================
279
+ // EXPRESS MIDDLEWARE
280
+ // ====================================================================
281
+ /**
282
+ * Creates Express authentication middleware for login tokens
283
+ * @llm-rule WHEN: Protecting routes that need authenticated users
284
+ * @llm-rule AVOID: Using for API routes - use requireApiToken instead
285
+ * @llm-rule NOTE: Validates login tokens (type: 'login') and sets req.user
286
+ */
287
+ requireLoginToken(options = {}) {
288
+ if (!this.config.jwt.secret) {
289
+ throw new Error('JWT secret required for authentication middleware');
290
+ }
291
+ const getToken = options.getToken || this.getDefaultTokenExtractor();
292
+ return (req, res, next) => {
293
+ try {
294
+ const token = getToken(req);
295
+ if (!token) {
296
+ return res.status(401).json({
297
+ error: 'Authentication required',
298
+ message: this.config.middleware.errorMessages.noToken,
299
+ });
300
+ }
301
+ const payload = this.verifyToken(token);
302
+ if (payload.type !== 'login') {
303
+ return res.status(401).json({
304
+ error: 'Invalid token type',
305
+ message: 'Login token required for this endpoint',
306
+ });
307
+ }
308
+ req.user = payload;
309
+ next();
310
+ }
311
+ catch (error) {
312
+ const isExpired = error.message === 'Token has expired';
313
+ const message = isExpired
314
+ ? this.config.middleware.errorMessages.expiredToken
315
+ : this.config.middleware.errorMessages.invalidToken;
316
+ return res.status(401).json({
317
+ error: 'Unauthorized',
318
+ message,
319
+ });
320
+ }
321
+ };
322
+ }
323
+ /**
324
+ * Creates Express role-based authorization middleware for authenticated users
325
+ * @llm-rule WHEN: Protecting routes that require specific user roles
326
+ * @llm-rule AVOID: Using without requireLoginToken - this assumes user is already authenticated
327
+ * @llm-rule AVOID: Using with API tokens - API tokens don't have user roles
328
+ * @llm-rule NOTE: User needs ANY role from the array (OR logic)
329
+ * @llm-rule NOTE: Role inheritance applies - admin.org can access admin.tenant routes
330
+ */
331
+ requireUserRoles(requiredRoles) {
332
+ if (!Array.isArray(requiredRoles) || requiredRoles.length === 0) {
333
+ throw new Error('requiredRoles must be a non-empty array');
334
+ }
335
+ // Validate all roles exist
336
+ for (const role of requiredRoles) {
337
+ if (!validateRoleLevel(role, this.config.roles)) {
338
+ throw new Error(`Invalid role.level for middleware: "${role}"`);
339
+ }
340
+ }
341
+ return (req, res, next) => {
342
+ const user = this.user(req);
343
+ if (!user) {
344
+ return res.status(401).json({
345
+ error: 'Authentication required',
346
+ message: this.config.middleware.errorMessages.noToken,
347
+ });
348
+ }
349
+ if (user.type !== 'login') {
350
+ return res.status(403).json({
351
+ error: 'Access denied',
352
+ message: 'User roles only apply to login tokens',
353
+ });
354
+ }
355
+ const userRoleLevel = `${user.role}.${user.level}`;
356
+ const hasRequiredRole = requiredRoles.some(requiredRole => this.hasRole(userRoleLevel, requiredRole));
357
+ if (!hasRequiredRole) {
358
+ return res.status(403).json({
359
+ error: 'Access denied',
360
+ message: this.config.middleware.errorMessages.insufficientRole,
361
+ });
362
+ }
363
+ next();
364
+ };
365
+ }
366
+ /**
367
+ * Creates Express permission-based authorization middleware for authenticated users
368
+ * @llm-rule WHEN: Protecting routes that require specific user permissions
369
+ * @llm-rule AVOID: Using without requireLoginToken - this assumes user is already authenticated
370
+ * @llm-rule AVOID: Using with API tokens - API tokens don't have user permissions
371
+ * @llm-rule NOTE: User needs ALL permissions from the array (AND logic)
372
+ * @llm-rule NOTE: Permission inheritance applies - manage:tenant can access edit:tenant routes
373
+ */
374
+ requireUserPermissions(requiredPermissions) {
375
+ if (!Array.isArray(requiredPermissions) || requiredPermissions.length === 0) {
376
+ throw new Error('requiredPermissions must be a non-empty array');
377
+ }
378
+ // Validate all permissions
379
+ for (const permission of requiredPermissions) {
380
+ if (!validatePermission(permission)) {
381
+ throw new Error(`Invalid permission format for middleware: "${permission}"`);
382
+ }
383
+ }
384
+ return (req, res, next) => {
385
+ const user = this.user(req);
386
+ if (!user) {
387
+ return res.status(401).json({
388
+ error: 'Authentication required',
389
+ message: this.config.middleware.errorMessages.noToken,
390
+ });
391
+ }
392
+ if (user.type !== 'login') {
393
+ return res.status(403).json({
394
+ error: 'Access denied',
395
+ message: 'User permissions only apply to login tokens',
396
+ });
397
+ }
398
+ const hasAllPermissions = requiredPermissions.every(permission => this.can(user, permission));
399
+ if (!hasAllPermissions) {
400
+ return res.status(403).json({
401
+ error: 'Access denied',
402
+ message: this.config.middleware.errorMessages.insufficientPermissions,
403
+ });
404
+ }
405
+ next();
406
+ };
407
+ }
408
+ /**
409
+ * Creates Express API token authentication middleware for external access
410
+ * @llm-rule WHEN: Protecting API routes for third-party integrations
411
+ * @llm-rule AVOID: Using for user routes - use requireLoginToken instead
412
+ * @llm-rule NOTE: Validates API tokens (type: 'api_key') and sets req.token
413
+ */
414
+ requireApiToken(options = {}) {
415
+ if (!this.config.jwt.secret) {
416
+ throw new Error('JWT secret required for API token authentication middleware');
417
+ }
418
+ const getToken = options.getToken || this.getDefaultTokenExtractor();
419
+ return (req, res, next) => {
420
+ try {
421
+ const token = getToken(req);
422
+ if (!token) {
423
+ return res.status(401).json({
424
+ error: 'API token required',
425
+ message: 'API token required for this endpoint',
426
+ });
427
+ }
428
+ const payload = this.verifyToken(token);
429
+ if (payload.type !== 'api_key') {
430
+ return res.status(401).json({
431
+ error: 'Invalid token type',
432
+ message: 'API token required for this endpoint',
433
+ });
434
+ }
435
+ req.token = payload;
436
+ next();
437
+ }
438
+ catch (error) {
439
+ const isExpired = error.message === 'Token has expired';
440
+ const message = isExpired
441
+ ? 'API token has expired'
442
+ : 'Invalid API token';
443
+ return res.status(401).json({
444
+ error: 'Unauthorized',
445
+ message,
446
+ });
447
+ }
448
+ };
449
+ }
450
+ /**
451
+ * Gets default token extractor that checks headers, cookies, and query params
452
+ * @llm-rule WHEN: Need custom token extraction logic
453
+ * @llm-rule AVOID: Modifying directly - pass custom getToken to middleware options
454
+ */
455
+ getDefaultTokenExtractor() {
456
+ return (request) => {
457
+ // Check Authorization header (Bearer token)
458
+ const authHeader = request.headers.authorization;
459
+ if (authHeader && typeof authHeader === 'string') {
460
+ const match = authHeader.match(/^Bearer\s+(.+)$/);
461
+ if (match) {
462
+ return match[1];
463
+ }
464
+ }
465
+ // Check cookies
466
+ if (request.cookies?.token) {
467
+ return request.cookies.token;
468
+ }
469
+ // Check query parameter
470
+ if (request.query?.token && typeof request.query.token === 'string') {
471
+ return request.query.token;
472
+ }
473
+ return null;
474
+ };
475
+ }
476
+ }
477
+ //# sourceMappingURL=auth.js.map