grannt 5.4.23

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/lib/config.js ADDED
@@ -0,0 +1,220 @@
1
+
2
+ var crypto = require('crypto')
3
+
4
+ var oauth = require('../config/oauth.json')
5
+ var reserved = require('../config/reserved.json')
6
+ var profile = require('../config/profile.json')
7
+
8
+
9
+ var compose = (...fns) =>
10
+ fns.reduce((x, y) => (...args) => y(x(...args)))
11
+
12
+ var dcopy = (obj) =>
13
+ JSON.parse(JSON.stringify(obj))
14
+
15
+ var merge = (...args) =>
16
+ Object.assign(...args.filter(Boolean).map(dcopy))
17
+
18
+ var filter = (obj) => Object.keys(obj)
19
+ .filter((key) =>
20
+ // empty string
21
+ obj[key] !== '' && (
22
+ // provider name
23
+ key === obj.name ||
24
+ // reserved key
25
+ reserved.includes(key)
26
+ ))
27
+ .reduce((all, key) => (all[key] = obj[key], all), {})
28
+
29
+ var format = {
30
+
31
+ oauth: ({oauth}) =>
32
+ parseInt(oauth) || undefined
33
+ ,
34
+
35
+ key: ({oauth, key, consumer_key, client_id}) =>
36
+ oauth === 1
37
+ ? key || consumer_key
38
+
39
+ : oauth === 2
40
+ ? key || client_id
41
+
42
+ : undefined
43
+ ,
44
+
45
+ secret: ({oauth, secret, consumer_secret, client_secret}) =>
46
+ oauth === 1
47
+ ? secret || consumer_secret
48
+
49
+ : oauth === 2
50
+ ? secret || client_secret
51
+
52
+ : undefined
53
+ ,
54
+
55
+ scope: ({scope, scope_delimiter = ','}) =>
56
+ scope instanceof Array
57
+ ? scope.filter(Boolean).join(scope_delimiter) || undefined
58
+
59
+ : typeof scope === 'object'
60
+ ? JSON.stringify(scope)
61
+
62
+ : scope || undefined
63
+ ,
64
+
65
+ state: ({state}) =>
66
+ state || undefined
67
+ ,
68
+
69
+ nonce: ({nonce}) =>
70
+ nonce || undefined
71
+ ,
72
+
73
+ redirect_uri: ({redirect_uri, origin, prefix, protocol, host, name}) =>
74
+ redirect_uri
75
+ ? redirect_uri
76
+
77
+ : origin
78
+ ? `${origin}${prefix}/${name}/callback`
79
+
80
+ : protocol && host
81
+ ? `${protocol}://${host}${prefix}/${name}/callback`
82
+
83
+ : undefined
84
+ ,
85
+
86
+ custom_params: (provider) => {
87
+ var params = provider.custom_params || {}
88
+
89
+ // remove falsy
90
+ params = Object.keys(params)
91
+ .filter((key) => params[key])
92
+ .reduce((all, key) => (all[key] = params[key], all), {})
93
+
94
+ return Object.keys(params).length ? params : undefined
95
+ },
96
+
97
+ overrides: (provider) => {
98
+ var overrides = provider.overrides || {}
99
+ delete provider.overrides
100
+
101
+ // remove nested
102
+ Object.keys(overrides).forEach((name) => {
103
+ overrides[name] = Object.keys(overrides[name])
104
+ .filter((key) => key !== 'overrides')
105
+ .reduce((all, key) => (all[key] = overrides[name][key], all), {})
106
+ })
107
+
108
+ overrides = Object.keys(overrides)
109
+ .reduce((all, key) => (all[key] = init(provider, overrides[key]), all), {})
110
+
111
+ return Object.keys(overrides).length ? overrides : undefined
112
+ },
113
+
114
+ }
115
+
116
+ var state = (provider, key = 'state', value = provider[key]) =>
117
+ value === true || value === 'true'
118
+ ? crypto.randomBytes(20).toString('hex')
119
+
120
+ : value === 'false'
121
+ ? undefined
122
+
123
+ : /string|number/.test(typeof value)
124
+ ? value.toString()
125
+
126
+ : undefined
127
+
128
+ var pkce = (code_verifier = crypto.randomBytes(40).toString('hex')) => ({
129
+ code_verifier,
130
+ code_challenge: crypto.createHash('sha256')
131
+ .update(code_verifier).digest().toString('base64')
132
+ .replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
133
+ })
134
+
135
+ var transform = (provider) => {
136
+
137
+ Object.keys(format)
138
+ .forEach((key) => provider[key] = format[key](provider))
139
+
140
+ // filter undefined
141
+ return dcopy(provider)
142
+ }
143
+
144
+ var init = compose(merge, filter, transform)
145
+
146
+ var compat = (config) =>
147
+ config.fitbit2 ? (Object.assign({}, config, {fitbit2: Object.assign({}, oauth.fitbit, profile.fitbit, config.fitbit2)})) :
148
+ config.linkedin2 ? (Object.assign({}, config, {linkedin2: Object.assign({}, oauth.linkedin, profile.linkedin, config.linkedin2)})) :
149
+ config.zeit ? (Object.assign({}, config, {zeit: Object.assign({}, oauth.vercel, profile.vercel, config.zeit)})) :
150
+ config
151
+
152
+ var defaults = ({path, prefix = '/connect', ...rest} = {}) => ({
153
+ ...rest,
154
+ prefix: path ? `${path}${prefix}` : prefix
155
+ })
156
+
157
+ // init all configured providers
158
+ var ctor = ((_defaults) => (config = {}, defaults = _defaults(config.defaults)) =>
159
+ Object.keys(compat(config))
160
+ .filter((name) => !/defaults/.test(name))
161
+ .reduce((all, name) => (
162
+ all[name] = init(oauth[name], profile[name], defaults, config[name], {name, [name]: true}),
163
+ all
164
+ ), {defaults})
165
+ )(defaults)
166
+
167
+ // get provider on connect
168
+ var provider = (config, session, _state = {}) => {
169
+ var name = session.provider
170
+ var provider = config[name]
171
+
172
+ if (!provider) {
173
+ if ((config.defaults || {}).dynamic !== true) {
174
+ return {}
175
+ }
176
+ provider = init(oauth[name], profile[name], config.defaults, {name, [name]: true})
177
+ }
178
+
179
+ if (session.override && provider.overrides) {
180
+ var override = provider.overrides[session.override]
181
+ if (override) {
182
+ provider = override
183
+ }
184
+ }
185
+
186
+ if ((session.dynamic && provider.dynamic) || _state.dynamic) {
187
+ var dynamic = Object.assign(
188
+ {},
189
+ _state.dynamic,
190
+ provider.dynamic === true
191
+ ? session.dynamic
192
+ : Object.keys(session.dynamic || {})
193
+ .filter((key) => provider.dynamic.includes(key))
194
+ .reduce((all, key) => (all[key] = session.dynamic[key], all), {})
195
+ )
196
+ provider = init(provider, dynamic)
197
+ }
198
+
199
+ if (provider.state) {
200
+ provider = dcopy(provider)
201
+ provider.state = state(provider)
202
+ }
203
+ if (provider.nonce) {
204
+ provider = dcopy(provider)
205
+ provider.nonce = state(provider, 'nonce')
206
+ }
207
+ if (provider.pkce) {
208
+ provider = dcopy(provider)
209
+ ;({
210
+ code_verifier: provider.code_verifier,
211
+ code_challenge: provider.code_challenge
212
+ } = pkce())
213
+ }
214
+
215
+ return provider
216
+ }
217
+
218
+ module.exports = Object.assign(ctor, {
219
+ compose, dcopy, merge, filter, format, state, pkce, transform, init, defaults, compat, provider
220
+ })
@@ -0,0 +1,145 @@
1
+
2
+ var qs = require('qs')
3
+ var request = require('../client')
4
+
5
+
6
+ exports.request = ({request:client}) => async ({provider, input}) => {
7
+ var options = {
8
+ method: 'POST',
9
+ url: provider.request_url,
10
+ oauth: {
11
+ callback: provider.redirect_uri,
12
+ consumer_key: provider.key,
13
+ consumer_secret: provider.secret
14
+ }
15
+ }
16
+ if (provider.private_key) {
17
+ options.oauth.signature_method = 'RSA-SHA1'
18
+ options.oauth.private_key = provider.private_key
19
+ delete options.oauth.consumer_secret
20
+ }
21
+ if (provider.etsy || provider.linkedin) {
22
+ options.qs = {scope: provider.scope}
23
+ }
24
+ if (provider.getpocket) {
25
+ delete options.oauth
26
+ options.headers = {
27
+ 'x-accept': 'application/x-www-form-urlencoded'
28
+ }
29
+ options.form = {
30
+ consumer_key: provider.key,
31
+ redirect_uri: provider.redirect_uri,
32
+ state: provider.state
33
+ }
34
+ }
35
+ if (provider.freshbooks) {
36
+ options.oauth.signature_method = 'PLAINTEXT'
37
+ }
38
+ if (provider.twitter) {
39
+ if (provider.scope) {
40
+ options.qs = {x_auth_access_type: [].concat(provider.scope).join()}
41
+ }
42
+ if (provider.custom_params) {
43
+ options.qs = {x_auth_access_type: provider.custom_params.x_auth_access_type}
44
+ }
45
+ }
46
+ if (provider.subdomain) {
47
+ options.url = options.url.replace('[subdomain]', provider.subdomain)
48
+ }
49
+ try {
50
+ var {body:output} = await request({...client, ...options})
51
+ if (provider.sellsy) {
52
+ output = qs.parse(output)
53
+ }
54
+ }
55
+ catch (err) {
56
+ var output = {error: err.body || err.message}
57
+ }
58
+ return {provider, input, output}
59
+ }
60
+
61
+ exports.authorize = async ({provider, input, output}) => {
62
+ if (!output.oauth_token && !output.code) {
63
+ output = Object.keys(output).length
64
+ ? output : {error: 'Grant: OAuth1 missing oauth_token parameter'}
65
+ return {provider, input, output}
66
+ }
67
+ var url = provider.authorize_url
68
+ var params = {
69
+ oauth_token: output.oauth_token
70
+ }
71
+ if (provider.custom_params) {
72
+ for (var key in provider.custom_params) {
73
+ params[key] = provider.custom_params[key]
74
+ }
75
+ }
76
+ if (provider.flickr && provider.scope) {
77
+ params.perms = provider.scope
78
+ }
79
+ if (provider.getpocket) {
80
+ params = {
81
+ request_token: output.code,
82
+ redirect_uri: provider.redirect_uri
83
+ }
84
+ }
85
+ if (provider.ravelry || provider.trello) {
86
+ params.scope = provider.scope
87
+ }
88
+ if (provider.tripit) {
89
+ params.oauth_callback = provider.redirect_uri
90
+ }
91
+ if (provider.subdomain) {
92
+ url = url.replace('[subdomain]', provider.subdomain)
93
+ }
94
+ return {provider, input, output: `${url}?${qs.stringify(params)}`}
95
+ }
96
+
97
+ exports.access = ({request:client}) => async ({provider, input, input:{session, query}}) => {
98
+ if (!query.oauth_token && !session.request.code) {
99
+ var output = Object.keys(query).length
100
+ ? query : {error: 'Grant: OAuth1 missing oauth_token parameter'}
101
+ return {provider, input, output}
102
+ }
103
+ var options = {
104
+ method: 'POST',
105
+ url: provider.access_url,
106
+ oauth: {
107
+ consumer_key: provider.key,
108
+ consumer_secret: provider.secret,
109
+ token: query.oauth_token,
110
+ token_secret: session.request.oauth_token_secret,
111
+ verifier: query.oauth_verifier
112
+ }
113
+ }
114
+ if (provider.private_key) {
115
+ options.oauth.signature_method = 'RSA-SHA1'
116
+ options.oauth.private_key = provider.private_key
117
+ delete options.oauth.consumer_secret
118
+ }
119
+ if (provider.freshbooks) {
120
+ options.oauth.signature_method = 'PLAINTEXT'
121
+ }
122
+ if (provider.getpocket) {
123
+ delete options.oauth
124
+ options.headers = {
125
+ 'x-accept': 'application/x-www-form-urlencoded'
126
+ }
127
+ options.form = {
128
+ consumer_key: provider.key,
129
+ code: session.request.code
130
+ }
131
+ }
132
+ if (provider.goodreads || provider.tripit) {
133
+ delete options.oauth.verifier
134
+ }
135
+ if (provider.subdomain) {
136
+ options.url = options.url.replace('[subdomain]', provider.subdomain)
137
+ }
138
+ try {
139
+ var {body:output} = await request({...client, ...options})
140
+ }
141
+ catch (err) {
142
+ var output = {error: err.body || err.message}
143
+ }
144
+ return {provider, input, output}
145
+ }
@@ -0,0 +1,220 @@
1
+
2
+ var crypto = require('crypto')
3
+ var qs = require('qs')
4
+ var request = require('../client')
5
+
6
+
7
+ exports.authorize = async ({provider, input}) => {
8
+ var url = provider.authorize_url
9
+ var params = {
10
+ client_id: provider.key,
11
+ response_type: 'code',
12
+ redirect_uri: provider.redirect_uri,
13
+ scope: provider.scope,
14
+ state: provider.state,
15
+ nonce: provider.nonce
16
+ }
17
+ if (provider.pkce) {
18
+ params.code_challenge_method = 'S256'
19
+ params.code_challenge = provider.code_challenge
20
+ }
21
+ if (provider.custom_params) {
22
+ for (var key in provider.custom_params) {
23
+ params[key] = provider.custom_params[key]
24
+ }
25
+ }
26
+ if (provider.basecamp) {
27
+ params.type = 'web_server'
28
+ }
29
+ if (provider.freelancer && params.scope) {
30
+ params.advanced_scopes = params.scope
31
+ delete params.scope
32
+ }
33
+ if (provider.instagram && /^\d+$/.test(provider.key)) {
34
+ params.app_id = params.client_id
35
+ delete params.client_id
36
+ params.scope = (params.scope || '').replace(/ /g, ',') || undefined
37
+ }
38
+ if (provider.optimizely && params.scope) {
39
+ params.scopes = params.scope
40
+ delete params.scope
41
+ }
42
+ if (provider.tiktok) {
43
+ params.client_key = params.client_id
44
+ delete params.client_id
45
+ }
46
+ if (provider.visualstudio) {
47
+ params.response_type = 'Assertion'
48
+ }
49
+ if (provider.wechat) {
50
+ params.appid = params.client_id
51
+ delete params.client_id
52
+ }
53
+ if (provider.subdomain) {
54
+ url = url.replace('[subdomain]', provider.subdomain)
55
+ }
56
+ var querystring = qs.stringify(params)
57
+ if (provider.unsplash && params.scope) {
58
+ var scope = params.scope
59
+ delete params.scope
60
+ querystring = qs.stringify(params) + '&scope=' + scope
61
+ }
62
+ return {provider, input, output: `${url}?${querystring}`}
63
+ }
64
+
65
+ exports.access = ({request:client}) => async ({provider, input, input:{query, body, session}}) => {
66
+ query = Object.keys(query).length ? query : body
67
+ if (!query.code) {
68
+ var output = Object.keys(query).length
69
+ ? query : {error: 'Grant: OAuth2 missing code parameter'}
70
+ return {provider, input, output}
71
+ }
72
+ else if (session.state && (query.state !== session.state)) {
73
+ var output = {error: 'Grant: OAuth2 state mismatch'}
74
+ return {provider, input, output}
75
+ }
76
+ var options = {
77
+ method: 'POST',
78
+ url: provider.access_url,
79
+ form: {
80
+ grant_type: 'authorization_code',
81
+ code: query.code,
82
+ client_id: provider.key,
83
+ client_secret: provider.secret,
84
+ redirect_uri: provider.redirect_uri
85
+ }
86
+ }
87
+ if (provider.pkce) {
88
+ options.form.code_verifier = session.code_verifier
89
+ }
90
+ if (provider.basecamp) {
91
+ options.form.type = 'web_server'
92
+ }
93
+ if (provider.concur) {
94
+ delete options.form
95
+ options.qs = {
96
+ code: query.code,
97
+ client_id: provider.key,
98
+ client_secret: provider.secret
99
+ }
100
+ }
101
+ if (/autodesk|ebay|fitbit|homeaway|hootsuite|notion|reddit|trustpilot/.test(provider.name)
102
+ || provider.token_endpoint_auth_method === 'client_secret_basic'
103
+ ) {
104
+ delete options.form.client_id
105
+ delete options.form.client_secret
106
+ options.auth = {user: provider.key, pass: provider.secret}
107
+ }
108
+ if (/twitter/.test(provider.name)) {
109
+ options.form.client_id = provider.key
110
+ delete options.form.client_secret
111
+ options.auth = {user: provider.key, pass: provider.secret}
112
+ }
113
+ if (provider.token_endpoint_auth_method === 'private_key_jwt') {
114
+ var jwt = ({kid, x5t, secret}) => ({
115
+ header: {
116
+ typ: 'JWT',
117
+ alg: provider.token_endpoint_auth_signing_alg || 'RS256',
118
+ kid,
119
+ x5t
120
+ },
121
+ payload: {
122
+ iss: provider.key,
123
+ sub: provider.key,
124
+ aud: provider.access_url,
125
+ jti: crypto.randomBytes(20).toString('hex'),
126
+ exp: Math.round(Date.now() / 1000) + 300,
127
+ iat: Math.round(Date.now() / 1000) - 120,
128
+ nbf: Math.round(Date.now() / 1000) - 120
129
+ },
130
+ secret
131
+ })
132
+
133
+ var assertion = (() => {
134
+ var oidc = require('../oidc')
135
+ var {public_key, private_key} = provider
136
+ return oidc.sign(jwt({
137
+ kid: private_key.kty ? oidc.kid(private_key) : undefined,
138
+ x5t: public_key ? public_key.kty ? public_key.x5t : oidc.x5t(public_key) : undefined,
139
+ secret: private_key.kty ? oidc.pem(private_key) : private_key,
140
+ }))
141
+ })()
142
+
143
+ options.form.client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
144
+ options.form.client_assertion = assertion
145
+ delete options.form.client_id
146
+ delete options.form.client_secret
147
+ }
148
+ if (provider.instagram && /^\d+$/.test(provider.key)) {
149
+ options.form.app_id = options.form.client_id
150
+ delete options.form.client_id
151
+ options.form.app_secret = options.form.client_secret
152
+ delete options.form.client_secret
153
+ }
154
+ if (provider.notion) {
155
+ options.json = options.form
156
+ delete options.form
157
+ }
158
+ if (provider.tiktok) {
159
+ options.form.client_key = options.form.client_id
160
+ delete options.form.client_id
161
+ }
162
+ if (provider.qq) {
163
+ options.method = 'GET'
164
+ options.qs = options.form
165
+ delete options.form
166
+ }
167
+ if (provider.untappd) {
168
+ options.method = 'GET'
169
+ options.qs = options.form
170
+ delete options.qs.grant_type
171
+ options.qs.response_type = 'code'
172
+ delete options.form
173
+ }
174
+ if (provider.wechat) {
175
+ options.method = 'GET'
176
+ options.qs = options.form
177
+ delete options.form
178
+ options.qs.appid = options.qs.client_id
179
+ options.qs.secret = options.qs.client_secret
180
+ delete options.qs.client_id
181
+ delete options.qs.client_secret
182
+ }
183
+ if (provider.smartsheet) {
184
+ delete options.form.client_secret
185
+ var hash = crypto.createHash('sha256')
186
+ hash.update(provider.secret + '|' + query.code)
187
+ options.form.hash = hash.digest('hex')
188
+ }
189
+ if (provider.surveymonkey) {
190
+ options.qs = {api_key: provider.custom_params.api_key}
191
+ }
192
+ if (provider.visualstudio) {
193
+ options.form = {
194
+ client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
195
+ client_assertion: provider.secret,
196
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
197
+ assertion: query.code,
198
+ redirect_uri: provider.redirect_uri
199
+ }
200
+ }
201
+ if (provider.withings) {
202
+ options.form.action = 'requesttoken'
203
+ }
204
+ if (provider.subdomain) {
205
+ options.url = options.url.replace('[subdomain]', provider.subdomain)
206
+ }
207
+ try {
208
+ var {body:output} = await request({...client, ...options})
209
+ if (provider.intuit) {
210
+ output.realmId = query.realmId
211
+ }
212
+ if (provider.withings) {
213
+ output = output.body
214
+ }
215
+ }
216
+ catch (err) {
217
+ var output = {error: err.body || err.message}
218
+ }
219
+ return {provider, input, output}
220
+ }
package/lib/grant.js ADDED
@@ -0,0 +1,31 @@
1
+
2
+ var {compose} = require('./util')
3
+ var {defaults, connect, callback} = require('./request')
4
+ var {data, transport} = require('./response')
5
+ var _config = require('./config')
6
+
7
+
8
+ module.exports = ({config, request, state, extend}) => {
9
+ config = _config(config)
10
+
11
+ if (!extend) {
12
+ extend = [require('./profile')]
13
+ }
14
+
15
+ var pipe = compose(
16
+ defaults(config),
17
+
18
+ ({provider, input, input:{params}}) => params.override !== 'callback'
19
+ ? connect({request})({provider, input})
20
+ : compose(
21
+ callback({request})({provider, input}),
22
+ data,
23
+ extend ? compose(...extend.map((fn) => fn({request, state}))) : (args) => ({...args})
24
+ )({provider, input}),
25
+
26
+ transport,
27
+ )
28
+
29
+ pipe.config = config
30
+ return pipe
31
+ }
@@ -0,0 +1,89 @@
1
+
2
+ var qs = require('qs')
3
+ var Grant = require('../grant')
4
+ var Session = require('../session')
5
+
6
+
7
+ module.exports = function (args = {}) {
8
+ var grant = Grant(args.config ? args : {config: args})
9
+ app.config = grant.config
10
+
11
+ var regex = new RegExp([
12
+ '^',
13
+ app.config.defaults.prefix,
14
+ /(?:\/([^\/\?]+?))/.source, // /:provider
15
+ /(?:\/([^\/\?]+?))?/.source, // /:override?
16
+ /(?:\/$|\/?\?+.*)?$/.source, // querystring
17
+ ].join(''), 'i')
18
+
19
+ var store = Session(args.session)
20
+
21
+ async function app (event, state) {
22
+ var req = params(event)
23
+ var session = store(req)
24
+ var match = regex.exec(req.path)
25
+ if (!match) {
26
+ return {session}
27
+ }
28
+
29
+ var {location, session:sess, state} = await grant({
30
+ method: req.method,
31
+ params: {provider: match[1], override: match[2]},
32
+ query: req.query,
33
+ body: req.body,
34
+ state,
35
+ session: (await session.get()).grant
36
+ })
37
+
38
+ await session.set({grant: sess})
39
+
40
+ return location
41
+ ? {session, redirect: redirect(event, location, session)}
42
+ : {session, response: state.response || sess.response}
43
+ }
44
+
45
+ return app
46
+ }
47
+
48
+ var path = ({version, path, rawPath, requestContext:ctx} = event) =>
49
+ version === '2.0' ? rawPath :
50
+ version === '1.0' ? path : ctx.path
51
+
52
+ var body = ({body, isBase64Encoded} = event) =>
53
+ body
54
+ ? isBase64Encoded ? Buffer.from(body, 'base64').toString()
55
+ : body : {}
56
+
57
+ var params = (event) =>
58
+ !event.version || event.version === '1.0' ?
59
+ {
60
+ method: event.httpMethod,
61
+ path: path(event),
62
+ query: qs.parse(event.queryStringParameters),
63
+ headers: event.headers,
64
+ body: qs.parse(body(event)),
65
+ }
66
+ : event.version === '2.0' ?
67
+ {
68
+ method: event.requestContext.http.method,
69
+ path: path(event),
70
+ query: qs.parse(event.rawQueryString),
71
+ headers: {...event.headers, Cookie: (event.cookies || []).join('; ')},
72
+ body: qs.parse(body(event)),
73
+ }
74
+ : {}
75
+
76
+ var redirect = (event, location, session) =>
77
+ !event.version || event.version === '1.0' ?
78
+ {
79
+ statusCode: 302,
80
+ headers: {location},
81
+ multiValueHeaders: {'set-cookie': session.headers['set-cookie']}
82
+ }
83
+ : event.version === '2.0' ?
84
+ {
85
+ statusCode: 302,
86
+ headers: {location},
87
+ cookies: session.headers['set-cookie']
88
+ }
89
+ : {}