@morojs/moro 1.0.3 → 1.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.
Files changed (55) hide show
  1. package/README.md +57 -2
  2. package/dist/core/auth/morojs-adapter.d.ts +94 -0
  3. package/dist/core/auth/morojs-adapter.js +288 -0
  4. package/dist/core/auth/morojs-adapter.js.map +1 -0
  5. package/dist/core/config/file-loader.d.ts +18 -0
  6. package/dist/core/config/file-loader.js +345 -0
  7. package/dist/core/config/file-loader.js.map +1 -0
  8. package/dist/core/config/index.d.ts +6 -0
  9. package/dist/core/config/index.js +15 -0
  10. package/dist/core/config/index.js.map +1 -1
  11. package/dist/core/config/loader.d.ts +2 -1
  12. package/dist/core/config/loader.js +15 -2
  13. package/dist/core/config/loader.js.map +1 -1
  14. package/dist/core/config/utils.js +50 -3
  15. package/dist/core/config/utils.js.map +1 -1
  16. package/dist/core/http/http-server.d.ts +2 -0
  17. package/dist/core/http/http-server.js +52 -9
  18. package/dist/core/http/http-server.js.map +1 -1
  19. package/dist/core/middleware/built-in/auth-helpers.d.ts +124 -0
  20. package/dist/core/middleware/built-in/auth-helpers.js +338 -0
  21. package/dist/core/middleware/built-in/auth-helpers.js.map +1 -0
  22. package/dist/core/middleware/built-in/auth-providers.d.ts +125 -0
  23. package/dist/core/middleware/built-in/auth-providers.js +394 -0
  24. package/dist/core/middleware/built-in/auth-providers.js.map +1 -0
  25. package/dist/core/middleware/built-in/auth.d.ts +29 -1
  26. package/dist/core/middleware/built-in/auth.js +259 -16
  27. package/dist/core/middleware/built-in/auth.js.map +1 -1
  28. package/dist/core/middleware/built-in/index.d.ts +3 -1
  29. package/dist/core/middleware/built-in/index.js +19 -1
  30. package/dist/core/middleware/built-in/index.js.map +1 -1
  31. package/dist/index.d.ts +5 -1
  32. package/dist/index.js +11 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/moro.d.ts +1 -0
  35. package/dist/moro.js +19 -1
  36. package/dist/moro.js.map +1 -1
  37. package/dist/types/auth.d.ts +367 -0
  38. package/dist/types/auth.js +28 -0
  39. package/dist/types/auth.js.map +1 -0
  40. package/package.json +6 -2
  41. package/src/core/auth/README.md +339 -0
  42. package/src/core/auth/morojs-adapter.ts +402 -0
  43. package/src/core/config/file-loader.ts +398 -0
  44. package/src/core/config/index.ts +18 -0
  45. package/src/core/config/loader.ts +18 -2
  46. package/src/core/config/utils.ts +53 -3
  47. package/src/core/http/http-server.ts +61 -10
  48. package/src/core/middleware/built-in/auth-helpers.ts +401 -0
  49. package/src/core/middleware/built-in/auth-providers.ts +480 -0
  50. package/src/core/middleware/built-in/auth.ts +306 -16
  51. package/src/core/middleware/built-in/index.ts +22 -0
  52. package/src/index.ts +30 -1
  53. package/src/moro.ts +29 -1
  54. package/src/types/auth.ts +440 -0
  55. package/tsconfig.json +1 -1
