-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathrequests.js
197 lines (188 loc) · 5.51 KB
/
requests.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/*
* Copyright 2022-2025 Digital Bazaar, Inc.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
import {
agent,
defaultHeaders,
didKeyDriver,
postHeaders,
sanitizeHeaders
} from './constants.js';
import {constructOAuthHeader} from './oauth2.js';
import {decodeSecretKeySeed} from 'bnid';
import {Ed25519Signature2020} from '@digitalbazaar/ed25519-signature-2020';
import {httpClient} from '@digitalbazaar/http-client';
import {ZcapClient} from '@digitalbazaar/ezcap';
/**
* Makes an https request.
*
* @param {object} options - Options to use.
* @param {URL} options.url - A url.
* @param {object} [options.json] - JSON for the request.
* @param {object} [options.body] - A body for the request.
* @param {object} [options.headers] - Headers for the request.
* @param {string} options.method - The HTTP method for the request.
* @param {object} [options.oauth2] - OAuth2 credentialss.
* @param {object} [options.searchParams] - URL Queries for the request.
*
* @returns {object} The results from the request.
*/
export async function makeHttpsRequest({
url,
json,
body,
headers,
method,
oauth2,
searchParams
}) {
let result;
let error;
if(oauth2) {
headers.Authorization = await constructOAuthHeader({...oauth2});
}
try {
result = await httpClient(url, {
body, method, json, headers, agent, searchParams
});
} catch(e) {
error = _sanitizeError({error: e});
}
const {data, statusCode} = _getDataAndStatus({result, error});
// if a result is returned sanitize it
if(result) {
result = _sanitizeResponse({response: result, data});
}
return {result, error, data, statusCode};
}
export async function zcapRequest({
endpoint,
json,
zcap,
headers = defaultHeaders
}) {
let result;
let error;
let capability = zcap.capability;
// we are storing the zcaps stringified right now
if(typeof capability === 'string') {
capability = JSON.parse(capability);
}
try {
// assume that the keySeed is set in the test environment
const secretKeySeed = process.env[zcap.keySeed];
if(!secretKeySeed) {
console.warn(`ENV variable ${zcap.keySeed} is required.`);
}
const zcapClient = await _getZcapClient({secretKeySeed});
result = await zcapClient.write({
url: endpoint,
json,
headers: {
...postHeaders,
// passed in headers will overwrite postHeaders
...headers
},
capability
});
} catch(e) {
error = _sanitizeError({error: e});
}
const {data, statusCode} = _getDataAndStatus({result, error});
// if a result is returned sanitize it
if(result) {
result = _sanitizeResponse({response: result, data});
}
return {result, error, data, statusCode};
}
async function _getZcapClient({secretKeySeed}) {
const seed = await decodeSecretKeySeed({secretKeySeed});
const didKey = await didKeyDriver.generate({seed});
const {didDocument: {capabilityInvocation}} = didKey;
return new ZcapClient({
SuiteClass: Ed25519Signature2020,
invocationSigner: didKey.keyPairs.get(capabilityInvocation[0]).signer(),
agent
});
}
function _getDataAndStatus({result = {}, error = {}}) {
let data = result.data || error.data;
// FIXME remove this once data returned from the issuers
// are finalized.
if(data && data.verifiableCredential) {
data = data.verifiableCredential;
}
const statusCode = result.status || error.status;
return {data, statusCode};
}
function _sanitizeError({error}) {
if(error.response) {
error.response = _sanitizeResponse({
response: error.response,
data: error.data
});
}
if(error.request) {
error.request = _sanitizeRequest({request: error.request});
}
return error;
}
function _sanitizeResponse({response, data}) {
const newResponse = new global.Response(JSON.stringify(data), {
headers: _sanitizeHeaders({httpMessage: response}),
status: response.status,
statusText: response.statusText
});
if(data) {
// transfer the already parsed data to the newResponse
// we are overwriting the data getter in Response here
Object.defineProperty(newResponse, 'data', {value: data});
}
return newResponse;
}
function _sanitizeRequest({request}) {
// get the url and the remaining properties from the request
const {url, ...props} = request;
// create an options object to pass to the new request
const options = {};
// do not copy these properties from the request
const skipKeys = new Set(['body', 'headers']);
for(const key in props) {
if(skipKeys.has(key)) {
continue;
}
options[key] = request[key];
}
return new global.Request(url, {
...options,
headers: _sanitizeHeaders({httpMessage: request})
});
}
/**
* Takes in either a response or request & sanitizes the headers.
*
* @private
*
* @param {object} options - Options to use.
* @param {global.Response|global.Request} options.httpMessage - A http message.
* @param {Array<string>} [options.headers=sanitizeHeaders] - A list of headers
* to sanitize from the http message.
*
* @returns {global.Headers} - Returns http headers.
*/
function _sanitizeHeaders({httpMessage, headers = sanitizeHeaders}) {
if(!httpMessage) {
return new global.Headers();
}
const {headers: messageHeaders} = httpMessage;
// Clone the response headers
const newHeaders = new global.Headers(messageHeaders);
for(const header of headers) {
// sanitize the headers to prevent
// authn tokens / information potentially in logs
newHeaders.set(header, 'sanitized to prevent exposure of secrets');
}
return newHeaders;
}