hightjs 0.1.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 (131) hide show
  1. package/.idea/HightJS.iml +9 -0
  2. package/.idea/copilot.data.migration.agent.xml +6 -0
  3. package/.idea/copilot.data.migration.ask.xml +6 -0
  4. package/.idea/copilot.data.migration.ask2agent.xml +6 -0
  5. package/.idea/copilot.data.migration.edit.xml +6 -0
  6. package/.idea/inspectionProfiles/Project_Default.xml +13 -0
  7. package/.idea/libraries/test_package.xml +9 -0
  8. package/.idea/libraries/ts_commonjs_default_export.xml +9 -0
  9. package/.idea/misc.xml +7 -0
  10. package/.idea/modules.xml +8 -0
  11. package/.idea/vcs.xml +6 -0
  12. package/LICENSE +13 -0
  13. package/README.md +508 -0
  14. package/dist/adapters/express.d.ts +7 -0
  15. package/dist/adapters/express.js +63 -0
  16. package/dist/adapters/factory.d.ts +23 -0
  17. package/dist/adapters/factory.js +122 -0
  18. package/dist/adapters/fastify.d.ts +25 -0
  19. package/dist/adapters/fastify.js +61 -0
  20. package/dist/adapters/native.d.ts +8 -0
  21. package/dist/adapters/native.js +203 -0
  22. package/dist/adapters/starters/express.d.ts +0 -0
  23. package/dist/adapters/starters/express.js +1 -0
  24. package/dist/adapters/starters/factory.d.ts +0 -0
  25. package/dist/adapters/starters/factory.js +1 -0
  26. package/dist/adapters/starters/fastify.d.ts +0 -0
  27. package/dist/adapters/starters/fastify.js +1 -0
  28. package/dist/adapters/starters/index.d.ts +0 -0
  29. package/dist/adapters/starters/index.js +1 -0
  30. package/dist/adapters/starters/native.d.ts +0 -0
  31. package/dist/adapters/starters/native.js +1 -0
  32. package/dist/api/console.d.ts +92 -0
  33. package/dist/api/console.js +276 -0
  34. package/dist/api/http.d.ts +180 -0
  35. package/dist/api/http.js +467 -0
  36. package/dist/auth/client.d.ts +14 -0
  37. package/dist/auth/client.js +68 -0
  38. package/dist/auth/components.d.ts +29 -0
  39. package/dist/auth/components.js +84 -0
  40. package/dist/auth/core.d.ts +38 -0
  41. package/dist/auth/core.js +124 -0
  42. package/dist/auth/index.d.ts +7 -0
  43. package/dist/auth/index.js +27 -0
  44. package/dist/auth/jwt.d.ts +41 -0
  45. package/dist/auth/jwt.js +169 -0
  46. package/dist/auth/providers.d.ts +5 -0
  47. package/dist/auth/providers.js +14 -0
  48. package/dist/auth/react/index.d.ts +6 -0
  49. package/dist/auth/react/index.js +32 -0
  50. package/dist/auth/react.d.ts +22 -0
  51. package/dist/auth/react.js +175 -0
  52. package/dist/auth/routes.d.ts +16 -0
  53. package/dist/auth/routes.js +104 -0
  54. package/dist/auth/types.d.ts +62 -0
  55. package/dist/auth/types.js +2 -0
  56. package/dist/bin/hightjs.d.ts +2 -0
  57. package/dist/bin/hightjs.js +35 -0
  58. package/dist/builder.d.ts +32 -0
  59. package/dist/builder.js +341 -0
  60. package/dist/client/DefaultNotFound.d.ts +1 -0
  61. package/dist/client/DefaultNotFound.js +53 -0
  62. package/dist/client/ErrorBoundary.d.ts +16 -0
  63. package/dist/client/ErrorBoundary.js +181 -0
  64. package/dist/client/clientRouter.d.ts +58 -0
  65. package/dist/client/clientRouter.js +116 -0
  66. package/dist/client/entry.client.d.ts +1 -0
  67. package/dist/client/entry.client.js +271 -0
  68. package/dist/client/routerContext.d.ts +26 -0
  69. package/dist/client/routerContext.js +62 -0
  70. package/dist/client.d.ts +3 -0
  71. package/dist/client.js +8 -0
  72. package/dist/components/Link.d.ts +7 -0
  73. package/dist/components/Link.js +13 -0
  74. package/dist/eslint/index.d.ts +32 -0
  75. package/dist/eslint/index.js +15 -0
  76. package/dist/eslint/use-client-rule.d.ts +19 -0
  77. package/dist/eslint/use-client-rule.js +99 -0
  78. package/dist/eslintSetup.d.ts +0 -0
  79. package/dist/eslintSetup.js +1 -0
  80. package/dist/example/src/web/routes/index.d.ts +3 -0
  81. package/dist/example/src/web/routes/index.js +15 -0
  82. package/dist/helpers.d.ts +18 -0
  83. package/dist/helpers.js +318 -0
  84. package/dist/hotReload.d.ts +23 -0
  85. package/dist/hotReload.js +292 -0
  86. package/dist/index.d.ts +17 -0
  87. package/dist/index.js +480 -0
  88. package/dist/renderer.d.ts +14 -0
  89. package/dist/renderer.js +106 -0
  90. package/dist/router.d.ts +78 -0
  91. package/dist/router.js +359 -0
  92. package/dist/types/framework.d.ts +37 -0
  93. package/dist/types/framework.js +2 -0
  94. package/dist/types.d.ts +43 -0
  95. package/dist/types.js +2 -0
  96. package/dist/typescript/use-client-plugin.d.ts +5 -0
  97. package/dist/typescript/use-client-plugin.js +113 -0
  98. package/dist/validation.d.ts +0 -0
  99. package/dist/validation.js +1 -0
  100. package/package.json +72 -0
  101. package/src/adapters/express.ts +70 -0
  102. package/src/adapters/factory.ts +96 -0
  103. package/src/adapters/fastify.ts +88 -0
  104. package/src/adapters/native.ts +223 -0
  105. package/src/api/console.ts +285 -0
  106. package/src/api/http.ts +515 -0
  107. package/src/auth/client.ts +74 -0
  108. package/src/auth/components.tsx +109 -0
  109. package/src/auth/core.ts +143 -0
  110. package/src/auth/index.ts +9 -0
  111. package/src/auth/jwt.ts +194 -0
  112. package/src/auth/providers.ts +13 -0
  113. package/src/auth/react/index.ts +9 -0
  114. package/src/auth/react.tsx +209 -0
  115. package/src/auth/routes.ts +133 -0
  116. package/src/auth/types.ts +73 -0
  117. package/src/bin/hightjs.js +40 -0
  118. package/src/builder.js +362 -0
  119. package/src/client/DefaultNotFound.tsx +68 -0
  120. package/src/client/clientRouter.ts +137 -0
  121. package/src/client/entry.client.tsx +302 -0
  122. package/src/client.ts +8 -0
  123. package/src/components/Link.tsx +22 -0
  124. package/src/helpers.ts +316 -0
  125. package/src/hotReload.ts +289 -0
  126. package/src/index.ts +514 -0
  127. package/src/renderer.tsx +122 -0
  128. package/src/router.ts +400 -0
  129. package/src/types/framework.ts +42 -0
  130. package/src/types.ts +54 -0
  131. package/tsconfig.json +17 -0
