@hemia/auth-sdk 0.0.2 → 0.0.4

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.
@@ -1,9 +1,9 @@
1
1
  import 'reflect-metadata';
2
- import { Get, Req, Res, Post, HttpError, BadRequestError, CustomHttpError, InternalServerError } from '@hemia/common';
2
+ import { BadRequestError, CustomHttpError, InternalServerError, Get, Req, Res, Post, HttpError } from '@hemia/common';
3
3
  import { HMNetworkServices } from '@hemia/network-services';
4
+ import { JwtManager } from '@hemia/jwt-manager';
4
5
  import { randomBytes, createHash } from 'crypto';
5
- import { injectable } from 'inversify';
6
- import { CacheService } from '@hemia/cache-manager';
6
+ import { injectable, inject } from 'inversify';
7
7
 
8
8
  /******************************************************************************
9
9
  Copyright (c) Microsoft Corporation.
@@ -42,196 +42,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
42
42
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
43
43
  };
44
44
 
45
- class SessionError extends Error {
46
- constructor(message, code, redirectTo) {
47
- super(message);
48
- this.code = code;
49
- this.redirectTo = redirectTo;
50
- this.name = this.constructor.name;
51
- Error.captureStackTrace(this, this.constructor);
52
- }
53
- }
54
- class SessionNotFoundError extends SessionError {
55
- constructor(message = 'Sesión no encontrada') {
56
- super(message, 'SESSION_NOT_FOUND', '/?session=required');
57
- }
58
- }
59
- class SessionExpiredError extends SessionError {
60
- constructor(message = 'Sesión expirada') {
61
- super(message, 'SESSION_EXPIRED', '/session-expired?session=expired');
62
- }
63
- }
64
- class SessionInvalidError extends SessionError {
65
- constructor(message = 'Sesión inválida') {
66
- super(message, 'SESSION_INVALID', '/?session=invalid');
67
- }
68
- }
69
- class TokenRefreshFailedError extends SessionError {
70
- constructor(message = 'Error al renovar tokens') {
71
- super(message, 'TOKEN_REFRESH_FAILED', '/session-expired?session=expired');
72
- }
73
- }
74
- class InvalidTokenFormatError extends SessionError {
75
- constructor(message = 'Formato de token inválido') {
76
- super(message, 'INVALID_TOKEN_FORMAT', '/?session=invalid');
77
- }
78
- }
79
-
80
- /**
81
- * Controller Abstracto Reutilizable
82
- * Gestiona automáticamente Login, Callback, Me y Logout.
83
- */
84
- class AbstractAuthController {
85
- constructor(authService) {
86
- this.authService = authService;
87
- }
88
- async login(req, res) {
89
- try {
90
- const autoParam = typeof req.query.auto === 'string' ? req.query.auto : 'false';
91
- const { loginUrl, tempState } = this.authService.generateLoginParams(autoParam);
92
- res.cookie('auth_flow', JSON.stringify(tempState), {
93
- httpOnly: true,
94
- secure: process.env.NODE_ENV === 'production',
95
- maxAge: 300000 // 5 min
96
- });
97
- res.redirect(loginUrl);
98
- }
99
- catch (error) {
100
- console.error('Login Error:', error);
101
- res.status(500).send('Login initialization failed');
102
- }
103
- }
104
- async callback(req, res) {
105
- try {
106
- const { code, state } = req.query;
107
- const authFlowCookie = req.cookies['auth_flow'];
108
- if (!authFlowCookie) {
109
- res.status(400).send('Missing auth flow cookie');
110
- return;
111
- }
112
- const storedState = JSON.parse(authFlowCookie);
113
- const result = await this.authService.handleCallback(code, state, storedState);
114
- res.cookie('x-session', result.sessionId, {
115
- httpOnly: true,
116
- secure: process.env.NODE_ENV === 'production',
117
- sameSite: 'lax',
118
- maxAge: result.expiresIn * 1000,
119
- path: '/'
120
- });
121
- res.clearCookie('auth_flow');
122
- res.redirect(result.redirectUrl);
123
- }
124
- catch (error) {
125
- console.error('Callback Error:', error);
126
- if (error instanceof HttpError) {
127
- res.status(error.statusCode).json({
128
- success: false,
129
- message: error.message,
130
- error: error.error
131
- });
132
- return;
133
- }
134
- res.status(500).json({
135
- success: false,
136
- message: 'Failed to complete authentication',
137
- error: error.message
138
- });
139
- }
140
- }
141
- async me(req, res) {
142
- const sessionId = req.cookies['x-session'];
143
- if (!sessionId) {
144
- return res.status(401).json({
145
- success: false,
146
- message: 'No session found',
147
- data: {
148
- redirect_to: '/?session=required'
149
- },
150
- error: {
151
- message: 'Session is missing',
152
- code: 'SESSION_MISSING'
153
- }
154
- });
155
- }
156
- try {
157
- const result = await this.authService.getSessionUser(sessionId);
158
- return res.status(200).json({
159
- success: true,
160
- data: result
161
- });
162
- }
163
- catch (error) {
164
- res.clearCookie('x-session', {
165
- httpOnly: true,
166
- secure: process.env.NODE_ENV === 'production',
167
- sameSite: 'lax',
168
- });
169
- if (error instanceof SessionError) {
170
- return res.status(401).json({
171
- success: false,
172
- message: error.message,
173
- data: {
174
- redirect_to: error.redirectTo || '/login'
175
- },
176
- error: {
177
- message: error.message,
178
- code: error.code
179
- }
180
- });
181
- }
182
- else {
183
- return res.status(500).json({
184
- success: false,
185
- message: 'Failed to retrieve session user',
186
- error: error.message
187
- });
188
- }
189
- }
190
- }
191
- async logout(req, res) {
192
- const sessionId = req.cookies['x-session'];
193
- if (sessionId) {
194
- await this.authService.logout(sessionId);
195
- }
196
- res.clearCookie('x-session', {
197
- httpOnly: true,
198
- secure: process.env.NODE_ENV === 'production',
199
- sameSite: 'lax',
200
- });
201
- return res.status(200).json({ success: true });
202
- }
203
- }
204
- __decorate([
205
- Get('/login'),
206
- __param(0, Req()),
207
- __param(1, Res()),
208
- __metadata("design:type", Function),
209
- __metadata("design:paramtypes", [Object, Object]),
210
- __metadata("design:returntype", Promise)
211
- ], AbstractAuthController.prototype, "login", null);
212
- __decorate([
213
- Get('/callback'),
214
- __metadata("design:type", Function),
215
- __metadata("design:paramtypes", [Object, Object]),
216
- __metadata("design:returntype", Promise)
217
- ], AbstractAuthController.prototype, "callback", null);
218
- __decorate([
219
- Get('/me'),
220
- __param(0, Req()),
221
- __param(1, Res()),
222
- __metadata("design:type", Function),
223
- __metadata("design:paramtypes", [Object, Object]),
224
- __metadata("design:returntype", Promise)
225
- ], AbstractAuthController.prototype, "me", null);
226
- __decorate([
227
- Post('/logout'),
228
- __param(0, Req()),
229
- __param(1, Res()),
230
- __metadata("design:type", Function),
231
- __metadata("design:paramtypes", [Object, Object]),
232
- __metadata("design:returntype", Promise)
233
- ], AbstractAuthController.prototype, "logout", null);
234
-
235
45
  /**
236
46
  * Utilidades para manejo de PKCE y codificación Base64URL
237
47
  */
