@vritti/api-sdk 0.1.1 → 0.1.2

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.
package/dist/index.cjs CHANGED
@@ -71,18 +71,113 @@ __export(index_exports, {
71
71
  ValidationException: () => ValidationException,
72
72
  VrittiAuthGuard: () => VrittiAuthGuard,
73
73
  addCorrelationIdToResponse: () => addCorrelationIdToResponse,
74
+ configureApiSdk: () => configureApiSdk,
74
75
  correlationStorage: () => correlationStorage,
76
+ defineConfig: () => defineConfig,
75
77
  generateCorrelationId: () => generateCorrelationId,
78
+ getConfig: () => getConfig,
76
79
  getCorrelationContext: () => getCorrelationContext,
77
80
  getHttpStatusTitle: () => getHttpStatusTitle,
81
+ getJwtExpiry: () => getJwtExpiry,
82
+ getRefreshCookieOptions: () => getRefreshCookieOptions,
83
+ hashToken: () => hashToken,
84
+ resetConfig: () => resetConfig,
78
85
  runWithCorrelationContext: () => runWithCorrelationContext,
79
- updateCorrelationContext: () => updateCorrelationContext
86
+ updateCorrelationContext: () => updateCorrelationContext,
87
+ verifyTokenHash: () => verifyTokenHash
80
88
  });
81
89
  module.exports = __toCommonJS(index_exports);
82
90
 
91
+ // src/config/index.ts
92
+ var defaultConfig = {
93
+ cookie: {
94
+ refreshCookieName: "vritti_refresh",
95
+ refreshCookieMaxAge: 30 * 24 * 60 * 60 * 1e3,
96
+ refreshCookiePath: "/",
97
+ refreshCookieSecure: process.env.NODE_ENV === "production",
98
+ refreshCookieSameSite: "strict"
99
+ },
100
+ jwt: {
101
+ accessTokenExpiry: "15m",
102
+ refreshTokenExpiry: "30d",
103
+ onboardingTokenExpiry: "24h",
104
+ validateTokenBinding: true
105
+ },
106
+ guard: {
107
+ tenantHeaderName: "x-tenant-id",
108
+ authHeaderName: "authorization",
109
+ tokenPrefix: "Bearer"
110
+ }
111
+ };
112
+ var currentConfig = {
113
+ ...defaultConfig
114
+ };
115
+ function defineConfig(config) {
116
+ return config;
117
+ }
118
+ __name(defineConfig, "defineConfig");
119
+ function configureApiSdk(userConfig) {
120
+ currentConfig = {
121
+ cookie: {
122
+ ...defaultConfig.cookie,
123
+ ...userConfig.cookie || {}
124
+ },
125
+ jwt: {
126
+ ...defaultConfig.jwt,
127
+ ...userConfig.jwt || {}
128
+ },
129
+ guard: {
130
+ ...defaultConfig.guard,
131
+ ...userConfig.guard || {}
132
+ }
133
+ };
134
+ }
135
+ __name(configureApiSdk, "configureApiSdk");
136
+ function getConfig() {
137
+ return currentConfig;
138
+ }
139
+ __name(getConfig, "getConfig");
140
+ function resetConfig() {
141
+ currentConfig = {
142
+ ...defaultConfig
143
+ };
144
+ }
145
+ __name(resetConfig, "resetConfig");
146
+ function getRefreshCookieOptions() {
147
+ return {
148
+ httpOnly: true,
149
+ secure: currentConfig.cookie.refreshCookieSecure,
150
+ sameSite: currentConfig.cookie.refreshCookieSameSite,
151
+ path: currentConfig.cookie.refreshCookiePath,
152
+ maxAge: currentConfig.cookie.refreshCookieMaxAge
153
+ };
154
+ }
155
+ __name(getRefreshCookieOptions, "getRefreshCookieOptions");
156
+ function getJwtExpiry() {
157
+ return {
158
+ access: currentConfig.jwt.accessTokenExpiry,
159
+ refresh: currentConfig.jwt.refreshTokenExpiry,
160
+ onboarding: currentConfig.jwt.onboardingTokenExpiry
161
+ };
162
+ }
163
+ __name(getJwtExpiry, "getJwtExpiry");
164
+
165
+ // src/auth/utils/token-hash.util.ts
166
+ var crypto = __toESM(require("crypto"), 1);
167
+ function hashToken(token) {
168
+ return crypto.createHash("sha256").update(token).digest("hex");
169
+ }
170
+ __name(hashToken, "hashToken");
171
+ function verifyTokenHash(token, expectedHash) {
172
+ const computedHash = hashToken(token);
173
+ if (computedHash.length !== expectedHash.length) return false;
174
+ return crypto.timingSafeEqual(Buffer.from(computedHash, "hex"), Buffer.from(expectedHash, "hex"));
175
+ }
176
+ __name(verifyTokenHash, "verifyTokenHash");
177
+
83
178
  // src/auth/auth-config.module.ts
