@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.js
CHANGED
|
@@ -1,6 +1,93 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
+
// src/config/index.ts
|
|
5
|
+
var defaultConfig = {
|
|
6
|
+
cookie: {
|
|
7
|
+
refreshCookieName: "vritti_refresh",
|
|
8
|
+
refreshCookieMaxAge: 30 * 24 * 60 * 60 * 1e3,
|
|
9
|
+
refreshCookiePath: "/",
|
|
10
|
+
refreshCookieSecure: process.env.NODE_ENV === "production",
|
|
11
|
+
refreshCookieSameSite: "strict"
|
|
12
|
+
},
|
|
13
|
+
jwt: {
|
|
14
|
+
accessTokenExpiry: "15m",
|
|
15
|
+
refreshTokenExpiry: "30d",
|
|
16
|
+
onboardingTokenExpiry: "24h",
|
|
17
|
+
validateTokenBinding: true
|
|
18
|
+
},
|
|
19
|
+
guard: {
|
|
20
|
+
tenantHeaderName: "x-tenant-id",
|
|
21
|
+
authHeaderName: "authorization",
|
|
22
|
+
tokenPrefix: "Bearer"
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var currentConfig = {
|
|
26
|
+
...defaultConfig
|
|
27
|
+
};
|
|
28
|
+
function defineConfig(config) {
|
|
29
|
+
return config;
|
|
30
|
+
}
|
|
31
|
+
__name(defineConfig, "defineConfig");
|
|
32
|
+
function configureApiSdk(userConfig) {
|
|
33
|
+
currentConfig = {
|
|
34
|
+
cookie: {
|
|
35
|
+
...defaultConfig.cookie,
|
|
36
|
+
...userConfig.cookie || {}
|
|
37
|
+
},
|
|
38
|
+
jwt: {
|
|
39
|
+
...defaultConfig.jwt,
|
|
40
|
+
...userConfig.jwt || {}
|
|
41
|
+
},
|
|
42
|
+
guard: {
|
|
43
|
+
...defaultConfig.guard,
|
|
44
|
+
...userConfig.guard || {}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
__name(configureApiSdk, "configureApiSdk");
|
|
49
|
+
function getConfig() {
|
|
50
|
+
return currentConfig;
|
|
51
|
+
}
|
|
52
|
+
__name(getConfig, "getConfig");
|
|
53
|
+
function resetConfig() {
|
|
54
|
+
currentConfig = {
|
|
55
|
+
...defaultConfig
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
__name(resetConfig, "resetConfig");
|
|
59
|
+
function getRefreshCookieOptions() {
|
|
60
|
+
return {
|
|
61
|
+
httpOnly: true,
|
|
62
|
+
secure: currentConfig.cookie.refreshCookieSecure,
|
|
63
|
+
sameSite: currentConfig.cookie.refreshCookieSameSite,
|
|
64
|
+
path: currentConfig.cookie.refreshCookiePath,
|
|
65
|
+
maxAge: currentConfig.cookie.refreshCookieMaxAge
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
__name(getRefreshCookieOptions, "getRefreshCookieOptions");
|
|
69
|
+
function getJwtExpiry() {
|
|
70
|
+
return {
|
|
71
|
+
access: currentConfig.jwt.accessTokenExpiry,
|
|
72
|
+
refresh: currentConfig.jwt.refreshTokenExpiry,
|
|
73
|
+
onboarding: currentConfig.jwt.onboardingTokenExpiry
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
__name(getJwtExpiry, "getJwtExpiry");
|
|
77
|
+
|
|
78
|
+
// src/auth/utils/token-hash.util.ts
|
|
79
|
+
import * as crypto from "crypto";
|
|
80
|
+
function hashToken(token) {
|
|
81
|
+
return crypto.createHash("sha256").update(token).digest("hex");
|
|
82
|
+
}
|
|
83
|
+
__name(hashToken, "hashToken");
|
|
84
|
+
function verifyTokenHash(token, expectedHash) {
|
|
85
|
+
const computedHash = hashToken(token);
|
|
86
|
+
if (computedHash.length !== expectedHash.length) return false;
|
|
87
|
+
return crypto.timingSafeEqual(Buffer.from(computedHash, "hex"), Buffer.from(expectedHash, "hex"));
|
|
88
|
+
}
|
|
89
|
+
__name(verifyTokenHash, "verifyTokenHash");
|
|
90
|
+
|
|
4
91
|
// src/auth/auth-config.module.ts
|
|
5
92
|
import { Global as Global2, Module as Module2 } from "@nestjs/common";
|
|
6
93
|
import { ConfigModule, ConfigService as ConfigService2 } from "@nestjs/config";
|
|
@@ -64,17 +151,18 @@ var RequestService = class {
|
|
|
64
151
|
return type === "Bearer" && token ? token : null;
|
|
65
152
|
}
|
|
66
153
|
/**
|
|
67
|
-
* Extract refresh token from
|
|
68
|
-
* Cookie name
|
|
154
|
+
* Extract refresh token from httpOnly cookie
|
|
155
|
+
* Cookie name is configurable via api-sdk config
|
|
69
156
|
* @returns Refresh token or null if not found
|
|
70
157
|
*/
|
|
71
158
|
getRefreshToken() {
|
|
72
159
|
try {
|
|
73
160
|
const cookies = this.request.cookies;
|
|
74
161
|
if (cookies && typeof cookies === "object") {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
162
|
+
const config = getConfig();
|
|
163
|
+
const refreshToken = cookies[config.cookie.refreshCookieName];
|
|
164
|
+
if (refreshToken) {
|
|
165
|
+
return refreshToken;
|
|
78
166
|
}
|
|
79
167
|
}
|
|
80
168
|
return null;
|
|
@@ -139,7 +227,6 @@ import { Injectable as Injectable3, Logger as Logger2, Scope as Scope2, Unauthor
|
|
|
139
227
|
import { ConfigService } from "@nestjs/config";
|
|
140
228
|
import { Reflector } from "@nestjs/core";
|
|
141
229
|
import { JwtService } from "@nestjs/jwt";
|
|
142
|
-
import * as jwt from "jsonwebtoken";
|
|
143
230
|
|
|
144
231
|
// src/database/services/primary-database.service.ts
|
|
145
232
|
import { Inject as Inject2, Injectable as Injectable2, InternalServerErrorException, Logger } from "@nestjs/common";
|
|
@@ -439,6 +526,7 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
|
439
526
|
}
|
|
440
527
|
const validatedToken2 = this.validateAccessToken(accessToken);
|
|
441
528
|
this.logger.debug("Onboarding token validated successfully");
|
|
529
|
+
this.validateRefreshTokenBinding(context, validatedToken2);
|
|
442
530
|
const userId2 = validatedToken2.userId;
|
|
443
531
|
request.user = {
|
|
444
532
|
id: userId2
|
|
@@ -451,13 +539,7 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
|
451
539
|
}
|
|
452
540
|
const validatedToken = this.validateAccessToken(accessToken);
|
|
453
541
|
this.logger.debug("Access token validated successfully");
|
|
454
|
-
|
|
455
|
-
if (!refreshToken) {
|
|
456
|
-
this.logger.warn("Refresh token (session-id) not found in cookies");
|
|
457
|
-
throw new UnauthorizedException("Refresh token not found");
|
|
458
|
-
}
|
|
459
|
-
this.validateRefreshToken(refreshToken);
|
|
460
|
-
this.logger.debug("Refresh token validated successfully");
|
|
542
|
+
this.validateRefreshTokenBinding(context, validatedToken);
|
|
461
543
|
const userId = validatedToken.userId;
|
|
462
544
|
request.user = {
|
|
463
545
|
id: userId
|
|
@@ -528,60 +610,36 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
|
528
610
|
}
|
|
529
611
|
}
|
|
530
612
|
/**
|
|
531
|
-
* Validate refresh token
|
|
532
|
-
*
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Helper to validate refresh token with specific secret
|
|
613
|
+
* Validate that the access token is bound to the refresh token in the cookie.
|
|
614
|
+
* This prevents token theft - a stolen access token is useless without the
|
|
615
|
+
* corresponding refresh token cookie.
|
|
616
|
+
*
|
|
617
|
+
* @param context - The execution context containing the request
|
|
618
|
+
* @param validatedToken - The decoded and validated JWT token
|
|
619
|
+
* @throws UnauthorizedException if token binding validation fails
|
|
540
620
|
*/
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
621
|
+
validateRefreshTokenBinding(context, validatedToken) {
|
|
622
|
+
const config = getConfig();
|
|
623
|
+
if (!config.jwt.validateTokenBinding) {
|
|
624
|
+
this.logger.debug("Token binding validation is disabled");
|
|
625
|
+
return;
|
|
545
626
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
this.logger.
|
|
555
|
-
|
|
556
|
-
const expiryTime = decoded.exp * 1e3;
|
|
557
|
-
const currentTime = Date.now();
|
|
558
|
-
if (currentTime > expiryTime) {
|
|
559
|
-
this.logger.warn("Refresh token has expired");
|
|
560
|
-
throw new UnauthorizedException("Refresh token has expired. Please login again");
|
|
561
|
-
}
|
|
562
|
-
const timeRemaining = expiryTime - currentTime;
|
|
563
|
-
this.logger.debug(`Refresh token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
|
|
564
|
-
}
|
|
565
|
-
} catch (error) {
|
|
566
|
-
if (error instanceof UnauthorizedException) {
|
|
567
|
-
throw error;
|
|
568
|
-
}
|
|
569
|
-
const jwtError = error;
|
|
570
|
-
if (jwtError?.name === "TokenExpiredError") {
|
|
571
|
-
this.logger.warn(`Refresh token expired at: ${jwtError?.expiredAt}`);
|
|
572
|
-
throw new UnauthorizedException("Refresh token has expired. Please login again");
|
|
573
|
-
}
|
|
574
|
-
if (jwtError?.name === "JsonWebTokenError") {
|
|
575
|
-
this.logger.warn(`Refresh token verification failed: ${jwtError?.message}`);
|
|
576
|
-
throw new UnauthorizedException("Invalid refresh token");
|
|
577
|
-
}
|
|
578
|
-
if (jwtError?.name === "NotBeforeError") {
|
|
579
|
-
this.logger.warn("Refresh token used before valid (nbf claim)");
|
|
580
|
-
throw new UnauthorizedException("Refresh token not yet valid");
|
|
581
|
-
}
|
|
582
|
-
this.logger.error("Unexpected error validating refresh token", error);
|
|
583
|
-
throw new UnauthorizedException("Refresh token validation failed");
|
|
627
|
+
if (!validatedToken.refreshTokenHash) {
|
|
628
|
+
this.logger.debug("Token does not contain refreshTokenHash, skipping binding validation");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const request = context.switchToHttp().getRequest();
|
|
632
|
+
const cookies = request.cookies || {};
|
|
633
|
+
const refreshToken = cookies[config.cookie.refreshCookieName];
|
|
634
|
+
if (!refreshToken) {
|
|
635
|
+
this.logger.warn("Session validation failed - refresh token cookie not found");
|
|
636
|
+
throw new UnauthorizedException("Session validation failed");
|
|
584
637
|
}
|
|
638
|
+
if (!verifyTokenHash(refreshToken, validatedToken.refreshTokenHash)) {
|
|
639
|
+
this.logger.warn("Session validation failed - token binding mismatch");
|
|
640
|
+
throw new UnauthorizedException("Session validation failed");
|
|
641
|
+
}
|
|
642
|
+
this.logger.debug("Token binding validated successfully");
|
|
585
643
|
}
|
|
586
644
|
};
|
|
587
645
|
VrittiAuthGuard = _ts_decorate4([
|
|
@@ -1230,6 +1288,10 @@ DatabaseModule = _ts_decorate10([
|
|
|
1230
1288
|
// src/database/repositories/primary-base.repository.ts
|
|
1231
1289
|
import { Logger as Logger6 } from "@nestjs/common";
|
|
1232
1290
|
import { eq as eq2, sql, getTableName } from "drizzle-orm";
|
|
1291
|
+
function snakeToCamel(str) {
|
|
1292
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1293
|
+
}
|
|
1294
|
+
__name(snakeToCamel, "snakeToCamel");
|
|
1233
1295
|
var PrimaryBaseRepository = class {
|
|
1234
1296
|
static {
|
|
1235
1297
|
__name(this, "PrimaryBaseRepository");
|
|
@@ -1239,7 +1301,8 @@ var PrimaryBaseRepository = class {
|
|
|
1239
1301
|
logger;
|
|
1240
1302
|
/**
|
|
1241
1303
|
* The table name extracted from the Drizzle table at runtime.
|
|
1242
|
-
*
|
|
1304
|
+
* Stored in camelCase to match Drizzle's query object keys.
|
|
1305
|
+
* Example: 'email_verifications' -> 'emailVerifications'
|
|
1243
1306
|
*/
|
|
1244
1307
|
tableName;
|
|
1245
1308
|
/**
|
|
@@ -1292,10 +1355,11 @@ var PrimaryBaseRepository = class {
|
|
|
1292
1355
|
constructor(database, table) {
|
|
1293
1356
|
this.database = database;
|
|
1294
1357
|
this.table = table;
|
|
1295
|
-
|
|
1358
|
+
const dbTableName = getTableName(table);
|
|
1359
|
+
this.tableName = snakeToCamel(dbTableName);
|
|
1296
1360
|
this.logger = new Logger6(this.constructor.name);
|
|
1297
1361
|
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1298
|
-
this.logger.debug(`Table name
|
|
1362
|
+
this.logger.debug(`Table name: '${dbTableName}' -> query key: '${this.tableName}'`);
|
|
1299
1363
|
}
|
|
1300
1364
|
/**
|
|
1301
1365
|
* Create a new record
|
|
@@ -3334,11 +3398,19 @@ export {
|
|
|
3334
3398
|
ValidationException,
|
|
3335
3399
|
VrittiAuthGuard,
|
|
3336
3400
|
addCorrelationIdToResponse,
|
|
3401
|
+
configureApiSdk,
|
|
3337
3402
|
correlationStorage,
|
|
3403
|
+
defineConfig,
|
|
3338
3404
|
generateCorrelationId,
|
|
3405
|
+
getConfig,
|
|
3339
3406
|
getCorrelationContext,
|
|
3340
3407
|
getHttpStatusTitle,
|
|
3408
|
+
getJwtExpiry,
|
|
3409
|
+
getRefreshCookieOptions,
|
|
3410
|
+
hashToken,
|
|
3411
|
+
resetConfig,
|
|
3341
3412
|
runWithCorrelationContext,
|
|
3342
|
-
updateCorrelationContext
|
|
3413
|
+
updateCorrelationContext,
|
|
3414
|
+
verifyTokenHash
|
|
3343
3415
|
};
|
|
3344
3416
|
//# sourceMappingURL=index.js.map
|