1 |
|
2 |
|
3 |
|
4 | import { DateUtils } from './Util';
|
5 | import {
|
6 | presignUrl,
|
7 | signRequest,
|
8 | TOKEN_QUERY_PARAM,
|
9 | } from './clients/middleware/signing/signer/signatureV4';
|
10 |
|
11 | const IOT_SERVICE_NAME = 'iotdevicegateway';
|
12 |
|
13 | const AWS_ENDPOINT_REGEX = /([^\.]+)\.(?:([^\.]*)\.)?amazonaws\.com(.cn)?$/;
|
14 |
|
15 | export class Signer {
|
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 | static sign(request, accessInfo, serviceInfo) {
|
53 | request.headers = request.headers || {};
|
54 |
|
55 | if (request.body && !request.data) {
|
56 | throw new Error(
|
57 | 'The attribute "body" was found on the request object. Please use the attribute "data" instead.'
|
58 | );
|
59 | }
|
60 |
|
61 | const requestToSign = {
|
62 | ...request,
|
63 | body: request.data,
|
64 | url: new URL(request.url as string),
|
65 | };
|
66 |
|
67 | const options = getOptions(requestToSign, accessInfo, serviceInfo);
|
68 | const signedRequest: any = signRequest(requestToSign, options);
|
69 |
|
70 |
|
71 | signedRequest.url = signedRequest.url.toString();
|
72 |
|
73 |
|
74 | signedRequest.headers.Authorization = signedRequest.headers.authorization;
|
75 | signedRequest.headers['X-Amz-Security-Token'] =
|
76 | signedRequest.headers['x-amz-security-token'];
|
77 | delete signedRequest.headers.authorization;
|
78 | delete signedRequest.headers['x-amz-security-token'];
|
79 | return signedRequest;
|
80 | }
|
81 |
|
82 | static signUrl(
|
83 | urlToSign: string,
|
84 | accessInfo: any,
|
85 | serviceInfo?: any,
|
86 | expiration?: number
|
87 | ): string;
|
88 | static signUrl(
|
89 | request: any,
|
90 | accessInfo: any,
|
91 | serviceInfo?: any,
|
92 | expiration?: number
|
93 | ): string;
|
94 | static signUrl(
|
95 | urlOrRequest: string | any,
|
96 | accessInfo: any,
|
97 | serviceInfo?: any,
|
98 | expiration?: number
|
99 | ): string {
|
100 | const urlToSign: string =
|
101 | typeof urlOrRequest === 'object' ? urlOrRequest.url : urlOrRequest;
|
102 | const method: string =
|
103 | typeof urlOrRequest === 'object' ? urlOrRequest.method : 'GET';
|
104 | const body: any =
|
105 | typeof urlOrRequest === 'object' ? urlOrRequest.body : undefined;
|
106 |
|
107 | const presignable = {
|
108 | body,
|
109 | method,
|
110 | url: new URL(urlToSign),
|
111 | };
|
112 |
|
113 | const options = getOptions(
|
114 | presignable,
|
115 | accessInfo,
|
116 | serviceInfo,
|
117 | expiration
|
118 | );
|
119 | const signedUrl = presignUrl(presignable, options);
|
120 | if (
|
121 | accessInfo.session_token &&
|
122 | !sessionTokenRequiredInSigning(options.signingService)
|
123 | ) {
|
124 | signedUrl.searchParams.append(
|
125 | TOKEN_QUERY_PARAM,
|
126 | accessInfo.session_token
|
127 | );
|
128 | }
|
129 | return signedUrl.toString();
|
130 | }
|
131 | }
|
132 |
|
133 | const getOptions = (request, accessInfo, serviceInfo, expiration?) => {
|
134 | const { access_key, secret_key, session_token } = accessInfo ?? {};
|
135 | const { region: urlRegion, service: urlService } = parseServiceInfo(
|
136 | request.url
|
137 | );
|
138 | const { region = urlRegion, service = urlService } = serviceInfo ?? {};
|
139 | const credentials = {
|
140 | accessKeyId: access_key,
|
141 | secretAccessKey: secret_key,
|
142 | ...(sessionTokenRequiredInSigning(service)
|
143 | ? { sessionToken: session_token }
|
144 | : {}),
|
145 | };
|
146 | return {
|
147 | credentials,
|
148 | signingDate: DateUtils.getDateWithClockOffset(),
|
149 | signingRegion: region,
|
150 | signingService: service,
|
151 | ...(expiration && { expiration }),
|
152 | };
|
153 | };
|
154 |
|
155 |
|
156 | const parseServiceInfo = (url: URL) => {
|
157 | const host = url.host;
|
158 | const matched = host.match(AWS_ENDPOINT_REGEX) ?? [];
|
159 | let parsed = matched.slice(1, 3);
|
160 |
|
161 | if (parsed[1] === 'es') {
|
162 |
|
163 | parsed = parsed.reverse();
|
164 | }
|
165 |
|
166 | return {
|
167 | service: parsed[0],
|
168 | region: parsed[1],
|
169 | };
|
170 | };
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | const sessionTokenRequiredInSigning = (service: string) =>
|
176 | service !== IOT_SERVICE_NAME;
|