@deployport/api-services-corelib 0.1.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/configurator/index.d.ts +2 -0
- package/configurator/index.js +12 -0
- package/configurator/index.ts +13 -0
- package/configurator/signer.d.ts +22 -0
- package/configurator/signer.js +256 -0
- package/configurator/signer.ts +361 -0
- package/package.json +24 -0
- package/specular.d.ts +23 -0
- package/specular.js +36 -0
- package/specular.ts +70 -0
- package/specular.yaml +6 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ConfigureSignatureV1 } from "./signer.js";
|
|
2
|
+
import { Runtime } from "@deployport/specular-runtime";
|
|
3
|
+
export function Configure(c) {
|
|
4
|
+
const requestConfigurators = c?.requestConfigurators ?? [];
|
|
5
|
+
requestConfigurators.push(async (sub) => {
|
|
6
|
+
console.warn("configuring request from config", sub.request);
|
|
7
|
+
await ConfigureSignatureV1(sub, c);
|
|
8
|
+
});
|
|
9
|
+
return Runtime.MergeClientConfig(c, {
|
|
10
|
+
requestConfigurators,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Config, ConfigureSignatureV1 } from "./signer.js";
|
|
2
|
+
import { Runtime } from "@deployport/specular-runtime";
|
|
3
|
+
|
|
4
|
+
export function Configure(c?: Runtime.ClientConfig): Runtime.ClientConfig {
|
|
5
|
+
const requestConfigurators = c?.requestConfigurators ?? [];
|
|
6
|
+
requestConfigurators.push(async (sub) => {
|
|
7
|
+
console.warn("configuring request from config", sub.request);
|
|
8
|
+
await ConfigureSignatureV1(sub, c as Config)
|
|
9
|
+
});
|
|
10
|
+
return Runtime.MergeClientConfig(c, {
|
|
11
|
+
requestConfigurators,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Runtime } from "@deployport/specular-runtime";
|
|
2
|
+
type Credentials = {
|
|
3
|
+
keyID: string;
|
|
4
|
+
secret: string;
|
|
5
|
+
};
|
|
6
|
+
export type Config = {
|
|
7
|
+
deployport?: {
|
|
8
|
+
region?: string;
|
|
9
|
+
accessKeyID?: string;
|
|
10
|
+
secretAccessKey?: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export declare function ConfigureSignatureV1(submission: Runtime.Submission, config: Config): Promise<void>;
|
|
14
|
+
export declare const getSigningKeyReal: (credentials: Credentials, shortDate: string, region: string, service: string, vendor: string) => Promise<Uint8Array>;
|
|
15
|
+
/**
|
|
16
|
+
* Converts a Uint8Array of binary data to a hexadecimal encoded string.
|
|
17
|
+
*
|
|
18
|
+
* @param bytes The binary data to encode
|
|
19
|
+
*/
|
|
20
|
+
export declare function toHex(bytes: Uint8Array): string;
|
|
21
|
+
export declare const iso8601: (time: Date) => string;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Sha256 } from '@aws-crypto/sha256-js';
|
|
2
|
+
const VendorCode = 'dpp';
|
|
3
|
+
export async function ConfigureSignatureV1(submission, config) {
|
|
4
|
+
// const { request } = submission;
|
|
5
|
+
// const { headers } = request;
|
|
6
|
+
// if (!headers["x-specular-signature"]) {
|
|
7
|
+
// headers["x-specular-signature"] = "v1";
|
|
8
|
+
// }
|
|
9
|
+
console.log("Hello from ConfigureSignatureV1 in runtime/signer.ts!", submission);
|
|
10
|
+
const annotations = submission.operation.resource.package.annotations;
|
|
11
|
+
console.log("service annotations", annotations);
|
|
12
|
+
const ServiceSignatureV1 = annotations.find((a) => a.constructor.name === "ServiceSignatureV1");
|
|
13
|
+
if (!ServiceSignatureV1) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
console.log("signing using config", config);
|
|
17
|
+
const { deployport } = config;
|
|
18
|
+
if (!deployport) {
|
|
19
|
+
console.warn("missing required deployport configuration for signing", config);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const { region, accessKeyID: keyID, secretAccessKey: secret } = deployport;
|
|
23
|
+
if (!region || !keyID || !secret) {
|
|
24
|
+
console.warn("missing required configuration for signing", deployport);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const { ServiceName: signingService } = ServiceSignatureV1;
|
|
28
|
+
console.log("annotations", annotations, VendorCode, signingService);
|
|
29
|
+
const { longDate, shortDate } = formatDate(new Date());
|
|
30
|
+
const signingScope = buildSigningScope(VendorCode, region, signingService, shortDate);
|
|
31
|
+
console.log("signingScope", signingScope);
|
|
32
|
+
const credentialPart = buildCredentialPart(keyID, signingScope);
|
|
33
|
+
console.log("credentialPart", credentialPart);
|
|
34
|
+
const authHeaderPrefix = vendorAlgorithm(VendorCode) + " " + credentialPart;
|
|
35
|
+
const bodyDigest = toHex(await hashSHA256(submission.request.body));
|
|
36
|
+
console.log("bodyDigest", bodyDigest);
|
|
37
|
+
const headers = submission.request.headers;
|
|
38
|
+
headers[vendorDateHeaderKey(VendorCode)] = longDate;
|
|
39
|
+
headers[vendorHeaderCanonicalNameKey(VendorCode, "Service")] = signingService;
|
|
40
|
+
headers[vendorHeaderCanonicalNameKey(VendorCode, "Resource")] = submission.operation.resource.packageUniqueName;
|
|
41
|
+
headers[vendorHeaderCanonicalNameKey(VendorCode, "Operation")] = submission.operation.name;
|
|
42
|
+
headers[vendorRegionHeaderKey(VendorCode)] = region;
|
|
43
|
+
headers[vendorHeaderCanonicalNameKey(VendorCode, "Content-Sha256")] = bodyDigest;
|
|
44
|
+
console.log("headers", headers);
|
|
45
|
+
const canonicalHeaders = getCanonicalHeaders(VendorCode, headers);
|
|
46
|
+
const credentials = {
|
|
47
|
+
keyID,
|
|
48
|
+
secret,
|
|
49
|
+
};
|
|
50
|
+
const signingKey = getSigningKey(credentials, region, shortDate, signingService, VendorCode);
|
|
51
|
+
const { request } = submission;
|
|
52
|
+
// const payloadHash = await getPayloadHash(request.body);
|
|
53
|
+
const signature = await getSignature(VendorCode, longDate, signingScope, signingKey, createCanonicalRequest(request, canonicalHeaders, bodyDigest));
|
|
54
|
+
headers["Authorization"] = [authHeaderPrefix, `SignedHeaders=${canonicalHeaders.signedHeaders}`, `Signature=${signature}`];
|
|
55
|
+
console.log("signed headers", headers);
|
|
56
|
+
}
|
|
57
|
+
function createCanonicalRequest(request, canonicalHeaders, payloadHash) {
|
|
58
|
+
const sortedHeaders = canonicalHeaders.names;
|
|
59
|
+
return `${request.method}
|
|
60
|
+
${getCanonicalPath(request)}
|
|
61
|
+
${getCanonicalQuery(request)}
|
|
62
|
+
${sortedHeaders.map((name) => `${name}:${canonicalHeaders.clean[name]}`).join("\n")}
|
|
63
|
+
|
|
64
|
+
${canonicalHeaders.signedHeaders}
|
|
65
|
+
${payloadHash}`;
|
|
66
|
+
}
|
|
67
|
+
function getCanonicalQuery({ path }) {
|
|
68
|
+
return ""; // TODO: hardcoded, we don't use query strings
|
|
69
|
+
}
|
|
70
|
+
function getCanonicalPath({ path }) {
|
|
71
|
+
console.log("getCanonicalPath path", path);
|
|
72
|
+
return path;
|
|
73
|
+
// if (this.uriEscapePath) {
|
|
74
|
+
// Non-S3 services, we normalize the path and then double URI encode it.
|
|
75
|
+
// Ref: "Remove Dot Segments" https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
|
|
76
|
+
const normalizedPathSegments = [];
|
|
77
|
+
for (const pathSegment of path.split("/")) {
|
|
78
|
+
if (pathSegment?.length === 0)
|
|
79
|
+
continue;
|
|
80
|
+
if (pathSegment === ".")
|
|
81
|
+
continue;
|
|
82
|
+
if (pathSegment === "..") {
|
|
83
|
+
normalizedPathSegments.pop();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
normalizedPathSegments.push(pathSegment);
|
|
87
|
+
}
|
|
88
|
+
// }
|
|
89
|
+
// Joining by single slashes to remove consecutive slashes.
|
|
90
|
+
const normalizedPath = `${path?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path?.endsWith("/") ? "/" : ""}`;
|
|
91
|
+
const doubleEncoded = encodeURIComponent(normalizedPath);
|
|
92
|
+
return doubleEncoded.replace(/%2F/g, "/");
|
|
93
|
+
}
|
|
94
|
+
// For S3, we shouldn't normalize the path. For example, object name
|
|
95
|
+
// my-object//example//photo.user should not be normalized to
|
|
96
|
+
// my-object/example/photo.user
|
|
97
|
+
console.log("getCanonicalPath path (curated)", path);
|
|
98
|
+
return path;
|
|
99
|
+
}
|
|
100
|
+
// /**
|
|
101
|
+
// * @private
|
|
102
|
+
// */
|
|
103
|
+
// export const getPayloadHash = async (
|
|
104
|
+
// body: string,
|
|
105
|
+
// ): Promise<string> => {
|
|
106
|
+
// const hashCtor = new Sha256();
|
|
107
|
+
// // new Sha256().update(toUint8Array(body));
|
|
108
|
+
// new Sha256().update(body);
|
|
109
|
+
// return toHex(await hashCtor.digest());
|
|
110
|
+
// };
|
|
111
|
+
async function getSignature(vendor, longDate, credentialScope, keyPromise, canonicalRequest) {
|
|
112
|
+
const stringToSign = await createStringToSign(vendor, longDate, credentialScope, canonicalRequest);
|
|
113
|
+
console.log("stringToSign", stringToSign);
|
|
114
|
+
const hash = new Sha256(await keyPromise);
|
|
115
|
+
hash.update(stringToSign);
|
|
116
|
+
return toHex(await hash.digest());
|
|
117
|
+
}
|
|
118
|
+
async function createStringToSign(vendor, longDate, credentialScope, canonicalRequest) {
|
|
119
|
+
console.log('canonicalRequest', canonicalRequest);
|
|
120
|
+
const hash = new Sha256();
|
|
121
|
+
hash.update(canonicalRequest);
|
|
122
|
+
const hashedRequest = await hash.digest();
|
|
123
|
+
return `${vendorAlgorithm(vendor)}
|
|
124
|
+
${longDate}
|
|
125
|
+
${credentialScope}
|
|
126
|
+
${toHex(hashedRequest)}`;
|
|
127
|
+
}
|
|
128
|
+
const toUtf8 = (input) => new TextDecoder("utf-8").decode(input);
|
|
129
|
+
// export const toUint8Array = (data: string | ArrayBuffer | ArrayBufferView): Uint8Array => {
|
|
130
|
+
// // if (typeof data === "string") {
|
|
131
|
+
// // return fromUtf8(data);
|
|
132
|
+
// // }
|
|
133
|
+
// // if (ArrayBuffer.isView(data)) {
|
|
134
|
+
// // return new Uint8Array(data.buffer, data.byteOffset, data.byteLength / Uint8Array.BYTES_PER_ELEMENT);
|
|
135
|
+
// // }
|
|
136
|
+
// // return new Uint8Array(data);
|
|
137
|
+
// return new Uint8Array(data);
|
|
138
|
+
// };
|
|
139
|
+
async function getSigningKey(credentials, region, shortDate, service, vendor) {
|
|
140
|
+
return getSigningKeyReal(credentials, shortDate, region, service, vendor);
|
|
141
|
+
}
|
|
142
|
+
function vendorKeyType(vendor) {
|
|
143
|
+
return vendor + "1_request";
|
|
144
|
+
}
|
|
145
|
+
export const getSigningKeyReal = async (credentials, shortDate, region, service, vendor) => {
|
|
146
|
+
let key = `${vendor}1${credentials.secret}`;
|
|
147
|
+
for (const signable of [shortDate, region, service, vendorKeyType(vendor)]) {
|
|
148
|
+
key = await hmac(key, signable);
|
|
149
|
+
}
|
|
150
|
+
return key;
|
|
151
|
+
};
|
|
152
|
+
// type signingData = string | Uint8Array;
|
|
153
|
+
const hmac = (secret, data) => {
|
|
154
|
+
const hash = new Sha256(secret);
|
|
155
|
+
hash.update(data);
|
|
156
|
+
return hash.digest();
|
|
157
|
+
};
|
|
158
|
+
function getCanonicalHeaders(vendorCode, headers) {
|
|
159
|
+
const validHeaderNames = [
|
|
160
|
+
"Host",
|
|
161
|
+
vendorDateHeaderKey(vendorCode),
|
|
162
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Service"),
|
|
163
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Resource"),
|
|
164
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Operation"),
|
|
165
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Content-Sha256"),
|
|
166
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Region"),
|
|
167
|
+
];
|
|
168
|
+
const cleaned = {};
|
|
169
|
+
for (const key in headers) {
|
|
170
|
+
if (!validHeaderNames.includes(key)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const val = headers[key];
|
|
174
|
+
if (Array.isArray(val)) {
|
|
175
|
+
cleaned[key] = val.map(v => v.trim());
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
cleaned[key] = val.trim();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const names = Object.keys(cleaned).sort();
|
|
182
|
+
return {
|
|
183
|
+
names,
|
|
184
|
+
signedHeaders: names.map(n => n.toLowerCase()).join(";"),
|
|
185
|
+
clean: cleaned,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const SHORT_TO_HEX = {};
|
|
189
|
+
for (let i = 0; i < 256; i++) {
|
|
190
|
+
let encodedByte = i.toString(16).toLowerCase();
|
|
191
|
+
if (encodedByte.length === 1) {
|
|
192
|
+
encodedByte = `0${encodedByte}`;
|
|
193
|
+
}
|
|
194
|
+
SHORT_TO_HEX[i] = encodedByte;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Converts a Uint8Array of binary data to a hexadecimal encoded string.
|
|
198
|
+
*
|
|
199
|
+
* @param bytes The binary data to encode
|
|
200
|
+
*/
|
|
201
|
+
export function toHex(bytes) {
|
|
202
|
+
let out = "";
|
|
203
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
204
|
+
out += SHORT_TO_HEX[bytes[i]];
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
// convert hashSHA256 to ts
|
|
209
|
+
async function hashSHA256(dataStr) {
|
|
210
|
+
const data = new TextEncoder().encode(dataStr);
|
|
211
|
+
const hash = new Sha256();
|
|
212
|
+
hash.update(data);
|
|
213
|
+
return await hash.digest();
|
|
214
|
+
}
|
|
215
|
+
const credentialPartPrefix = "Credential=";
|
|
216
|
+
function buildCredentialPart(keyID, signingScope) {
|
|
217
|
+
return `${credentialPartPrefix}${keyID}/${signingScope}`;
|
|
218
|
+
}
|
|
219
|
+
function buildSigningScope(vendorCode, region, service, shortDate) {
|
|
220
|
+
return [
|
|
221
|
+
shortDate,
|
|
222
|
+
region,
|
|
223
|
+
service,
|
|
224
|
+
vendorRequestCode(vendorCode),
|
|
225
|
+
].join("/");
|
|
226
|
+
}
|
|
227
|
+
function vendorRequestCode(vendorCode) {
|
|
228
|
+
return vendorCode + "1_request";
|
|
229
|
+
}
|
|
230
|
+
function vendorAlgorithm(vendorCode) {
|
|
231
|
+
return vendorCode.toUpperCase() + "1-HMAC-SHA256";
|
|
232
|
+
}
|
|
233
|
+
// function formatShortTime(dt: Date): string {
|
|
234
|
+
// return dt.toISOString().replace(/[-:]/g, "").slice(0, 15);
|
|
235
|
+
// }
|
|
236
|
+
export const iso8601 = (time) => time.toISOString()
|
|
237
|
+
.replace(/\.\d{3}Z$/, "Z");
|
|
238
|
+
const formatDate = (now) => {
|
|
239
|
+
const longDate = iso8601(now).replace(/[\-:]/g, "");
|
|
240
|
+
return {
|
|
241
|
+
longDate,
|
|
242
|
+
shortDate: longDate.slice(0, 8),
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
function vendorDateHeaderKey(vendorCode) {
|
|
246
|
+
return vendorHeaderCanonicalNameKey(vendorCode, "Date");
|
|
247
|
+
}
|
|
248
|
+
function vendorRegionHeaderKey(vendorCode) {
|
|
249
|
+
return vendorHeaderCanonicalNameKey(vendorCode, "Region");
|
|
250
|
+
}
|
|
251
|
+
function vendorHeaderCanonicalNameKey(vendorCode, header) {
|
|
252
|
+
return httpHeaderStandardName("x-" + vendorCode + "-" + header.toLowerCase());
|
|
253
|
+
}
|
|
254
|
+
function httpHeaderStandardName(header) {
|
|
255
|
+
return header.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("-");
|
|
256
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { Sha256 } from '@aws-crypto/sha256-js';
|
|
2
|
+
import { Runtime } from "@deployport/specular-runtime";
|
|
3
|
+
import { SourceData } from "@aws-sdk/types";
|
|
4
|
+
|
|
5
|
+
type Credentials = {
|
|
6
|
+
keyID: string;
|
|
7
|
+
secret: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type Config = {
|
|
11
|
+
deployport?: {
|
|
12
|
+
region?: string;
|
|
13
|
+
accessKeyID?: string;
|
|
14
|
+
secretAccessKey?: string;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const VendorCode = 'dpp';
|
|
19
|
+
|
|
20
|
+
export async function ConfigureSignatureV1(submission: Runtime.Submission, config: Config): Promise<void> {
|
|
21
|
+
|
|
22
|
+
// const { request } = submission;
|
|
23
|
+
// const { headers } = request;
|
|
24
|
+
// if (!headers["x-specular-signature"]) {
|
|
25
|
+
// headers["x-specular-signature"] = "v1";
|
|
26
|
+
// }
|
|
27
|
+
console.log("Hello from ConfigureSignatureV1 in runtime/signer.ts!", submission)
|
|
28
|
+
const annotations = submission.operation.resource.package.annotations;
|
|
29
|
+
console.log("service annotations", annotations);
|
|
30
|
+
const ServiceSignatureV1 = annotations.find((a) => a.constructor.name === "ServiceSignatureV1");
|
|
31
|
+
if (!ServiceSignatureV1) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log("signing using config", config)
|
|
35
|
+
const { deployport } = config;
|
|
36
|
+
if (!deployport) {
|
|
37
|
+
console.warn("missing required deployport configuration for signing", config);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const { region, accessKeyID: keyID, secretAccessKey: secret } = deployport;
|
|
41
|
+
if (!region || !keyID || !secret) {
|
|
42
|
+
console.warn("missing required configuration for signing", deployport);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const { ServiceName: signingService } = ServiceSignatureV1;
|
|
46
|
+
console.log("annotations", annotations, VendorCode, signingService);
|
|
47
|
+
const { longDate, shortDate } = formatDate(new Date());
|
|
48
|
+
const signingScope = buildSigningScope(VendorCode, region, signingService, shortDate);
|
|
49
|
+
console.log("signingScope", signingScope);
|
|
50
|
+
|
|
51
|
+
const credentialPart = buildCredentialPart(keyID, signingScope);
|
|
52
|
+
console.log("credentialPart", credentialPart);
|
|
53
|
+
|
|
54
|
+
const authHeaderPrefix = vendorAlgorithm(VendorCode) + " " + credentialPart
|
|
55
|
+
|
|
56
|
+
const bodyDigest = toHex(await hashSHA256(submission.request.body));
|
|
57
|
+
console.log("bodyDigest", bodyDigest);
|
|
58
|
+
const headers = submission.request.headers;
|
|
59
|
+
headers[vendorDateHeaderKey(VendorCode)] = longDate;
|
|
60
|
+
headers[vendorHeaderCanonicalNameKey(VendorCode, "Service")] = signingService;
|
|
61
|
+
headers[vendorHeaderCanonicalNameKey(VendorCode, "Resource")] = submission.operation.resource.packageUniqueName;
|
|
62
|
+
headers[vendorHeaderCanonicalNameKey(VendorCode, "Operation")] = submission.operation.name;
|
|
63
|
+
headers[vendorRegionHeaderKey(VendorCode)] = region;
|
|
64
|
+
headers[vendorHeaderCanonicalNameKey(VendorCode, "Content-Sha256")] = bodyDigest;
|
|
65
|
+
console.log("headers", headers);
|
|
66
|
+
const canonicalHeaders = getCanonicalHeaders(VendorCode, headers);
|
|
67
|
+
|
|
68
|
+
const credentials: Credentials = {
|
|
69
|
+
keyID,
|
|
70
|
+
secret,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const signingKey = getSigningKey(credentials, region, shortDate, signingService, VendorCode);
|
|
74
|
+
|
|
75
|
+
const { request } = submission;
|
|
76
|
+
|
|
77
|
+
// const payloadHash = await getPayloadHash(request.body);
|
|
78
|
+
const signature = await getSignature(
|
|
79
|
+
VendorCode,
|
|
80
|
+
longDate,
|
|
81
|
+
signingScope,
|
|
82
|
+
signingKey,
|
|
83
|
+
createCanonicalRequest(request, canonicalHeaders, bodyDigest)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
headers["Authorization"] = [authHeaderPrefix, `SignedHeaders=${canonicalHeaders.signedHeaders}`, `Signature=${signature}`] as any;
|
|
87
|
+
console.log("signed headers", headers);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createCanonicalRequest(request: Runtime.HTTPRequest, canonicalHeaders: CanonicalHeaders, payloadHash: string): string {
|
|
91
|
+
const sortedHeaders = canonicalHeaders.names;
|
|
92
|
+
return `${request.method}
|
|
93
|
+
${getCanonicalPath(request)}
|
|
94
|
+
${getCanonicalQuery(request)}
|
|
95
|
+
${sortedHeaders.map((name) => `${name}:${canonicalHeaders.clean[name]}`).join("\n")}
|
|
96
|
+
|
|
97
|
+
${canonicalHeaders.signedHeaders}
|
|
98
|
+
${payloadHash}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getCanonicalQuery({ path }: Runtime.HTTPRequest): string {
|
|
102
|
+
return ""; // TODO: hardcoded, we don't use query strings
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getCanonicalPath({ path }: Runtime.HTTPRequest): string {
|
|
106
|
+
console.log("getCanonicalPath path", path);
|
|
107
|
+
return path;
|
|
108
|
+
// if (this.uriEscapePath) {
|
|
109
|
+
// Non-S3 services, we normalize the path and then double URI encode it.
|
|
110
|
+
// Ref: "Remove Dot Segments" https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
|
|
111
|
+
const normalizedPathSegments: string[] = [];
|
|
112
|
+
for (const pathSegment of path.split("/")) {
|
|
113
|
+
if (pathSegment?.length === 0) continue;
|
|
114
|
+
if (pathSegment === ".") continue;
|
|
115
|
+
if (pathSegment === "..") {
|
|
116
|
+
normalizedPathSegments.pop();
|
|
117
|
+
} else {
|
|
118
|
+
normalizedPathSegments.push(pathSegment);
|
|
119
|
+
}
|
|
120
|
+
// }
|
|
121
|
+
// Joining by single slashes to remove consecutive slashes.
|
|
122
|
+
const normalizedPath = `${path?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path?.endsWith("/") ? "/" : ""
|
|
123
|
+
}`;
|
|
124
|
+
|
|
125
|
+
const doubleEncoded = encodeURIComponent(normalizedPath);
|
|
126
|
+
return doubleEncoded.replace(/%2F/g, "/");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// For S3, we shouldn't normalize the path. For example, object name
|
|
130
|
+
// my-object//example//photo.user should not be normalized to
|
|
131
|
+
// my-object/example/photo.user
|
|
132
|
+
|
|
133
|
+
console.log("getCanonicalPath path (curated)", path);
|
|
134
|
+
return path;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
// /**
|
|
139
|
+
// * @private
|
|
140
|
+
// */
|
|
141
|
+
// export const getPayloadHash = async (
|
|
142
|
+
// body: string,
|
|
143
|
+
// ): Promise<string> => {
|
|
144
|
+
// const hashCtor = new Sha256();
|
|
145
|
+
// // new Sha256().update(toUint8Array(body));
|
|
146
|
+
// new Sha256().update(body);
|
|
147
|
+
// return toHex(await hashCtor.digest());
|
|
148
|
+
// };
|
|
149
|
+
|
|
150
|
+
async function getSignature(
|
|
151
|
+
vendor: string,
|
|
152
|
+
longDate: string,
|
|
153
|
+
credentialScope: string,
|
|
154
|
+
keyPromise: Promise<Uint8Array>,
|
|
155
|
+
canonicalRequest: string
|
|
156
|
+
): Promise<string> {
|
|
157
|
+
const stringToSign = await createStringToSign(vendor, longDate, credentialScope, canonicalRequest);
|
|
158
|
+
console.log("stringToSign", stringToSign);
|
|
159
|
+
|
|
160
|
+
const hash = new Sha256(await keyPromise);
|
|
161
|
+
hash.update(stringToSign);
|
|
162
|
+
return toHex(await hash.digest());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function createStringToSign(
|
|
166
|
+
vendor: string,
|
|
167
|
+
longDate: string,
|
|
168
|
+
credentialScope: string,
|
|
169
|
+
canonicalRequest: string
|
|
170
|
+
): Promise<string> {
|
|
171
|
+
console.log('canonicalRequest', canonicalRequest);
|
|
172
|
+
const hash = new Sha256();
|
|
173
|
+
hash.update(canonicalRequest);
|
|
174
|
+
const hashedRequest = await hash.digest();
|
|
175
|
+
|
|
176
|
+
return `${vendorAlgorithm(vendor)}
|
|
177
|
+
${longDate}
|
|
178
|
+
${credentialScope}
|
|
179
|
+
${toHex(hashedRequest)}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const toUtf8 = (input: Uint8Array): string => new TextDecoder("utf-8").decode(input);
|
|
183
|
+
|
|
184
|
+
// export const toUint8Array = (data: string | ArrayBuffer | ArrayBufferView): Uint8Array => {
|
|
185
|
+
// // if (typeof data === "string") {
|
|
186
|
+
// // return fromUtf8(data);
|
|
187
|
+
// // }
|
|
188
|
+
|
|
189
|
+
// // if (ArrayBuffer.isView(data)) {
|
|
190
|
+
// // return new Uint8Array(data.buffer, data.byteOffset, data.byteLength / Uint8Array.BYTES_PER_ELEMENT);
|
|
191
|
+
// // }
|
|
192
|
+
|
|
193
|
+
// // return new Uint8Array(data);
|
|
194
|
+
// return new Uint8Array(data);
|
|
195
|
+
// };
|
|
196
|
+
|
|
197
|
+
async function getSigningKey(
|
|
198
|
+
credentials: Credentials,
|
|
199
|
+
region: string,
|
|
200
|
+
shortDate: string,
|
|
201
|
+
service: string,
|
|
202
|
+
vendor: string,
|
|
203
|
+
): Promise<Uint8Array> {
|
|
204
|
+
return getSigningKeyReal(credentials, shortDate, region, service, vendor);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function vendorKeyType(vendor: string): string {
|
|
208
|
+
return vendor + "1_request";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const getSigningKeyReal = async (
|
|
212
|
+
credentials: Credentials,
|
|
213
|
+
shortDate: string,
|
|
214
|
+
region: string,
|
|
215
|
+
service: string,
|
|
216
|
+
vendor: string,
|
|
217
|
+
): Promise<Uint8Array> => {
|
|
218
|
+
let key: SourceData = `${vendor}1${credentials.secret}`;
|
|
219
|
+
for (const signable of [shortDate, region, service, vendorKeyType(vendor)]) {
|
|
220
|
+
key = await hmac(key, signable);
|
|
221
|
+
}
|
|
222
|
+
return key as Uint8Array;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// type signingData = string | Uint8Array;
|
|
226
|
+
|
|
227
|
+
const hmac = (
|
|
228
|
+
secret: SourceData,
|
|
229
|
+
data: SourceData
|
|
230
|
+
): Promise<Uint8Array> => {
|
|
231
|
+
const hash = new Sha256(secret);
|
|
232
|
+
hash.update(data);
|
|
233
|
+
return hash.digest();
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
type CanonicalHeaders = {
|
|
238
|
+
/**
|
|
239
|
+
* ordered list of header names
|
|
240
|
+
*/
|
|
241
|
+
names: string[]
|
|
242
|
+
clean: Runtime.HTTPHeaders
|
|
243
|
+
signedHeaders: string
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getCanonicalHeaders(vendorCode: string, headers: Runtime.HTTPHeaders): CanonicalHeaders {
|
|
247
|
+
const validHeaderNames = [
|
|
248
|
+
"Host",
|
|
249
|
+
vendorDateHeaderKey(vendorCode),
|
|
250
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Service"),
|
|
251
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Resource"),
|
|
252
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Operation"),
|
|
253
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Content-Sha256"),
|
|
254
|
+
vendorHeaderCanonicalNameKey(vendorCode, "Region"),
|
|
255
|
+
];
|
|
256
|
+
const cleaned: Runtime.HTTPHeaders = {};
|
|
257
|
+
for (const key in headers) {
|
|
258
|
+
if (!validHeaderNames.includes(key)) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const val = headers[key];
|
|
262
|
+
if (Array.isArray(val)) {
|
|
263
|
+
cleaned[key] = val.map(v => v.trim());
|
|
264
|
+
} else {
|
|
265
|
+
cleaned[key] = val.trim();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const names = Object.keys(cleaned).sort();
|
|
269
|
+
return {
|
|
270
|
+
names,
|
|
271
|
+
signedHeaders: names.map(n => n.toLowerCase()).join(";"),
|
|
272
|
+
clean: cleaned,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
const SHORT_TO_HEX: { [key: number]: string } = {};
|
|
278
|
+
for (let i = 0; i < 256; i++) {
|
|
279
|
+
let encodedByte = i.toString(16).toLowerCase();
|
|
280
|
+
if (encodedByte.length === 1) {
|
|
281
|
+
encodedByte = `0${encodedByte}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
SHORT_TO_HEX[i] = encodedByte;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Converts a Uint8Array of binary data to a hexadecimal encoded string.
|
|
288
|
+
*
|
|
289
|
+
* @param bytes The binary data to encode
|
|
290
|
+
*/
|
|
291
|
+
export function toHex(bytes: Uint8Array): string {
|
|
292
|
+
let out = "";
|
|
293
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
294
|
+
out += SHORT_TO_HEX[bytes[i]];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// convert hashSHA256 to ts
|
|
301
|
+
async function hashSHA256(dataStr: string): Promise<Uint8Array> {
|
|
302
|
+
const data = new TextEncoder().encode(dataStr);
|
|
303
|
+
const hash = new Sha256();
|
|
304
|
+
hash.update(data);
|
|
305
|
+
return await hash.digest();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const credentialPartPrefix = "Credential=";
|
|
309
|
+
|
|
310
|
+
function buildCredentialPart(keyID: string, signingScope: string): string {
|
|
311
|
+
return `${credentialPartPrefix}${keyID}/${signingScope}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function buildSigningScope(vendorCode: string, region: string, service: string, shortDate: string): string {
|
|
315
|
+
return [
|
|
316
|
+
shortDate,
|
|
317
|
+
region,
|
|
318
|
+
service,
|
|
319
|
+
vendorRequestCode(vendorCode),
|
|
320
|
+
].join("/");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function vendorRequestCode(vendorCode: string): string {
|
|
324
|
+
return vendorCode + "1_request";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function vendorAlgorithm(vendorCode: string): string {
|
|
328
|
+
return vendorCode.toUpperCase() + "1-HMAC-SHA256";
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// function formatShortTime(dt: Date): string {
|
|
332
|
+
// return dt.toISOString().replace(/[-:]/g, "").slice(0, 15);
|
|
333
|
+
// }
|
|
334
|
+
|
|
335
|
+
export const iso8601 = (time: Date): string =>
|
|
336
|
+
time.toISOString()
|
|
337
|
+
.replace(/\.\d{3}Z$/, "Z");
|
|
338
|
+
|
|
339
|
+
const formatDate = (now: Date): { longDate: string; shortDate: string } => {
|
|
340
|
+
const longDate = iso8601(now).replace(/[\-:]/g, "");
|
|
341
|
+
return {
|
|
342
|
+
longDate,
|
|
343
|
+
shortDate: longDate.slice(0, 8),
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
function vendorDateHeaderKey(vendorCode: string): string {
|
|
348
|
+
return vendorHeaderCanonicalNameKey(vendorCode, "Date");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function vendorRegionHeaderKey(vendorCode: string): string {
|
|
352
|
+
return vendorHeaderCanonicalNameKey(vendorCode, "Region");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function vendorHeaderCanonicalNameKey(vendorCode: string, header: string): string {
|
|
356
|
+
return httpHeaderStandardName("x-" + vendorCode + "-" + header.toLowerCase());
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function httpHeaderStandardName(header: string): string {
|
|
360
|
+
return header.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("-");
|
|
361
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deployport/api-services-corelib",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./specular.js",
|
|
6
|
+
"types": "./specular.d.ts",
|
|
7
|
+
"author": "bithavoc",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prepublishOnly": "npm run build-configurator",
|
|
10
|
+
"build-configurator": "tsc configurator/index.ts --declaration --module nodenext",
|
|
11
|
+
"clean": "rm -rf node_modules && rm -f **/*.js **/*.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@aws-crypto/sha256-js": "^5.2.0",
|
|
17
|
+
"@aws-sdk/types": "^3.515.0",
|
|
18
|
+
"@deployport/specular-runtime": "^0.1.5"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@tsconfig/node18": "^18.2.2",
|
|
22
|
+
"@types/node": "^20.8.3"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/specular.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Runtime, Metadata } from '@deployport/specular-runtime';
|
|
2
|
+
export declare class SignedOperationV1 {
|
|
3
|
+
}
|
|
4
|
+
export declare class ServiceSignatureV1 {
|
|
5
|
+
ServiceName: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const AccessDeniedProblemMeta: Metadata.Struct;
|
|
8
|
+
export interface AccessDeniedProblemProperties {
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AccessDeniedProblem extends AccessDeniedProblemProperties, Runtime.StructInterface {
|
|
12
|
+
}
|
|
13
|
+
export declare class AccessDeniedProblem extends Error {
|
|
14
|
+
}
|
|
15
|
+
export declare const ForbiddenProblemMeta: Metadata.Struct;
|
|
16
|
+
export interface ForbiddenProblemProperties {
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ForbiddenProblem extends ForbiddenProblemProperties, Runtime.StructInterface {
|
|
20
|
+
}
|
|
21
|
+
export declare class ForbiddenProblem extends Error {
|
|
22
|
+
}
|
|
23
|
+
export declare function SpecularPackage(): Metadata.Package;
|
package/specular.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// JS gen package
|
|
2
|
+
import { Metadata, } from '@deployport/specular-runtime';
|
|
3
|
+
// SignedOperationV1 entity
|
|
4
|
+
// Marks an operation with digital signature authentication for id4ntity verification
|
|
5
|
+
export class SignedOperationV1 {
|
|
6
|
+
}
|
|
7
|
+
// ServiceSignatureV1 entity
|
|
8
|
+
export class ServiceSignatureV1 {
|
|
9
|
+
ServiceName;
|
|
10
|
+
}
|
|
11
|
+
const _pkg = new Metadata.Package('Deployport/CoreLib');
|
|
12
|
+
export const AccessDeniedProblemMeta = new Metadata.Struct(_pkg, "AccessDeniedProblem");
|
|
13
|
+
new Metadata.Property(AccessDeniedProblemMeta, "message", {
|
|
14
|
+
NonNullable: true,
|
|
15
|
+
SubType: "builtin",
|
|
16
|
+
Builtin: "string"
|
|
17
|
+
});
|
|
18
|
+
;
|
|
19
|
+
;
|
|
20
|
+
export class AccessDeniedProblem extends Error {
|
|
21
|
+
}
|
|
22
|
+
AccessDeniedProblemMeta.problemInstantiate = (msg) => new AccessDeniedProblem(msg);
|
|
23
|
+
export const ForbiddenProblemMeta = new Metadata.Struct(_pkg, "ForbiddenProblem");
|
|
24
|
+
new Metadata.Property(ForbiddenProblemMeta, "message", {
|
|
25
|
+
NonNullable: true,
|
|
26
|
+
SubType: "builtin",
|
|
27
|
+
Builtin: "string"
|
|
28
|
+
});
|
|
29
|
+
;
|
|
30
|
+
;
|
|
31
|
+
export class ForbiddenProblem extends Error {
|
|
32
|
+
}
|
|
33
|
+
ForbiddenProblemMeta.problemInstantiate = (msg) => new ForbiddenProblem(msg);
|
|
34
|
+
export function SpecularPackage() {
|
|
35
|
+
return _pkg;
|
|
36
|
+
}
|
package/specular.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// JS gen package
|
|
2
|
+
import {
|
|
3
|
+
Runtime,
|
|
4
|
+
Metadata,
|
|
5
|
+
} from '@deployport/specular-runtime';
|
|
6
|
+
|
|
7
|
+
// SignedOperationV1 entity
|
|
8
|
+
// Marks an operation with digital signature authentication for id4ntity verification
|
|
9
|
+
export class SignedOperationV1 {
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ServiceSignatureV1 entity
|
|
13
|
+
export class ServiceSignatureV1 {
|
|
14
|
+
public ServiceName: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const _pkg = new Metadata.Package(
|
|
18
|
+
'Deployport/CoreLib',
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const AccessDeniedProblemMeta = new Metadata.Struct(
|
|
22
|
+
_pkg,
|
|
23
|
+
"AccessDeniedProblem",
|
|
24
|
+
);
|
|
25
|
+
new Metadata.Property(AccessDeniedProblemMeta, "message", {
|
|
26
|
+
NonNullable: true,
|
|
27
|
+
SubType: "builtin",
|
|
28
|
+
Builtin: "string"
|
|
29
|
+
});
|
|
30
|
+
// AccessDeniedProblem entity
|
|
31
|
+
export interface AccessDeniedProblemProperties
|
|
32
|
+
{
|
|
33
|
+
// /**
|
|
34
|
+
// * Returns "Deployport/CoreLib:AccessDeniedProblem"
|
|
35
|
+
// */
|
|
36
|
+
// fqtn: "Deployport/CoreLib:AccessDeniedProblem";
|
|
37
|
+
message : string;
|
|
38
|
+
};
|
|
39
|
+
export interface AccessDeniedProblem extends AccessDeniedProblemProperties, Runtime.StructInterface{};
|
|
40
|
+
export class AccessDeniedProblem
|
|
41
|
+
extends Error
|
|
42
|
+
{}
|
|
43
|
+
AccessDeniedProblemMeta.problemInstantiate = (msg: string) => new AccessDeniedProblem(msg);
|
|
44
|
+
export const ForbiddenProblemMeta = new Metadata.Struct(
|
|
45
|
+
_pkg,
|
|
46
|
+
"ForbiddenProblem",
|
|
47
|
+
);
|
|
48
|
+
new Metadata.Property(ForbiddenProblemMeta, "message", {
|
|
49
|
+
NonNullable: true,
|
|
50
|
+
SubType: "builtin",
|
|
51
|
+
Builtin: "string"
|
|
52
|
+
});
|
|
53
|
+
// ForbiddenProblem entity
|
|
54
|
+
export interface ForbiddenProblemProperties
|
|
55
|
+
{
|
|
56
|
+
// /**
|
|
57
|
+
// * Returns "Deployport/CoreLib:ForbiddenProblem"
|
|
58
|
+
// */
|
|
59
|
+
// fqtn: "Deployport/CoreLib:ForbiddenProblem";
|
|
60
|
+
message : string;
|
|
61
|
+
};
|
|
62
|
+
export interface ForbiddenProblem extends ForbiddenProblemProperties, Runtime.StructInterface{};
|
|
63
|
+
export class ForbiddenProblem
|
|
64
|
+
extends Error
|
|
65
|
+
{}
|
|
66
|
+
ForbiddenProblemMeta.problemInstantiate = (msg: string) => new ForbiddenProblem(msg);
|
|
67
|
+
|
|
68
|
+
export function SpecularPackage() {
|
|
69
|
+
return _pkg;
|
|
70
|
+
}
|