@morojs/moro 1.1.0 → 1.2.1

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 (43) hide show
  1. package/README.md +56 -1
  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/http/http-server.d.ts +2 -0
  6. package/dist/core/http/http-server.js +52 -9
  7. package/dist/core/http/http-server.js.map +1 -1
  8. package/dist/core/logger/logger.d.ts +1 -0
  9. package/dist/core/logger/logger.js +5 -2
  10. package/dist/core/logger/logger.js.map +1 -1
  11. package/dist/core/middleware/built-in/auth-helpers.d.ts +124 -0
  12. package/dist/core/middleware/built-in/auth-helpers.js +338 -0
  13. package/dist/core/middleware/built-in/auth-helpers.js.map +1 -0
  14. package/dist/core/middleware/built-in/auth-providers.d.ts +125 -0
  15. package/dist/core/middleware/built-in/auth-providers.js +394 -0
  16. package/dist/core/middleware/built-in/auth-providers.js.map +1 -0
  17. package/dist/core/middleware/built-in/auth.d.ts +29 -1
  18. package/dist/core/middleware/built-in/auth.js +259 -16
  19. package/dist/core/middleware/built-in/auth.js.map +1 -1
  20. package/dist/core/middleware/built-in/index.d.ts +3 -1
  21. package/dist/core/middleware/built-in/index.js +19 -1
  22. package/dist/core/middleware/built-in/index.js.map +1 -1
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.js +10 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/moro.d.ts +1 -0
  27. package/dist/moro.js +23 -1
  28. package/dist/moro.js.map +1 -1
  29. package/dist/types/auth.d.ts +367 -0
  30. package/dist/types/auth.js +28 -0
  31. package/dist/types/auth.js.map +1 -0
  32. package/package.json +6 -2
  33. package/src/core/auth/README.md +339 -0
  34. package/src/core/auth/morojs-adapter.ts +402 -0
  35. package/src/core/http/http-server.ts +61 -10
  36. package/src/core/logger/logger.ts +8 -2
  37. package/src/core/middleware/built-in/auth-helpers.ts +401 -0
  38. package/src/core/middleware/built-in/auth-providers.ts +480 -0
  39. package/src/core/middleware/built-in/auth.ts +306 -16
  40. package/src/core/middleware/built-in/index.ts +22 -0
  41. package/src/index.ts +26 -0
  42. package/src/moro.ts +34 -1
  43. package/src/types/auth.ts +440 -0
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Auth.js Adapter for MoroJS
3
+ *
4
+ * This adapter allows Auth.js to work seamlessly with MoroJS framework.
5
+ * It can be contributed to the Auth.js project as @auth/morojs
6
+ *
7
+ * @see https://authjs.dev/guides/adapters/creating-a-custom-adapter
8
+ * @see https://github.com/nextauthjs/next-auth/tree/main/packages
9
+ */
10
+
11
+ // Mock Auth.js types until we have the actual package
12
+ // These would come from @auth/core in a real implementation
13
+ export interface AuthConfig {
14
+ providers: any[];
15
+ secret?: string;
16
+ session?: any;
17
+ callbacks?: any;
18
+ events?: any;
19
+ pages?: any;
20
+ adapter?: any;
21
+ debug?: boolean;
22
+ basePath?: string;
23
+ [key: string]: any;
24
+ }
25
+
26
+ export interface Session {
27
+ user: {
28
+ id: string;
29
+ name?: string | null;
30
+ email?: string | null;
31
+ image?: string | null;
32
+ [key: string]: any;
33
+ };
34
+ expires: string;
35
+ [key: string]: any;
36
+ }
37
+
38
+ export type AuthAction = 'signin' | 'signout' | 'callback' | 'session' | 'providers' | 'csrf';
39
+
40
+ // Mock Auth function - would be imported from @auth/core
41
+ const Auth = async (request: Request, config: any): Promise<Response> => {
42
+ // This is a placeholder implementation
43
+ // In the real version, this would be the actual Auth.js core function
44
+ const url = new URL(request.url);
45
+ const pathname = url.pathname;
46
+
47
+ if (pathname === '/session') {
48
+ return new Response(JSON.stringify({ user: null }), {
49
+ status: 200,
50
+ headers: { 'content-type': 'application/json' },
51
+ });
52
+ }
53
+
54
+ return new Response('Not implemented', { status: 501 });
55
+ };
56
+
57
+ // MoroJS-specific types
58
+ export interface MoroJSAuthConfig extends Omit<AuthConfig, 'raw'> {
59
+ /**
60
+ * Base path for auth routes in MoroJS
61
+ * @default "/api/auth"
62
+ */
63
+ basePath?: string;
64
+
65
+ /**
66
+ * MoroJS-specific options
67
+ */
68
+ morojs?: {
69
+ /**
70
+ * Enable MoroJS-specific logging
71
+ * @default false
72
+ */
73
+ debug?: boolean;
74
+
75
+ /**
76
+ * Custom request/response transformers
77
+ */
78
+ transformers?: {
79
+ request?: (req: any) => any;
80
+ response?: (res: any) => any;
81
+ };
82
+ };
83
+ }
84
+
85
+ export interface MoroJSRequest {
86
+ url?: string;
87
+ method?: string;
88
+ headers?: Record<string, string>;
89
+ body?: any;
90
+ query?: Record<string, string>;
91
+ cookies?: Record<string, string>;
92
+ }
93
+
94
+ export interface MoroJSResponse {
95
+ status(code: number): MoroJSResponse;
96
+ json(data: any): Promise<void>;
97
+ redirect(url: string): void;
98
+ setHeader(name: string, value: string): void;
99
+ cookie(name: string, value: string, options?: any): void;
100
+ send(data: any): void;
101
+ end(data?: any): void;
102
+ headersSent: boolean;
103
+ }
104
+
105
+ /**
106
+ * Convert MoroJS request to Auth.js Web API Request
107
+ */
108
+ function toWebRequest(req: MoroJSRequest, basePath: string): Request {
109
+ const url = new URL(req.url || '/', 'http://localhost:3000');
110
+
111
+ // Handle auth routes
112
+ if (url.pathname.startsWith(basePath)) {
113
+ url.pathname = url.pathname.replace(basePath, '');
114
+ }
115
+
116
+ const headers = new Headers();
117
+ if (req.headers) {
118
+ Object.entries(req.headers).forEach(([key, value]) => {
119
+ headers.set(key, value);
120
+ });
121
+ }
122
+
123
+ // Add cookies to headers if not present
124
+ if (req.cookies && Object.keys(req.cookies).length > 0) {
125
+ const cookieHeader = Object.entries(req.cookies)
126
+ .map(([name, value]) => `${name}=${value}`)
127
+ .join('; ');
128
+
129
+ if (!headers.has('cookie')) {
130
+ headers.set('cookie', cookieHeader);
131
+ }
132
+ }
133
+
134
+ const body = req.body ? JSON.stringify(req.body) : undefined;
135
+
136
+ return new Request(url.toString(), {
137
+ method: req.method || 'GET',
138
+ headers,
139
+ body,
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Convert Auth.js Web API Response to MoroJS response
145
+ */
146
+ async function fromWebResponse(webResponse: Response, moroResponse: MoroJSResponse): Promise<void> {
147
+ // Set status
148
+ moroResponse.status(webResponse.status);
149
+
150
+ // Set headers
151
+ webResponse.headers.forEach((value, key) => {
152
+ if (key.toLowerCase() === 'set-cookie') {
153
+ // Handle cookies specially for MoroJS
154
+ const cookies = value.split(', ');
155
+ cookies.forEach(cookie => {
156
+ const [nameValue, ...options] = cookie.split('; ');
157
+ const [name, cookieValue] = nameValue.split('=');
158
+
159
+ // Parse cookie options
160
+ const cookieOptions: any = {};
161
+ options.forEach(option => {
162
+ const [optKey, optValue] = option.split('=');
163
+ switch (optKey.toLowerCase()) {
164
+ case 'max-age':
165
+ cookieOptions.maxAge = parseInt(optValue, 10);
166
+ break;
167
+ case 'expires':
168
+ cookieOptions.expires = new Date(optValue);
169
+ break;
170
+ case 'httponly':
171
+ cookieOptions.httpOnly = true;
172
+ break;
173
+ case 'secure':
174
+ cookieOptions.secure = true;
175
+ break;
176
+ case 'samesite':
177
+ cookieOptions.sameSite = optValue;
178
+ break;
179
+ case 'path':
180
+ cookieOptions.path = optValue;
181
+ break;
182
+ case 'domain':
183
+ cookieOptions.domain = optValue;
184
+ break;
185
+ }
186
+ });
187
+
188
+ moroResponse.cookie(name, cookieValue, cookieOptions);
189
+ });
190
+ } else if (key.toLowerCase() === 'location') {
191
+ // Handle redirects
192
+ moroResponse.redirect(value);
193
+ return;
194
+ } else {
195
+ moroResponse.setHeader(key, value);
196
+ }
197
+ });
198
+
199
+ // Handle response body
200
+ const contentType = webResponse.headers.get('content-type');
201
+
202
+ if (webResponse.status >= 300 && webResponse.status < 400) {
203
+ // Redirect - already handled above
204
+ return;
205
+ } else if (contentType?.includes('application/json')) {
206
+ const data = await webResponse.json();
207
+ await moroResponse.json(data);
208
+ } else {
209
+ const text = await webResponse.text();
210
+ moroResponse.send(text);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Main MoroJS Auth.js handler
216
+ *
217
+ * This is the core function that integrates Auth.js with MoroJS
218
+ */
219
+ export async function MoroJSAuth(config: MoroJSAuthConfig): Promise<{
220
+ handler: (req: MoroJSRequest, res: MoroJSResponse) => Promise<void>;
221
+ auth: (req: MoroJSRequest) => Promise<Session | null>;
222
+ }> {
223
+ const basePath = config.basePath || '/api/auth';
224
+
225
+ return {
226
+ /**
227
+ * Main request handler for auth routes
228
+ */
229
+ handler: async (req: MoroJSRequest, res: MoroJSResponse) => {
230
+ try {
231
+ // Convert MoroJS request to Web API request
232
+ const webRequest = toWebRequest(req, basePath);
233
+
234
+ // Determine the auth action from the URL
235
+ const url = new URL(webRequest.url);
236
+ const action = (url.pathname.split('/')[1] as AuthAction) || 'session';
237
+
238
+ // Apply request transformer if provided
239
+ let transformedRequest = webRequest;
240
+ if (config.morojs?.transformers?.request) {
241
+ transformedRequest = config.morojs.transformers.request(webRequest);
242
+ }
243
+
244
+ // Call Auth.js core
245
+ const authResponse = await Auth(transformedRequest, {
246
+ ...config,
247
+ basePath,
248
+ raw: (code: any, ...message: any[]) => {
249
+ if (config.morojs?.debug) {
250
+ console.log(`[MoroJS Auth] ${code}:`, ...message);
251
+ }
252
+ },
253
+ });
254
+
255
+ // Apply response transformer if provided
256
+ let finalResponse = authResponse;
257
+ if (config.morojs?.transformers?.response) {
258
+ finalResponse = config.morojs.transformers.response(authResponse);
259
+ }
260
+
261
+ // Convert Web API response to MoroJS response
262
+ await fromWebResponse(finalResponse, res);
263
+ } catch (error) {
264
+ console.error('[MoroJS Auth] Error:', error);
265
+ // Robust error handling - check if response methods exist
266
+ if (typeof (res as any).status === 'function' && typeof (res as any).json === 'function') {
267
+ (res as any).status(500).json({
268
+ error: 'Internal server error',
269
+ message: config.morojs?.debug ? (error as Error).message : 'Authentication error',
270
+ });
271
+ } else {
272
+ // Fallback to basic Node.js response methods
273
+ (res as any).statusCode = 500;
274
+ (res as any).setHeader('Content-Type', 'application/json');
275
+ (res as any).end(
276
+ JSON.stringify({
277
+ error: 'Internal server error',
278
+ message: config.morojs?.debug ? (error as Error).message : 'Authentication error',
279
+ })
280
+ );
281
+ }
282
+ }
283
+ },
284
+
285
+ /**
286
+ * Get session for the current request
287
+ */
288
+ auth: async (req: MoroJSRequest): Promise<Session | null> => {
289
+ try {
290
+ // Create a session request
291
+ const sessionUrl = new URL('/session', 'http://localhost:3000');
292
+ const sessionRequest = new Request(sessionUrl.toString(), {
293
+ method: 'GET',
294
+ headers: req.headers ? new Headers(req.headers) : new Headers(),
295
+ });
296
+
297
+ // Get session from Auth.js
298
+ const response = await Auth(sessionRequest, {
299
+ ...config,
300
+ basePath,
301
+ });
302
+
303
+ if (response.status === 200) {
304
+ const session = await response.json();
305
+ return session as Session;
306
+ }
307
+
308
+ return null;
309
+ } catch (error) {
310
+ if (config.morojs?.debug) {
311
+ console.error('[MoroJS Auth] Session error:', error);
312
+ }
313
+ return null;
314
+ }
315
+ },
316
+ };
317
+ }
318
+
319
+ /**
320
+ * MoroJS Auth middleware factory
321
+ *
322
+ * This creates a MoroJS-compatible middleware for authentication
323
+ */
324
+ export function createAuthMiddleware(config: MoroJSAuthConfig) {
325
+ console.log('🏭 createAuthMiddleware called - creating middleware function');
326
+ // Return a function that MoroJS can call directly
327
+ return async (app: any) => {
328
+ console.log('🔧 Installing Auth.js middleware...');
329
+ console.log('📦 App object received:', typeof app, app.constructor.name);
330
+
331
+ // Get the hooks from the app's middleware system
332
+ const hooks =
333
+ (app as any).coreFramework?.middlewareManager?.hooks || (app as any).middlewareManager?.hooks;
334
+
335
+ if (!hooks) {
336
+ console.error('❌ Could not access MoroJS hooks system');
337
+ return;
338
+ }
339
+
340
+ const options = {};
341
+ const mergedConfig = { ...config, ...options };
342
+ const { handler, auth } = await MoroJSAuth(mergedConfig);
343
+ const basePath = mergedConfig.basePath || '/api/auth';
344
+
345
+ // Register request hook
346
+ hooks.before('request', async (context: any) => {
347
+ console.log('🔒 Native adapter hook starting...');
348
+ const req = context.request;
349
+ console.log('📝 Request path:', req.path || req.url);
350
+
351
+ try {
352
+ // Just add auth object to request - don't touch response
353
+ req.auth = {
354
+ session: null,
355
+ user: null,
356
+ isAuthenticated: false,
357
+
358
+ // Helper methods
359
+ getSession: () => Promise.resolve(null),
360
+ getUser: () => null,
361
+
362
+ // Sign in/out helpers (redirect to auth routes)
363
+ signIn: (provider?: string, options?: any) => {
364
+ const params = new URLSearchParams();
365
+ if (provider) params.set('provider', provider);
366
+ if (options?.callbackUrl) params.set('callbackUrl', options.callbackUrl);
367
+
368
+ const signInUrl = `${basePath}/signin${provider ? `/${provider}` : ''}${
369
+ params.toString() ? `?${params.toString()}` : ''
370
+ }`;
371
+
372
+ return { url: signInUrl };
373
+ },
374
+
375
+ signOut: (options?: any) => {
376
+ const params = new URLSearchParams();
377
+ if (options?.callbackUrl) params.set('callbackUrl', options.callbackUrl);
378
+
379
+ const signOutUrl = `${basePath}/signout${
380
+ params.toString() ? `?${params.toString()}` : ''
381
+ }`;
382
+
383
+ return { url: signOutUrl };
384
+ },
385
+ };
386
+ console.log('✅ Native adapter hook completed successfully');
387
+ } catch (error) {
388
+ console.error('❌ Error in native adapter hook:', error);
389
+ throw error;
390
+ }
391
+ });
392
+
393
+ console.log('✅ Auth.js middleware installed successfully!');
394
+ };
395
+ }
396
+
397
+ // Types are already exported above, no need to re-export
398
+
399
+ /**
400
+ * Default export for convenience
401
+ */
402
+ export default MoroJSAuth;
@@ -16,6 +16,7 @@ export class MoroHttpServer {
16
16
  private compressionEnabled = true;
17
17
  private compressionThreshold = 1024;
18
18
  private logger = createFrameworkLogger('HttpServer');
19
+ private hookManager: any;
19
20
 
20
21
  constructor() {
21
22
  this.server = createServer(this.handleRequest.bind(this));
@@ -26,6 +27,11 @@ export class MoroHttpServer {
26
27
  this.globalMiddleware.push(middleware);
27
28
  }
28
29
 
30
+ // Set hooks manager for request processing
31
+ setHookManager(hookManager: any): void {
32
+ this.hookManager = hookManager;
33
+ }
34
+
29
35
  // Routing methods
30
36
  get(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
31
37
  this.addRoute('GET', path, handlers);
@@ -94,6 +100,14 @@ export class MoroHttpServer {
94
100
  httpReq.body = await this.parseBody(req);
95
101
  }
96
102
 
103
+ // Execute hooks before request processing
104
+ if (this.hookManager) {
105
+ await this.hookManager.execute('request', {
106
+ request: httpReq,
107
+ response: httpRes,
108
+ });
109
+ }
110
+
97
111
  // Execute global middleware first
98
112
  await this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
99
113
 
@@ -124,6 +138,14 @@ export class MoroHttpServer {
124
138
  // Execute handler
125
139
  await route.handler(httpReq, httpRes);
126
140
  } catch (error) {
141
+ // Debug: Log the actual error and where it came from
142
+ console.log('🚨 MoroJS Request Error Details:');
143
+ console.log('📍 Error type:', typeof error);
144
+ console.log('📍 Error message:', error instanceof Error ? error.message : String(error));
145
+ console.log('📍 Error stack:', error instanceof Error ? error.stack : 'No stack trace');
146
+ console.log('📍 Request path:', req.url);
147
+ console.log('📍 Request method:', req.method);
148
+
127
149
  this.logger.error('Request error', 'RequestHandler', {
128
150
  error: error instanceof Error ? error.message : String(error),
129
151
  requestId: httpReq.requestId,
@@ -132,11 +154,39 @@ export class MoroHttpServer {
132
154
  });
133
155
 
134
156
  if (!httpRes.headersSent) {
135
- httpRes.status(500).json({
136
- success: false,
137
- error: 'Internal server error',
138
- requestId: httpReq.requestId,
139
- });
157
+ // Ensure response is properly enhanced before using custom methods
158
+ if (typeof httpRes.status === 'function' && typeof httpRes.json === 'function') {
159
+ httpRes.status(500).json({
160
+ success: false,
161
+ error: 'Internal server error',
162
+ requestId: httpReq.requestId,
163
+ });
164
+ } else {
165
+ // Ultra-defensive fallback - check each method individually
166
+ if (typeof httpRes.setHeader === 'function') {
167
+ httpRes.statusCode = 500;
168
+ httpRes.setHeader('Content-Type', 'application/json');
169
+ } else {
170
+ // Even setHeader doesn't exist - object is completely wrong
171
+ console.error(
172
+ '❌ Response object is not a proper ServerResponse:',
173
+ typeof httpRes,
174
+ Object.keys(httpRes)
175
+ );
176
+ }
177
+
178
+ if (typeof httpRes.end === 'function') {
179
+ httpRes.end(
180
+ JSON.stringify({
181
+ success: false,
182
+ error: 'Internal server error',
183
+ requestId: httpReq.requestId,
184
+ })
185
+ );
186
+ } else {
187
+ console.error('❌ Cannot send error response - end() method missing');
188
+ }
189
+ }
140
190
  }
141
191
  }
142
192
  }
@@ -174,6 +224,12 @@ export class MoroHttpServer {
174
224
  private enhanceResponse(res: ServerResponse): HttpResponse {
175
225
  const httpRes = res as HttpResponse;
176
226
 
227
+ // BULLETPROOF status method - always works
228
+ httpRes.status = (code: number) => {
229
+ httpRes.statusCode = code;
230
+ return httpRes;
231
+ };
232
+
177
233
  httpRes.json = async (data: any) => {
178
234
  if (httpRes.headersSent) return;
179
235
 
@@ -205,11 +261,6 @@ export class MoroHttpServer {
205
261
  httpRes.end(buffer);
206
262
  };
207
263
 
208
- httpRes.status = (code: number) => {
209
- httpRes.statusCode = code;
210
- return httpRes;
211
- };
212
-
213
264
  httpRes.send = (data: string | Buffer) => {
214
265
  if (httpRes.headersSent) return;
215
266
 
@@ -29,6 +29,7 @@ export class MoroLogger implements Logger {
29
29
  private startTime = Date.now();
30
30
  private contextPrefix?: string;
31
31
  private contextMetadata?: Record<string, any>;
32
+ private parent?: MoroLogger; // Reference to parent logger for level inheritance
32
33
 
33
34
  private static readonly LEVELS: Record<LogLevel, number> = {
34
35
  debug: 0,
@@ -129,6 +130,10 @@ export class MoroLogger implements Logger {
129
130
  childLogger.contextMetadata = { ...this.contextMetadata, ...metadata };
130
131
  childLogger.outputs = this.outputs;
131
132
  childLogger.filters = this.filters;
133
+
134
+ // Keep reference to parent for level inheritance
135
+ (childLogger as any).parent = this;
136
+
132
137
  return childLogger;
133
138
  }
134
139
 
@@ -190,8 +195,9 @@ export class MoroLogger implements Logger {
190
195
  context?: string,
191
196
  metadata?: Record<string, any>
192
197
  ): void {
193
- // Check level threshold
194
- if (MoroLogger.LEVELS[level] < MoroLogger.LEVELS[this.level]) {
198
+ // Check level threshold - use parent level if available (for child loggers)
199
+ const effectiveLevel = this.parent ? this.parent.level : this.level;
200
+ if (MoroLogger.LEVELS[level] < MoroLogger.LEVELS[effectiveLevel as LogLevel]) {
195
201
  return;
196
202
  }
197
203