@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 +155 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +195 -18
- package/dist/index.d.ts +195 -18
- package/dist/index.js +140 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
147
|
-
* Cookie name
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
611
|
-
*
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
this.logger.
|
|
634
|
-
|
|
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
|
|
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
|
-
|
|
772
|
+
import_config4.ConfigModule,
|
|
707
773
|
RequestModule,
|
|
708
774
|
import_jwt2.JwtModule.registerAsync({
|
|
709
775
|
imports: [
|
|
710
|
-
|
|
776
|
+
import_config4.ConfigModule
|
|
711
777
|
],
|
|
712
778
|
inject: [
|
|
713
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|