@fastify/cookie 6.0.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/plugin.d.ts ADDED
@@ -0,0 +1,116 @@
1
+ /// <reference types='node' />
2
+
3
+ import { FastifyPluginCallback } from 'fastify';
4
+
5
+ declare module 'fastify' {
6
+ interface FastifyInstance {
7
+ /**
8
+ * Unsigns the specified cookie using the secret provided.
9
+ * @param value Cookie value
10
+ */
11
+ unsignCookie(value: string): {
12
+ valid: boolean;
13
+ renew: boolean;
14
+ value: string | null;
15
+ };
16
+ /**
17
+ * Manual cookie parsing method
18
+ * @docs https://github.com/fastify/fastify-cookie#manual-cookie-parsing
19
+ * @param cookieHeader Raw cookie header value
20
+ */
21
+ parseCookie(cookieHeader: string): {
22
+ [key: string]: string;
23
+ };
24
+ }
25
+
26
+ interface FastifyRequest {
27
+ /**
28
+ * Request cookies
29
+ */
30
+ cookies: { [cookieName: string]: string };
31
+
32
+ /**
33
+ * Unsigns the specified cookie using the secret provided.
34
+ * @param value Cookie value
35
+ */
36
+ unsignCookie(value: string): {
37
+ valid: boolean;
38
+ renew: boolean;
39
+ value: string | null;
40
+ };
41
+ }
42
+
43
+ export type setCookieWrapper = (
44
+ name: string,
45
+ value: string,
46
+ options?: CookieSerializeOptions
47
+ ) => FastifyReply;
48
+
49
+ interface FastifyReply {
50
+ /**
51
+ * Set response cookie
52
+ * @name setCookie
53
+ * @param name Cookie name
54
+ * @param value Cookie value
55
+ * @param options Serialize options
56
+ */
57
+ setCookie(
58
+ name: string,
59
+ value: string,
60
+ options?: CookieSerializeOptions
61
+ ): this;
62
+
63
+ /**
64
+ * @alias setCookie
65
+ */
66
+ cookie(name: string, value: string, options?: CookieSerializeOptions): this;
67
+
68
+ /**
69
+ * clear response cookie
70
+ * @param name Cookie name
71
+ * @param options Serialize options
72
+ */
73
+ clearCookie(name: string, options?: CookieSerializeOptions): this;
74
+
75
+ /**
76
+ * Unsigns the specified cookie using the secret provided.
77
+ * @param value Cookie value
78
+ */
79
+ unsignCookie(value: string): {
80
+ valid: boolean;
81
+ renew: boolean;
82
+ value: string | null;
83
+ };
84
+ }
85
+ }
86
+
87
+ export interface CookieSerializeOptions {
88
+ domain?: string;
89
+ encode?(val: string): string;
90
+ expires?: Date;
91
+ httpOnly?: boolean;
92
+ maxAge?: number;
93
+ path?: string;
94
+ sameSite?: boolean | 'lax' | 'strict' | 'none';
95
+ secure?: boolean;
96
+ signed?: boolean;
97
+ }
98
+
99
+ interface Signer {
100
+ sign: (input: string) => string;
101
+ unsign: (input: string) => {
102
+ valid: boolean;
103
+ renew: boolean;
104
+ value: string | null;
105
+ };
106
+ }
107
+
108
+ export interface FastifyCookieOptions {
109
+ secret?: string | string[] | Signer;
110
+ parseOptions?: CookieSerializeOptions;
111
+ }
112
+
113
+ declare const fastifyCookie: FastifyPluginCallback<NonNullable<FastifyCookieOptions>>;
114
+
115
+ export default fastifyCookie;
116
+ export { fastifyCookie };
package/plugin.js ADDED
@@ -0,0 +1,110 @@
1
+ 'use strict'
2
+
3
+ const fp = require('fastify-plugin')
4
+ const cookie = require('./cookie')
5
+
6
+ const signerFactory = require('./signer')
7
+
8
+ function fastifyCookieSetCookie (reply, name, value, options, signer) {
9
+ const opts = Object.assign({}, options)
10
+ if (opts.expires && Number.isInteger(opts.expires)) {
11
+ opts.expires = new Date(opts.expires)
12
+ }
13
+
14
+ if (opts.signed) {
15
+ value = signer.sign(value)
16
+ }
17
+
18
+ const serialized = cookie.serialize(name, value, opts)
19
+ let setCookie = reply.getHeader('Set-Cookie')
20
+ if (!setCookie) {
21
+ reply.header('Set-Cookie', serialized)
22
+ return reply
23
+ }
24
+
25
+ if (typeof setCookie === 'string') {
26
+ setCookie = [setCookie]
27
+ }
28
+
29
+ setCookie.push(serialized)
30
+ reply.removeHeader('Set-Cookie')
31
+ reply.header('Set-Cookie', setCookie)
32
+ return reply
33
+ }
34
+
35
+ function fastifyCookieClearCookie (reply, name, options) {
36
+ const opts = Object.assign({ path: '/' }, options || { }, {
37
+ expires: new Date(1),
38
+ signed: undefined,
39
+ maxAge: undefined
40
+ })
41
+ return fastifyCookieSetCookie(reply, name, '', opts)
42
+ }
43
+
44
+ function onReqHandlerWrapper (fastify) {
45
+ return function fastifyCookieOnReqHandler (fastifyReq, fastifyRes, done) {
46
+ fastifyReq.cookies = {} // New container per request. Issue #53
47
+ const cookieHeader = fastifyReq.raw.headers.cookie
48
+ if (cookieHeader) {
49
+ fastifyReq.cookies = fastify.parseCookie(cookieHeader)
50
+ }
51
+ done()
52
+ }
53
+ }
54
+
55
+ function plugin (fastify, options, next) {
56
+ const secret = options.secret || ''
57
+ const enableRotation = Array.isArray(secret)
58
+ const signer = typeof secret === 'string' || enableRotation ? signerFactory(secret) : secret
59
+
60
+ fastify.decorate('parseCookie', parseCookie)
61
+ fastify.decorate('unsignCookie', unsignCookie)
62
+
63
+ fastify.decorateRequest('cookies', null)
64
+ fastify.decorateRequest('unsignCookie', unsignCookie)
65
+ fastify.decorateReply('setCookie', setCookie)
66
+ fastify.decorateReply('cookie', setCookie)
67
+ fastify.decorateReply('clearCookie', clearCookie)
68
+ fastify.decorateReply('unsignCookie', unsignCookie)
69
+
70
+ fastify.addHook('onRequest', onReqHandlerWrapper(fastify))
71
+
72
+ next()
73
+
74
+ // ***************************
75
+ function parseCookie (cookieHeader) {
76
+ return cookie.parse(cookieHeader, options.parseOptions)
77
+ }
78
+
79
+ function unsignCookie (value) {
80
+ return signer.unsign(value)
81
+ }
82
+
83
+ function setCookie (name, value, cookieOptions) {
84
+ const opts = Object.assign({}, options.parseOptions, cookieOptions)
85
+ return fastifyCookieSetCookie(this, name, value, opts, signer)
86
+ }
87
+
88
+ function clearCookie (name, options) {
89
+ return fastifyCookieClearCookie(this, name, options)
90
+ }
91
+ }
92
+
93
+ const fastifyCookie = fp(plugin, {
94
+ fastify: '>=3',
95
+ name: 'fastify-cookie'
96
+ })
97
+
98
+ /**
99
+ * These export configurations enable JS and TS developers
100
+ * to consume fastify-cookie in whatever way best suits their needs.
101
+ * Some examples of supported import syntax includes:
102
+ * - `const fastifyCookie = require('fastify-cookie')`
103
+ * - `const { fastifyCookie } = require('fastify-cookie')`
104
+ * - `import * as fastifyCookie from 'fastify-cookie'`
105
+ * - `import { fastifyCookie } from 'fastify-cookie'`
106
+ * - `import fastifyCookie from 'fastify-cookie'`
107
+ */
108
+ fastifyCookie.fastifyCookie = fastifyCookie
109
+ fastifyCookie.default = fastifyCookie
110
+ module.exports = fastifyCookie
package/signer.js ADDED
@@ -0,0 +1,32 @@
1
+ 'use strict'
2
+
3
+ const cookieSignature = require('cookie-signature')
4
+
5
+ module.exports = function (secret) {
6
+ const secrets = Array.isArray(secret) ? secret : [secret]
7
+ const [signingKey] = secrets
8
+
9
+ return {
10
+ sign (value) {
11
+ return cookieSignature.sign(value, signingKey)
12
+ },
13
+ unsign (signedValue) {
14
+ let valid = false
15
+ let renew = false
16
+ let value = null
17
+
18
+ for (const key of secrets) {
19
+ const result = cookieSignature.unsign(signedValue, key)
20
+
21
+ if (result !== false) {
22
+ valid = true
23
+ renew = key !== signingKey
24
+ value = result
25
+ break
26
+ }
27
+ }
28
+
29
+ return { valid, renew, value }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,205 @@
1
+ 'use strict'
2
+
3
+ const tap = require('tap')
4
+ const test = tap.test
5
+
6
+ const cookie = require('../cookie')
7
+
8
+ test('parse: argument validation', (t) => {
9
+ t.plan(2)
10
+ t.throws(cookie.parse.bind(), /argument str must be a string/)
11
+ t.throws(cookie.parse.bind(null, 42), /argument str must be a string/)
12
+ t.end()
13
+ })
14
+
15
+ test('parse: basic', (t) => {
16
+ t.plan(2)
17
+ t.same(cookie.parse('foo=bar'), { foo: 'bar' })
18
+ t.same(cookie.parse('foo=123'), { foo: '123' })
19
+ t.end()
20
+ })
21
+
22
+ test('parse: ignore spaces', (t) => {
23
+ t.plan(1)
24
+ t.same(cookie.parse('FOO = bar; baz = raz'), { FOO: 'bar', baz: 'raz' })
25
+ t.end()
26
+ })
27
+
28
+ test('parse: escaping', (t) => {
29
+ t.plan(2)
30
+ t.same(cookie.parse('foo="bar=123456789&name=Magic+Mouse"'), { foo: 'bar=123456789&name=Magic+Mouse' })
31
+ t.same(cookie.parse('email=%20%22%2c%3b%2f'), { email: ' ",;/' })
32
+ t.end()
33
+ })
34
+
35
+ test('parse: ignore escaping error and return original value', (t) => {
36
+ t.plan(1)
37
+ t.same(cookie.parse('foo=%1;bar=bar'), { foo: '%1', bar: 'bar' })
38
+ t.end()
39
+ })
40
+
41
+ test('parse: ignore non values', (t) => {
42
+ t.plan(1)
43
+ t.same(cookie.parse('foo=%1;bar=bar;HttpOnly;Secure'),
44
+ { foo: '%1', bar: 'bar' })
45
+ t.end()
46
+ })
47
+
48
+ test('parse: unencoded', (t) => {
49
+ t.plan(2)
50
+ t.same(cookie.parse('foo="bar=123456789&name=Magic+Mouse"', {
51
+ decode: function (v) { return v }
52
+ }), { foo: 'bar=123456789&name=Magic+Mouse' })
53
+
54
+ t.same(cookie.parse('email=%20%22%2c%3b%2f', {
55
+ decode: function (v) { return v }
56
+ }), { email: '%20%22%2c%3b%2f' })
57
+ t.end()
58
+ })
59
+
60
+ test('parse: dates', (t) => {
61
+ t.plan(1)
62
+ t.same(cookie.parse('priority=true; expires=Wed, 29 Jan 2014 17:43:25 GMT; Path=/', {
63
+ decode: function (v) { return v }
64
+ }), { priority: 'true', Path: '/', expires: 'Wed, 29 Jan 2014 17:43:25 GMT' })
65
+ t.end()
66
+ })
67
+
68
+ test('parse: missing value', (t) => {
69
+ t.plan(1)
70
+ t.same(cookie.parse('foo; bar=1; fizz= ; buzz=2', {
71
+ decode: function (v) { return v }
72
+ }), { bar: '1', fizz: '', buzz: '2' })
73
+ t.end()
74
+ })
75
+
76
+ test('parse: assign only once', (t) => {
77
+ t.plan(3)
78
+ t.same(cookie.parse('foo=%1;bar=bar;foo=boo'), { foo: '%1', bar: 'bar' })
79
+ t.same(cookie.parse('foo=false;bar=bar;foo=true'), { foo: 'false', bar: 'bar' })
80
+ t.same(cookie.parse('foo=;bar=bar;foo=boo'), { foo: '', bar: 'bar' })
81
+ t.end()
82
+ })
83
+
84
+ test('serializer: basic', (t) => {
85
+ t.plan(6)
86
+ t.same(cookie.serialize('foo', 'bar'), 'foo=bar')
87
+ t.same(cookie.serialize('foo', 'bar baz'), 'foo=bar%20baz')
88
+ t.same(cookie.serialize('foo', ''), 'foo=')
89
+ t.throws(cookie.serialize.bind(cookie, 'foo\n', 'bar'), /argument name is invalid/)
90
+ t.throws(cookie.serialize.bind(cookie, 'foo\u280a', 'bar'), /argument name is invalid/)
91
+ t.throws(cookie.serialize.bind(cookie, 'foo', 'bar', { encode: 42 }), /option encode is invalid/)
92
+ t.end()
93
+ })
94
+
95
+ test('serializer: path', (t) => {
96
+ t.plan(2)
97
+ t.same(cookie.serialize('foo', 'bar', { path: '/' }), 'foo=bar; Path=/')
98
+ t.throws(cookie.serialize.bind(cookie, 'foo', 'bar', {
99
+ path: '/\n'
100
+ }), /option path is invalid/)
101
+ t.end()
102
+ })
103
+
104
+ test('serializer: secure', (t) => {
105
+ t.plan(2)
106
+ t.same(cookie.serialize('foo', 'bar', { secure: true }), 'foo=bar; Secure')
107
+ t.same(cookie.serialize('foo', 'bar', { secure: false }), 'foo=bar')
108
+ t.end()
109
+ })
110
+
111
+ test('serializer: domain', (t) => {
112
+ t.plan(2)
113
+ t.same(cookie.serialize('foo', 'bar', { domain: 'example.com' }), 'foo=bar; Domain=example.com')
114
+ t.throws(cookie.serialize.bind(cookie, 'foo', 'bar', {
115
+ domain: 'example.com\n'
116
+ }), /option domain is invalid/)
117
+ t.end()
118
+ })
119
+
120
+ test('serializer: httpOnly', (t) => {
121
+ t.plan(1)
122
+ t.same(cookie.serialize('foo', 'bar', { httpOnly: true }), 'foo=bar; HttpOnly')
123
+ t.end()
124
+ })
125
+
126
+ test('serializer: maxAge', (t) => {
127
+ t.plan(9)
128
+ t.throws(function () {
129
+ cookie.serialize('foo', 'bar', {
130
+ maxAge: 'buzz'
131
+ })
132
+ }, /option maxAge is invalid/)
133
+
134
+ t.throws(function () {
135
+ cookie.serialize('foo', 'bar', {
136
+ maxAge: Infinity
137
+ })
138
+ }, /option maxAge is invalid/)
139
+
140
+ t.same(cookie.serialize('foo', 'bar', { maxAge: 1000 }), 'foo=bar; Max-Age=1000')
141
+ t.same(cookie.serialize('foo', 'bar', { maxAge: '1000' }), 'foo=bar; Max-Age=1000')
142
+ t.same(cookie.serialize('foo', 'bar', { maxAge: 0 }), 'foo=bar; Max-Age=0')
143
+ t.same(cookie.serialize('foo', 'bar', { maxAge: '0' }), 'foo=bar; Max-Age=0')
144
+ t.same(cookie.serialize('foo', 'bar', { maxAge: null }), 'foo=bar')
145
+ t.same(cookie.serialize('foo', 'bar', { maxAge: undefined }), 'foo=bar')
146
+ t.same(cookie.serialize('foo', 'bar', { maxAge: 3.14 }), 'foo=bar; Max-Age=3')
147
+ t.end()
148
+ })
149
+
150
+ test('serializer: expires', (t) => {
151
+ t.plan(2)
152
+ t.same(cookie.serialize('foo', 'bar', {
153
+ expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900))
154
+ }), 'foo=bar; Expires=Sun, 24 Dec 2000 10:30:59 GMT')
155
+
156
+ t.throws(cookie.serialize.bind(cookie, 'foo', 'bar', {
157
+ expires: Date.now()
158
+ }), /option expires is invalid/)
159
+ t.end()
160
+ })
161
+
162
+ test('sameSite', (t) => {
163
+ t.plan(9)
164
+ t.same(cookie.serialize('foo', 'bar', { sameSite: true }), 'foo=bar; SameSite=Strict')
165
+ t.same(cookie.serialize('foo', 'bar', { sameSite: 'Strict' }), 'foo=bar; SameSite=Strict')
166
+ t.same(cookie.serialize('foo', 'bar', { sameSite: 'strict' }), 'foo=bar; SameSite=Strict')
167
+ t.same(cookie.serialize('foo', 'bar', { sameSite: 'Lax' }), 'foo=bar; SameSite=Lax')
168
+ t.same(cookie.serialize('foo', 'bar', { sameSite: 'lax' }), 'foo=bar; SameSite=Lax')
169
+ t.same(cookie.serialize('foo', 'bar', { sameSite: 'None' }), 'foo=bar; SameSite=None')
170
+ t.same(cookie.serialize('foo', 'bar', { sameSite: 'none' }), 'foo=bar; SameSite=None')
171
+ t.same(cookie.serialize('foo', 'bar', { sameSite: false }), 'foo=bar')
172
+
173
+ t.throws(cookie.serialize.bind(cookie, 'foo', 'bar', {
174
+ sameSite: 'foo'
175
+ }), /option sameSite is invalid/)
176
+ t.end()
177
+ })
178
+
179
+ test('escaping', (t) => {
180
+ t.plan(1)
181
+ t.same(cookie.serialize('cat', '+ '), 'cat=%2B%20')
182
+ t.end()
183
+ })
184
+
185
+ test('parse->serialize', (t) => {
186
+ t.plan(2)
187
+ t.same(cookie.parse(cookie.serialize('cat', 'foo=123&name=baz five')),
188
+ { cat: 'foo=123&name=baz five' })
189
+
190
+ t.same(cookie.parse(cookie.serialize('cat', ' ";/')),
191
+ { cat: ' ";/' })
192
+ t.end()
193
+ })
194
+
195
+ test('unencoded', (t) => {
196
+ t.plan(2)
197
+ t.same(cookie.serialize('cat', '+ ', {
198
+ encode: function (value) { return value }
199
+ }), 'cat=+ ')
200
+
201
+ t.throws(cookie.serialize.bind(cookie, 'cat', '+ \n', {
202
+ encode: function (value) { return value }
203
+ }), /argument val is invalid/)
204
+ t.end()
205
+ })