Skip to content

Commit

Permalink
Monorepo and new package (#26)
Browse files Browse the repository at this point in the history
* split packages

* tests

* tests

* fix build

* unit tests

* coverage

* fix: todo http request test
  • Loading branch information
zirkelc authored Jan 12, 2025
1 parent f9535cd commit 175e38b
Show file tree
Hide file tree
Showing 45 changed files with 3,201 additions and 921 deletions.
8 changes: 8 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
79 changes: 16 additions & 63 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
if: ${{ steps.install.conclusion == 'success' }}
uses: davelosert/vitest-coverage-report-action@v2
with:
vite-config-path: vitest.config.unit.ts
vite-config-path: vitest.base.ts

e2e-test:
name: E2E Test
Expand All @@ -100,69 +100,22 @@ jobs:
- name: Test
run: pnpm test:e2e

preview:
name: Preview
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- run: corepack enable

- name: Setup and Install
id: install
uses: zirkelc/setup-and-install@v1
with:
node-version: 20

- name: Build
run: pnpm build
# preview:
# name: Preview
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4

- name: Publish Preview
run: npx pkg-pr-new publish --pnpm --packageManager=pnpm
# - run: corepack enable

release:
name: Release
runs-on: ubuntu-latest
needs: [lint, unit-test, e2e-test]
if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4

- name: Setup and Install
id: install
uses: zirkelc/setup-and-install@v1
# - name: Setup and Install
# id: install
# uses: zirkelc/setup-and-install@v1
# with:
# node-version: 20

- name: Build
run: pnpm build
# - name: Build
# run: pnpm build

- name: Publish to NPM
id: publish
uses: JS-DevTools/npm-publish@v3
with:
token: ${{ secrets.NPM_TOKEN }}
dry-run: false
provenance: true

- name: Post publish
if: steps.publish.outputs.type != ''
run: |
echo "Published ${{ steps.publish.outputs.type }} version: ${{ steps.publish.outputs.version }}"
- name: Publish skipped
if: steps.publish.outputs.type == ''
run: |
echo "Version in package.json has not changed. Skipping."
exit 0
- name: Create Release
if: steps.publish.outputs.type != ''
id: release
uses: ncipollo/release-action@v1 # https://github.com/ncipollo/release-action
with:
allowUpdates: true
generateReleaseNotes: true
commit: ${{ github.sha }}
draft: false
name: v${{ steps.publish.outputs.version }}
tag: v${{ steps.publish.outputs.version }}
# - name: Publish Preview
# run: npx pkg-pr-new publish --pnpm --packageManager=pnpm
41 changes: 41 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Release

on:
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
checks: write
contents: write
pull-requests: write
packages: read

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup and Install
id: install
uses: zirkelc/setup-and-install@v1
with:
node-version: 20

- name: Publish
id: changesets
uses: changesets/action@v1
with:
publish: pnpm release
env:
# Use PAT to create a new GitHub release PR, because the default GITHUB_TOKEN doesn't trigger workflows
# https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
# https://github.com/orgs/community/discussions/55906
GITHUB_TOKEN: ${{ secrets.PAT }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
159 changes: 41 additions & 118 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,151 +1,70 @@
[![CI](https://github.com/zirkelc/aws-sigv4-fetch/actions/workflows/ci.yml/badge.svg)](https://github.com/zirkelc/aws-sigv4-fetch/actions/workflows/ci.yml)
[![CI](https://github.com/zirkelc/aws-sigv4/actions/workflows/ci.yml/badge.svg)](https://github.com/zirkelc/aws-sigv4/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/aws-sigv4-fetch)](https://www.npmjs.com/package/aws-sigv4-fetch)
[![npm](https://img.shields.io/npm/dt/aws-sigv4-fetch)](https://www.npmjs.com/package/aws-sigv4-fetch)

# aws-sigv4-fetch
A lightweight wrapper around the fetch API that automatically signs HTTP requests with AWS Signature Version 4 (SigV4) authentication. Built on the official AWS SDK for JS v3.
# AWS SigV4 libraries

## Signature Version 4
> Signature Version 4 (SigV4) is the process to add authentication information to AWS API requests sent by HTTP. For security, most requests to AWS must be signed with an access key. The access key consists of an access key ID and secret access key, which are commonly referred to as your security credentials
[AWS documentation on Signature Version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)

## Install
```sh
npm install --save aws-sigv4-fetch
```

## ESM and CommonJS
This package ships with ES Module and CommonJS support. That means you can `import` or `require` the package in your project depending on your module format.

```ts
// ESM
import { createSignedFetcher } from 'aws-sigv4-fetch';

// CommonJS
const { createSignedFetcher } = require('aws-sigv4-fetch');
```

## Usage
This package exports a function `createSignedFetcher` that returns a `fetch` function to automatically sign HTTP requests with AWS Signature V4 for the given AWS service and region.
The returned `signedFetch` function accepts the same arguments as the default `fetch` function.


```ts
import { createSignedFetcher } from 'aws-sigv4-fetch';

const signedFetch = createSignedFetcher({ service: 's3', region: 'eu-west-1' });
This repository contains two libraries to sign HTTP requests with AWS Signature Version 4 (SigV4):

// signedFetch(input: string)
const response = await signedFetch('https://s3.eu-west-1.amazonaws.com/my-bucket/my-key.json');
- `aws-sigv4-fetch` creates a `fetch` function to automatically sign HTTP requests.
- `aws-sigv4-sign` creates a `Request` object with signed headers that can be used with any other HTTP library.

// signedFetch(input: URL)
const response = await signedFetch(new URL('https://s3.eu-west-1.amazonaws.com/my-bucket/my-key.json'));

// signedFetch(input: Request)
const response = await signedFetch(new Request('https://s3.eu-west-1.amazonaws.com/my-bucket/my-key.json'));

// signedFetch(input: string, init?: RequestInit)
const response = await signedFetch('https://s3.eu-west-1.amazonaws.com/my-bucket/my-key.json', {
method: 'POST',
body: JSON.stringify({ a: 1 }),
headers: { 'Content-Type': 'application/json' }
});
```

## API
The `createSignedFetcher(options: SignedFetcherOptions)` function accepts the following options:
## What is Signature Version 4?
> Signature Version 4 (SigV4) is the process to add authentication information to AWS API requests sent by HTTP. For security, most requests to AWS must be signed with an access key. The access key consists of an access key ID and secret access key, which are commonly referred to as your security credentials
```ts
type SignedFetcherOptions = {
service: string;
region?: string;
credentials?: AwsCredentialIdentity;
fetch?: typeof fetch;
};
```
[AWS documentation on Signature Version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)

### Service
The `service` is required and must match the AWS service you are signing requests for.
If it doesn't match, the request will fail with an error like:
> Credential should be scoped to correct service: 'service'
## Which library should I use?

### Region
The `region` is optional and defaults to `us-east-1` if not provided. Some services like IAM are global and don't require a region.
### Are you using the `fetch` API?

### Credentials
The `credentials` is optional. If not provided, the credentials will be retrieved from the environment by the package [`@aws-sdk/credential-provider-node`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_credential_provider_node.html).
Install the `aws-sigv4-fetch` package and use the `createSignedFetcher` function to create a signed fetch function:

```ts
import { createSignedFetcher } from 'aws-sigv4-fetch';

// credentials will be retrieved from the environment
const signedFetch = createSignedFetcher({ service: 's3', region: 'eu-west-1' });
const signedFetch = createSignedFetcher({ service: 'lambda', region: 'eu-west-1' });

// credentials will be passed directly to the function
const signedFetch = createSignedFetcher({ service: 's3', region: 'eu-west-1', credentials: { accessKeyId: '...', secretAccessKey: '...' } });
const response = await signedFetch('https://mylambda.lambda-url.eu-west-1.on.aws/');
```

### Fetch
The `fetch` function is optional. If not provided, the `fetch` function from the environment will be used. Native `fetch` is supported in Node.js >= v18. If you are running in an environment where native `fetch` is **not** available, the `fetch` function must be polyfilled or provided as an argument to `createSignedFetcher`. This allows to use the same `fetch` function that is already used in your application. There are several ways to do this:
### Are you using `Axios`, `Ky`, `Got`, `node:http` or any other HTTP library?

#### Native `fetch`
If native `fetch` is available, you don't have to pass it as argument to `createSignedFetcher`.
Install the `aws-sigv4-sign` package and use the `signRequest` function to create a signed request:

```ts
import { createSignedFetcher } from 'aws-sigv4-fetch';
import { signRequest } from 'aws-sigv4-sign';
import axios from 'axios';

// native fetch is available and doesn't have to be passed as argument
const signedFetch = createSignedFetcher({ service: 'iam', region: 'eu-west-1' });
```
const signedRequest = await signRequest('https://mylambda.lambda-url.eu-west-1.on.aws/', {
service: 'lambda',
region: 'eu-west-1'
});

#### Polyfill `fetch`
Install a fetch package like [`cross-fetch`](https://www.npmjs.com/package/cross-fetch) and import it as [polyfill](https://en.wikipedia.org/wiki/Polyfill_(programming)). The `fetch` function will be available **globally** after importing the polyfill.
const { headers } = signedRequest;

```ts
import 'cross-fetch/polyfill';
import { createSignedFetcher } from 'aws-sigv4-fetch';
console.log(headers.get('authorization')); // AWS4-HMAC-SHA256 Credential=.../20250101/us-east-1/lambda/aws4_request, SignedHeaders=host;x-amz-date;x-amz-content-sha256;x-amz-security-token, Signature=...
console.log(headers.get('host')); // mylambda.lambda-url.eu-west-1.on.aws
console.log(headers.get('x-amz-date')); // 20250101T000000Z
console.log(headers.get('x-amz-content-sha256')); // ...
console.log(headers.get('x-amz-security-token')); // only if credentials include a session token

// fetch was imported globally and doesn't have to be passed as argument
const signedFetch = createSignedFetcher({ service: 'iam', region: 'eu-west-1' });
```
// Axios
const response = await axios(signedRequest);

#### Pass `fetch` as an argument
Install a fetch package like [`cross-fetch`](https://www.npmjs.com/package/cross-fetch) and import it as [ponyfill](https://github.com/sindresorhus/ponyfill). The `fetch` function will be available **locally** after importing the ponyfill. Pass the `fetch` function as an argument to `createSignedFetcher`:
// Ky
const response = await ky.request(signedRequest);

```ts
import fetch from 'cross-fetch';
import { createSignedFetcher } from 'aws-sigv4-fetch';
// Got
const response = await got(signedRequest);

// fetch was imported locally and must be passed as argument
const signedFetch = createSignedFetcher({ service: 'iam', region: 'eu-west-1', fetch });
// node:http
const response = await httpRequest(signedRequest);
```

## Examples
### Are you using `graphql-request`?

### AWS
Here are some examples of common AWS services.

```ts
// API Gateway
const signedFetch = createSignedFetcher({ service: 'execute-api', region: 'eu-west-1' });
const response = await signedFetch('https://myapi.execute-api.eu-west-1.amazonaws.com/my-stage/my-resource');

// Lambda Function URL
const signedFetch = createSignedFetcher({ service: 'lambda', region: 'eu-west-1' });
const response = await signedFetch(new URL('https://mylambda.lambda-url.eu-west-1.on.aws/'));

// AppSync
const signedFetch = createSignedFetcher({ service: 'appsync', region: 'eu-west-1' });
const response = await signedFetch('https://mygraphqlapi.appsync-api.eu-west-1.amazonaws.com/graphql', {
method: 'POST',
body: JSON.stringify({ a: 1 }),
headers: {'Content-Type': 'application/json'}
});
```

### Automatically sign GraphQL Requests with `graphql-request`
If you are using [`graphql-request`](https://www.npmjs.com/package/graphql-request) as GraphQL library, you can easily sign all HTTP requests. The library has `fetch` option to pass a [custom `fetch` method](https://github.com/prisma-labs/graphql-request#using-a-custom-fetch-method):
Install the `aws-sigv4-fetch` package and use the `createSignedFetcher` function to create a signed fetch function and pass it to the `fetch` option of the `GraphQLClient`:

```ts
import { createSignedFetcher } from 'aws-sigv4-fetch';
Expand Down Expand Up @@ -175,6 +94,10 @@ const client = new GraphQLClient('https://mygraphqlapi.appsync-api.eu-west-1.ama
const result = await client.request(query, variables);
```

## Usage

Go to the docs of [aws-sigv4-fetch](packages/aws-sigv4-fetch/README.md) or [aws-sigv4-sign](packages/aws-sigv4-sign/README.md) for more information.

## Resources
- [Sign GraphQL Request with AWS IAM and Signature V4](https://dev.to/zirkelc/sign-graphql-request-with-aws-iam-and-signature-v4-2il6)
- [Amplify Signing a request from Lambda](https://docs.amplify.aws/lib/graphqlapi/graphql-from-nodejs/q/platform/js/#signing-a-request-from-lambda)
Expand Down
Loading

0 comments on commit 175e38b

Please sign in to comment.