@@ -276,31 +86,51 @@ class Generators {
276
86
  }
277
87
  }
278
88
 
279
- let AuthCacheService = class AuthCacheService {
280
- constructor(cacheClient) {
281
- this.cacheClient = cacheClient;
89
+ class SessionError extends Error {
90
+ constructor(message, code, redirectTo) {
91
+ super(message);
92
+ this.code = code;
93
+ this.redirectTo = redirectTo;
94
+ this.name = this.constructor.name;
95
+ Error.captureStackTrace(this, this.constructor);
282
96
  }
283
- async set(key, value, ttlSeconds) {
284
- await this.cacheClient.setObject(key, value, ttlSeconds);
97
+ }
98
+ class SessionNotFoundError extends SessionError {
99
+ constructor(message = 'Sesión no encontrada') {
100
+ super(message, 'SESSION_NOT_FOUND', '/?session=required');
285
101
  }
286
- async get(key) {
287
- return await this.cacheClient.getObject(key);
102
+ }
103
+ class SessionExpiredError extends SessionError {
104
+ constructor(message = 'Sesión expirada') {
105
+ super(message, 'SESSION_EXPIRED', '/session-expired?session=expired');
288
106
  }
289
- async delete(key) {
290
- await this.cacheClient.deleteKey(key);
107
+ }
108
+ class SessionInvalidError extends SessionError {
109
+ constructor(message = 'Sesión inválida') {
110
+ super(message, 'SESSION_INVALID', '/?session=invalid');
291
111
  }
292
- };
293
- AuthCacheService = __decorate([
294
- injectable(),
295
- __metadata("design:paramtypes", [CacheService])
296
- ], AuthCacheService);
112
+ }
113
+ class TokenRefreshFailedError extends SessionError {
114
+ constructor(message = 'Error al renovar tokens') {
115
+ super(message, 'TOKEN_REFRESH_FAILED', '/session-expired?session=expired');
116
+ }
117
+ }
118
+ class InvalidTokenFormatError extends SessionError {
119
+ constructor(message = 'Formato de token inválido') {
120
+ super(message, 'INVALID_TOKEN_FORMAT', '/?session=invalid');
121
+ }
122
+ }
297
123
 
298
124
  let AuthService = class AuthService {
299
- constructor(config, storage, jwtManager) {
125
+ // private networkServices: HMNetworkServices;
126
+ // private jwtManager: JwtManager
127
+ constructor(config, storage, networkServices, jwtManager) {
300
128
  this.config = config;
301
129
  this.storage = storage;
130
+ this.networkServices = networkServices;
302
131
  this.jwtManager = jwtManager;
303
- this.networkServices = new HMNetworkServices(this.config.ssoBaseUrl);
132
+ // this.networkServices = new HMNetworkServices(this.config.ssoBaseUrl);
133
+ // this.jwtManager = new JwtManager();
304
134
  }
305
135
  /**
306
136
  * Genera los parámetros necesarios para iniciar el login SSO
@@ -437,7 +267,7 @@ let AuthService = class AuthService {
437
267
  */
438
268
  async decodeIdToken(idToken) {
439
269
  try {
440
- const decode = this.jwtManager.decode(idToken);
270
+ const decode = this.jwtManager.getClaims(idToken);
441
271
  if (!decode) {
442
272
  return null;
443
273
  }
@@ -484,13 +314,196 @@ let AuthService = class AuthService {
484
314
  };
485
315
  AuthService = __decorate([
486
316
  injectable(),
487
- __metadata("design:paramtypes", [Object, AuthCacheService, Object])
317
+ __metadata("design:paramtypes", [Object, Object, HMNetworkServices,
318
+ JwtManager])
488
319
  ], AuthService);
489
320
 
490
- const registerAuthSdk = (container, config, cacheService, jwtManager) => {
491
- container.bind(AuthService).toDynamicValue(() => {
492
- return new AuthService(config, cacheService, jwtManager);
321
+ const AUTH_SERVICE_ID = 'AuthService';
322
+
323
+ /**
324
+ * Controller Abstracto Reutilizable
325
+ * Gestiona automáticamente Login, Callback, Me y Logout.
326
+ */
327
+ let AbstractAuthController = class AbstractAuthController {
328
+ constructor() { }
329
+ async login(req, res) {
330
+ try {
331
+ const autoParam = typeof req.query.auto === 'string' ? req.query.auto : 'false';
332
+ const { loginUrl, tempState } = this.authService.generateLoginParams(autoParam);
333
+ res.cookie('auth_flow', JSON.stringify(tempState), {
334
+ httpOnly: true,
335
+ secure: process.env.NODE_ENV === 'production',
336
+ maxAge: 300000 // 5 min
337
+ });
338
+ res.redirect(loginUrl);
339
+ }
340
+ catch (error) {
341
+ console.error('Login Error:', error);
342
+ res.status(500).send('Login initialization failed');
343
+ }
344
+ }
345
+ async callback(req, res) {
346
+ try {
347
+ const { code, state } = req.query;
348
+ const authFlowCookie = req.cookies['auth_flow'];
349
+ if (!authFlowCookie) {
350
+ res.status(400).send('Missing auth flow cookie');
351
+ return;
352
+ }
353
+ const storedState = JSON.parse(authFlowCookie);
354
+ const result = await this.authService.handleCallback(code, state, storedState);
355
+ res.cookie('x-session', result.sessionId, {
356
+ httpOnly: true,
357
+ secure: process.env.NODE_ENV === 'production',
358
+ sameSite: 'lax',
359
+ maxAge: result.expiresIn * 1000,
360
+ path: '/'
361
+ });
362
+ res.clearCookie('auth_flow');
363
+ res.redirect(result.redirectUrl);
364
+ }
365
+ catch (error) {
366
+ console.error('Callback Error:', error);
367
+ if (error instanceof HttpError) {
368
+ res.status(error.statusCode).json({
369
+ success: false,
370
+ message: error.message,
371
+ error: error.error
372
+ });
373
+ return;
374
+ }
375
+ res.status(500).json({
376
+ success: false,
377
+ message: 'Failed to complete authentication',
378
+ error: error.message
379
+ });
380
+ }
381
+ }
382
+ async me(req, res) {
383
+ const sessionId = req.cookies['x-session'];
384
+ if (!sessionId) {
385
+ return res.status(401).json({
386
+ success: false,
387
+ message: 'No session found',
388
+ data: {
389
+ redirect_to: '/?session=required'
390
+ },
391
+ error: {
392
+ message: 'Session is missing',
393
+ code: 'SESSION_MISSING'
394
+ }
395
+ });
396
+ }
397
+ try {
398
+ const result = await this.authService.getSessionUser(sessionId);
399
+ return res.status(200).json({
400
+ success: true,
401
+ data: result
402
+ });
403
+ }
404
+ catch (error) {
405
+ res.clearCookie('x-session', {
406
+ httpOnly: true,
407
+ secure: process.env.NODE_ENV === 'production',
408
+ sameSite: 'lax',
409
+ });
410
+ if (error instanceof SessionError) {
411
+ return res.status(401).json({
412
+ success: false,
413
+ message: error.message,
414
+ data: {
415
+ redirect_to: error.redirectTo || '/login'
416
+ },
417
+ error: {
418
+ message: error.message,
419
+ code: error.code
420
+ }
421
+ });
422
+ }
423
+ else {
424
+ return res.status(500).json({
425
+ success: false,
426
+ message: 'Failed to retrieve session user',
427
+ error: error.message
428
+ });
429
+ }
430
+ }
431
+ }
432
+ async logout(req, res) {
433
+ const sessionId = req.cookies['x-session'];
434
+ if (sessionId) {
435
+ await this.authService.logout(sessionId);
436
+ }
437
+ res.clearCookie('x-session', {
438
+ httpOnly: true,
439
+ secure: process.env.NODE_ENV === 'production',
440
+ sameSite: 'lax',
441
+ });
442
+ return res.status(200).json({ success: true });
443
+ }
444
+ };
445
+ __decorate([
446
+ inject(AUTH_SERVICE_ID),
447
+ __metadata("design:type", AuthService)
448
+ ], AbstractAuthController.prototype, "authService", void 0);
449
+ __decorate([
450
+ Get('/login'),
451
+ __param(0, Req()),
452
+ __param(1, Res()),
453
+ __metadata("design:type", Function),
454
+ __metadata("design:paramtypes", [Object, Object]),
455
+ __metadata("design:returntype", Promise)
456
+ ], AbstractAuthController.prototype, "login", null);
457
+ __decorate([
458
+ Get('/callback'),
459
+ __metadata("design:type", Function),
460
+ __metadata("design:paramtypes", [Object, Object]),
461
+ __metadata("design:returntype", Promise)
462
+ ], AbstractAuthController.prototype, "callback", null);
463
+ __decorate([
464
+ Get('/me'),
465
+ __param(0, Req()),
466
+ __param(1, Res()),
467
+ __metadata("design:type", Function),
468
+ __metadata("design:paramtypes", [Object, Object]),
469
+ __metadata("design:returntype", Promise)
470
+ ], AbstractAuthController.prototype, "me", null);
471
+ __decorate([
472
+ Post('/logout'),
473
+ __param(0, Req()),
474
+ __param(1, Res()),
475
+ __metadata("design:type", Function),
476
+ __metadata("design:paramtypes", [Object, Object]),
477
+ __metadata("design:returntype", Promise)
478
+ ], AbstractAuthController.prototype, "logout", null);
479
+ AbstractAuthController = __decorate([
480
+ injectable(),
481
+ __metadata("design:paramtypes", [])
482
+ ], AbstractAuthController);
483
+
484
+ class AuthCacheAdapter {
485
+ constructor(externalCache) {
486
+ this.externalCache = externalCache;
487
+ }
488
+ async set(key, value, ttlSeconds) {
489
+ await this.externalCache.setObject(key, value, ttlSeconds);
490
+ }
491
+ async get(key) {
492
+ return await this.externalCache.getObject(key);
493
+ }
494
+ async delete(key) {
495
+ await this.externalCache.deleteKey(key);
496
+ }
497
+ }
498
+
499
+ const registerAuthSdk = (bind, config, cacheFactory) => {
500
+ bind(AUTH_SERVICE_ID).toDynamicValue(async (context) => {
501
+ const rawCache = await Promise.resolve(cacheFactory(context));
502
+ const storageAdapter = new AuthCacheAdapter(rawCache);
503
+ const network = new HMNetworkServices(config.ssoBaseUrl);
504
+ const jwt = new JwtManager();
505
+ return new AuthService(config, storageAdapter, network, jwt);
493
506
  }).inSingletonScope();
494
507
  };
495
508
 
496
- export { AbstractAuthController, AuthCacheService, AuthService, InvalidTokenFormatError, SessionError, SessionExpiredError, SessionInvalidError, SessionNotFoundError, TokenRefreshFailedError, registerAuthSdk };
509
+ export { AUTH_SERVICE_ID, AbstractAuthController, AuthService, InvalidTokenFormatError, SessionError, SessionExpiredError, SessionInvalidError, SessionNotFoundError, TokenRefreshFailedError, registerAuthSdk };
@@ -3,9 +3,9 @@
3
3
  require('reflect-metadata');
4
4
  var common = require('@hemia/common');
5
5
  var networkServices = require('@hemia/network-services');
6
+ var jwtManager = require('@hemia/jwt-manager');
6
7
  var crypto = require('crypto');
7
8
  var inversify = require('inversify');
8
- var cacheManager = require('@hemia/cache-manager');
9
9
 
10
10
  /******************************************************************************
11
11
  Copyright (c) Microsoft Corporation.
@@ -44,196 +44,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
44
44
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
45
45
  };
46
46
 
47
- class SessionError extends Error {
48
- constructor(message, code, redirectTo) {
49
- super(message);
50
- this.code = code;
51
- this.redirectTo = redirectTo;
52
- this.name = this.constructor.name;
53
- Error.captureStackTrace(this, this.constructor);
54
- }
55
- }
56
- class SessionNotFoundError extends SessionError {
57
- constructor(message = 'Sesión no encontrada') {
58
- super(message, 'SESSION_NOT_FOUND', '/?session=required');
59
- }
60
- }
61
- class SessionExpiredError extends SessionError {
62
- constructor(message = 'Sesión expirada') {
63
- super(message, 'SESSION_EXPIRED', '/session-expired?session=expired');
64
- }
65
- }
66
- class SessionInvalidError extends SessionError {
67
- constructor(message = 'Sesión inválida') {
68
- super(message, 'SESSION_INVALID', '/?session=invalid');
69
- }
70
- }
71
- class TokenRefreshFailedError extends SessionError {
72
- constructor(message = 'Error al renovar tokens') {
73
- super(message, 'TOKEN_REFRESH_FAILED', '/session-expired?session=expired');
74
- }
75
- }
76
- class InvalidTokenFormatError extends SessionError {
77
- constructor(message = 'Formato de token inválido') {
78
- super(message, 'INVALID_TOKEN_FORMAT', '/?session=invalid');
79
- }
80
- }
81
-
82
- /**
83
- * Controller Abstracto Reutilizable
84
- * Gestiona automáticamente Login, Callback, Me y Logout.
85
- */
86
- class AbstractAuthController {
87
- constructor(authService) {
88
- this.authService = authService;
89
- }
90
- async login(req, res) {
91
- try {
92
- const autoParam = typeof req.query.auto === 'string' ? req.query.auto : 'false';
93
- const { loginUrl, tempState } = this.authService.generateLoginParams(autoParam);
94
- res.cookie('auth_flow', JSON.stringify(tempState), {
95
- httpOnly: true,
96
- secure: process.env.NODE_ENV === 'production',
97
- maxAge: 300000 // 5 min
98
- });
99
- res.redirect(loginUrl);
100
- }
101
- catch (error) {
102
- console.error('Login Error:', error);
103
- res.status(500).send('Login initialization failed');
104
- }
105
- }
106
- async callback(req, res) {
107
- try {
108
- const { code, state } = req.query;
109
- const authFlowCookie = req.cookies['auth_flow'];
110
- if (!authFlowCookie) {
111
- res.status(400).send('Missing auth flow cookie');
112
- return;
113
- }
114
- const storedState = JSON.parse(authFlowCookie);
115
- const result = await this.authService.handleCallback(code, state, storedState);
116
- res.cookie('x-session', result.sessionId, {
117
- httpOnly: true,
118
- secure: process.env.NODE_ENV === 'production',
119
- sameSite: 'lax',
120
- maxAge: result.expiresIn * 1000,
121
- path: '/'
122
- });
123
- res.clearCookie('auth_flow');
124
- res.redirect(result.redirectUrl);
125
- }
126
- catch (error) {
127
- console.error('Callback Error:', error);
128
- if (error instanceof common.HttpError) {
129
- res.status(error.statusCode).json({
130
- success: false,
131
- message: error.message,
132
- error: error.error
133
- });
134
- return;
135
- }
136
- res.status(500).json({
137
- success: false,
138
- message: 'Failed to complete authentication',
139
- error: error.message
140
- });
141
- }
142
- }
143
- async me(req, res) {
144
- const sessionId = req.cookies['x-session'];
145
- if (!sessionId) {
146
- return res.status(401).json({
147
- success: false,
148
- message: 'No session found',
149
- data: {
150
- redirect_to: '/?session=required'
151
- },
152
- error: {
153
- message: 'Session is missing',
154
- code: 'SESSION_MISSING'
155
- }
156
- });
157
- }
158
- try {
159
- const result = await this.authService.getSessionUser(sessionId);
160
- return res.status(200).json({
161
- success: true,
162
- data: result
163
- });
164
- }
165
- catch (error) {
166
- res.clearCookie('x-session', {
167
- httpOnly: true,
168
- secure: process.env.NODE_ENV === 'production',
169
- sameSite: 'lax',
170
- });
171
- if (error instanceof SessionError) {
172
- return res.status(401).json({
173
- success: false,
174
- message: error.message,
175
- data: {
176
- redirect_to: error.redirectTo || '/login'
177
- },
178
- error: {
179
- message: error.message,
180
- code: error.code
181
- }
182
- });
183
- }
184
- else {
185
- return res.status(500).json({
186
- success: false,
187
- message: 'Failed to retrieve session user',
188
- error: error.message
189
- });
190
- }
191
- }
192
- }
193
- async logout(req, res) {
194
- const sessionId = req.cookies['x-session'];
195
- if (sessionId) {
196
- await this.authService.logout(sessionId);
197
- }
198
- res.clearCookie('x-session', {
199
- httpOnly: true,
200
- secure: process.env.NODE_ENV === 'production',
201
- sameSite: 'lax',
202
- });
203
- return res.status(200).json({ success: true });
204
- }
205
- }
206
- __decorate([
207
- common.Get('/login'),
208
- __param(0, common.Req()),
209
- __param(1, common.Res()),
210
- __metadata("design:type", Function),
211
- __metadata("design:paramtypes", [Object, Object]),
212
- __metadata("design:returntype", Promise)
213
- ], AbstractAuthController.prototype, "login", null);
214
- __decorate([
215
- common.Get('/callback'),
216
- __metadata("design:type", Function),
217
- __metadata("design:paramtypes", [Object, Object]),
218
- __metadata("design:returntype", Promise)
219
- ], AbstractAuthController.prototype, "callback", null);
220
- __decorate([
221
- common.Get('/me'),
222
- __param(0, common.Req()),
223
- __param(1, common.Res()),
224
- __metadata("design:type", Function),
225
- __metadata("design:paramtypes", [Object, Object]),
226
- __metadata("design:returntype", Promise)
227
- ], AbstractAuthController.prototype, "me", null);
228
- __decorate([
229
- common.Post('/logout'),
230
- __param(0, common.Req()),
231
- __param(1, common.Res()),
232
- __metadata("design:type", Function),
233
- __metadata("design:paramtypes", [Object, Object]),
234
- __metadata("design:returntype", Promise)
235
- ], AbstractAuthController.prototype, "logout", null);
236
-
237
47
  /**
238
48
  * Utilidades para manejo de PKCE y codificación Base64URL
239
49
  */
@@ -278,31 +88,51 @@ class Generators {
278
88
  }
279
89
  }
280
90
 
281
- exports.AuthCacheService = class AuthCacheService {
282
- constructor(cacheClient) {
283
- this.cacheClient = cacheClient;
91
+ class SessionError extends Error {
92
+ constructor(message, code, redirectTo) {
93
+ super(message);
94
+ this.code = code;
95
+ this.redirectTo = redirectTo;
96
+ this.name = this.constructor.name;
97
+ Error.captureStackTrace(this, this.constructor);
284
98
  }
285
- async set(key, value, ttlSeconds) {
286
- await this.cacheClient.setObject(key, value, ttlSeconds);
99
+ }
100
+ class SessionNotFoundError extends SessionError {
101
+ constructor(message = 'Sesión no encontrada') {
102
+ super(message, 'SESSION_NOT_FOUND', '/?session=required');
287
103
  }
288
- async get(key) {
289
- return await this.cacheClient.getObject(key);
104
+ }
105
+ class SessionExpiredError extends SessionError {
106
+ constructor(message = 'Sesión expirada') {
107
+ super(message, 'SESSION_EXPIRED', '/session-expired?session=expired');
290
108
  }
291
- async delete(key) {
292
- await this.cacheClient.deleteKey(key);
109
+ }
110
+ class SessionInvalidError extends SessionError {
111
+ constructor(message = 'Sesión inválida') {
112
+ super(message, 'SESSION_INVALID', '/?session=invalid');
293
113
  }
294
- };
295
- exports.AuthCacheService = __decorate([
296
- inversify.injectable(),
297
- __metadata("design:paramtypes", [cacheManager.CacheService])
298
- ], exports.AuthCacheService);
114
+ }
115
+ class TokenRefreshFailedError extends SessionError {
116
+ constructor(message = 'Error al renovar tokens') {
117
+ super(message, 'TOKEN_REFRESH_FAILED', '/session-expired?session=expired');
118
+ }
119
+ }
120
+ class InvalidTokenFormatError extends SessionError {
121
+ constructor(message = 'Formato de token inválido') {
122
+ super(message, 'INVALID_TOKEN_FORMAT', '/?session=invalid');
123
+ }
124
+ }
299
125
 
300
126
  exports.AuthService = class AuthService {
301
- constructor(config, storage, jwtManager) {
127
+ // private networkServices: HMNetworkServices;
128
+ // private jwtManager: JwtManager
129
+ constructor(config, storage, networkServices, jwtManager) {
302
130
  this.config = config;
303
131
  this.storage = storage;
132
+ this.networkServices = networkServices;
304
133
  this.jwtManager = jwtManager;
305
- this.networkServices = new networkServices.HMNetworkServices(this.config.ssoBaseUrl);
134
+ // this.networkServices = new HMNetworkServices(this.config.ssoBaseUrl);
135
+ // this.jwtManager = new JwtManager();
306
136
  }
307
137
  /**
308
138
  * Genera los parámetros necesarios para iniciar el login SSO
@@ -439,7 +269,7 @@ exports.AuthService = class AuthService {
439
269
  */
440
270
  async decodeIdToken(idToken) {
441
271
  try {
442
- const decode = this.jwtManager.decode(idToken);
272
+ const decode = this.jwtManager.getClaims(idToken);
443
273
  if (!decode) {
444
274
  return null;
445
275
  }
@@ -486,16 +316,199 @@ exports.AuthService = class AuthService {
486
316
  };
487
317
  exports.AuthService = __decorate([
488
318
  inversify.injectable(),
489
- __metadata("design:paramtypes", [Object, exports.AuthCacheService, Object])
319
+ __metadata("design:paramtypes", [Object, Object, networkServices.HMNetworkServices,
320
+ jwtManager.JwtManager])
490
321
  ], exports.AuthService);
491
322
 
492
- const registerAuthSdk = (container, config, cacheService, jwtManager) => {
493
- container.bind(exports.AuthService).toDynamicValue(() => {
494
- return new exports.AuthService(config, cacheService, jwtManager);
323
+ const AUTH_SERVICE_ID = 'AuthService';
324
+
325
+ /**
326
+ * Controller Abstracto Reutilizable
327
+ * Gestiona automáticamente Login, Callback, Me y Logout.
328
+ */
329
+ exports.AbstractAuthController = class AbstractAuthController {
330
+ constructor() { }
331
+ async login(req, res) {
332
+ try {
333
+ const autoParam = typeof req.query.auto === 'string' ? req.query.auto : 'false';
334
+ const { loginUrl, tempState } = this.authService.generateLoginParams(autoParam);
335
+ res.cookie('auth_flow', JSON.stringify(tempState), {
336
+ httpOnly: true,
337
+ secure: process.env.NODE_ENV === 'production',
338
+ maxAge: 300000 // 5 min
339
+ });
340
+ res.redirect(loginUrl);
341
+ }
342
+ catch (error) {
343
+ console.error('Login Error:', error);
344
+ res.status(500).send('Login initialization failed');
345
+ }
346
+ }
347
+ async callback(req, res) {
348
+ try {
349
+ const { code, state } = req.query;
350
+ const authFlowCookie = req.cookies['auth_flow'];
351
+ if (!authFlowCookie) {
352
+ res.status(400).send('Missing auth flow cookie');
353
+ return;
354
+ }
355
+ const storedState = JSON.parse(authFlowCookie);
356
+ const result = await this.authService.handleCallback(code, state, storedState);
357
+ res.cookie('x-session', result.sessionId, {
358
+ httpOnly: true,
359
+ secure: process.env.NODE_ENV === 'production',
360
+ sameSite: 'lax',
361
+ maxAge: result.expiresIn * 1000,
362
+ path: '/'
363
+ });
364
+ res.clearCookie('auth_flow');
365
+ res.redirect(result.redirectUrl);
366
+ }
367
+ catch (error) {
368
+ console.error('Callback Error:', error);
369
+ if (error instanceof common.HttpError) {
370
+ res.status(error.statusCode).json({
371
+ success: false,
372
+ message: error.message,
373
+ error: error.error
374
+ });
375
+ return;
376
+ }
377
+ res.status(500).json({
378
+ success: false,
379
+ message: 'Failed to complete authentication',
380
+ error: error.message
381
+ });
382
+ }
383
+ }
384
+ async me(req, res) {
385
+ const sessionId = req.cookies['x-session'];
386
+ if (!sessionId) {
387
+ return res.status(401).json({
388
+ success: false,
389
+ message: 'No session found',
390
+ data: {
391
+ redirect_to: '/?session=required'
392
+ },
393
+ error: {
394
+ message: 'Session is missing',
395
+ code: 'SESSION_MISSING'
396
+ }
397
+ });
398
+ }
399
+ try {
400
+ const result = await this.authService.getSessionUser(sessionId);
401
+ return res.status(200).json({
402
+ success: true,
403
+ data: result
404
+ });
405
+ }
406
+ catch (error) {
407
+ res.clearCookie('x-session', {
408
+ httpOnly: true,
409
+ secure: process.env.NODE_ENV === 'production',
410
+ sameSite: 'lax',
411
+ });
412
+ if (error instanceof SessionError) {
413
+ return res.status(401).json({
414
+ success: false,
415
+ message: error.message,
416
+ data: {
417
+ redirect_to: error.redirectTo || '/login'
418
+ },
419
+ error: {
420
+ message: error.message,
421
+ code: error.code
422
+ }
423
+ });
424
+ }
425
+ else {
426
+ return res.status(500).json({
427
+ success: false,
428
+ message: 'Failed to retrieve session user',
429
+ error: error.message
430
+ });
431
+ }
432
+ }
433
+ }
434
+ async logout(req, res) {
435
+ const sessionId = req.cookies['x-session'];
436
+ if (sessionId) {
437
+ await this.authService.logout(sessionId);
438
+ }
439
+ res.clearCookie('x-session', {
440
+ httpOnly: true,
441
+ secure: process.env.NODE_ENV === 'production',
442
+ sameSite: 'lax',
443
+ });
444
+ return res.status(200).json({ success: true });
445
+ }
446
+ };
447
+ __decorate([
448
+ inversify.inject(AUTH_SERVICE_ID),
449
+ __metadata("design:type", exports.AuthService)
450
+ ], exports.AbstractAuthController.prototype, "authService", void 0);
451
+ __decorate([
452
+ common.Get('/login'),
453
+ __param(0, common.Req()),
454
+ __param(1, common.Res()),
455
+ __metadata("design:type", Function),
456
+ __metadata("design:paramtypes", [Object, Object]),
457
+ __metadata("design:returntype", Promise)
458
+ ], exports.AbstractAuthController.prototype, "login", null);
459
+ __decorate([
460
+ common.Get('/callback'),
461
+ __metadata("design:type", Function),
462
+ __metadata("design:paramtypes", [Object, Object]),
463
+ __metadata("design:returntype", Promise)
464
+ ], exports.AbstractAuthController.prototype, "callback", null);
465
+ __decorate([
466
+ common.Get('/me'),
467
+ __param(0, common.Req()),
468
+ __param(1, common.Res()),
469
+ __metadata("design:type", Function),
470
+ __metadata("design:paramtypes", [Object, Object]),
471
+ __metadata("design:returntype", Promise)
472
+ ], exports.AbstractAuthController.prototype, "me", null);
473
+ __decorate([
474
+ common.Post('/logout'),
475
+ __param(0, common.Req()),
476
+ __param(1, common.Res()),
477
+ __metadata("design:type", Function),
478
+ __metadata("design:paramtypes", [Object, Object]),
479
+ __metadata("design:returntype", Promise)
480
+ ], exports.AbstractAuthController.prototype, "logout", null);
481
+ exports.AbstractAuthController = __decorate([
482
+ inversify.injectable(),
483
+ __metadata("design:paramtypes", [])
484
+ ], exports.AbstractAuthController);
485
+
486
+ class AuthCacheAdapter {
487
+ constructor(externalCache) {
488
+ this.externalCache = externalCache;
489
+ }
490
+ async set(key, value, ttlSeconds) {
491
+ await this.externalCache.setObject(key, value, ttlSeconds);
492
+ }
493
+ async get(key) {
494
+ return await this.externalCache.getObject(key);
495
+ }
496
+ async delete(key) {
497
+ await this.externalCache.deleteKey(key);
498
+ }
499
+ }
500
+
501
+ const registerAuthSdk = (bind, config, cacheFactory) => {
502
+ bind(AUTH_SERVICE_ID).toDynamicValue(async (context) => {
503
+ const rawCache = await Promise.resolve(cacheFactory(context));
504
+ const storageAdapter = new AuthCacheAdapter(rawCache);
505
+ const network = new networkServices.HMNetworkServices(config.ssoBaseUrl);
506
+ const jwt = new jwtManager.JwtManager();
507
+ return new exports.AuthService(config, storageAdapter, network, jwt);
495
508
  }).inSingletonScope();
496
509
  };
497
510
 
498
- exports.AbstractAuthController = AbstractAuthController;
511
+ exports.AUTH_SERVICE_ID = AUTH_SERVICE_ID;
499
512
  exports.InvalidTokenFormatError = InvalidTokenFormatError;
500
513
  exports.SessionError = SessionError;
501
514
  exports.SessionExpiredError = SessionExpiredError;
@@ -0,0 +1 @@
1
+ export * from "./internal.adapter";
@@ -0,0 +1,13 @@
1
+ import { ISessionStorage } from "../types";
2
+ export interface IHemiaCacheService {
3
+ setObject<T>(key: string, value: T, expireTime?: number): Promise<void>;
4
+ getObject<T>(key: string): Promise<T | null>;
5
+ deleteKey(key: string): Promise<void>;
6
+ }
7
+ export declare class AuthCacheAdapter implements ISessionStorage {
8
+ private externalCache;
9
+ constructor(externalCache: IHemiaCacheService);
10
+ set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
11
+ get<T>(key: string): Promise<T | null>;
12
+ delete(key: string): Promise<void>;
13
+ }
@@ -0,0 +1 @@
1
+ export declare const AUTH_SERVICE_ID = "AuthService";
@@ -6,7 +6,7 @@ import { AuthService } from "../services/auth.service";
6
6
  */
7
7
  export declare abstract class AbstractAuthController {
8
8
  protected readonly authService: AuthService;
9
- protected constructor(authService: AuthService);
9
+ protected constructor();
10
10
  login(req: Request, res: Response): Promise<void>;
11
11
  callback(req: Request, res: Response): Promise<void>;
12
12
  me(req: Request, res: Response): Promise<Response>;
@@ -4,3 +4,4 @@ export * from "./services";
4
4
  export * from "./types";
5
5
  export * from "./errors";
6
6
  export * from "./ioc";
7
+ export * from "./constants";
@@ -1,4 +1,5 @@
1
- import { Container } from "inversify";
2
- import { IAuthConfig, IJwtManager } from "./types";
3
- import { AuthCacheService } from "./services";
4
- export declare const registerAuthSdk: (container: Container, config: IAuthConfig, cacheService: AuthCacheService, jwtManager: IJwtManager) => void;
1
+ import { Bind, ResolutionContext } from "inversify";
2
+ import { IAuthConfig } from "./types";
3
+ import { IHemiaCacheService } from "./adapters";
4
+ export type CacheFactory = (context: ResolutionContext) => Promise<IHemiaCacheService> | IHemiaCacheService;
5
+ export declare const registerAuthSdk: (bind: Bind, config: IAuthConfig, cacheFactory: CacheFactory) => void;
@@ -1,11 +1,12 @@
1
- import { IAuthConfig, ICallbackResponse, IJwtManager, ILoginParams, ISessionUser, IStoredState } from '../types';
2
- import { AuthCacheService } from './cache.service';
1
+ import { HMNetworkServices } from '@hemia/network-services';
2
+ import { JwtManager } from '@hemia/jwt-manager';
3
+ import { IAuthConfig, ICallbackResponse, ILoginParams, ISessionStorage, ISessionUser, IStoredState } from '../types';
3
4
  export declare class AuthService {
4
- private config;
5
- private storage;
6
- private jwtManager;
7
- private networkServices;
8
- constructor(config: IAuthConfig, storage: AuthCacheService, jwtManager: IJwtManager);
5
+ private readonly config;
6
+ private readonly storage;
7
+ private readonly networkServices;
8
+ private readonly jwtManager;
9
+ constructor(config: IAuthConfig, storage: ISessionStorage, networkServices: HMNetworkServices, jwtManager: JwtManager);
9
10
  /**
10
11
  * Genera los parámetros necesarios para iniciar el login SSO
11
12
  * @param auto
@@ -1,2 +1 @@
1
1
  export * from "./auth.service";
2
- export * from "./cache.service";
@@ -8,3 +8,4 @@ export * from "./session-user-response.interface";
8
8
  export * from "./jwt-manager.interface";
9
9
  export * from "./standard-claims.interface";
10
10
  export * from "./session-data.interface";
11
+ export * from "./session-storage.interface";
@@ -0,0 +1,5 @@
1
+ export interface ISessionStorage {
2
+ set(key: string, value: any, ttlSeconds: number): Promise<void>;
3
+ get<T>(key: string): Promise<T | null>;
4
+ delete(key: string): Promise<void>;
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hemia/auth-sdk",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Hemia SDK for authentication",
5
5
  "main": "dist/hemia-auth-sdk.js",
6
6
  "module": "dist/hemia-auth-sdk.esm.js",
@@ -23,6 +23,7 @@
23
23
  "@hemia/network-services": "^0.0.3",
24
24
  "@hemia/common": "^0.0.2",
25
25
  "@hemia/cache-manager": "^0.0.5",
26
+ "@hemia/jwt-manager": "^0.0.4",
26
27
  "@types/express": "^4.17.21",
27
28
  "express": "^5.2.1",
28
29
  "events": "^3.3.0",
@@ -45,9 +46,10 @@
45
46
  "inversify": "^7.11.0",
46
47
  "@hemia/common": "^0.0.2",
47
48
  "@hemia/network-services": "^0.0.3",
48
- "@hemia/cache-manager": "^0.0.5"
49
+ "@hemia/cache-manager": "^0.0.5",
50
+ "@hemia/jwt-manager": "^0.0.4"
49
51
  },
50
52
  "dependencies": {
51
-
53
+
52
54
  }
53
55
  }
@@ -1,8 +0,0 @@
1
- import { CacheService as CacheServiceClient } from "@hemia/cache-manager";
2
- export declare class AuthCacheService {
3
- private cacheClient;
4
- constructor(cacheClient: CacheServiceClient);
5
- set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
6
- get<T>(key: string): Promise<T | null>;
7
- delete(key: string): Promise<void>;
8
- }