84
179
  var import_common5 = require("@nestjs/common");
85
- var import_config2 = require("@nestjs/config");
180
+ var import_config4 = require("@nestjs/config");
86
181
  var import_core3 = require("@nestjs/core");
87
182
  var import_jwt2 = require("@nestjs/jwt");
88
183
 
@@ -143,17 +238,18 @@ var RequestService = class {
143
238
  return type === "Bearer" && token ? token : null;
144
239
  }
145
240
  /**
146
- * Extract refresh token from session-id cookie
147
- * Cookie name: session-id
241
+ * Extract refresh token from httpOnly cookie
242
+ * Cookie name is configurable via api-sdk config
148
243
  * @returns Refresh token or null if not found
149
244
  */
150
245
  getRefreshToken() {
151
246
  try {
152
247
  const cookies = this.request.cookies;
153
248
  if (cookies && typeof cookies === "object") {
154
- const sessionId = cookies["session-id"];
155
- if (sessionId) {
156
- return sessionId;
249
+ const config = getConfig();
250
+ const refreshToken = cookies[config.cookie.refreshCookieName];
251
+ if (refreshToken) {
252
+ return refreshToken;
157
253
  }
158
254
  }
159
255
  return null;
@@ -215,10 +311,9 @@ RequestModule = _ts_decorate2([
215
311
 
216
312
  // src/auth/guards/vritti-auth.guard.ts
217
313
  var import_common4 = require("@nestjs/common");
218
- var import_config = require("@nestjs/config");
314
+ var import_config2 = require("@nestjs/config");
219
315
  var import_core2 = require("@nestjs/core");
220
316
  var import_jwt = require("@nestjs/jwt");
221
- var jwt = __toESM(require("jsonwebtoken"), 1);
222
317
 
223
318
  // src/database/services/primary-database.service.ts
224
319
  var import_common3 = require("@nestjs/common");
@@ -518,6 +613,7 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
518
613
  }
519
614
  const validatedToken2 = this.validateAccessToken(accessToken);
520
615
  this.logger.debug("Onboarding token validated successfully");
616
+ this.validateRefreshTokenBinding(context, validatedToken2);
521
617
  const userId2 = validatedToken2.userId;
522
618
  request.user = {
523
619
  id: userId2
@@ -530,13 +626,7 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
530
626
  }
531
627
  const validatedToken = this.validateAccessToken(accessToken);
532
628
  this.logger.debug("Access token validated successfully");
533
- const refreshToken = this.requestService.getRefreshToken();
534
- if (!refreshToken) {
535
- this.logger.warn("Refresh token (session-id) not found in cookies");
536
- throw new import_common4.UnauthorizedException("Refresh token not found");
537
- }
538
- this.validateRefreshToken(refreshToken);
539
- this.logger.debug("Refresh token validated successfully");
629
+ this.validateRefreshTokenBinding(context, validatedToken);
540
630
  const userId = validatedToken.userId;
541
631
  request.user = {
542
632
  id: userId
@@ -607,60 +697,36 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
607
697
  }
608
698
  }
609
699
  /**
610
- * Validate refresh token with proper expiry checks
611
- * Throws UnauthorizedException if token is invalid or expired
612
- */
613
- validateRefreshToken(token) {
614
- const jwtSecret = this.configService.get("JWT_REFRESH_SECRET") || this.configService.get("JWT_SECRET");
615
- this.validateRefreshTokenWithSecret(token, jwtSecret);
616
- }
617
- /**
618
- * Helper to validate refresh token with specific secret
700
+ * Validate that the access token is bound to the refresh token in the cookie.
701
+ * This prevents token theft - a stolen access token is useless without the
702
+ * corresponding refresh token cookie.
703
+ *
704
+ * @param context - The execution context containing the request
705
+ * @param validatedToken - The decoded and validated JWT token
706
+ * @throws UnauthorizedException if token binding validation fails
619
707
  */
620
- validateRefreshTokenWithSecret(token, secret) {
621
- if (!secret) {
622
- this.logger.error("JWT secret not configured for refresh token validation");
623
- throw new import_common4.UnauthorizedException("Server configuration error");
708
+ validateRefreshTokenBinding(context, validatedToken) {
709
+ const config = getConfig();
710
+ if (!config.jwt.validateTokenBinding) {
711
+ this.logger.debug("Token binding validation is disabled");
712
+ return;
624
713
  }
625
- try {
626
- const decoded = jwt.verify(token, secret, {
627
- algorithms: [
628
- "HS256",
629
- "HS512",
630
- "RS256"
631
- ]
632
- });
633
- this.logger.debug(`Refresh token decoded for user: ${decoded.userId}`);
634
- if (decoded.exp) {
635
- const expiryTime = decoded.exp * 1e3;
636
- const currentTime = Date.now();
637
- if (currentTime > expiryTime) {
638
- this.logger.warn("Refresh token has expired");
639
- throw new import_common4.UnauthorizedException("Refresh token has expired. Please login again");
640
- }
641
- const timeRemaining = expiryTime - currentTime;
642
- this.logger.debug(`Refresh token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
643
- }
644
- } catch (error) {
645
- if (error instanceof import_common4.UnauthorizedException) {
646
- throw error;
647
- }
648
- const jwtError = error;
649
- if (jwtError?.name === "TokenExpiredError") {
650
- this.logger.warn(`Refresh token expired at: ${jwtError?.expiredAt}`);
651
- throw new import_common4.UnauthorizedException("Refresh token has expired. Please login again");
652
- }
653
- if (jwtError?.name === "JsonWebTokenError") {
654
- this.logger.warn(`Refresh token verification failed: ${jwtError?.message}`);
655
- throw new import_common4.UnauthorizedException("Invalid refresh token");
656
- }
657
- if (jwtError?.name === "NotBeforeError") {
658
- this.logger.warn("Refresh token used before valid (nbf claim)");
659
- throw new import_common4.UnauthorizedException("Refresh token not yet valid");
660
- }
661
- this.logger.error("Unexpected error validating refresh token", error);
662
- throw new import_common4.UnauthorizedException("Refresh token validation failed");
714
+ if (!validatedToken.refreshTokenHash) {
715
+ this.logger.debug("Token does not contain refreshTokenHash, skipping binding validation");
716
+ return;
717
+ }
718
+ const request = context.switchToHttp().getRequest();
719
+ const cookies = request.cookies || {};
720
+ const refreshToken = cookies[config.cookie.refreshCookieName];
721
+ if (!refreshToken) {
722
+ this.logger.warn("Session validation failed - refresh token cookie not found");
723
+ throw new import_common4.UnauthorizedException("Session validation failed");
663
724
  }
725
+ if (!verifyTokenHash(refreshToken, validatedToken.refreshTokenHash)) {
726
+ this.logger.warn("Session validation failed - token binding mismatch");
727
+ throw new import_common4.UnauthorizedException("Session validation failed");
728
+ }
729
+ this.logger.debug("Token binding validated successfully");
664
730
  }
665
731
  };
666
732
  VrittiAuthGuard = _ts_decorate4([
@@ -670,7 +736,7 @@ VrittiAuthGuard = _ts_decorate4([
670
736
  _ts_metadata3("design:type", Function),
671
737
  _ts_metadata3("design:paramtypes", [
672
738
  typeof import_core2.Reflector === "undefined" ? Object : import_core2.Reflector,
673
- typeof import_config.ConfigService === "undefined" ? Object : import_config.ConfigService,
739
+ typeof import_config2.ConfigService === "undefined" ? Object : import_config2.ConfigService,
674
740
  typeof import_jwt.JwtService === "undefined" ? Object : import_jwt.JwtService,
675
741
  typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
676
742
  typeof RequestService === "undefined" ? Object : RequestService
@@ -703,14 +769,14 @@ var AuthConfigModule = class _AuthConfigModule {
703
769
  return {
704
770
  module: _AuthConfigModule,
705
771
  imports: [
706
- import_config2.ConfigModule,
772
+ import_config4.ConfigModule,
707
773
  RequestModule,
708
774
  import_jwt2.JwtModule.registerAsync({
709
775
  imports: [
710
- import_config2.ConfigModule
776
+ import_config4.ConfigModule
711
777
  ],
712
778
  inject: [
713
- import_config2.ConfigService
779
+ import_config4.ConfigService
714
780
  ],
715
781
  useFactory: /* @__PURE__ */ __name((config) => ({
716
782
  secret: config.get("JWT_SECRET"),
@@ -1309,6 +1375,10 @@ DatabaseModule = _ts_decorate10([
1309
1375
  // src/database/repositories/primary-base.repository.ts
1310
1376
  var import_common11 = require("@nestjs/common");
1311
1377
  var import_drizzle_orm2 = require("drizzle-orm");
1378
+ function snakeToCamel(str) {
1379
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
1380
+ }
1381
+ __name(snakeToCamel, "snakeToCamel");
1312
1382
  var PrimaryBaseRepository = class {
1313
1383
  static {
1314
1384
  __name(this, "PrimaryBaseRepository");
@@ -1318,7 +1388,8 @@ var PrimaryBaseRepository = class {
1318
1388
  logger;
1319
1389
  /**
1320
1390
  * The table name extracted from the Drizzle table at runtime.
1321
- * Used to access the query API for this repository's table.
1391
+ * Stored in camelCase to match Drizzle's query object keys.
1392
+ * Example: 'email_verifications' -> 'emailVerifications'
1322
1393
  */
1323
1394
  tableName;
1324
1395
  /**
@@ -1371,10 +1442,11 @@ var PrimaryBaseRepository = class {
1371
1442
  constructor(database, table) {
1372
1443
  this.database = database;
1373
1444
  this.table = table;
1374
- this.tableName = (0, import_drizzle_orm2.getTableName)(table);
1445
+ const dbTableName = (0, import_drizzle_orm2.getTableName)(table);
1446
+ this.tableName = snakeToCamel(dbTableName);
1375
1447
  this.logger = new import_common11.Logger(this.constructor.name);
1376
1448
  this.logger.debug(`Initialized ${this.constructor.name}`);
1377
- this.logger.debug(`Table name from getTableName: '${this.tableName}'`);
1449
+ this.logger.debug(`Table name: '${dbTableName}' -> query key: '${this.tableName}'`);
1378
1450
  }
1379
1451
  /**
1380
1452
  * Create a new record
@@ -3414,11 +3486,19 @@ LoggerModule = _ts_decorate17([
3414
3486
  ValidationException,
3415
3487
  VrittiAuthGuard,
3416
3488
  addCorrelationIdToResponse,
3489
+ configureApiSdk,
3417
3490
  correlationStorage,
3491
+ defineConfig,
3418
3492
  generateCorrelationId,
3493
+ getConfig,
3419
3494
  getCorrelationContext,
3420
3495
  getHttpStatusTitle,
3496
+ getJwtExpiry,
3497
+ getRefreshCookieOptions,
3498
+ hashToken,
3499
+ resetConfig,
3421
3500
  runWithCorrelationContext,
3422
- updateCorrelationContext
3501
+ updateCorrelationContext,
3502
+ verifyTokenHash
3423
3503
  });
3424
3504
  //# sourceMappingURL=index.cjs.map