Skip to content

Commit

Permalink
✨ Introduce a serverless token auth proxy, fixes #20
Browse files Browse the repository at this point in the history
We're using a serverless approach to GitHub GraphQL API authentication
thanks to a Cloud Function. It acts as a proxy to the API handling the
Personal Access Token on the server side. The serverless function should
be configured with `GITHUB_GRAPHQL_API_URL` and `GITHUB_PAT` and is
deployed using Terraform.
The serverless function is currently deployed to:
https://us-east1-gitpop3.cloudfunctions.net/gitpop3-graphql
  • Loading branch information
AndreMiras committed Jan 28, 2024
1 parent 1673510 commit c3234e8
Show file tree
Hide file tree
Showing 21 changed files with 1,433 additions and 38 deletions.
14 changes: 10 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# the base64 encoded Personal Access Token
# https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token
# echo $PAT | base64
REACT_APP_GITHUB_PAT=<base64_encoded_personal_access_token>
## React environment variables (loaded automatically)
# the GraphQL endpoint that handles the Personal Access Token before forwarding to the GitHub GraphQL API
REACT_APP_GRAPHQL_ENDPOINT=https://us-east1-gitpop3.cloudfunctions.net/gitpop3-graphql
## Cloud Function environment variables (must be manually sourced)
# CORS header
ALLOWED_ORIGINS=http://localhost:3000,https://andremiras.github.io
# the API we proxy to
GITHUB_GRAPHQL_API_URL=https://api.github.com/graphql
# the Personal Access Token with the `public_repo` scope
GITHUB_PAT=
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ jobs:
- run: yarn install
- run: yarn build
env:
REACT_APP_GITHUB_PAT: ${{ secrets.REACT_APP_GITHUB_PAT }}
REACT_APP_GRAPHQL_ENDPOINT: https://us-east1-gitpop3.cloudfunctions.net/gitpop3-graphql
REACT_APP_SENTRY_DSN: https://46cd951350084768a0306d2c223f7805@o87984.ingest.sentry.io/5575586
- run: yarn deploy
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- run: yarn install
- run: yarn test
env:
REACT_APP_GITHUB_PAT: ${{ secrets.REACT_APP_GITHUB_PAT }}
REACT_APP_GRAPHQL_ENDPOINT: https://us-east1-gitpop3.cloudfunctions.net/gitpop3-graphql
- run: yarn lint
- uses: coverallsapp/github-action@v1.1.2
with:
Expand Down
11 changes: 9 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
.env

# dependencies
/node_modules
node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build
build
cloud_function_archive

# misc
.DS_Store
Expand All @@ -24,3 +25,9 @@ yarn-error.log*

# Optional eslint cache
.eslintcache

# Terraform
terraform.tfvars
terraform-service-key.json
.terraform*
terraform.tfstate*
32 changes: 32 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
SHELL=/bin/bash


devops/terraform/fmt:
terraform -chdir=terraform fmt -recursive -diff

devops/terraform/init:
terraform -chdir=terraform init

devops/terraform/plan:
terraform -chdir=terraform plan

devops/terraform/apply:
terraform -chdir=terraform apply -auto-approve

devops/terraform/output:
terraform -chdir=terraform output

lint/terraform:
terraform -chdir=terraform fmt -recursive -check -diff

lint/node:
yarn lint

lint: lint/node lint/terraform

format/node:
yarn format

format/terraform: devops/terraform/fmt

format: format/node format/terraform
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,29 @@ It allows you to sort forks by "Stars", "Forks" or "Commits" count.
See [GitPop2](https://github.com/AndreMiras/gitpop2) for the same tool using backend tech.

## Run

```sh
yarn start
```

## Test

```sh
yarn lint
yarn test
```

## Deployment

The app can be deployed on GitHub pages when releasing via:

```sh
yarn deploy
```

Note a [Personal Access Token](https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#authenticating-with-graphql) should be generated with the `public_repo` scope and set to its base64 form to the `REACT_APP_GITHUB_PAT` environment variable.
This is required for [Authenticating with GraphQL](https://docs.github.com/en/free-pro-team@latest/graphql/guides/forming-calls-with-graphql#authenticating-with-graphql).

## Cloud Function

See the [serverless](serverless) folder documentation.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"build": "react-scripts build",
"test": "react-scripts test --coverage",
"eject": "react-scripts eject",
"lint": "npx prettier --check src",
"format": "npx prettier --write src",
"lint": "npx prettier --check README.md src serverless terraform",
"format": "npx prettier --write README.md src serverless terraform",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
Expand Down
30 changes: 30 additions & 0 deletions serverless/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Serverless function

We're using a serverless approach to GitHub GraphQL API authentication thanks to a Cloud Function.
It acts as a proxy to the API handling the Personal Access Token on the server side.

## Configuration

It requires the following environment variables to be setup

- `ALLOWED_ORIGINS`
- `GITHUB_GRAPHQL_API_URL`
- `GITHUB_PAT`

## Run

To run locally, build and run with:

```
npx tsc
source .env
npx functions-framework --target=main
```

## Deployment

The Cloud Function is deployed using Terraform from the project root directory.

```sh
make devops/terraform/apply
```
14 changes: 14 additions & 0 deletions serverless/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "gitpop3-graphql",
"version": "1.0.0",
"description": "A GitHub GraphQL API proxy to handle PAT token authentication server side.",
"main": "build/index.js",
"scripts": {
"start": "npx functions-framework --target=main"
},
"author": "",
"dependencies": {},
"devDependencies": {
"@google-cloud/functions-framework": "^3.3.0"
}
}
54 changes: 54 additions & 0 deletions serverless/src/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Request, Response } from "express";
import { HttpFunction } from "@google-cloud/functions-framework";

/**
* Sets CORS headers on the response based on the request's origin.
* Allows only origins specified in the allowedOrigins array.
*
* @param {Request} req - The incoming request object.
* @param {Response} res - The outgoing response object.
* @param {string[]} allowedOrigins - A list of allowed origins for CORS.
*/
const setCors = (req: Request, res: Response, allowedOrigins: string[]) => {
const origin = req.headers.origin;
if (origin && allowedOrigins.includes(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
}
res.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
res.set("Access-Control-Allow-Headers", "Content-Type");
};

/**
* Main function for handling incoming HTTP requests.
* Forwards the request to the GitHub GraphQL API with appropriate authorization.
*
* @param {Request} req - The incoming request object.
* @param {Response} res - The outgoing response object.
*/
const main: HttpFunction = async (req: Request, res: Response) => {
const githubApiUrl = process.env.GITHUB_GRAPHQL_API_URL;
const githubPat = process.env.GITHUB_PAT;
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [];
if (!githubApiUrl || !githubPat) {
res.status(500).send("GitHub API URL or PAT is not configured.");
return;
}
setCors(req, res, allowedOrigins);
try {
const response = await fetch(githubApiUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${githubPat}`,
"Content-Type": "application/json",
},
body: JSON.stringify(req.body),
});
const data = await response.text();
res.status(response.status).send(data);
} catch (error) {
console.error("Error forwarding request to GitHub:", error);
res.status(500).send("Error forwarding request to GitHub.");
}
};

export { main };
1 change: 1 addition & 0 deletions serverless/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { main } from "./controller";
16 changes: 16 additions & 0 deletions serverless/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"esModuleInterop": true,
"moduleResolution": "node",
"declaration": true,
"outDir": "./build",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true,
"preserveConstEnums": true
},
"include": ["src/**/*"]
}
Loading

0 comments on commit c3234e8

Please sign in to comment.