@@ -0,0 +1,515 @@
1
+ import { GenericRequest, GenericResponse, CookieOptions } from '../types/framework';
2
+
3
+ // Input validation and sanitization utilities
4
+ class SecurityUtils {
5
+ static readonly MAX_HEADER_LENGTH = 8192;
6
+ static readonly MAX_COOKIE_LENGTH = 4096;
7
+ static readonly MAX_URL_LENGTH = 2048;
8
+ static readonly MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
9
+
10
+ static sanitizeHeader(value: string | string[]): string | string[] {
11
+ if (Array.isArray(value)) {
12
+ return value.map(v => this.sanitizeString(v, this.MAX_HEADER_LENGTH));
13
+ }
14
+ return this.sanitizeString(value, this.MAX_HEADER_LENGTH);
15
+ }
16
+
17
+ static sanitizeString(str: string, maxLength: number): string {
18
+ if (typeof str !== 'string') return '';
19
+
20
+ // Remove null bytes and control characters except newline/tab
21
+ let clean = str.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
22
+
23
+ // Limit length
24
+ if (clean.length > maxLength) {
25
+ clean = clean.substring(0, maxLength);
26
+ }
27
+
28
+ return clean;
29
+ }
30
+
31
+ static isValidURL(url: string): boolean {
32
+ if (!url || typeof url !== 'string') return false;
33
+ if (url.length > this.MAX_URL_LENGTH) return false;
34
+
35
+ // Basic URL validation - prevent dangerous protocols
36
+ const dangerousProtocols = ['javascript:', 'data:', 'vbscript:', 'file:'];
37
+ const lowerUrl = url.toLowerCase();
38
+
39
+ return !dangerousProtocols.some(protocol => lowerUrl.startsWith(protocol));
40
+ }
41
+
42
+ static validateContentLength(length: number): boolean {
43
+ return length >= 0 && length <= this.MAX_BODY_SIZE;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Abstração sobre a requisição HTTP de entrada.
49
+ * Funciona com qualquer framework web (Express, Fastify, etc.)
50
+ */
51
+ export class HightJSRequest {
52
+ /** A requisição genérica parseada pelo adapter */
53
+ private readonly _req: GenericRequest;
54
+
55
+ constructor(req: GenericRequest) {
56
+ // Validate and sanitize request data
57
+ this._req = this.validateAndSanitizeRequest(req);
58
+ }
59
+
60
+ private validateAndSanitizeRequest(req: GenericRequest): GenericRequest {
61
+ // Validate URL
62
+ if (!SecurityUtils.isValidURL(req.url)) {
63
+ throw new Error('Invalid URL format');
64
+ }
65
+
66
+ // Sanitize headers
67
+ const sanitizedHeaders: Record<string, string | string[]> = {};
68
+ for (const [key, value] of Object.entries(req.headers || {})) {
69
+ const cleanKey = SecurityUtils.sanitizeString(key.toLowerCase(), 100);
70
+ if (cleanKey && value) {
71
+ sanitizedHeaders[cleanKey] = SecurityUtils.sanitizeHeader(value);
72
+ }
73
+ }
74
+
75
+ // Validate content length
76
+ const contentLength = req.headers['content-length'];
77
+ if (contentLength) {
78
+ const length = parseInt(Array.isArray(contentLength) ? contentLength[0] : contentLength, 10);
79
+ if (!SecurityUtils.validateContentLength(length)) {
80
+ throw new Error('Request too large');
81
+ }
82
+ }
83
+
84
+ // Sanitize cookies
85
+ const sanitizedCookies: Record<string, string> = {};
86
+ for (const [key, value] of Object.entries(req.cookies || {})) {
87
+ const cleanKey = SecurityUtils.sanitizeString(key, 100);
88
+ const cleanValue = SecurityUtils.sanitizeString(value, SecurityUtils.MAX_COOKIE_LENGTH);
89
+ if (cleanKey && cleanValue) {
90
+ sanitizedCookies[cleanKey] = cleanValue;
91
+ }
92
+ }
93
+
94
+ return {
95
+ ...req,
96
+ headers: sanitizedHeaders,
97
+ cookies: sanitizedCookies,
98
+ url: SecurityUtils.sanitizeString(req.url, SecurityUtils.MAX_URL_LENGTH)
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Retorna o método HTTP da requisição (GET, POST, etc.)
104
+ */
105
+ get method(): string {
106
+ return this._req.method;
107
+ }
108
+
109
+ /**
110
+ * Retorna a URL completa da requisição
111
+ */
112
+ get url(): string {
113
+ return this._req.url;
114
+ }
115
+
116
+ /**
117
+ * Retorna todos os headers da requisição
118
+ */
119
+ get headers(): Record<string, string | string[]> {
120
+ return this._req.headers;
121
+ }
122
+
123
+ /**
124
+ * Retorna um header específico com validação
125
+ */
126
+ header(name: string): string | string[] | undefined {
127
+ if (!name || typeof name !== 'string') return undefined;
128
+ const cleanName = SecurityUtils.sanitizeString(name.toLowerCase(), 100);
129
+ return this._req.headers[cleanName];
130
+ }
131
+
132
+ /**
133
+ * Retorna todos os query parameters
134
+ */
135
+ get query(): Record<string, any> {
136
+ return this._req.query || {};
137
+ }
138
+
139
+ /**
140
+ * Retorna todos os parâmetros de rota
141
+ */
142
+ get params(): Record<string, string> {
143
+ return this._req.params || {};
144
+ }
145
+
146
+ /**
147
+ * Retorna todos os cookies
148
+ */
149
+ get cookies(): Record<string, string> {
150
+ return this._req.cookies || {};
151
+ }
152
+
153
+ /**
154
+ * Retorna um cookie específico com validação
155
+ */
156
+ cookie(name: string): string | undefined {
157
+ if (!name || typeof name !== 'string') return undefined;
158
+ const cleanName = SecurityUtils.sanitizeString(name, 100);
159
+ return this._req.cookies?.[cleanName];
160
+ }
161
+
162
+ /**
163
+ * Retorna o corpo (body) da requisição, já parseado como JSON com validação
164
+ */
165
+ async json<T = any>(): Promise<T> {
166
+ try {
167
+ const body = this._req.body;
168
+
169
+ // Validate JSON structure
170
+ if (typeof body === 'string') {
171
+ // Check for potential JSON bombs
172
+ if (body.length > SecurityUtils.MAX_BODY_SIZE) {
173
+ throw new Error('Request body too large');
174
+ }
175
+ return JSON.parse(body);
176
+ }
177
+
178
+ return body;
179
+ } catch (error) {
180
+ if (error instanceof SyntaxError) {
181
+ throw new Error('Invalid JSON format');
182
+ }
183
+ throw error;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Retorna o corpo da requisição como texto
189
+ */
190
+ async text(): Promise<string> {
191
+ if (typeof this._req.body === 'string') {
192
+ return this._req.body;
193
+ }
194
+ return JSON.stringify(this._req.body);
195
+ }
196
+
197
+ /**
198
+ * Retorna o corpo da requisição como FormData (para uploads)
199
+ */
200
+ async formData(): Promise<any> {
201
+ return this._req.body;
202
+ }
203
+
204
+ /**
205
+ * Retorna a requisição original do framework
206
+ */
207
+ get raw(): any {
208
+ return this._req.raw;
209
+ }
210
+
211
+ /**
212
+ * Verifica se a requisição tem um content-type específico
213
+ */
214
+ is(type: string): boolean {
215
+ const contentType = this.header('content-type');
216
+ if (!contentType) return false;
217
+
218
+ const ct = Array.isArray(contentType) ? contentType[0] : contentType;
219
+ return ct.toLowerCase().includes(type.toLowerCase());
220
+ }
221
+
222
+ /**
223
+ * Verifica se a requisição é AJAX/XHR
224
+ */
225
+ get isAjax(): boolean {
226
+ const xhr = this.header('x-requested-with');
227
+ return xhr === 'XMLHttpRequest';
228
+ }
229
+
230
+ /**
231
+ * Retorna o IP do cliente com validação melhorada
232
+ */
233
+ get ip(): string {
234
+ // Check X-Forwarded-For with validation
235
+ const forwarded = this.header('x-forwarded-for');
236
+ if (forwarded) {
237
+ const ips = Array.isArray(forwarded) ? forwarded[0] : forwarded;
238
+ const firstIp = ips.split(',')[0].trim();
239
+
240
+ // Basic IP validation
241
+ if (this.isValidIP(firstIp)) {
242
+ return firstIp;
243
+ }
244
+ }
245
+
246
+ // Check X-Real-IP
247
+ const realIp = this.header('x-real-ip');
248
+ if (realIp) {
249
+ const ip = Array.isArray(realIp) ? realIp[0] : realIp;
250
+ if (this.isValidIP(ip)) {
251
+ return ip;
252
+ }
253
+ }
254
+
255
+ return 'unknown';
256
+ }
257
+
258
+ private isValidIP(ip: string): boolean {
259
+ if (!ip || typeof ip !== 'string') return false;
260
+
261
+ // Basic IPv4 validation
262
+ const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
263
+ if (ipv4Regex.test(ip)) {
264
+ const parts = ip.split('.');
265
+ return parts.every(part => {
266
+ const num = parseInt(part, 10);
267
+ return num >= 0 && num <= 255;
268
+ });
269
+ }
270
+
271
+ // Basic IPv6 validation (simplified)
272
+ const ipv6Regex = /^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/;
273
+ return ipv6Regex.test(ip);
274
+ }
275
+
276
+ /**
277
+ * Retorna o User-Agent
278
+ */
279
+ get userAgent(): string | undefined {
280
+ const ua = this.header('user-agent');
281
+ return Array.isArray(ua) ? ua[0] : ua;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Abstração para construir a resposta HTTP.
287
+ * Funciona com qualquer framework web (Express, Fastify, etc.)
288
+ */
289
+ export class HightJSResponse {
290
+ private _status: number = 200;
291
+ private _headers: Record<string, string> = {};
292
+ private _cookies: Array<{ name: string; value: string; options?: CookieOptions }> = [];
293
+ private _body: any = null;
294
+ private _sent: boolean = false;
295
+
296
+ /**
297
+ * Define o status HTTP da resposta
298
+ */
299
+ status(code: number): HightJSResponse {
300
+ this._status = code;
301
+ return this;
302
+ }
303
+
304
+ /**
305
+ * Define um header da resposta
306
+ */
307
+ header(name: string, value: string): HightJSResponse {
308
+ this._headers[name] = value;
309
+ return this;
310
+ }
311
+
312
+ /**
313
+ * Define múltiplos headers
314
+ */
315
+ headers(headers: Record<string, string>): HightJSResponse {
316
+ Object.assign(this._headers, headers);
317
+ return this;
318
+ }
319
+
320
+ /**
321
+ * Define um cookie
322
+ */
323
+ cookie(name: string, value: string, options?: CookieOptions): HightJSResponse {
324
+ this._cookies.push({ name, value, options });
325
+ return this;
326
+ }
327
+
328
+ /**
329
+ * Remove um cookie
330
+ */
331
+ clearCookie(name: string, options?: CookieOptions): HightJSResponse {
332
+ this._cookies.push({
333
+ name,
334
+ value: '',
335
+ options: { ...options, expires: new Date(0) }
336
+ });
337
+ return this;
338
+ }
339
+
340
+ /**
341
+ * Envia resposta JSON
342
+ */
343
+ json(data: any): HightJSResponse {
344
+ this._headers['Content-Type'] = 'application/json';
345
+ this._body = JSON.stringify(data);
346
+ this._sent = true;
347
+ return this;
348
+ }
349
+
350
+ /**
351
+ * Envia resposta de texto
352
+ */
353
+ text(data: string): HightJSResponse {
354
+ this._headers['Content-Type'] = 'text/plain; charset=utf-8';
355
+ this._body = data;
356
+ this._sent = true;
357
+ return this;
358
+ }
359
+
360
+ /**
361
+ * Envia resposta HTML
362
+ */
363
+ html(data: string): HightJSResponse {
364
+ this._headers['Content-Type'] = 'text/html; charset=utf-8';
365
+ this._body = data;
366
+ this._sent = true;
367
+ return this;
368
+ }
369
+
370
+ /**
371
+ * Envia qualquer tipo de dados
372
+ */
373
+ send(data: any): HightJSResponse {
374
+ this._body = data;
375
+ this._sent = true;
376
+ return this;
377
+ }
378
+
379
+ /**
380
+ * Redireciona para uma URL
381
+ */
382
+ redirect(url: string, status: number = 302): HightJSResponse {
383
+ this._status = status;
384
+ this._headers['Location'] = url;
385
+ this._sent = true;
386
+ return this;
387
+ }
388
+
389
+ /**
390
+ * Método interno para aplicar a resposta ao objeto de resposta do framework
391
+ */
392
+ public _applyTo(res: GenericResponse): void {
393
+ // Aplica status
394
+ res.status(this._status);
395
+
396
+ // Aplica headers
397
+ Object.entries(this._headers).forEach(([name, value]) => {
398
+ res.header(name, value);
399
+ });
400
+
401
+ // Aplica cookies
402
+ this._cookies.forEach(({ name, value, options }) => {
403
+ if (options?.expires && options.expires.getTime() === 0) {
404
+ res.clearCookie(name, options);
405
+ } else {
406
+ res.cookie(name, value, options);
407
+ }
408
+ });
409
+
410
+ // Envia o corpo se foi definido
411
+ if (this._sent && this._body !== null) {
412
+ if (this._headers['Content-Type']?.includes('application/json')) {
413
+ res.json(JSON.parse(this._body));
414
+ } else if (this._headers['Location']) {
415
+ res.redirect(this._headers['Location']);
416
+ } else {
417
+ res.send(this._body);
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Método de compatibilidade com versão anterior (Express)
424
+ */
425
+ public _send(res: any): void {
426
+ // Assume que é Express se tem os métodos específicos
427
+ if (res.set && res.status && res.send) {
428
+ res.set(this._headers).status(this._status);
429
+
430
+ this._cookies.forEach(({ name, value, options }) => {
431
+ if (options?.expires && options.expires.getTime() === 0) {
432
+ res.clearCookie(name, options);
433
+ } else {
434
+ res.cookie(name, value, options);
435
+ }
436
+ });
437
+
438
+ res.send(this._body);
439
+ }
440
+ }
441
+
442
+ // === MÉTODOS ESTÁTICOS DE CONVENIÊNCIA ===
443
+
444
+ /**
445
+ * Cria uma resposta JSON
446
+ */
447
+ static json(data: any, options?: { status?: number, headers?: Record<string, string> }): HightJSResponse {
448
+ const response = new HightJSResponse();
449
+ if (options?.status) response.status(options.status);
450
+ if (options?.headers) response.headers(options.headers);
451
+ return response.json(data);
452
+ }
453
+
454
+ /**
455
+ * Cria uma resposta de texto
456
+ */
457
+ static text(data: string, options?: { status?: number, headers?: Record<string, string> }): HightJSResponse {
458
+ const response = new HightJSResponse();
459
+ if (options?.status) response.status(options.status);
460
+ if (options?.headers) response.headers(options.headers);
461
+ return response.text(data);
462
+ }
463
+
464
+ /**
465
+ * Cria uma resposta HTML
466
+ */
467
+ static html(data: string, options?: { status?: number, headers?: Record<string, string> }): HightJSResponse {
468
+ const response = new HightJSResponse();
469
+ if (options?.status) response.status(options.status);
470
+ if (options?.headers) response.headers(options.headers);
471
+ return response.html(data);
472
+ }
473
+
474
+ /**
475
+ * Cria um redirecionamento
476
+ */
477
+ static redirect(url: string, status: number = 302): HightJSResponse {
478
+ return new HightJSResponse().redirect(url, status);
479
+ }
480
+
481
+ /**
482
+ * Cria uma resposta 404
483
+ */
484
+ static notFound(message: string = 'Not Found'): HightJSResponse {
485
+ return HightJSResponse.text(message, { status: 404 });
486
+ }
487
+
488
+ /**
489
+ * Cria uma resposta 500
490
+ */
491
+ static error(message: string = 'Internal Server Error'): HightJSResponse {
492
+ return HightJSResponse.text(message, { status: 500 });
493
+ }
494
+
495
+ /**
496
+ * Cria uma resposta 400
497
+ */
498
+ static badRequest(message: string = 'Bad Request'): HightJSResponse {
499
+ return HightJSResponse.text(message, { status: 400 });
500
+ }
501
+
502
+ /**
503
+ * Cria uma resposta 401
504
+ */
505
+ static unauthorized(message: string = 'Unauthorized'): HightJSResponse {
506
+ return HightJSResponse.text(message, { status: 401 });
507
+ }
508
+
509
+ /**
510
+ * Cria uma resposta 403
511
+ */
512
+ static forbidden(message: string = 'Forbidden'): HightJSResponse {
513
+ return HightJSResponse.text(message, { status: 403 });
514
+ }
515
+ }
@@ -0,0 +1,74 @@
1
+ import type { SignInOptions, SignInResult, Session } from './types';
2
+ // Configuração global do client
3
+ let basePath = '/api/auth';
4
+
5
+ export function setBasePath(path: string) {
6
+ basePath = path;
7
+ }
8
+
9
+
10
+
11
+
12
+ /**
13
+ * Função para obter a sessão atual (similar ao NextAuth getSession)
14
+ */
15
+ export async function getSession(): Promise<Session | null> {
16
+ try {
17
+ const response = await fetch(`${basePath}/session`, {
18
+ credentials: 'include'
19
+ });
20
+
21
+ if (!response.ok) {
22
+ return null;
23
+ }
24
+
25
+ const data = await response.json();
26
+ return data.session || null;
27
+ } catch (error) {
28
+ console.error('[hweb-auth] Erro ao buscar sessão:', error);
29
+ return null;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Função para obter token CSRF
35
+ */
36
+ export async function getCsrfToken(): Promise<string | null> {
37
+ try {
38
+ const response = await fetch(`${basePath}/csrf`, {
39
+ credentials: 'include'
40
+ });
41
+
42
+ if (!response.ok) {
43
+ return null;
44
+ }
45
+
46
+ const data = await response.json();
47
+ return data.csrfToken || null;
48
+ } catch (error) {
49
+ console.error('[hweb-auth] Erro ao buscar CSRF token:', error);
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Função para obter providers disponíveis
56
+ */
57
+ export async function getProviders(): Promise<any[] | null> {
58
+ try {
59
+ const response = await fetch(`${basePath}/providers`, {
60
+ credentials: 'include'
61
+ });
62
+
63
+ if (!response.ok) {
64
+ return null;
65
+ }
66
+
67
+ const data = await response.json();
68
+ return data.providers || [];
69
+ } catch (error) {
70
+ console.error('[hweb-auth] Erro ao buscar providers:', error);
71
+ return null;
72
+ }
73
+ }
74
+
@@ -0,0 +1,109 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { useAuth } from './react';
3
+ import { router } from '../client/clientRouter';
4
+ interface ProtectedRouteProps {
5
+ children: ReactNode;
6
+ fallback?: ReactNode;
7
+ redirectTo?: string;
8
+ requireAuth?: boolean;
9
+ }
10
+
11
+ /**
12
+ * Componente para proteger rotas que requerem autenticação
13
+ */
14
+ export function ProtectedRoute({
15
+ children,
16
+ fallback,
17
+ redirectTo = '/auth/signin',
18
+ requireAuth = true
19
+ }: ProtectedRouteProps) {
20
+ const { isAuthenticated, isLoading } = useAuth();
21
+
22
+ // Ainda carregando
23
+ if (isLoading) {
24
+ return fallback || <div>Carregando...</div>;
25
+ }
26
+
27
+ // Requer auth mas não está autenticado
28
+ if (requireAuth && !isAuthenticated) {
29
+ if (typeof window !== 'undefined' && redirectTo) {
30
+ window.location.href = redirectTo;
31
+ return null;
32
+ }
33
+ return fallback || <div>Não autorizado</div>;
34
+ }
35
+
36
+ // Não requer auth mas está autenticado (ex: página de login)
37
+ if (!requireAuth && isAuthenticated && redirectTo) {
38
+ if (typeof window !== 'undefined') {
39
+ window.location.href = redirectTo;
40
+ return null;
41
+ }
42
+ }
43
+
44
+ return <>{children}</>;
45
+ }
46
+
47
+ interface GuardProps {
48
+ children: ReactNode;
49
+ fallback?: ReactNode;
50
+ redirectTo?: string;
51
+ }
52
+
53
+ /**
54
+ * Guard simples que só renderiza children se estiver autenticado
55
+ */
56
+ export function AuthGuard({ children, fallback, redirectTo }: GuardProps) {
57
+ const { isAuthenticated, isLoading } = useAuth();
58
+
59
+ if(redirectTo && !isLoading && !isAuthenticated) {
60
+ router.push(redirectTo);
61
+ }
62
+
63
+ if (isLoading) {
64
+ return fallback || <div></div>;
65
+ }
66
+
67
+ if (!isAuthenticated) {
68
+ return fallback || null;
69
+ }
70
+
71
+ return <>{children}</>;
72
+ }
73
+
74
+ /**
75
+ * Componente para mostrar conteúdo apenas para usuários não autenticados
76
+ */
77
+ export function GuestOnly({ children, fallback, redirectTo }: GuardProps) {
78
+ const { isAuthenticated, isLoading } = useAuth();
79
+
80
+ if(redirectTo && !isLoading && isAuthenticated) {
81
+ router.push(redirectTo);
82
+ }
83
+
84
+ if (isLoading || isAuthenticated) {
85
+ return fallback || <div></div>;
86
+ }
87
+
88
+ return <>{children}</>;
89
+ }
90
+
91
+ /**
92
+ * Hook para redirecionar baseado no status de autenticação
93
+ */
94
+ export function useAuthRedirect(
95
+ authenticatedRedirect?: string,
96
+ unauthenticatedRedirect?: string
97
+ ) {
98
+ const { isAuthenticated, isLoading } = useAuth();
99
+
100
+ React.useEffect(() => {
101
+ if (isLoading) return;
102
+
103
+ if (isAuthenticated && authenticatedRedirect) {
104
+ window.location.href = authenticatedRedirect;
105
+ } else if (!isAuthenticated && unauthenticatedRedirect) {
106
+ window.location.href = unauthenticatedRedirect;
107
+ }
108
+ }, [isAuthenticated, isLoading, authenticatedRedirect, unauthenticatedRedirect]);
109
+ }