@depup/nestjs__core 12.0.3-depup.0 → 12.1.0-depup.0
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/README.md +2 -2
- package/Readme.md +4 -0
- package/adapters/http-adapter.d.ts +53 -3
- package/adapters/http-adapter.js +77 -1
- package/application-config.d.ts +4 -0
- package/application-config.js +7 -0
- package/changes.json +1 -1
- package/errors/exceptions/invalid-provider.exception.d.ts +5 -0
- package/errors/exceptions/invalid-provider.exception.js +7 -0
- package/errors/messages.d.ts +2 -1
- package/errors/messages.js +6 -0
- package/helpers/cookies/cookie-signer.d.ts +32 -0
- package/helpers/cookies/cookie-signer.js +70 -0
- package/helpers/cookies/parse-cookie-header.d.ts +19 -0
- package/helpers/cookies/parse-cookie-header.js +47 -0
- package/helpers/cookies/request-cookies.d.ts +30 -0
- package/helpers/cookies/request-cookies.js +83 -0
- package/helpers/cookies/serialize-cookie.d.ts +14 -0
- package/helpers/cookies/serialize-cookie.js +106 -0
- package/helpers/handler-metadata-storage.d.ts +1 -1
- package/injector/container.js +7 -1
- package/injector/injector.d.ts +8 -0
- package/injector/injector.js +24 -5
- package/injector/module.js +4 -0
- package/middleware/middleware-module.js +1 -1
- package/nest-application.d.ts +26 -1
- package/nest-application.js +70 -0
- package/package.json +5 -5
- package/router/route-params-factory.d.ts +3 -0
- package/router/route-params-factory.js +13 -0
- package/router/router-execution-context.d.ts +6 -0
- package/router/router-execution-context.js +67 -6
- package/router/router-explorer.js +9 -6
- package/security/cross-origin-protection.d.ts +98 -0
- package/security/cross-origin-protection.js +276 -0
- package/security/http-security-hook.d.ts +29 -0
- package/security/http-security-hook.js +42 -0
- package/security/security-headers.d.ts +34 -0
- package/security/security-headers.js +342 -0
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ npm install @depup/nestjs__core
|
|
|
13
13
|
|
|
14
14
|
| Field | Value |
|
|
15
15
|
|-------|-------|
|
|
16
|
-
| Original | [@nestjs/core](https://www.npmjs.com/package/@nestjs/core) @ 12.0
|
|
17
|
-
| Processed | 2026-09-
|
|
16
|
+
| Original | [@nestjs/core](https://www.npmjs.com/package/@nestjs/core) @ 12.1.0 |
|
|
17
|
+
| Processed | 2026-09-23 |
|
|
18
18
|
| Smoke test | failed |
|
|
19
19
|
| Deps updated | 0 |
|
|
20
20
|
|
package/Readme.md
CHANGED
|
@@ -47,6 +47,10 @@ For questions and support please use the official [Discord channel](https://disc
|
|
|
47
47
|
|
|
48
48
|
Please make sure to read the [Issue Reporting Checklist](https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md#-submitting-an-issue) before opening an issue. Issues not conforming to the guidelines may be closed immediately.
|
|
49
49
|
|
|
50
|
+
## Observability
|
|
51
|
+
|
|
52
|
+
[NestJS Observe](https://observe.nestjs.com) is the official observability platform for Nest applications. Install the `@nestjs/observe` SDK, pass an API key, and requests, background jobs, errors, logs, and distributed traces start streaming to a dashboard - no manual span wiring and no collector to run. Because the SDK hooks into Nest's own request lifecycle, a trace reads like a call graph of your controllers and providers instead of a bare HTTP route. Free for up to 300,000 events a month, and there is a [live demo](https://www.observe-demo.nestjs.com/dashboard) with no signup.
|
|
53
|
+
|
|
50
54
|
## Consulting
|
|
51
55
|
|
|
52
56
|
With official support, you can get expert help straight from the Nest core team. We provide dedicated technical support, migration strategies, advice on best practices (and design decisions), PR reviews, and team augmentation. Read more about [support here](https://enterprise.nestjs.com).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { HttpServer, RequestMethod, VersioningOptions } from '@nestjs/common';
|
|
2
|
-
import type { RequestHandler, VersionValue } from '@nestjs/common/internal';
|
|
1
|
+
import type { CookieSerializeOptions, HttpServer, RequestMethod, VersioningOptions } from '@nestjs/common';
|
|
2
|
+
import type { RequestHandler, SecurityRequestHook, VersionValue } from '@nestjs/common/internal';
|
|
3
3
|
import type { NestApplicationOptions } from '@nestjs/common';
|
|
4
|
+
import type { CookieSigner } from '../helpers/cookies/cookie-signer.js';
|
|
4
5
|
/**
|
|
5
6
|
* Base class for HTTP platform adapters (see `ExpressAdapter` and
|
|
6
7
|
* `FastifyAdapter` for reference implementations).
|
|
@@ -13,9 +14,12 @@ import type { NestApplicationOptions } from '@nestjs/common';
|
|
|
13
14
|
* - default implementations that delegate to the wrapped framework
|
|
14
15
|
* `instance` (`use()`, the HTTP-verb methods, `listen()`) or are inert
|
|
15
16
|
* (`init()`, `normalizePath()`, `mapException()`, `beforeClose()`, the
|
|
16
|
-
* `setOn*Hook()` setters)
|
|
17
|
+
* `setOn*Hook()` setters), plus an Express-style, `use()`-based
|
|
18
|
+
* `registerSecurityHook()`;
|
|
17
19
|
* - storage for the native server (`httpServer`) and the framework instance
|
|
18
20
|
* (`instance`), with their accessors;
|
|
21
|
+
* - `setCookie()` and `clearCookie()`, implemented once on top of
|
|
22
|
+
* `appendHeader()`, so they behave the same on every platform;
|
|
19
23
|
* - the introspection hooks used by instrumentation tooling.
|
|
20
24
|
*
|
|
21
25
|
* Every remaining {@link HttpServer} member is declared abstract here, even
|
|
@@ -52,6 +56,11 @@ export declare abstract class AbstractHttpAdapter<TServer = any, TRequest = any,
|
|
|
52
56
|
* {@link AbstractHttpAdapter.setOnRouteTriggered}, if any.
|
|
53
57
|
*/
|
|
54
58
|
protected onRouteTriggered: ((requestMethod: RequestMethod, path: string) => void) | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Signer built from the `cookies.secret` application option, used by
|
|
61
|
+
* {@link AbstractHttpAdapter.setCookie} for `signed` cookies.
|
|
62
|
+
*/
|
|
63
|
+
protected cookieSigner: CookieSigner | undefined;
|
|
55
64
|
/**
|
|
56
65
|
* @param instance The framework application instance to delegate to (e.g.
|
|
57
66
|
* an Express `Application`). Subclasses typically create a default one
|
|
@@ -298,6 +307,47 @@ export declare abstract class AbstractHttpAdapter<TServer = any, TRequest = any,
|
|
|
298
307
|
* `BadRequestException`.
|
|
299
308
|
*/
|
|
300
309
|
mapException(error: unknown): unknown;
|
|
310
|
+
/**
|
|
311
|
+
* Installs the request hook of the built-in HTTP security features as a
|
|
312
|
+
* global middleware (`use()`), which fits Express-like frameworks: the hook
|
|
313
|
+
* gets the request and the response, and a rejection is handed to
|
|
314
|
+
* `next(error)`, i.e. to the exception layer. Override when the framework
|
|
315
|
+
* offers an earlier request hook (the Fastify adapter uses `onRequest`).
|
|
316
|
+
*
|
|
317
|
+
* @see {@link HttpServer.registerSecurityHook}
|
|
318
|
+
*/
|
|
319
|
+
registerSecurityHook(hook: SecurityRequestHook<TRequest>): any;
|
|
320
|
+
/**
|
|
321
|
+
* Appends a `Set-Cookie` header through {@link AbstractHttpAdapter.appendHeader},
|
|
322
|
+
* so every cookie set during a request is sent, on every platform. The
|
|
323
|
+
* value is percent-encoded, `path` defaults to `/` and `maxAge` is in
|
|
324
|
+
* seconds (not milliseconds, unlike Express' `res.cookie()`); with
|
|
325
|
+
* `signed: true`, the value is signed with the first `cookies.secret`.
|
|
326
|
+
*
|
|
327
|
+
* Throws a `TypeError` when the name, the value or an attribute is not
|
|
328
|
+
* valid per RFC 6265 (which rules out header injection through `;`, CR or
|
|
329
|
+
* LF) or when `sameSite: 'none'` or `partitioned` is set without `secure`,
|
|
330
|
+
* and an `Error` when `signed` is set but no secret is configured.
|
|
331
|
+
*
|
|
332
|
+
* @see {@link HttpServer.setCookie}
|
|
333
|
+
*/
|
|
334
|
+
setCookie(response: TResponse, name: string, value: string, options?: CookieSerializeOptions): any;
|
|
335
|
+
/**
|
|
336
|
+
* Appends a `Set-Cookie` header that expires the cookie immediately.
|
|
337
|
+
* `path` and `domain` must match the ones the cookie was set with; `maxAge`,
|
|
338
|
+
* `expires` and `signed` are ignored.
|
|
339
|
+
*
|
|
340
|
+
* @see {@link HttpServer.clearCookie}
|
|
341
|
+
*/
|
|
342
|
+
clearCookie(response: TResponse, name: string, options?: CookieSerializeOptions): any;
|
|
343
|
+
/**
|
|
344
|
+
* Sets the signer used for `signed` cookies. Called by `NestApplication`
|
|
345
|
+
* with the signer it builds from the `cookies.secret` application option;
|
|
346
|
+
* configure that option instead of calling this method.
|
|
347
|
+
*
|
|
348
|
+
* @internal
|
|
349
|
+
*/
|
|
350
|
+
setCookieSigner(signer: CookieSigner | undefined): void;
|
|
301
351
|
/**
|
|
302
352
|
* Stops the server; called by `app.close()`. May return a promise.
|
|
303
353
|
*
|
package/adapters/http-adapter.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serializeCookie } from '../helpers/cookies/serialize-cookie.js';
|
|
1
2
|
/**
|
|
2
3
|
* Base class for HTTP platform adapters (see `ExpressAdapter` and
|
|
3
4
|
* `FastifyAdapter` for reference implementations).
|
|
@@ -10,9 +11,12 @@
|
|
|
10
11
|
* - default implementations that delegate to the wrapped framework
|
|
11
12
|
* `instance` (`use()`, the HTTP-verb methods, `listen()`) or are inert
|
|
12
13
|
* (`init()`, `normalizePath()`, `mapException()`, `beforeClose()`, the
|
|
13
|
-
* `setOn*Hook()` setters)
|
|
14
|
+
* `setOn*Hook()` setters), plus an Express-style, `use()`-based
|
|
15
|
+
* `registerSecurityHook()`;
|
|
14
16
|
* - storage for the native server (`httpServer`) and the framework instance
|
|
15
17
|
* (`instance`), with their accessors;
|
|
18
|
+
* - `setCookie()` and `clearCookie()`, implemented once on top of
|
|
19
|
+
* `appendHeader()`, so they behave the same on every platform;
|
|
16
20
|
* - the introspection hooks used by instrumentation tooling.
|
|
17
21
|
*
|
|
18
22
|
* Every remaining {@link HttpServer} member is declared abstract here, even
|
|
@@ -49,6 +53,11 @@ export class AbstractHttpAdapter {
|
|
|
49
53
|
* {@link AbstractHttpAdapter.setOnRouteTriggered}, if any.
|
|
50
54
|
*/
|
|
51
55
|
onRouteTriggered;
|
|
56
|
+
/**
|
|
57
|
+
* Signer built from the `cookies.secret` application option, used by
|
|
58
|
+
* {@link AbstractHttpAdapter.setCookie} for `signed` cookies.
|
|
59
|
+
*/
|
|
60
|
+
cookieSigner;
|
|
52
61
|
/**
|
|
53
62
|
* @param instance The framework application instance to delegate to (e.g.
|
|
54
63
|
* an Express `Application`). Subclasses typically create a default one
|
|
@@ -220,4 +229,71 @@ export class AbstractHttpAdapter {
|
|
|
220
229
|
mapException(error) {
|
|
221
230
|
return error;
|
|
222
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Installs the request hook of the built-in HTTP security features as a
|
|
234
|
+
* global middleware (`use()`), which fits Express-like frameworks: the hook
|
|
235
|
+
* gets the request and the response, and a rejection is handed to
|
|
236
|
+
* `next(error)`, i.e. to the exception layer. Override when the framework
|
|
237
|
+
* offers an earlier request hook (the Fastify adapter uses `onRequest`).
|
|
238
|
+
*
|
|
239
|
+
* @see {@link HttpServer.registerSecurityHook}
|
|
240
|
+
*/
|
|
241
|
+
registerSecurityHook(hook) {
|
|
242
|
+
return this.use((request, response, next) => {
|
|
243
|
+
const error = hook(request, response);
|
|
244
|
+
return error ? next(error) : next();
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Appends a `Set-Cookie` header through {@link AbstractHttpAdapter.appendHeader},
|
|
249
|
+
* so every cookie set during a request is sent, on every platform. The
|
|
250
|
+
* value is percent-encoded, `path` defaults to `/` and `maxAge` is in
|
|
251
|
+
* seconds (not milliseconds, unlike Express' `res.cookie()`); with
|
|
252
|
+
* `signed: true`, the value is signed with the first `cookies.secret`.
|
|
253
|
+
*
|
|
254
|
+
* Throws a `TypeError` when the name, the value or an attribute is not
|
|
255
|
+
* valid per RFC 6265 (which rules out header injection through `;`, CR or
|
|
256
|
+
* LF) or when `sameSite: 'none'` or `partitioned` is set without `secure`,
|
|
257
|
+
* and an `Error` when `signed` is set but no secret is configured.
|
|
258
|
+
*
|
|
259
|
+
* @see {@link HttpServer.setCookie}
|
|
260
|
+
*/
|
|
261
|
+
setCookie(response, name, value, options = {}) {
|
|
262
|
+
let cookieValue = value;
|
|
263
|
+
if (options.signed) {
|
|
264
|
+
if (!this.cookieSigner) {
|
|
265
|
+
throw new Error(`Cannot sign cookie "${name}": no cookie secret is configured. ` +
|
|
266
|
+
'Pass "cookies: { secret }" to NestFactory.create().');
|
|
267
|
+
}
|
|
268
|
+
if (typeof value !== 'string') {
|
|
269
|
+
throw new TypeError(`Invalid value for cookie "${name}": expected a string, received ${typeof value}`);
|
|
270
|
+
}
|
|
271
|
+
cookieValue = this.cookieSigner.sign(value);
|
|
272
|
+
}
|
|
273
|
+
return this.appendHeader(response, 'Set-Cookie', serializeCookie(name, cookieValue, options));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Appends a `Set-Cookie` header that expires the cookie immediately.
|
|
277
|
+
* `path` and `domain` must match the ones the cookie was set with; `maxAge`,
|
|
278
|
+
* `expires` and `signed` are ignored.
|
|
279
|
+
*
|
|
280
|
+
* @see {@link HttpServer.clearCookie}
|
|
281
|
+
*/
|
|
282
|
+
clearCookie(response, name, options = {}) {
|
|
283
|
+
return this.appendHeader(response, 'Set-Cookie', serializeCookie(name, '', {
|
|
284
|
+
...options,
|
|
285
|
+
maxAge: 0,
|
|
286
|
+
expires: new Date(0),
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Sets the signer used for `signed` cookies. Called by `NestApplication`
|
|
291
|
+
* with the signer it builds from the `cookies.secret` application option;
|
|
292
|
+
* configure that option instead of calling this method.
|
|
293
|
+
*
|
|
294
|
+
* @internal
|
|
295
|
+
*/
|
|
296
|
+
setCookieSigner(signer) {
|
|
297
|
+
this.cookieSigner = signer;
|
|
298
|
+
}
|
|
223
299
|
}
|
package/application-config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CanActivate, ExceptionFilter, NestInterceptor, PipeTransform, PreRequestHook, RouteConflictPolicy, RouteResolutionStrategy, VersioningOptions, WebSocketAdapter } from '@nestjs/common';
|
|
2
2
|
import type { GlobalPrefixOptions } from '@nestjs/common/internal';
|
|
3
|
+
import type { CookieSigner } from './helpers/cookies/cookie-signer.js';
|
|
3
4
|
import { InstanceWrapper } from './injector/instance-wrapper.js';
|
|
4
5
|
import { ExcludeRouteMetadata } from './router/interfaces/exclude-route-metadata.interface.js';
|
|
5
6
|
export declare class ApplicationConfig {
|
|
@@ -14,6 +15,7 @@ export declare class ApplicationConfig {
|
|
|
14
15
|
private versioningOptions;
|
|
15
16
|
private routeConflictPolicy;
|
|
16
17
|
private routeResolutionStrategy;
|
|
18
|
+
private cookieSigner;
|
|
17
19
|
private readonly globalRequestPipes;
|
|
18
20
|
private readonly globalRequestFilters;
|
|
19
21
|
private readonly globalRequestInterceptors;
|
|
@@ -53,4 +55,6 @@ export declare class ApplicationConfig {
|
|
|
53
55
|
getRouteConflictPolicy(): RouteConflictPolicy | undefined;
|
|
54
56
|
setRouteResolutionStrategy(strategy: RouteResolutionStrategy | undefined): void;
|
|
55
57
|
getRouteResolutionStrategy(): RouteResolutionStrategy | undefined;
|
|
58
|
+
setCookieSigner(signer: CookieSigner | undefined): void;
|
|
59
|
+
getCookieSigner(): CookieSigner | undefined;
|
|
56
60
|
}
|
package/application-config.js
CHANGED
|
@@ -10,6 +10,7 @@ export class ApplicationConfig {
|
|
|
10
10
|
versioningOptions;
|
|
11
11
|
routeConflictPolicy;
|
|
12
12
|
routeResolutionStrategy;
|
|
13
|
+
cookieSigner;
|
|
13
14
|
globalRequestPipes = [];
|
|
14
15
|
globalRequestFilters = [];
|
|
15
16
|
globalRequestInterceptors = [];
|
|
@@ -123,4 +124,10 @@ export class ApplicationConfig {
|
|
|
123
124
|
getRouteResolutionStrategy() {
|
|
124
125
|
return this.routeResolutionStrategy;
|
|
125
126
|
}
|
|
127
|
+
setCookieSigner(signer) {
|
|
128
|
+
this.cookieSigner = signer;
|
|
129
|
+
}
|
|
130
|
+
getCookieSigner() {
|
|
131
|
+
return this.cookieSigner;
|
|
132
|
+
}
|
|
126
133
|
}
|
package/changes.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { INVALID_PROVIDER_MESSAGE } from '../messages.js';
|
|
2
|
+
import { RuntimeException } from './runtime.exception.js';
|
|
3
|
+
export class InvalidProviderException extends RuntimeException {
|
|
4
|
+
constructor(token, moduleName) {
|
|
5
|
+
super(INVALID_PROVIDER_MESSAGE(token, moduleName));
|
|
6
|
+
}
|
|
7
|
+
}
|
package/errors/messages.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ForwardReference, Type } from '@nestjs/common';
|
|
2
|
-
import { InjectorDependencyContext } from '../injector/injector.js';
|
|
2
|
+
import { InjectorDependency, InjectorDependencyContext } from '../injector/injector.js';
|
|
3
3
|
import { Module } from '../injector/module.js';
|
|
4
4
|
export declare const UNKNOWN_DEPENDENCIES_MESSAGE: (type: string | symbol, unknownDependencyContext: InjectorDependencyContext, moduleRef: Module | undefined) => string;
|
|
5
5
|
export declare const INVALID_MIDDLEWARE_MESSAGE: (text: TemplateStringsArray, name: string) => string;
|
|
@@ -7,6 +7,7 @@ export declare const UNDEFINED_FORWARDREF_MESSAGE: (scope: Type<any>[]) => strin
|
|
|
7
7
|
export declare const INVALID_MODULE_MESSAGE: (parentModule: any, index: number, scope: any[], receivedValue: unknown) => string;
|
|
8
8
|
export declare const USING_INVALID_CLASS_AS_A_MODULE_MESSAGE: (metatypeUsedAsAModule: Type | ForwardReference, scope: any[], classKind: "provider" | "controller" | "filter") => string;
|
|
9
9
|
export declare const UNDEFINED_MODULE_MESSAGE: (parentModule: any, index: number, scope: any[]) => string;
|
|
10
|
+
export declare const INVALID_PROVIDER_MESSAGE: (token: InjectorDependency, moduleName: string) => string;
|
|
10
11
|
export declare const UNKNOWN_EXPORT_MESSAGE: (token: string | symbol | undefined, module: string) => string;
|
|
11
12
|
export declare const INVALID_CLASS_MESSAGE: (text: TemplateStringsArray, value: any) => string;
|
|
12
13
|
export declare const INVALID_CLASS_SCOPE_MESSAGE: (text: TemplateStringsArray, name: string | undefined) => string;
|
package/errors/messages.js
CHANGED
|
@@ -163,6 +163,12 @@ Potential causes:
|
|
|
163
163
|
|
|
164
164
|
Scope [${stringifyScope(scope)}]`;
|
|
165
165
|
};
|
|
166
|
+
export const INVALID_PROVIDER_MESSAGE = (token, moduleName) => `Nest cannot create the ${moduleName} instance.
|
|
167
|
+
The provider ${getDependencyName(token, 'provider')} in the ${moduleName} "providers" array does not define a valid "useClass", "useValue", "useFactory" or "useExisting" property.
|
|
168
|
+
|
|
169
|
+
Potential causes:
|
|
170
|
+
- The value of "useClass", "useFactory" or "useExisting" is undefined at runtime, often because of a circular import between files. Check your import statements.
|
|
171
|
+
- None of these properties is set, or the one that is set is null.`;
|
|
166
172
|
export const UNKNOWN_EXPORT_MESSAGE = (token = 'item', module) => {
|
|
167
173
|
token = isSymbol(token) ? token.toString() : token;
|
|
168
174
|
return `Nest cannot export a provider/module that is not a part of the currently processed module (${module}). Please verify whether the exported ${token} is available in this particular context.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefix that marks a signed cookie value, as written by Express'
|
|
3
|
+
* `res.cookie(name, value, { signed: true })` and read by `cookie-parser`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SIGNED_COOKIE_PREFIX = "s:";
|
|
6
|
+
/**
|
|
7
|
+
* Signs and verifies cookie values with HMAC-SHA256.
|
|
8
|
+
*
|
|
9
|
+
* The format is the one of the `cookie-signature` package (used by
|
|
10
|
+
* `cookie-parser` and `express-session`): `value.signature`, where the
|
|
11
|
+
* signature is the base64 digest without `=` padding. Signed values carry the
|
|
12
|
+
* `s:` prefix, so cookies signed by `cookie-parser`/Express with the same
|
|
13
|
+
* secret keep verifying, and so do un-prefixed values signed by
|
|
14
|
+
* `@fastify/cookie`.
|
|
15
|
+
*
|
|
16
|
+
* With several secrets, values are signed with the first one and verified
|
|
17
|
+
* against each of them (secret rotation).
|
|
18
|
+
*/
|
|
19
|
+
export declare class CookieSigner {
|
|
20
|
+
private readonly secrets;
|
|
21
|
+
constructor(secret: string | string[]);
|
|
22
|
+
/**
|
|
23
|
+
* Returns `s:<value>.<signature>`, signed with the first secret.
|
|
24
|
+
*/
|
|
25
|
+
sign(value: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the original value when `input` carries a valid signature for
|
|
28
|
+
* any of the secrets, `undefined` otherwise. The `s:` prefix is optional.
|
|
29
|
+
*/
|
|
30
|
+
unsign(input: string): string | undefined;
|
|
31
|
+
private digest;
|
|
32
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Prefix that marks a signed cookie value, as written by Express'
|
|
4
|
+
* `res.cookie(name, value, { signed: true })` and read by `cookie-parser`.
|
|
5
|
+
*/
|
|
6
|
+
export const SIGNED_COOKIE_PREFIX = 's:';
|
|
7
|
+
/**
|
|
8
|
+
* Signs and verifies cookie values with HMAC-SHA256.
|
|
9
|
+
*
|
|
10
|
+
* The format is the one of the `cookie-signature` package (used by
|
|
11
|
+
* `cookie-parser` and `express-session`): `value.signature`, where the
|
|
12
|
+
* signature is the base64 digest without `=` padding. Signed values carry the
|
|
13
|
+
* `s:` prefix, so cookies signed by `cookie-parser`/Express with the same
|
|
14
|
+
* secret keep verifying, and so do un-prefixed values signed by
|
|
15
|
+
* `@fastify/cookie`.
|
|
16
|
+
*
|
|
17
|
+
* With several secrets, values are signed with the first one and verified
|
|
18
|
+
* against each of them (secret rotation).
|
|
19
|
+
*/
|
|
20
|
+
export class CookieSigner {
|
|
21
|
+
secrets;
|
|
22
|
+
constructor(secret) {
|
|
23
|
+
const secrets = Array.isArray(secret) ? secret : [secret];
|
|
24
|
+
if (secrets.length === 0 ||
|
|
25
|
+
secrets.some(item => typeof item !== 'string' || item.length === 0)) {
|
|
26
|
+
throw new TypeError('The "cookies.secret" option must be a non-empty string or a non-empty array of non-empty strings.');
|
|
27
|
+
}
|
|
28
|
+
this.secrets = [...secrets];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns `s:<value>.<signature>`, signed with the first secret.
|
|
32
|
+
*/
|
|
33
|
+
sign(value) {
|
|
34
|
+
return `${SIGNED_COOKIE_PREFIX}${value}.${this.digest(value, this.secrets[0])}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the original value when `input` carries a valid signature for
|
|
38
|
+
* any of the secrets, `undefined` otherwise. The `s:` prefix is optional.
|
|
39
|
+
*/
|
|
40
|
+
unsign(input) {
|
|
41
|
+
if (typeof input !== 'string') {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const signed = input.startsWith(SIGNED_COOKIE_PREFIX)
|
|
45
|
+
? input.slice(SIGNED_COOKIE_PREFIX.length)
|
|
46
|
+
: input;
|
|
47
|
+
const dotIndex = signed.lastIndexOf('.');
|
|
48
|
+
if (dotIndex === -1) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const value = signed.slice(0, dotIndex);
|
|
52
|
+
const signature = Buffer.from(signed.slice(dotIndex + 1));
|
|
53
|
+
let valid = false;
|
|
54
|
+
for (const secret of this.secrets) {
|
|
55
|
+
const expected = Buffer.from(this.digest(value, secret));
|
|
56
|
+
// Every secret is checked, so timing does not reveal which one matched.
|
|
57
|
+
if (expected.length === signature.length &&
|
|
58
|
+
timingSafeEqual(expected, signature)) {
|
|
59
|
+
valid = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return valid ? value : undefined;
|
|
63
|
+
}
|
|
64
|
+
digest(value, secret) {
|
|
65
|
+
return createHmac('sha256', secret)
|
|
66
|
+
.update(value)
|
|
67
|
+
.digest('base64')
|
|
68
|
+
.replace(/=+$/, '');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dictionary of cookies keyed by name. Created without a prototype, so names
|
|
3
|
+
* such as `__proto__` or `constructor` are stored as plain entries.
|
|
4
|
+
*/
|
|
5
|
+
export type CookieRecord = Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Parses a `Cookie` request header (RFC 6265, section 5.4) into a
|
|
8
|
+
* prototype-less dictionary.
|
|
9
|
+
*
|
|
10
|
+
* - pairs are separated by `;`, and split on the first `=` only;
|
|
11
|
+
* - names and values are trimmed, and a value wrapped in double quotes is
|
|
12
|
+
* unquoted;
|
|
13
|
+
* - values are percent-decoded, falling back to the raw value when they are
|
|
14
|
+
* not valid percent-encoding;
|
|
15
|
+
* - pairs without `=` or with an empty name are ignored;
|
|
16
|
+
* - when a name occurs more than once, the first occurrence wins (browsers
|
|
17
|
+
* send the most specific path first).
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseCookieHeader(header: string | string[] | undefined | null): CookieRecord;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a `Cookie` request header (RFC 6265, section 5.4) into a
|
|
3
|
+
* prototype-less dictionary.
|
|
4
|
+
*
|
|
5
|
+
* - pairs are separated by `;`, and split on the first `=` only;
|
|
6
|
+
* - names and values are trimmed, and a value wrapped in double quotes is
|
|
7
|
+
* unquoted;
|
|
8
|
+
* - values are percent-decoded, falling back to the raw value when they are
|
|
9
|
+
* not valid percent-encoding;
|
|
10
|
+
* - pairs without `=` or with an empty name are ignored;
|
|
11
|
+
* - when a name occurs more than once, the first occurrence wins (browsers
|
|
12
|
+
* send the most specific path first).
|
|
13
|
+
*/
|
|
14
|
+
export function parseCookieHeader(header) {
|
|
15
|
+
const cookies = Object.create(null);
|
|
16
|
+
if (!header) {
|
|
17
|
+
return cookies;
|
|
18
|
+
}
|
|
19
|
+
const source = Array.isArray(header) ? header.join('; ') : String(header);
|
|
20
|
+
for (const pair of source.split(';')) {
|
|
21
|
+
const eqIndex = pair.indexOf('=');
|
|
22
|
+
if (eqIndex === -1) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const name = pair.slice(0, eqIndex).trim();
|
|
26
|
+
if (!name || name in cookies) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
let value = pair.slice(eqIndex + 1).trim();
|
|
30
|
+
if (value.length >= 2 && value[0] === '"' && value.endsWith('"')) {
|
|
31
|
+
value = value.slice(1, -1);
|
|
32
|
+
}
|
|
33
|
+
cookies[name] = safeDecode(value);
|
|
34
|
+
}
|
|
35
|
+
return cookies;
|
|
36
|
+
}
|
|
37
|
+
function safeDecode(value) {
|
|
38
|
+
if (!value.includes('%')) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return decodeURIComponent(value);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CookieSigner } from './cookie-signer.js';
|
|
2
|
+
import { CookieRecord } from './parse-cookie-header.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the request cookies. `req.cookies`, when a cookie middleware
|
|
5
|
+
* (`cookie-parser`, `@fastify/cookie`) already populated it, takes precedence
|
|
6
|
+
* over parsing the `Cookie` header.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getRequestCookies(req: Record<string, any>): CookieRecord;
|
|
9
|
+
/**
|
|
10
|
+
* Returns a single request cookie, or `undefined` when it was not sent. Only
|
|
11
|
+
* own entries count, so names such as `constructor` never resolve to
|
|
12
|
+
* inherited members of a middleware-provided `req.cookies` object.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getRequestCookie(req: Record<string, any>, name: string): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the request cookies whose signature verifies, with their unsigned
|
|
17
|
+
* value. Cookies that are not signed, or whose signature does not verify, are
|
|
18
|
+
* left out.
|
|
19
|
+
*
|
|
20
|
+
* Without a signer (no `cookies.secret` option), falls back to
|
|
21
|
+
* `req.signedCookies` as populated by `cookie-parser`, dropping the entries it
|
|
22
|
+
* marked invalid (`false`). Throws when neither is available.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getRequestSignedCookies(req: Record<string, any>, signer: CookieSigner | undefined): CookieRecord;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the unsigned value of a single signed cookie, or `undefined` when it
|
|
27
|
+
* is missing or its signature does not verify. Same fallbacks as
|
|
28
|
+
* {@link getRequestSignedCookies}.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getRequestSignedCookie(req: Record<string, any>, name: string, signer: CookieSigner | undefined): string | undefined;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { isObject } from '@nestjs/common/internal';
|
|
2
|
+
import { parseCookieHeader } from './parse-cookie-header.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parsed `Cookie` header per request, so the header is parsed at most once
|
|
5
|
+
* however many parameters read cookies.
|
|
6
|
+
*/
|
|
7
|
+
const parsedCookiesCache = new WeakMap();
|
|
8
|
+
function getParsedCookieHeader(req) {
|
|
9
|
+
let cookies = parsedCookiesCache.get(req);
|
|
10
|
+
if (!cookies) {
|
|
11
|
+
cookies = parseCookieHeader(req.headers?.cookie);
|
|
12
|
+
parsedCookiesCache.set(req, cookies);
|
|
13
|
+
}
|
|
14
|
+
return cookies;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns the request cookies. `req.cookies`, when a cookie middleware
|
|
18
|
+
* (`cookie-parser`, `@fastify/cookie`) already populated it, takes precedence
|
|
19
|
+
* over parsing the `Cookie` header.
|
|
20
|
+
*/
|
|
21
|
+
export function getRequestCookies(req) {
|
|
22
|
+
if (isObject(req.cookies)) {
|
|
23
|
+
return req.cookies;
|
|
24
|
+
}
|
|
25
|
+
return getParsedCookieHeader(req);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns a single request cookie, or `undefined` when it was not sent. Only
|
|
29
|
+
* own entries count, so names such as `constructor` never resolve to
|
|
30
|
+
* inherited members of a middleware-provided `req.cookies` object.
|
|
31
|
+
*/
|
|
32
|
+
export function getRequestCookie(req, name) {
|
|
33
|
+
const cookies = getRequestCookies(req);
|
|
34
|
+
return Object.prototype.hasOwnProperty.call(cookies, name)
|
|
35
|
+
? cookies[name]
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns the request cookies whose signature verifies, with their unsigned
|
|
40
|
+
* value. Cookies that are not signed, or whose signature does not verify, are
|
|
41
|
+
* left out.
|
|
42
|
+
*
|
|
43
|
+
* Without a signer (no `cookies.secret` option), falls back to
|
|
44
|
+
* `req.signedCookies` as populated by `cookie-parser`, dropping the entries it
|
|
45
|
+
* marked invalid (`false`). Throws when neither is available.
|
|
46
|
+
*/
|
|
47
|
+
export function getRequestSignedCookies(req, signer) {
|
|
48
|
+
const signedCookies = Object.create(null);
|
|
49
|
+
if (signer) {
|
|
50
|
+
const cookies = getParsedCookieHeader(req);
|
|
51
|
+
for (const name of Object.keys(cookies)) {
|
|
52
|
+
const value = signer.unsign(cookies[name]);
|
|
53
|
+
if (value !== undefined) {
|
|
54
|
+
signedCookies[name] = value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return signedCookies;
|
|
58
|
+
}
|
|
59
|
+
if (isObject(req.signedCookies)) {
|
|
60
|
+
for (const [name, value] of Object.entries(req.signedCookies)) {
|
|
61
|
+
// `false` marks an invalid signature. Other values are kept as is,
|
|
62
|
+
// including the objects cookie-parser makes of `j:` JSON cookies.
|
|
63
|
+
if (value !== false) {
|
|
64
|
+
signedCookies[name] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return signedCookies;
|
|
68
|
+
}
|
|
69
|
+
throw new Error('Cannot read signed cookies: no cookie secret is configured. ' +
|
|
70
|
+
'Pass "cookies: { secret }" to NestFactory.create().');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Returns the unsigned value of a single signed cookie, or `undefined` when it
|
|
74
|
+
* is missing or its signature does not verify. Same fallbacks as
|
|
75
|
+
* {@link getRequestSignedCookies}.
|
|
76
|
+
*/
|
|
77
|
+
export function getRequestSignedCookie(req, name, signer) {
|
|
78
|
+
if (signer) {
|
|
79
|
+
const value = getParsedCookieHeader(req)[name];
|
|
80
|
+
return value === undefined ? undefined : signer.unsign(value);
|
|
81
|
+
}
|
|
82
|
+
return getRequestSignedCookies(req, signer)[name];
|
|
83
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CookieSerializeOptions } from '@nestjs/common';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the value of a `Set-Cookie` response header (RFC 6265, section 4.1).
|
|
4
|
+
*
|
|
5
|
+
* The value is percent-encoded with `encodeURIComponent()`, whose output only
|
|
6
|
+
* contains RFC 6265 `cookie-octet`s. `path` defaults to `/`. Throws a
|
|
7
|
+
* `TypeError` when the name, the value or an attribute would produce an
|
|
8
|
+
* invalid header or smuggle extra attributes (`;`, CR, LF, ...), and when
|
|
9
|
+
* `sameSite: 'none'` or `partitioned` is set without `secure`, since browsers
|
|
10
|
+
* reject such cookies.
|
|
11
|
+
*
|
|
12
|
+
* Signing is not handled here: pass an already signed value.
|
|
13
|
+
*/
|
|
14
|
+
export declare function serializeCookie(name: string, value: string, options?: CookieSerializeOptions): string;
|