@nauth-toolkit/nestjs 0.1.18 → 0.1.22
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/auth.module.d.ts.map +1 -1
- package/dist/auth.module.js +29 -14
- package/dist/auth.module.js.map +1 -1
- package/dist/factories/storage-adapter.factory.d.ts.map +1 -1
- package/dist/factories/storage-adapter.factory.js +204 -63
- package/dist/factories/storage-adapter.factory.js.map +1 -1
- package/dist/guards/auth.guard.d.ts +3 -4
- package/dist/guards/auth.guard.d.ts.map +1 -1
- package/dist/guards/auth.guard.js +49 -60
- package/dist/guards/auth.guard.js.map +1 -1
- package/dist/guards/nauth-context.guard.d.ts +44 -0
- package/dist/guards/nauth-context.guard.d.ts.map +1 -0
- package/dist/guards/nauth-context.guard.js +140 -0
- package/dist/guards/nauth-context.guard.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/interceptors/cookie-token.interceptor.d.ts +3 -7
- package/dist/interceptors/cookie-token.interceptor.d.ts.map +1 -1
- package/dist/interceptors/cookie-token.interceptor.js +25 -139
- package/dist/interceptors/cookie-token.interceptor.js.map +1 -1
- package/dist/interceptors/index.d.ts +1 -1
- package/dist/interceptors/index.d.ts.map +1 -1
- package/dist/interceptors/index.js +1 -1
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/nauth-context.interceptor.d.ts +27 -0
- package/dist/interceptors/nauth-context.interceptor.d.ts.map +1 -0
- package/dist/interceptors/nauth-context.interceptor.js +64 -0
- package/dist/interceptors/nauth-context.interceptor.js.map +1 -0
- package/dist/services/token-delivery-http.service.d.ts +68 -0
- package/dist/services/token-delivery-http.service.d.ts.map +1 -0
- package/dist/services/token-delivery-http.service.js +194 -0
- package/dist/services/token-delivery-http.service.js.map +1 -0
- package/package.json +2 -2
- package/dist/interceptors/client-info.interceptor.d.ts +0 -50
- package/dist/interceptors/client-info.interceptor.d.ts.map +0 -1
- package/dist/interceptors/client-info.interceptor.js +0 -196
- package/dist/interceptors/client-info.interceptor.js.map +0 -1
|
@@ -8,18 +8,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
-
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
-
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
12
|
exports.CookieTokenInterceptor = void 0;
|
|
16
13
|
const common_1 = require("@nestjs/common");
|
|
17
14
|
const core_1 = require("@nestjs/core");
|
|
18
15
|
const operators_1 = require("rxjs/operators");
|
|
19
|
-
const core_2 = require("@nauth-toolkit/core");
|
|
20
|
-
const internal_1 = require("@nauth-toolkit/core/internal");
|
|
21
16
|
const token_delivery_decorator_1 = require("../decorators/token-delivery.decorator");
|
|
22
|
-
const
|
|
17
|
+
const token_delivery_http_service_1 = require("../services/token-delivery-http.service");
|
|
23
18
|
/**
|
|
24
19
|
* Cookie Token Interceptor
|
|
25
20
|
*
|
|
@@ -37,56 +32,33 @@ const csrf_service_1 = require("../services/csrf.service");
|
|
|
37
32
|
* It does nothing in other contexts (e.g., WebSocket, GraphQL).
|
|
38
33
|
*/
|
|
39
34
|
let CookieTokenInterceptor = class CookieTokenInterceptor {
|
|
40
|
-
|
|
41
|
-
jwtService;
|
|
35
|
+
tokenDeliveryHttp;
|
|
42
36
|
reflector;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.config = config;
|
|
46
|
-
this.jwtService = jwtService;
|
|
37
|
+
constructor(tokenDeliveryHttp, reflector) {
|
|
38
|
+
this.tokenDeliveryHttp = tokenDeliveryHttp;
|
|
47
39
|
this.reflector = reflector;
|
|
48
|
-
this.csrfService = csrfService;
|
|
49
40
|
}
|
|
50
41
|
intercept(context, next) {
|
|
51
42
|
// Only operate in HTTP context
|
|
52
43
|
if (context.getType() !== 'http') {
|
|
53
44
|
return next.handle();
|
|
54
45
|
}
|
|
55
|
-
const deliveryConfig = this.config.tokenDelivery;
|
|
56
46
|
const http = context.switchToHttp();
|
|
57
47
|
const req = http.getRequest();
|
|
58
48
|
const res = http.getResponse();
|
|
59
49
|
// Determine effective delivery for this request
|
|
60
50
|
const routeMode = this.reflector.get(token_delivery_decorator_1.TOKEN_DELIVERY_KEY, context.getHandler());
|
|
61
|
-
const
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (method === 'cookies') {
|
|
73
|
-
throw new core_2.NAuthException(core_2.AuthErrorCode.BEARER_NOT_ALLOWED, "Route-level JSON delivery requested, but tokenDelivery.method is 'cookies' (JSON/Bearer tokens disabled)");
|
|
74
|
-
}
|
|
75
|
-
// method === 'json' or 'hybrid' - both allow JSON, so OK
|
|
76
|
-
}
|
|
77
|
-
let effective = 'json';
|
|
78
|
-
if (routeMode) {
|
|
79
|
-
effective = routeMode;
|
|
80
|
-
}
|
|
81
|
-
else if (method === 'hybrid') {
|
|
82
|
-
effective = (0, core_2.resolveDeliveryForRequest)(req, deliveryConfig?.hybridPolicy);
|
|
83
|
-
}
|
|
84
|
-
else if (method === 'cookies') {
|
|
85
|
-
effective = 'cookies';
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
effective = 'json';
|
|
89
|
-
}
|
|
51
|
+
const effective = this.tokenDeliveryHttp.resolveEffectiveDelivery(req, routeMode);
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Expose route-level delivery decision to downstream handlers
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Social redirect endpoints (and other framework-neutral handlers) may need to
|
|
56
|
+
// know the route-level delivery override in hybrid deployments. Provider callbacks
|
|
57
|
+
// often omit `Origin`, so the only deterministic signal is the route itself.
|
|
58
|
+
//
|
|
59
|
+
// We store it on the request object using a non-enumerable-ish private-ish key.
|
|
60
|
+
// Frameworks tolerate extra properties on the request object (Express/Fastify).
|
|
61
|
+
req.__nauthRouteDelivery = routeMode;
|
|
90
62
|
return next.handle().pipe((0, operators_1.map)((data) => {
|
|
91
63
|
// ============================================================================
|
|
92
64
|
// Safety: Only process object responses
|
|
@@ -105,102 +77,18 @@ let CookieTokenInterceptor = class CookieTokenInterceptor {
|
|
|
105
77
|
if (!hasAccessToken && !hasDeviceTokenOnly) {
|
|
106
78
|
return responseData;
|
|
107
79
|
}
|
|
108
|
-
// Smart default cookie options
|
|
109
|
-
const opt = deliveryConfig?.cookieOptions;
|
|
110
|
-
// Cookie domain handling:
|
|
111
|
-
// - undefined: Cookie set for exact host:port (e.g., localhost:3000)
|
|
112
|
-
// For cross-port requests (localhost:4200 → localhost:3000), cookies work IF:
|
|
113
|
-
// - Frontend sends withCredentials: true
|
|
114
|
-
// - Backend CORS allows credentials
|
|
115
|
-
// - SameSite allows cross-site requests (e.g., 'lax' or 'none')
|
|
116
|
-
//
|
|
117
|
-
// - 'localhost' or '.localhost': Some browsers accept this, others reject it
|
|
118
|
-
// Modern browsers generally allow 'localhost' without domain attribute
|
|
119
|
-
//
|
|
120
|
-
// - '.example.com': For production cross-subdomain sharing
|
|
121
|
-
// Allows cookies set by api.example.com to be sent from app.example.com
|
|
122
|
-
const cookieOptions = {
|
|
123
|
-
httpOnly: true,
|
|
124
|
-
secure: opt?.secure !== false, // default true
|
|
125
|
-
sameSite: (opt?.sameSite || 'strict'),
|
|
126
|
-
path: opt?.path || '/',
|
|
127
|
-
};
|
|
128
|
-
// Include domain if provided (browsers handle localhost differently - some accept, some reject)
|
|
129
|
-
// For cross-port testing (like Cognito), omitting domain works with sameSite: 'lax' or 'none'
|
|
130
|
-
if (opt?.domain) {
|
|
131
|
-
cookieOptions.domain = opt.domain;
|
|
132
|
-
}
|
|
133
|
-
// Derive expiry strictly from JWT claims (no fallback)
|
|
134
|
-
// We decode here (signature already trusted as tokens are freshly issued);
|
|
135
|
-
// full validation and blacklist checks happen in the AuthGuard on subsequent requests.
|
|
136
|
-
let accessTokenMaxAgeMs = 0;
|
|
137
|
-
if (hasAccessToken && responseData.accessToken) {
|
|
138
|
-
const accessPayload = this.jwtService.decodeToken(responseData.accessToken);
|
|
139
|
-
if (!accessPayload?.exp) {
|
|
140
|
-
throw new core_2.NAuthException(core_2.AuthErrorCode.TOKEN_INVALID, 'Access token missing or invalid exp claim; refusing to set cookies');
|
|
141
|
-
}
|
|
142
|
-
const accessExpSeconds = accessPayload.exp;
|
|
143
|
-
accessTokenMaxAgeMs = Math.max(0, accessExpSeconds * 1000 - Date.now());
|
|
144
|
-
if (accessTokenMaxAgeMs <= 0) {
|
|
145
|
-
throw new core_2.NAuthException(core_2.AuthErrorCode.TOKEN_INVALID, 'Access token already expired; refusing to set cookies');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const setCookie = (name, value, options) => {
|
|
149
|
-
if (res && typeof res.cookie === 'function') {
|
|
150
|
-
res.cookie(name, value, options);
|
|
151
|
-
}
|
|
152
|
-
else if (res && typeof res.setCookie === 'function') {
|
|
153
|
-
res.setCookie(name, value, options);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
80
|
// Set cookies only when effective is 'cookies'
|
|
157
81
|
if (effective === 'cookies' && hasAccessToken && responseData.accessToken) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
82
|
+
this.tokenDeliveryHttp.setAuthCookies(res, {
|
|
83
|
+
accessToken: responseData.accessToken,
|
|
84
|
+
refreshToken: typeof responseData.refreshToken === 'string' ? responseData.refreshToken : undefined,
|
|
85
|
+
deviceToken: typeof responseData.deviceToken === 'string' ? responseData.deviceToken : undefined,
|
|
162
86
|
});
|
|
87
|
+
this.tokenDeliveryHttp.setCsrfCookie(res, responseData.accessToken);
|
|
163
88
|
}
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
throw new core_2.NAuthException(core_2.AuthErrorCode.TOKEN_INVALID, 'Refresh token missing or invalid exp claim; refusing to set cookies');
|
|
168
|
-
}
|
|
169
|
-
const refreshExpSeconds = refreshPayload.exp;
|
|
170
|
-
const refreshTokenMaxAgeMs = Math.max(0, refreshExpSeconds * 1000 - Date.now());
|
|
171
|
-
if (refreshTokenMaxAgeMs <= 0) {
|
|
172
|
-
throw new core_2.NAuthException(core_2.AuthErrorCode.TOKEN_INVALID, 'Refresh token already expired; refusing to set cookies');
|
|
173
|
-
}
|
|
174
|
-
const refreshTokenCookieName = (0, core_2.getRefreshTokenCookieName)(this.config);
|
|
175
|
-
setCookie(refreshTokenCookieName, responseData.refreshToken, {
|
|
176
|
-
...cookieOptions,
|
|
177
|
-
maxAge: refreshTokenMaxAgeMs,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
// Set device token cookie for trusted device feature (web)
|
|
181
|
-
// Only set cookie when deviceToken is present and effective is cookies
|
|
182
|
-
// (hybrid mode may resolve to cookies for web origins)
|
|
183
|
-
if (typeof responseData.deviceToken === 'string' && responseData.deviceToken && effective === 'cookies') {
|
|
184
|
-
const rememberDeviceDays = this.config.mfa?.rememberDeviceDays || 30;
|
|
185
|
-
const deviceTokenMaxAgeMs = rememberDeviceDays * 24 * 60 * 60 * 1000; // Convert days to milliseconds
|
|
186
|
-
const deviceTokenCookieName = (0, core_2.getDeviceTokenCookieName)(this.config);
|
|
187
|
-
setCookie(deviceTokenCookieName, responseData.deviceToken, {
|
|
188
|
-
...cookieOptions,
|
|
189
|
-
maxAge: deviceTokenMaxAgeMs,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
// Set CSRF token cookie when using cookie-based token delivery
|
|
193
|
-
// CSRF token is required for state-changing requests to prevent CSRF attacks
|
|
194
|
-
if (effective === 'cookies' && this.csrfService && this.config.security?.csrf) {
|
|
195
|
-
const csrfToken = this.csrfService.generateToken();
|
|
196
|
-
const csrfCookieName = this.csrfService.getCookieName();
|
|
197
|
-
const csrfCookieOptions = this.csrfService.getCookieOptions();
|
|
198
|
-
// Use same max age as access token for CSRF cookie
|
|
199
|
-
// This ensures CSRF token expires when access token expires
|
|
200
|
-
setCookie(csrfCookieName, csrfToken, {
|
|
201
|
-
...csrfCookieOptions,
|
|
202
|
-
maxAge: accessTokenMaxAgeMs > 0 ? accessTokenMaxAgeMs : undefined,
|
|
203
|
-
});
|
|
89
|
+
else if (effective === 'cookies' && hasDeviceTokenOnly && responseData.deviceToken) {
|
|
90
|
+
// trust-device endpoint: cookie only
|
|
91
|
+
this.tokenDeliveryHttp.setDeviceTokenCookie(res, responseData.deviceToken);
|
|
204
92
|
}
|
|
205
93
|
// Strip tokens, deviceToken, and expiration fields only when effective is cookies (strict web path)
|
|
206
94
|
// Expiration is managed by cookie maxAge, so these fields are not needed
|
|
@@ -220,9 +108,7 @@ let CookieTokenInterceptor = class CookieTokenInterceptor {
|
|
|
220
108
|
exports.CookieTokenInterceptor = CookieTokenInterceptor;
|
|
221
109
|
exports.CookieTokenInterceptor = CookieTokenInterceptor = __decorate([
|
|
222
110
|
(0, common_1.Injectable)(),
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
core_1.Reflector,
|
|
226
|
-
csrf_service_1.CsrfService])
|
|
111
|
+
__metadata("design:paramtypes", [token_delivery_http_service_1.TokenDeliveryHttpService,
|
|
112
|
+
core_1.Reflector])
|
|
227
113
|
], CookieTokenInterceptor);
|
|
228
114
|
//# sourceMappingURL=cookie-token.interceptor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookie-token.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/cookie-token.interceptor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cookie-token.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/cookie-token.interceptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/interceptors/index.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/interceptors/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC"}
|
|
@@ -14,6 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./
|
|
17
|
+
__exportStar(require("./nauth-context.interceptor"), exports);
|
|
18
18
|
__exportStar(require("./cookie-token.interceptor"), exports);
|
|
19
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/interceptors/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/interceptors/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8DAA4C;AAC5C,6DAA2C"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
/**
|
|
4
|
+
* NAuth Context Interceptor
|
|
5
|
+
*
|
|
6
|
+
* Re-enters the AsyncLocalStorage context that was initialized by NAuthContextGuard.
|
|
7
|
+
* This ensures controllers and services run inside the same request-scoped context.
|
|
8
|
+
*
|
|
9
|
+
* **Why This is Needed:**
|
|
10
|
+
* - Guards and interceptors run in separate execution contexts
|
|
11
|
+
* - AsyncLocalStorage context from the guard is not automatically available to controllers
|
|
12
|
+
* - This interceptor restores the context using `ContextStorage.enterStore()`
|
|
13
|
+
*
|
|
14
|
+
* **Pattern:**
|
|
15
|
+
* - Mirrors the core FastifyAdapter pattern for context restoration
|
|
16
|
+
* - Works with both Express and Fastify adapters
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Registered globally in AuthModule as APP_INTERCEPTOR
|
|
21
|
+
* // No manual usage required - runs automatically for all HTTP requests
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare class NAuthContextInterceptor implements NestInterceptor {
|
|
25
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=nauth-context.interceptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nauth-context.interceptor.d.ts","sourceRoot":"","sources":["../../src/interceptors/nauth-context.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC5F,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAIlC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBACa,uBAAwB,YAAW,eAAe;IAC7D,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;CA2B7E"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.NAuthContextInterceptor = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const rxjs_1 = require("rxjs");
|
|
12
|
+
const core_1 = require("@nauth-toolkit/core");
|
|
13
|
+
const nauth_context_guard_1 = require("../guards/nauth-context.guard");
|
|
14
|
+
/**
|
|
15
|
+
* NAuth Context Interceptor
|
|
16
|
+
*
|
|
17
|
+
* Re-enters the AsyncLocalStorage context that was initialized by NAuthContextGuard.
|
|
18
|
+
* This ensures controllers and services run inside the same request-scoped context.
|
|
19
|
+
*
|
|
20
|
+
* **Why This is Needed:**
|
|
21
|
+
* - Guards and interceptors run in separate execution contexts
|
|
22
|
+
* - AsyncLocalStorage context from the guard is not automatically available to controllers
|
|
23
|
+
* - This interceptor restores the context using `ContextStorage.enterStore()`
|
|
24
|
+
*
|
|
25
|
+
* **Pattern:**
|
|
26
|
+
* - Mirrors the core FastifyAdapter pattern for context restoration
|
|
27
|
+
* - Works with both Express and Fastify adapters
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Registered globally in AuthModule as APP_INTERCEPTOR
|
|
32
|
+
* // No manual usage required - runs automatically for all HTTP requests
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
let NAuthContextInterceptor = class NAuthContextInterceptor {
|
|
36
|
+
intercept(context, next) {
|
|
37
|
+
// Only operate in HTTP context
|
|
38
|
+
if (context.getType() !== 'http') {
|
|
39
|
+
return next.handle();
|
|
40
|
+
}
|
|
41
|
+
const request = context.switchToHttp().getRequest();
|
|
42
|
+
// Get the context store that was created by NAuthContextGuard
|
|
43
|
+
const store = (0, nauth_context_guard_1.getNAuthContextStore)(request);
|
|
44
|
+
if (!store) {
|
|
45
|
+
// No context available - continue without context (shouldn't happen with proper setup)
|
|
46
|
+
return next.handle();
|
|
47
|
+
}
|
|
48
|
+
// Re-enter the context store so controllers/services have access to it
|
|
49
|
+
return new rxjs_1.Observable((subscriber) => {
|
|
50
|
+
core_1.ContextStorage.enterStore(store, () => {
|
|
51
|
+
next.handle().subscribe({
|
|
52
|
+
next: (value) => subscriber.next(value),
|
|
53
|
+
error: (err) => subscriber.error(err),
|
|
54
|
+
complete: () => subscriber.complete(),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
exports.NAuthContextInterceptor = NAuthContextInterceptor;
|
|
61
|
+
exports.NAuthContextInterceptor = NAuthContextInterceptor = __decorate([
|
|
62
|
+
(0, common_1.Injectable)()
|
|
63
|
+
], NAuthContextInterceptor);
|
|
64
|
+
//# sourceMappingURL=nauth-context.interceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nauth-context.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/nauth-context.interceptor.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4F;AAC5F,+BAAkC;AAClC,8CAAqD;AACrD,uEAAqE;AAErE;;;;;;;;;;;;;;;;;;;;GAoBG;AAEI,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAClC,SAAS,CAAC,OAAyB,EAAE,IAAiB;QACpD,+BAA+B;QAC/B,IAAI,OAAO,CAAC,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAEpD,8DAA8D;QAC9D,MAAM,KAAK,GAAG,IAAA,0CAAoB,EAAC,OAAO,CAAC,CAAC;QAE5C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,uFAAuF;YACvF,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,CAAC;QAED,uEAAuE;QACvE,OAAO,IAAI,iBAAU,CAAC,CAAC,UAAU,EAAE,EAAE;YACnC,qBAAc,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE;gBACpC,IAAI,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC;oBACtB,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;oBACvC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC;oBACrC,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE;iBACtC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AA5BY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;GACA,uBAAuB,CA4BnC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { NAuthConfig } from '@nauth-toolkit/core';
|
|
2
|
+
import { JwtService } from '@nauth-toolkit/core/internal';
|
|
3
|
+
import { CsrfService } from './csrf.service';
|
|
4
|
+
import type { RouteDelivery } from '../decorators/token-delivery.decorator';
|
|
5
|
+
/**
|
|
6
|
+
* Token delivery helper for HTTP controllers/interceptors (NestJS)
|
|
7
|
+
*
|
|
8
|
+
* Centralizes logic for:
|
|
9
|
+
* - determining effective token delivery ('cookies' | 'json') in hybrid mode
|
|
10
|
+
* - setting httpOnly auth cookies (and device token cookie) consistently
|
|
11
|
+
* - setting CSRF cookie when using cookie-based delivery
|
|
12
|
+
*
|
|
13
|
+
* This keeps consumer applications lightweight by letting toolkit controllers
|
|
14
|
+
* (like social redirect/callback) reuse the same cookie semantics as interceptors.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const effective = service.resolveEffectiveDelivery(req, undefined);
|
|
19
|
+
* if (effective === 'cookies') {
|
|
20
|
+
* service.setAuthCookies(res, { accessToken, refreshToken });
|
|
21
|
+
* service.setCsrfCookie(res, accessToken);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class TokenDeliveryHttpService {
|
|
26
|
+
private readonly config;
|
|
27
|
+
private readonly jwtService;
|
|
28
|
+
private readonly csrfService?;
|
|
29
|
+
constructor(config: NAuthConfig, jwtService: JwtService, csrfService?: CsrfService | undefined);
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the effective delivery mode for the current request.
|
|
32
|
+
*
|
|
33
|
+
* @param req - Framework request object (only `headers.origin` is used in hybrid mode)
|
|
34
|
+
* @param routeMode - Optional route-level override ('cookies' | 'json')
|
|
35
|
+
* @returns Effective delivery mode
|
|
36
|
+
* @throws {NAuthException} If route requests a mode not allowed by global config
|
|
37
|
+
*/
|
|
38
|
+
resolveEffectiveDelivery(req: unknown, routeMode?: RouteDelivery): 'cookies' | 'json';
|
|
39
|
+
/**
|
|
40
|
+
* Set auth cookies on the response.
|
|
41
|
+
*
|
|
42
|
+
* @param res - HTTP response object (FastifyReply or Express response)
|
|
43
|
+
* @param tokens - Tokens to set
|
|
44
|
+
* @throws {NAuthException} If token exp claims are missing/invalid
|
|
45
|
+
*/
|
|
46
|
+
setAuthCookies(res: unknown, tokens: {
|
|
47
|
+
accessToken: string;
|
|
48
|
+
refreshToken?: string;
|
|
49
|
+
deviceToken?: string;
|
|
50
|
+
}): void;
|
|
51
|
+
/**
|
|
52
|
+
* Set only the device token cookie (used by trust-device endpoint).
|
|
53
|
+
*
|
|
54
|
+
* @param res - HTTP response
|
|
55
|
+
* @param deviceToken - Device token to persist as cookie
|
|
56
|
+
*/
|
|
57
|
+
setDeviceTokenCookie(res: unknown, deviceToken: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Set CSRF cookie for cookie-based delivery.
|
|
60
|
+
*
|
|
61
|
+
* @param res - HTTP response
|
|
62
|
+
* @param accessToken - Access token used to align CSRF cookie lifetime
|
|
63
|
+
*/
|
|
64
|
+
setCsrfCookie(res: unknown, accessToken: string): void;
|
|
65
|
+
private buildCookieOptions;
|
|
66
|
+
private getSetCookieFn;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=token-delivery-http.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-delivery-http.service.d.ts","sourceRoot":"","sources":["../../src/services/token-delivery-http.service.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,WAAW,EAMZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBACa,wBAAwB;IAGjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAFZ,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,UAAU,EACtB,WAAW,CAAC,EAAE,WAAW,YAAA;IAG5C;;;;;;;OAOG;IACH,wBAAwB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM;IA0BrF;;;;;;OAMG;IACH,cAAc,CACZ,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3E,IAAI;IAuCP;;;;;OAKG;IACH,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAe7D;;;;;OAKG;IACH,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IA0BtD,OAAO,CAAC,kBAAkB;IA0B1B,OAAO,CAAC,cAAc;CAmBvB"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.TokenDeliveryHttpService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const core_1 = require("@nauth-toolkit/core");
|
|
18
|
+
const internal_1 = require("@nauth-toolkit/core/internal");
|
|
19
|
+
const csrf_service_1 = require("./csrf.service");
|
|
20
|
+
/**
|
|
21
|
+
* Token delivery helper for HTTP controllers/interceptors (NestJS)
|
|
22
|
+
*
|
|
23
|
+
* Centralizes logic for:
|
|
24
|
+
* - determining effective token delivery ('cookies' | 'json') in hybrid mode
|
|
25
|
+
* - setting httpOnly auth cookies (and device token cookie) consistently
|
|
26
|
+
* - setting CSRF cookie when using cookie-based delivery
|
|
27
|
+
*
|
|
28
|
+
* This keeps consumer applications lightweight by letting toolkit controllers
|
|
29
|
+
* (like social redirect/callback) reuse the same cookie semantics as interceptors.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const effective = service.resolveEffectiveDelivery(req, undefined);
|
|
34
|
+
* if (effective === 'cookies') {
|
|
35
|
+
* service.setAuthCookies(res, { accessToken, refreshToken });
|
|
36
|
+
* service.setCsrfCookie(res, accessToken);
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
let TokenDeliveryHttpService = class TokenDeliveryHttpService {
|
|
41
|
+
config;
|
|
42
|
+
jwtService;
|
|
43
|
+
csrfService;
|
|
44
|
+
constructor(config, jwtService, csrfService) {
|
|
45
|
+
this.config = config;
|
|
46
|
+
this.jwtService = jwtService;
|
|
47
|
+
this.csrfService = csrfService;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the effective delivery mode for the current request.
|
|
51
|
+
*
|
|
52
|
+
* @param req - Framework request object (only `headers.origin` is used in hybrid mode)
|
|
53
|
+
* @param routeMode - Optional route-level override ('cookies' | 'json')
|
|
54
|
+
* @returns Effective delivery mode
|
|
55
|
+
* @throws {NAuthException} If route requests a mode not allowed by global config
|
|
56
|
+
*/
|
|
57
|
+
resolveEffectiveDelivery(req, routeMode) {
|
|
58
|
+
const method = this.config.tokenDelivery?.method || 'json';
|
|
59
|
+
// Validate route override against global configuration
|
|
60
|
+
if (routeMode === 'cookies' && method === 'json') {
|
|
61
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.COOKIES_NOT_ALLOWED, "Route-level cookie delivery requested, but tokenDelivery.method is 'json' (cookies disabled)");
|
|
62
|
+
}
|
|
63
|
+
if (routeMode === 'json' && method === 'cookies') {
|
|
64
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.BEARER_NOT_ALLOWED, "Route-level JSON delivery requested, but tokenDelivery.method is 'cookies' (JSON/Bearer tokens disabled)");
|
|
65
|
+
}
|
|
66
|
+
if (routeMode) {
|
|
67
|
+
return routeMode;
|
|
68
|
+
}
|
|
69
|
+
if (method === 'hybrid') {
|
|
70
|
+
return (0, core_1.resolveDeliveryForRequest)(req, this.config.tokenDelivery?.hybridPolicy);
|
|
71
|
+
}
|
|
72
|
+
return method === 'cookies' ? 'cookies' : 'json';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Set auth cookies on the response.
|
|
76
|
+
*
|
|
77
|
+
* @param res - HTTP response object (FastifyReply or Express response)
|
|
78
|
+
* @param tokens - Tokens to set
|
|
79
|
+
* @throws {NAuthException} If token exp claims are missing/invalid
|
|
80
|
+
*/
|
|
81
|
+
setAuthCookies(res, tokens) {
|
|
82
|
+
const deliveryConfig = this.config.tokenDelivery;
|
|
83
|
+
const opt = deliveryConfig?.cookieOptions;
|
|
84
|
+
const cookieOptions = this.buildCookieOptions();
|
|
85
|
+
const setCookie = this.getSetCookieFn(res);
|
|
86
|
+
// Derive expiry from JWT exp claims (strict)
|
|
87
|
+
const accessPayload = this.jwtService.decodeToken(tokens.accessToken);
|
|
88
|
+
if (!accessPayload?.exp) {
|
|
89
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.TOKEN_INVALID, 'Access token missing exp claim; refusing to set cookies');
|
|
90
|
+
}
|
|
91
|
+
const accessMaxAgeMs = Math.max(0, accessPayload.exp * 1000 - Date.now());
|
|
92
|
+
if (accessMaxAgeMs <= 0) {
|
|
93
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.TOKEN_INVALID, 'Access token already expired; refusing to set cookies');
|
|
94
|
+
}
|
|
95
|
+
const accessTokenCookieName = (0, core_1.getAccessTokenCookieName)(this.config);
|
|
96
|
+
setCookie(accessTokenCookieName, tokens.accessToken, { ...cookieOptions, maxAge: accessMaxAgeMs });
|
|
97
|
+
if (typeof tokens.refreshToken === 'string' && tokens.refreshToken) {
|
|
98
|
+
const refreshPayload = this.jwtService.decodeToken(tokens.refreshToken);
|
|
99
|
+
if (!refreshPayload?.exp) {
|
|
100
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.TOKEN_INVALID, 'Refresh token missing exp claim; refusing to set cookies');
|
|
101
|
+
}
|
|
102
|
+
const refreshMaxAgeMs = Math.max(0, refreshPayload.exp * 1000 - Date.now());
|
|
103
|
+
if (refreshMaxAgeMs <= 0) {
|
|
104
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.TOKEN_INVALID, 'Refresh token already expired; refusing to set cookies');
|
|
105
|
+
}
|
|
106
|
+
const refreshTokenCookieName = (0, core_1.getRefreshTokenCookieName)(this.config);
|
|
107
|
+
setCookie(refreshTokenCookieName, tokens.refreshToken, { ...cookieOptions, maxAge: refreshMaxAgeMs });
|
|
108
|
+
}
|
|
109
|
+
// Trusted device cookie (optional)
|
|
110
|
+
if (typeof tokens.deviceToken === 'string' && tokens.deviceToken) {
|
|
111
|
+
this.setDeviceTokenCookie(res, tokens.deviceToken);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Set only the device token cookie (used by trust-device endpoint).
|
|
116
|
+
*
|
|
117
|
+
* @param res - HTTP response
|
|
118
|
+
* @param deviceToken - Device token to persist as cookie
|
|
119
|
+
*/
|
|
120
|
+
setDeviceTokenCookie(res, deviceToken) {
|
|
121
|
+
const token = typeof deviceToken === 'string' ? deviceToken : '';
|
|
122
|
+
if (!token) {
|
|
123
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'deviceToken is required', { field: 'deviceToken' });
|
|
124
|
+
}
|
|
125
|
+
const cookieOptions = this.buildCookieOptions();
|
|
126
|
+
const setCookie = this.getSetCookieFn(res);
|
|
127
|
+
const rememberDeviceDays = this.config.mfa?.rememberDeviceDays || 30;
|
|
128
|
+
const deviceTokenMaxAgeMs = rememberDeviceDays * 24 * 60 * 60 * 1000;
|
|
129
|
+
const deviceTokenCookieName = (0, core_1.getDeviceTokenCookieName)(this.config);
|
|
130
|
+
setCookie(deviceTokenCookieName, token, { ...cookieOptions, maxAge: deviceTokenMaxAgeMs });
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Set CSRF cookie for cookie-based delivery.
|
|
134
|
+
*
|
|
135
|
+
* @param res - HTTP response
|
|
136
|
+
* @param accessToken - Access token used to align CSRF cookie lifetime
|
|
137
|
+
*/
|
|
138
|
+
setCsrfCookie(res, accessToken) {
|
|
139
|
+
if (!this.csrfService || !this.config.security?.csrf) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const accessPayload = this.jwtService.decodeToken(accessToken);
|
|
143
|
+
if (!accessPayload?.exp) {
|
|
144
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.TOKEN_INVALID, 'Access token missing exp claim; refusing to set CSRF cookie');
|
|
145
|
+
}
|
|
146
|
+
const accessMaxAgeMs = Math.max(0, accessPayload.exp * 1000 - Date.now());
|
|
147
|
+
const csrfToken = this.csrfService.generateToken();
|
|
148
|
+
const csrfCookieName = this.csrfService.getCookieName();
|
|
149
|
+
const csrfCookieOptions = this.csrfService.getCookieOptions();
|
|
150
|
+
const setCookie = this.getSetCookieFn(res);
|
|
151
|
+
setCookie(csrfCookieName, csrfToken, {
|
|
152
|
+
...csrfCookieOptions,
|
|
153
|
+
maxAge: accessMaxAgeMs > 0 ? accessMaxAgeMs : undefined,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// Private helpers
|
|
158
|
+
// ============================================================================
|
|
159
|
+
buildCookieOptions() {
|
|
160
|
+
const opt = this.config.tokenDelivery?.cookieOptions;
|
|
161
|
+
const cookieOptions = {
|
|
162
|
+
httpOnly: true,
|
|
163
|
+
secure: opt?.secure !== false,
|
|
164
|
+
sameSite: (opt?.sameSite || 'strict'),
|
|
165
|
+
path: opt?.path || '/',
|
|
166
|
+
};
|
|
167
|
+
if (opt?.domain) {
|
|
168
|
+
cookieOptions.domain = opt.domain;
|
|
169
|
+
}
|
|
170
|
+
return cookieOptions;
|
|
171
|
+
}
|
|
172
|
+
getSetCookieFn(res) {
|
|
173
|
+
return (name, value, options) => {
|
|
174
|
+
const r = res;
|
|
175
|
+
if (typeof r.cookie === 'function') {
|
|
176
|
+
r.cookie(name, value, options);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (typeof r.setCookie === 'function') {
|
|
180
|
+
r.setCookie(name, value, options);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
throw new core_1.NAuthException(core_1.AuthErrorCode.INTERNAL_ERROR, 'Response does not support setting cookies');
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
exports.TokenDeliveryHttpService = TokenDeliveryHttpService;
|
|
188
|
+
exports.TokenDeliveryHttpService = TokenDeliveryHttpService = __decorate([
|
|
189
|
+
(0, common_1.Injectable)(),
|
|
190
|
+
__param(0, (0, common_1.Inject)('NAUTH_CONFIG')),
|
|
191
|
+
__metadata("design:paramtypes", [Object, internal_1.JwtService,
|
|
192
|
+
csrf_service_1.CsrfService])
|
|
193
|
+
], TokenDeliveryHttpService);
|
|
194
|
+
//# sourceMappingURL=token-delivery-http.service.js.map
|