@@ -0,0 +1,401 @@
1
+ // Auth Helper Middleware and Utilities
2
+ import { AuthRequest } from '../../../types/auth';
3
+
4
+ export interface AuthGuardOptions {
5
+ redirectTo?: string;
6
+ redirectOnAuth?: string; // Redirect if already authenticated
7
+ authorize?: (user: any) => boolean | Promise<boolean>;
8
+ roles?: string[];
9
+ permissions?: string[];
10
+ allowUnauthenticated?: boolean;
11
+ onUnauthorized?: (req: any, res: any) => void;
12
+ onForbidden?: (req: any, res: any) => void;
13
+ }
14
+
15
+ export interface AuthRouteOptions {
16
+ requireAuth?: boolean;
17
+ roles?: string[];
18
+ permissions?: string[];
19
+ redirectTo?: string;
20
+ }
21
+
22
+ /**
23
+ * Auth Guard Middleware - Protects routes with authentication and authorization
24
+ */
25
+ export function requireAuth(options: AuthGuardOptions = {}) {
26
+ return async (req: any, res: any, next: any) => {
27
+ const auth: AuthRequest = req.auth;
28
+
29
+ if (!auth) {
30
+ throw new Error('Auth middleware must be installed before using requireAuth');
31
+ }
32
+
33
+ // Check if already authenticated and should redirect
34
+ if (auth.isAuthenticated && options.redirectOnAuth) {
35
+ return res.redirect(options.redirectOnAuth);
36
+ }
37
+
38
+ // Check authentication requirement
39
+ if (!options.allowUnauthenticated && !auth.isAuthenticated) {
40
+ if (options.onUnauthorized) {
41
+ return options.onUnauthorized(req, res);
42
+ }
43
+
44
+ if (options.redirectTo) {
45
+ return res.redirect(`${options.redirectTo}?callbackUrl=${encodeURIComponent(req.url)}`);
46
+ }
47
+
48
+ return res.status(401).json({
49
+ error: 'Authentication required',
50
+ message: 'You must be logged in to access this resource',
51
+ signInUrl: '/api/auth/signin',
52
+ });
53
+ }
54
+
55
+ // Skip further checks if not authenticated but allowed
56
+ if (!auth.isAuthenticated && options.allowUnauthenticated) {
57
+ return next();
58
+ }
59
+
60
+ const user = auth.user;
61
+
62
+ // Check roles if specified
63
+ if (options.roles && options.roles.length > 0) {
64
+ const userRoles = user?.roles || [];
65
+ const hasRole = options.roles.some(role => userRoles.includes(role));
66
+
67
+ if (!hasRole) {
68
+ if (options.onForbidden) {
69
+ return options.onForbidden(req, res);
70
+ }
71
+
72
+ return res.status(403).json({
73
+ error: 'Insufficient permissions',
74
+ message: `Required roles: ${options.roles.join(', ')}`,
75
+ userRoles,
76
+ });
77
+ }
78
+ }
79
+
80
+ // Check permissions if specified
81
+ if (options.permissions && options.permissions.length > 0) {
82
+ const userPermissions = user?.permissions || [];
83
+ const hasPermission = options.permissions.every(permission =>
84
+ userPermissions.includes(permission)
85
+ );
86
+
87
+ if (!hasPermission) {
88
+ if (options.onForbidden) {
89
+ return options.onForbidden(req, res);
90
+ }
91
+
92
+ return res.status(403).json({
93
+ error: 'Insufficient permissions',
94
+ message: `Required permissions: ${options.permissions.join(', ')}`,
95
+ userPermissions,
96
+ });
97
+ }
98
+ }
99
+
100
+ // Custom authorization function
101
+ if (options.authorize) {
102
+ try {
103
+ const authorized = await options.authorize(user);
104
+
105
+ if (!authorized) {
106
+ if (options.onForbidden) {
107
+ return options.onForbidden(req, res);
108
+ }
109
+
110
+ return res.status(403).json({
111
+ error: 'Access denied',
112
+ message: 'Custom authorization check failed',
113
+ });
114
+ }
115
+ } catch (error) {
116
+ return res.status(500).json({
117
+ error: 'Authorization error',
118
+ message: 'Failed to verify authorization',
119
+ });
120
+ }
121
+ }
122
+
123
+ // All checks passed
124
+ next();
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Role-based access control middleware
130
+ */
131
+ export function requireRole(
132
+ role: string | string[],
133
+ options: Omit<AuthGuardOptions, 'roles'> = {}
134
+ ) {
135
+ const roles = Array.isArray(role) ? role : [role];
136
+ return requireAuth({ ...options, roles });
137
+ }
138
+
139
+ /**
140
+ * Permission-based access control middleware
141
+ */
142
+ export function requirePermission(
143
+ permission: string | string[],
144
+ options: Omit<AuthGuardOptions, 'permissions'> = {}
145
+ ) {
146
+ const permissions = Array.isArray(permission) ? permission : [permission];
147
+ return requireAuth({ ...options, permissions });
148
+ }
149
+
150
+ /**
151
+ * Admin-only access middleware
152
+ */
153
+ export function requireAdmin(options: Omit<AuthGuardOptions, 'roles'> = {}) {
154
+ return requireRole('admin', options);
155
+ }
156
+
157
+ /**
158
+ * Guest-only middleware (redirect if authenticated)
159
+ */
160
+ export function guestOnly(redirectTo = '/dashboard') {
161
+ return requireAuth({
162
+ allowUnauthenticated: true,
163
+ redirectOnAuth: redirectTo,
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Optional auth middleware (allows both authenticated and unauthenticated)
169
+ */
170
+ export function optionalAuth() {
171
+ return requireAuth({
172
+ allowUnauthenticated: true,
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Route-level auth decorator
178
+ */
179
+ export function withAuth(options: AuthRouteOptions = {}) {
180
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
181
+ const originalMethod = descriptor.value;
182
+
183
+ descriptor.value = async function (req: any, res: any, next: any) {
184
+ if (options.requireAuth !== false) {
185
+ const authMiddleware = requireAuth({
186
+ roles: options.roles,
187
+ redirectTo: options.redirectTo,
188
+ });
189
+
190
+ await new Promise<void>((resolve, reject) => {
191
+ authMiddleware(req, res, (error: any) => {
192
+ if (error) reject(error);
193
+ else resolve();
194
+ });
195
+ });
196
+ }
197
+
198
+ return originalMethod.call(this, req, res, next);
199
+ };
200
+
201
+ return descriptor;
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Auth utilities for manual checks in route handlers
207
+ */
208
+ export const authUtils = {
209
+ /**
210
+ * Check if user is authenticated
211
+ */
212
+ isAuthenticated(req: any): boolean {
213
+ return req.auth?.isAuthenticated || false;
214
+ },
215
+
216
+ /**
217
+ * Get current user
218
+ */
219
+ getUser(req: any) {
220
+ return req.auth?.user || null;
221
+ },
222
+
223
+ /**
224
+ * Check if user has role
225
+ */
226
+ hasRole(req: any, role: string | string[]): boolean {
227
+ const user = this.getUser(req);
228
+ if (!user?.roles) return false;
229
+
230
+ const roles = Array.isArray(role) ? role : [role];
231
+ return roles.some(r => user.roles.includes(r));
232
+ },
233
+
234
+ /**
235
+ * Check if user has permission
236
+ */
237
+ hasPermission(req: any, permission: string | string[]): boolean {
238
+ const user = this.getUser(req);
239
+ if (!user?.permissions) return false;
240
+
241
+ const permissions = Array.isArray(permission) ? permission : [permission];
242
+ return permissions.every(p => user.permissions.includes(p));
243
+ },
244
+
245
+ /**
246
+ * Check if user is admin
247
+ */
248
+ isAdmin(req: any): boolean {
249
+ return this.hasRole(req, 'admin');
250
+ },
251
+
252
+ /**
253
+ * Get user ID
254
+ */
255
+ getUserId(req: any): string | null {
256
+ return this.getUser(req)?.id || null;
257
+ },
258
+
259
+ /**
260
+ * Force authentication check and redirect if needed
261
+ */
262
+ ensureAuth(req: any, res: any, redirectTo = '/api/auth/signin'): boolean {
263
+ if (!this.isAuthenticated(req)) {
264
+ res.redirect(`${redirectTo}?callbackUrl=${encodeURIComponent(req.url)}`);
265
+ return false;
266
+ }
267
+ return true;
268
+ },
269
+
270
+ /**
271
+ * Create auth response for API endpoints
272
+ */
273
+ createAuthResponse(req: any) {
274
+ const auth = req.auth;
275
+
276
+ return {
277
+ isAuthenticated: auth?.isAuthenticated || false,
278
+ user: auth?.user || null,
279
+ session: auth?.session || null,
280
+ timestamp: new Date().toISOString(),
281
+ };
282
+ },
283
+ };
284
+
285
+ /**
286
+ * API Response helpers for auth endpoints
287
+ */
288
+ export const authResponses = {
289
+ unauthorized: (res: any, message = 'Authentication required') => {
290
+ return res.status(401).json({
291
+ error: 'Unauthorized',
292
+ message,
293
+ code: 'AUTH_REQUIRED',
294
+ signInUrl: '/api/auth/signin',
295
+ });
296
+ },
297
+
298
+ forbidden: (res: any, message = 'Insufficient permissions') => {
299
+ return res.status(403).json({
300
+ error: 'Forbidden',
301
+ message,
302
+ code: 'INSUFFICIENT_PERMISSIONS',
303
+ });
304
+ },
305
+
306
+ authSuccess: (res: any, user: any, message = 'Authentication successful') => {
307
+ return res.json({
308
+ success: true,
309
+ message,
310
+ user: {
311
+ id: user.id,
312
+ name: user.name,
313
+ email: user.email,
314
+ roles: user.roles || [],
315
+ permissions: user.permissions || [],
316
+ },
317
+ });
318
+ },
319
+
320
+ authError: (res: any, error: string, message = 'Authentication failed') => {
321
+ return res.status(400).json({
322
+ error,
323
+ message,
324
+ code: 'AUTH_ERROR',
325
+ });
326
+ },
327
+ };
328
+
329
+ /**
330
+ * Higher-order function to create protected route handlers
331
+ */
332
+ export function protectedRoute(
333
+ handler: (req: any, res: any, next?: any) => any,
334
+ options: AuthGuardOptions = {}
335
+ ) {
336
+ return async (req: any, res: any, next: any) => {
337
+ const authMiddleware = requireAuth(options);
338
+
339
+ return new Promise<void>((resolve, reject) => {
340
+ authMiddleware(req, res, (error: any) => {
341
+ if (error) {
342
+ reject(error);
343
+ } else {
344
+ Promise.resolve(handler(req, res, next))
345
+ .then(() => resolve())
346
+ .catch(reject);
347
+ }
348
+ });
349
+ });
350
+ };
351
+ }
352
+
353
+ /**
354
+ * Session management helpers
355
+ */
356
+ export const sessionHelpers = {
357
+ /**
358
+ * Store data in session
359
+ */
360
+ async setSessionData(req: any, key: string, value: any) {
361
+ if (req.session) {
362
+ req.session[key] = value;
363
+ await req.session.save();
364
+ }
365
+ },
366
+
367
+ /**
368
+ * Get data from session
369
+ */
370
+ getSessionData(req: any, key: string) {
371
+ return req.session?.[key] || null;
372
+ },
373
+
374
+ /**
375
+ * Remove data from session
376
+ */
377
+ async removeSessionData(req: any, key: string) {
378
+ if (req.session && key in req.session.data) {
379
+ delete req.session.data[key];
380
+ await req.session.save();
381
+ }
382
+ },
383
+
384
+ /**
385
+ * Clear entire session
386
+ */
387
+ async clearSession(req: any) {
388
+ if (req.session) {
389
+ await req.session.destroy();
390
+ }
391
+ },
392
+
393
+ /**
394
+ * Regenerate session ID
395
+ */
396
+ async regenerateSession(req: any) {
397
+ if (req.session) {
398
+ return await req.session.regenerate();
399
+ }
400
+ },
401
+ };