Skip to content

Commit b46d822

Browse files
committed
signer.sign now takes Request
1 parent d2d1aa9 commit b46d822

File tree

4 files changed

+103
-62
lines changed

4 files changed

+103
-62
lines changed

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,15 @@ The below example will generate signed headers based on the region and credentia
1818
import { AWSSignerV4 } from "https://deno.land/x/aws_sign_v4@0.1.0/mod.ts";
1919

2020
const signer = new AWSSignerV4();
21-
const url = "https://test-bucket.s3.amazonaws.com/test";
22-
const method = "PUT";
23-
const body = new TextEncoder().encode("Hello World!")
24-
const headers = { "content-length": body.length };
25-
const signedHeaders = signer.sign("s3", url, method, headers, body);
26-
27-
const response = await fetch(url, {
28-
headers: signedHeaders,
29-
method,
30-
body: payload,
21+
const body = new TextEncoder().encode("Hello World!");
22+
const request = new Request("https://test-bucket.s3.amazonaws.com/test", {
23+
method: "PUT",
24+
headers: { "content-length": body.length.toString() },
25+
body,
3126
});
27+
const req = await signer.sign("s3", request);
28+
29+
const response = await fetch(req);
3230
```
3331

3432
You can also explicitly specify credentials and a region when constructing a new `AWSSignerV4`:

mod.ts

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { sha256Hex } from "./deps.ts";
22
import { toAmz, toDateStamp } from "./src/date.ts";
33
import { getSignatureKey, signAwsV4 } from "./src/signing.ts";
44
import type { Credentials, RequestHeaders } from "./src/types.ts";
5+
import { equal } from "https://deno.land/std@0.68.0/testing/asserts.ts";
56
export type { Credentials, RequestHeaders };
67

78
/**
@@ -15,17 +16,14 @@ export type { Credentials, RequestHeaders };
1516
*
1617
* ```ts
1718
* const signer = new AWSSignerV4();
18-
* const url = "https://test-bucket.s3.amazonaws.com/test";
19-
* const method = "PUT";
2019
* const body = new TextEncoder().encode("Hello World!")
21-
* const headers = { "content-length": body.length };
22-
* const signedHeaders = signer.sign("s3", url, method, headers, body);
23-
*
24-
* const response = await fetch(url, {
25-
* headers: signedHeaders,
26-
* method,
27-
* body: payload,
20+
* const request = new Request("https://test-bucket.s3.amazonaws.com/test", {
21+
* method: "PUT",
22+
* headers: { "content-length": body.length.toString() },
23+
* body,
2824
* });
25+
* const req = await signer.sign("s3", request);
26+
* const response = await fetch(req);
2927
* ```
3028
*/
3129
export class AWSSignerV4 {
@@ -44,6 +42,7 @@ export class AWSSignerV4 {
4442
this.region = region || this.#getDefaultRegion();
4543
this.credentials = credentials || this.#getDefaultCredentials();
4644
}
45+
4746
/**
4847
* Use this to create the signed headers required to
4948
* make a call to an AWS API.
@@ -55,42 +54,42 @@ export class AWSSignerV4 {
5554
* @param body The body for PUT/POST methods.
5655
* @returns {RequestHeaders} - the signed request headers
5756
*/
58-
public sign = (
57+
public async sign(
5958
service: string,
60-
url: string,
61-
method: string = "GET",
62-
headers: RequestHeaders,
63-
body?: Uint8Array | string,
64-
): RequestHeaders => {
59+
request: Request,
60+
): Promise<Request> {
6561
const date = new Date();
6662
const amzdate = toAmz(date);
6763
const datestamp = toDateStamp(date);
6864

69-
const urlObj = new URL(url);
65+
const urlObj = new URL(request.url);
7066
const { host, pathname, searchParams } = urlObj;
7167
const canonicalQuerystring = searchParams.toString();
7268

73-
headers["x-amz-date"] = amzdate;
69+
const headers = new Headers(request.headers);
70+
71+
headers.set("x-amz-date", amzdate);
7472
if (this.credentials.sessionToken) {
75-
headers["x-amz-security-token"] = this.credentials.sessionToken;
73+
headers.set("x-amz-security-token", this.credentials.sessionToken);
7674
}
77-
78-
headers["host"] = host;
75+
headers.set("host", host);
7976

8077
let canonicalHeaders = "";
8178
let signedHeaders = "";
82-
for (const key of Object.keys(headers).sort()) {
83-
canonicalHeaders += `${key.toLowerCase()}:${headers[key]}\n`;
79+
for (const key of [...headers.keys()].sort()) {
80+
canonicalHeaders += `${key.toLowerCase()}:${headers.get(key)}\n`;
8481
signedHeaders += `${key.toLowerCase()};`;
8582
}
8683
signedHeaders = signedHeaders.substring(0, signedHeaders.length - 1);
87-
const payload = body ?? "";
88-
const payloadHash = sha256Hex(payload);
84+
const body = request.body
85+
? new Uint8Array(await request.arrayBuffer())
86+
: new Uint8Array();
87+
const payloadHash = sha256Hex(body);
8988

9089
const { awsAccessKeyId, awsSecretKey } = this.credentials;
9190

9291
const canonicalRequest =
93-
`${method}\n${pathname}\n${canonicalQuerystring}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`;
92+
`${request.method}\n${pathname}\n${canonicalQuerystring}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`;
9493
const canonicalRequestDigest = sha256Hex(canonicalRequest);
9594

9695
const algorithm = "AWS4-HMAC-SHA256";
@@ -111,10 +110,27 @@ export class AWSSignerV4 {
111110
const authHeader =
112111
`${algorithm} Credential=${awsAccessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
113112

114-
headers.Authorization = authHeader;
115-
116-
return headers;
117-
};
113+
headers.set("Authorization", authHeader);
114+
115+
const req = new Request(
116+
request.url,
117+
{
118+
headers,
119+
method: request.method,
120+
body,
121+
cache: request.cache,
122+
credentials: request.credentials,
123+
integrity: request.integrity,
124+
keepalive: request.keepalive,
125+
mode: request.mode,
126+
redirect: request.redirect,
127+
referrer: request.referrer,
128+
referrerPolicy: request.referrerPolicy,
129+
signal: request.signal,
130+
},
131+
);
132+
return req;
133+
}
118134

119135
#getDefaultCredentials = (): Credentials => {
120136
const AWS_ACCESS_KEY_ID = Deno.env.get("AWS_ACCESS_KEY_ID");

mod_test.ts

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,85 @@
11
import { AWSSignerV4 } from "./mod.ts";
22
import {
33
assertEquals,
4-
assert,
54
assertStringContains,
65
} from "https://deno.land/std@0.68.0/testing/asserts.ts";
76

8-
Deno.test("construct from env vars", () => {
7+
Deno.test("construct from env vars", async () => {
98
Deno.env.set("AWS_ACCESS_KEY_ID", "examplekey");
109
Deno.env.set("AWS_SECRET_ACCESS_KEY", "secretkey");
1110
Deno.env.set("AWS_SESSION_TOKEN", "sessiontoken");
1211
Deno.env.set("AWS_REGION", "us-east-1");
1312

1413
const signer = new AWSSignerV4();
15-
const headers = signer.sign(
14+
const req = await signer.sign(
1615
"dynamodb",
17-
"https://test.dynamodb.us-east-1.amazonaws.com",
18-
"GET",
19-
{ "x-hello": "world" },
20-
"A dynamodb request!",
16+
new Request(
17+
"https://test.dynamodb.us-east-1.amazonaws.com",
18+
{
19+
method: "GET",
20+
headers: { "x-hello": "world" },
21+
body: "A dynamodb request!",
22+
},
23+
),
2124
);
2225
const now = new Date();
2326
const today = `${now.getFullYear()}${
2427
(now.getMonth() + 1).toString().padStart(2, "0")
2528
}${now.getDate().toString().padStart(2, "0")}`;
26-
assertStringContains(headers["x-amz-date"], `${today}T`);
27-
assertEquals(headers["x-amz-security-token"], "sessiontoken");
28-
assertEquals(headers["x-hello"], "world");
29-
assertEquals(headers["host"], "test.dynamodb.us-east-1.amazonaws.com");
29+
assertStringContains(req.headers.get("x-amz-date")!, `${today}T`);
30+
assertEquals(req.headers.get("x-amz-security-token"), "sessiontoken");
31+
assertEquals(req.headers.get("x-hello"), "world");
32+
assertEquals(
33+
req.headers.get("host"),
34+
"test.dynamodb.us-east-1.amazonaws.com",
35+
);
3036
assertStringContains(
31-
headers["Authorization"],
37+
req.headers.get("Authorization")!,
3238
`AWS4-HMAC-SHA256 Credential=examplekey/${today}/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token;x-hello, Signature=`,
3339
);
3440
});
3541

36-
Deno.test("construct manually", () => {
42+
Deno.test("construct manually", async () => {
3743
const signer = new AWSSignerV4("us-east-2", {
3844
awsAccessKeyId: "example_key",
3945
awsSecretKey: "secret_key",
4046
sessionToken: "session_token",
4147
});
42-
const headers = signer.sign(
48+
const req = await signer.sign(
4349
"dynamodb",
44-
"https://test.dynamodb.us-east-1.amazonaws.com",
45-
"GET",
46-
{ "x-hello": "world" },
47-
"A dynamodb request!",
50+
new Request(
51+
"https://test.dynamodb.us-east-1.amazonaws.com",
52+
{
53+
method: "GET",
54+
headers: { "x-hello": "world" },
55+
body: "A dynamodb request!",
56+
},
57+
),
4858
);
4959
const now = new Date();
5060
const today = `${now.getFullYear()}${
5161
(now.getMonth() + 1).toString().padStart(2, "0")
5262
}${now.getDate().toString().padStart(2, "0")}`;
53-
assertStringContains(headers["x-amz-date"], `${today}T`);
54-
assertEquals(headers["x-amz-security-token"], "session_token");
55-
assertEquals(headers["x-hello"], "world");
63+
assertStringContains(req.headers.get("x-amz-date")!, `${today}T`);
64+
assertEquals(req.headers.get("x-amz-security-token"), "session_token");
65+
assertEquals(req.headers.get("x-hello"), "world");
66+
assertEquals(
67+
req.headers.get("host"),
68+
"test.dynamodb.us-east-1.amazonaws.com",
69+
);
5670
assertStringContains(
57-
headers["Authorization"],
71+
req.headers.get("Authorization")!,
5872
`AWS4-HMAC-SHA256 Credential=example_key/${today}/us-east-2/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token;x-hello, Signature=`,
5973
);
6074
});
75+
76+
Deno.test("example", async () => {
77+
const signer = new AWSSignerV4();
78+
const body = new TextEncoder().encode("Hello World!");
79+
const request = new Request("https://test-bucket.s3.amazonaws.com/test", {
80+
method: "PUT",
81+
headers: { "content-length": body.length.toString() },
82+
body,
83+
});
84+
const req = await signer.sign("s3", request);
85+
});

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ export interface Credentials {
66

77
export type Method = "GET" | "PUT" | "POST" | "DELETE";
88

9-
export type RequestHeaders = { [header: string]: string };
9+
export interface RequestHeaders {
10+
[header: string]: string;
11+
}

0 commit comments

Comments
 (0)