@fastify/cookie 7.1.0 → 7.3.1
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 +47 -2
- package/package.json +2 -2
- package/plugin.d.ts +75 -32
- package/plugin.js +30 -0
- package/test/cookie.test.js +58 -0
- package/test/plugin.test-d.ts +18 -13
package/README.md
CHANGED
|
@@ -99,7 +99,8 @@ with a value of `'foo'` on the cookie path `/`.
|
|
|
99
99
|
+ `name`: a string name for the cookie to be set
|
|
100
100
|
+ `value`: a string value for the cookie
|
|
101
101
|
+ `options`: an options object as described in the [cookie serialize][cs] documentation
|
|
102
|
-
|
|
102
|
+
+ `options.signed`: the cookie should be signed
|
|
103
|
+
+ `options.secure`: if set to `true` it will set the Secure-flag. If it is set to `"auto"` Secure-flag is set when the connection is using tls.
|
|
103
104
|
|
|
104
105
|
#### Securing the cookie
|
|
105
106
|
|
|
@@ -201,7 +202,7 @@ the provided signer's (or the default signer if no custom implementation is prov
|
|
|
201
202
|
fastify.register(require('@fastify/cookie'), { secret: 'my-secret' })
|
|
202
203
|
|
|
203
204
|
fastify.get('/', (req, rep) => {
|
|
204
|
-
if (fastify.
|
|
205
|
+
if (fastify.unsignCookie(req.cookie.foo).valid === false) {
|
|
205
206
|
rep.send('cookie is invalid')
|
|
206
207
|
return
|
|
207
208
|
}
|
|
@@ -210,6 +211,50 @@ fastify.get('/', (req, rep) => {
|
|
|
210
211
|
})
|
|
211
212
|
```
|
|
212
213
|
|
|
214
|
+
### Other cases of manual signing
|
|
215
|
+
|
|
216
|
+
Sometimes the service under test should only accept requests with signed cookies, but it does not generate them itself.
|
|
217
|
+
|
|
218
|
+
**Example:**
|
|
219
|
+
|
|
220
|
+
```js
|
|
221
|
+
|
|
222
|
+
test('Request requires signed cookie', async () => {
|
|
223
|
+
const response = await app.inject({
|
|
224
|
+
method: 'GET',
|
|
225
|
+
url: '/',
|
|
226
|
+
headers: {
|
|
227
|
+
cookies : {
|
|
228
|
+
'sid': app.signCookie(sidValue)
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(response.statusCode).toBe(200);
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Manual signing/unsigning with low level utilities
|
|
238
|
+
|
|
239
|
+
with signerFactory
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
const { fastifyCookie } = require('@fastify/cookie');
|
|
243
|
+
|
|
244
|
+
const signer = fastifyCookie.signerFactory('secret');
|
|
245
|
+
const signedValue = signer.sign('test');
|
|
246
|
+
const {valid, renew, value } = signer.unsign(signedValue);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
with sign/unsign utilities
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
const { fastifyCookie } = require('@fastify/cookie');
|
|
253
|
+
|
|
254
|
+
const signedValue = fastifyCookie.sign('test', 'secret');
|
|
255
|
+
const unsignedvalue = fastifyCookie.unsign(signedValue, 'secret');
|
|
256
|
+
```
|
|
257
|
+
|
|
213
258
|
|
|
214
259
|
## License
|
|
215
260
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/cookie",
|
|
3
|
-
"version": "7.1
|
|
3
|
+
"version": "7.3.1",
|
|
4
4
|
"description": "Plugin for fastify to add support for cookies",
|
|
5
5
|
"main": "plugin.js",
|
|
6
6
|
"types": "plugin.d.ts",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"cookie": "^0.5.0",
|
|
54
54
|
"cookie-signature": "^1.1.0",
|
|
55
|
-
"fastify-plugin": "^
|
|
55
|
+
"fastify-plugin": "^4.0.0"
|
|
56
56
|
},
|
|
57
57
|
"tsd": {
|
|
58
58
|
"directory": "test"
|
package/plugin.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types='node' />
|
|
2
2
|
|
|
3
|
-
import { FastifyPluginCallback } from
|
|
3
|
+
import { FastifyPluginCallback } from "fastify";
|
|
4
4
|
|
|
5
|
-
declare module
|
|
5
|
+
declare module "fastify" {
|
|
6
6
|
interface FastifyInstance {
|
|
7
7
|
/**
|
|
8
8
|
* Unsigns the specified cookie using the secret provided.
|
|
@@ -21,6 +21,12 @@ declare module 'fastify' {
|
|
|
21
21
|
parseCookie(cookieHeader: string): {
|
|
22
22
|
[key: string]: string;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Manual cookie signing method
|
|
26
|
+
* @docs https://github.com/fastify/fastify-cookie#manual-cookie-parsing
|
|
27
|
+
* @param value cookie value
|
|
28
|
+
*/
|
|
29
|
+
signCookie(value: string): string;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
interface FastifyRequest {
|
|
@@ -43,7 +49,7 @@ declare module 'fastify' {
|
|
|
43
49
|
export type setCookieWrapper = (
|
|
44
50
|
name: string,
|
|
45
51
|
value: string,
|
|
46
|
-
options?: CookieSerializeOptions
|
|
52
|
+
options?: fastifyCookie.CookieSerializeOptions
|
|
47
53
|
) => FastifyReply;
|
|
48
54
|
|
|
49
55
|
interface FastifyReply {
|
|
@@ -57,20 +63,27 @@ declare module 'fastify' {
|
|
|
57
63
|
setCookie(
|
|
58
64
|
name: string,
|
|
59
65
|
value: string,
|
|
60
|
-
options?: CookieSerializeOptions
|
|
66
|
+
options?: fastifyCookie.CookieSerializeOptions
|
|
61
67
|
): this;
|
|
62
68
|
|
|
63
69
|
/**
|
|
64
70
|
* @alias setCookie
|
|
65
71
|
*/
|
|
66
|
-
cookie(
|
|
72
|
+
cookie(
|
|
73
|
+
name: string,
|
|
74
|
+
value: string,
|
|
75
|
+
options?: fastifyCookie.CookieSerializeOptions
|
|
76
|
+
): this;
|
|
67
77
|
|
|
68
78
|
/**
|
|
69
79
|
* clear response cookie
|
|
70
80
|
* @param name Cookie name
|
|
71
81
|
* @param options Serialize options
|
|
72
82
|
*/
|
|
73
|
-
clearCookie(
|
|
83
|
+
clearCookie(
|
|
84
|
+
name: string,
|
|
85
|
+
options?: fastifyCookie.CookieSerializeOptions
|
|
86
|
+
): this;
|
|
74
87
|
|
|
75
88
|
/**
|
|
76
89
|
* Unsigns the specified cookie using the secret provided.
|
|
@@ -84,34 +97,64 @@ declare module 'fastify' {
|
|
|
84
97
|
}
|
|
85
98
|
}
|
|
86
99
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
expires?: Date;
|
|
91
|
-
httpOnly?: boolean;
|
|
92
|
-
maxAge?: number;
|
|
93
|
-
path?: string;
|
|
94
|
-
priority?: 'low' | 'medium' | 'high';
|
|
95
|
-
sameSite?: boolean | 'lax' | 'strict' | 'none';
|
|
96
|
-
secure?: boolean;
|
|
97
|
-
signed?: boolean;
|
|
98
|
-
}
|
|
100
|
+
type FastifyCookiePlugin = FastifyPluginCallback<
|
|
101
|
+
NonNullable<fastifyCookie.FastifyCookieOptions>
|
|
102
|
+
>;
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
104
|
+
declare namespace fastifyCookie {
|
|
105
|
+
export interface Signer {
|
|
106
|
+
sign: (input: string) => string;
|
|
107
|
+
unsign: (input: string) => {
|
|
108
|
+
valid: boolean;
|
|
109
|
+
renew: boolean;
|
|
110
|
+
value: string | null;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface CookieSerializeOptions {
|
|
115
|
+
domain?: string;
|
|
116
|
+
encode?(val: string): string;
|
|
117
|
+
expires?: Date;
|
|
118
|
+
httpOnly?: boolean;
|
|
119
|
+
maxAge?: number;
|
|
120
|
+
path?: string;
|
|
121
|
+
priority?: "low" | "medium" | "high";
|
|
122
|
+
sameSite?: boolean | "lax" | "strict" | "none";
|
|
123
|
+
secure?: boolean;
|
|
124
|
+
signed?: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface FastifyCookieOptions {
|
|
128
|
+
secret?: string | string[] | Signer;
|
|
129
|
+
parseOptions?: fastifyCookie.CookieSerializeOptions;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type Sign = (value: string, secret: string) => string;
|
|
133
|
+
export type Unsign = (input: string, secret: string) => string | false;
|
|
134
|
+
export type SignerFactory = (secret: string) => Signer;
|
|
135
|
+
|
|
136
|
+
export const signerFactory: SignerFactory;
|
|
137
|
+
export const sign: Sign;
|
|
138
|
+
export const unsign: Unsign;
|
|
139
|
+
|
|
140
|
+
export interface FastifyCookie extends FastifyCookiePlugin {
|
|
141
|
+
signerFactory: SignerFactory;
|
|
142
|
+
sign: Sign;
|
|
143
|
+
unsign: Unsign;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const fastifyCookie: FastifyCookie;
|
|
147
|
+
|
|
148
|
+
export interface FastifyCookieOptions {
|
|
149
|
+
secret?: string | string[] | Signer;
|
|
150
|
+
parseOptions?: CookieSerializeOptions;
|
|
151
|
+
}
|
|
108
152
|
|
|
109
|
-
export
|
|
110
|
-
secret?: string | string[] | Signer;
|
|
111
|
-
parseOptions?: CookieSerializeOptions;
|
|
153
|
+
export { fastifyCookie as default };
|
|
112
154
|
}
|
|
113
155
|
|
|
114
|
-
declare
|
|
156
|
+
declare function fastifyCookie(
|
|
157
|
+
...params: Parameters<FastifyCookiePlugin>
|
|
158
|
+
): ReturnType<FastifyCookiePlugin>;
|
|
115
159
|
|
|
116
|
-
export
|
|
117
|
-
export { fastifyCookie };
|
|
160
|
+
export = fastifyCookie;
|
package/plugin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { sign, unsign } = require('cookie-signature')
|
|
3
4
|
const fp = require('fastify-plugin')
|
|
4
5
|
const cookie = require('cookie')
|
|
5
6
|
|
|
@@ -15,6 +16,15 @@ function fastifyCookieSetCookie (reply, name, value, options, signer) {
|
|
|
15
16
|
value = signer.sign(value)
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
if (opts.secure === 'auto') {
|
|
20
|
+
if (isConnectionSecure(reply.request)) {
|
|
21
|
+
opts.secure = true
|
|
22
|
+
} else {
|
|
23
|
+
opts.sameSite = 'lax'
|
|
24
|
+
opts.secure = false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
const serialized = cookie.serialize(name, value, opts)
|
|
19
29
|
let setCookie = reply.getHeader('Set-Cookie')
|
|
20
30
|
if (!setCookie) {
|
|
@@ -58,6 +68,7 @@ function plugin (fastify, options, next) {
|
|
|
58
68
|
const signer = typeof secret === 'string' || enableRotation ? signerFactory(secret) : secret
|
|
59
69
|
|
|
60
70
|
fastify.decorate('parseCookie', parseCookie)
|
|
71
|
+
fastify.decorate('signCookie', signCookie)
|
|
61
72
|
fastify.decorate('unsignCookie', unsignCookie)
|
|
62
73
|
|
|
63
74
|
fastify.decorateRequest('cookies', null)
|
|
@@ -76,6 +87,10 @@ function plugin (fastify, options, next) {
|
|
|
76
87
|
return cookie.parse(cookieHeader, options.parseOptions)
|
|
77
88
|
}
|
|
78
89
|
|
|
90
|
+
function signCookie (value) {
|
|
91
|
+
return signer.sign(value)
|
|
92
|
+
}
|
|
93
|
+
|
|
79
94
|
function unsignCookie (value) {
|
|
80
95
|
return signer.unsign(value)
|
|
81
96
|
}
|
|
@@ -90,6 +105,13 @@ function plugin (fastify, options, next) {
|
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
107
|
|
|
108
|
+
function isConnectionSecure (request) {
|
|
109
|
+
return (
|
|
110
|
+
request.raw.socket?.encrypted === true ||
|
|
111
|
+
request.headers['x-forwarded-proto'] === 'https'
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
93
115
|
const fastifyCookie = fp(plugin, {
|
|
94
116
|
fastify: '4.x',
|
|
95
117
|
name: '@fastify/cookie'
|
|
@@ -108,3 +130,11 @@ const fastifyCookie = fp(plugin, {
|
|
|
108
130
|
fastifyCookie.fastifyCookie = fastifyCookie
|
|
109
131
|
fastifyCookie.default = fastifyCookie
|
|
110
132
|
module.exports = fastifyCookie
|
|
133
|
+
|
|
134
|
+
fastifyCookie.fastifyCookie.signerFactory = signerFactory
|
|
135
|
+
fastifyCookie.fastifyCookie.sign = sign
|
|
136
|
+
fastifyCookie.fastifyCookie.unsign = unsign
|
|
137
|
+
|
|
138
|
+
module.exports.signerFactory = signerFactory
|
|
139
|
+
module.exports.sign = sign
|
|
140
|
+
module.exports.unsign = unsign
|
package/test/cookie.test.js
CHANGED
|
@@ -756,3 +756,61 @@ test('cookies set with plugin options parseOptions field', (t) => {
|
|
|
756
756
|
}
|
|
757
757
|
)
|
|
758
758
|
})
|
|
759
|
+
|
|
760
|
+
test('create signed cookie manually using signCookie decorator', async (t) => {
|
|
761
|
+
const fastify = Fastify()
|
|
762
|
+
|
|
763
|
+
await fastify.register(plugin, { secret: 'secret' })
|
|
764
|
+
|
|
765
|
+
fastify.get('/test1', (req, reply) => {
|
|
766
|
+
reply.send({
|
|
767
|
+
unsigned: req.unsignCookie(req.cookies.foo)
|
|
768
|
+
})
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
const res = await fastify.inject({
|
|
772
|
+
method: 'GET',
|
|
773
|
+
url: '/test1',
|
|
774
|
+
headers: { cookie: `foo=${fastify.signCookie('bar')}` }
|
|
775
|
+
})
|
|
776
|
+
t.equal(res.statusCode, 200)
|
|
777
|
+
t.same(JSON.parse(res.body), { unsigned: { value: 'bar', renew: false, valid: true } })
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
test('handle secure:auto of cookieOptions', async (t) => {
|
|
781
|
+
const fastify = Fastify()
|
|
782
|
+
|
|
783
|
+
await fastify.register(plugin)
|
|
784
|
+
|
|
785
|
+
fastify.get('/test1', (req, reply) => {
|
|
786
|
+
reply
|
|
787
|
+
.setCookie('foo', 'foo', { path: '/', secure: 'auto' })
|
|
788
|
+
.send()
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
const res = await fastify.inject({
|
|
792
|
+
method: 'GET',
|
|
793
|
+
url: '/test1',
|
|
794
|
+
headers: { 'x-forwarded-proto': 'https' }
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
const cookies = res.cookies
|
|
798
|
+
t.equal(cookies.length, 1)
|
|
799
|
+
t.equal(cookies[0].name, 'foo')
|
|
800
|
+
t.equal(cookies[0].value, 'foo')
|
|
801
|
+
t.equal(cookies[0].secure, true)
|
|
802
|
+
t.equal(cookies[0].path, '/')
|
|
803
|
+
|
|
804
|
+
const res2 = await fastify.inject({
|
|
805
|
+
method: 'GET',
|
|
806
|
+
url: '/test1'
|
|
807
|
+
})
|
|
808
|
+
|
|
809
|
+
const cookies2 = res2.cookies
|
|
810
|
+
t.equal(cookies2.length, 1)
|
|
811
|
+
t.equal(cookies2[0].name, 'foo')
|
|
812
|
+
t.equal(cookies2[0].value, 'foo')
|
|
813
|
+
t.equal(cookies2[0].sameSite, 'Lax')
|
|
814
|
+
t.same(cookies2[0].secure, null)
|
|
815
|
+
t.equal(cookies2[0].path, '/')
|
|
816
|
+
})
|
package/test/plugin.test-d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Server } from 'http';
|
|
1
|
+
import cookie from '../plugin';
|
|
3
2
|
import { expectType } from 'tsd';
|
|
4
3
|
import * as fastifyCookieStar from '..';
|
|
5
|
-
import fastifyCookieDefault, {
|
|
6
|
-
fastifyCookie as fastifyCookieNamed
|
|
7
|
-
} from '..';
|
|
8
|
-
import cookie, { FastifyCookieOptions } from '../plugin';
|
|
9
|
-
|
|
10
4
|
import fastifyCookieCjsImport = require('..');
|
|
5
|
+
import fastifyCookieDefault, { fastifyCookie as fastifyCookieNamed } from '..';
|
|
6
|
+
import fastify, { FastifyInstance, FastifyReply, setCookieWrapper } from 'fastify';
|
|
7
|
+
|
|
11
8
|
const fastifyCookieCjs = require('..');
|
|
12
9
|
|
|
13
10
|
const app: FastifyInstance = fastify();
|
|
@@ -19,16 +16,24 @@ app.register(fastifyCookieCjsImport.fastifyCookie);
|
|
|
19
16
|
app.register(fastifyCookieStar.default);
|
|
20
17
|
app.register(fastifyCookieStar.fastifyCookie);
|
|
21
18
|
|
|
22
|
-
expectType<
|
|
23
|
-
expectType<
|
|
24
|
-
expectType<
|
|
25
|
-
expectType<
|
|
26
|
-
expectType<
|
|
27
|
-
expectType<
|
|
19
|
+
expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieNamed);
|
|
20
|
+
expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieDefault);
|
|
21
|
+
expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieCjsImport.default);
|
|
22
|
+
expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieCjsImport.fastifyCookie);
|
|
23
|
+
expectType<fastifyCookieStar.FastifyCookie>(fastifyCookieStar.default);
|
|
24
|
+
expectType<fastifyCookieStar.FastifyCookie>(
|
|
28
25
|
fastifyCookieStar.fastifyCookie
|
|
29
26
|
);
|
|
30
27
|
expectType<any>(fastifyCookieCjs);
|
|
31
28
|
|
|
29
|
+
expectType<fastifyCookieStar.Sign>(fastifyCookieDefault.sign);
|
|
30
|
+
expectType<fastifyCookieStar.Unsign>(fastifyCookieDefault.unsign);
|
|
31
|
+
expectType<fastifyCookieStar.SignerFactory >(fastifyCookieDefault.signerFactory );
|
|
32
|
+
|
|
33
|
+
expectType<fastifyCookieStar.Sign>(fastifyCookieNamed.sign);
|
|
34
|
+
expectType<fastifyCookieStar.Unsign>(fastifyCookieNamed.unsign);
|
|
35
|
+
expectType<fastifyCookieStar.SignerFactory >(fastifyCookieNamed.signerFactory );
|
|
36
|
+
|
|
32
37
|
const server = fastify();
|
|
33
38
|
|
|
34
39
|
server.register(cookie);
|