Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect to API Gateway #153

Merged
merged 37 commits into from
Jun 7, 2021
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e07dad4
WIP: Need to load app certificate in request.
kevmo May 10, 2021
9f1ba1f
Merge branch 'main' of https://github.com/cagov/ui-claim-tracker into…
kevmo May 11, 2021
36942ea
Fixing TS errors
kevmo May 11, 2021
0093c74
WIP: Console logging data.
kevmo May 14, 2021
530d7ff
App is successfully connecting to API gateway, if proper .p12 file is…
kevmo May 19, 2021
c46d872
Cleanup & improvement of codebase.
kevmo May 20, 2021
91c73a4
Code cleanup.
kevmo May 20, 2021
ef236ff
Development cruft removal.
kevmo May 20, 2021
d3a8430
Move pfx file name into an environmental variable.
kevmo May 20, 2021
781fe00
buildApiUrl function.
kevmo May 21, 2021
686c65e
remove unneeded comment
kevmo May 21, 2021
d27731b
Removing comments.
kevmo May 21, 2021
a64b2ba
TypeScript finesse.
kevmo May 21, 2021
9516c4b
Merge commit.
kevmo May 24, 2021
fcf1121
WIP
kevmo May 25, 2021
3250af2
WIP
kevmo May 26, 2021
3d58ece
TS Error resolved
kevmo May 27, 2021
457c0d4
TS Errors Resolved + agent.destroy
kevmo May 27, 2021
7ba14b4
Only load .env file when doing local development.
kevmo May 28, 2021
03a1f95
Remove console.log
kevmo May 28, 2021
ec1c2db
Merge main.
kevmo May 28, 2021
8455c6b
Stop adding in previously deleted file.
kevmo May 28, 2021
2a60679
Snapshots.
kevmo May 28, 2021
95cd590
prettier fixes and eslint bypass
kalvinwang May 28, 2021
99a160f
revert unnecessary change
kalvinwang May 28, 2021
11887b7
Commenting.
kevmo May 28, 2021
50fb37c
debug
kalvinwang May 28, 2021
a537aa0
debug
kalvinwang May 28, 2021
bed35a0
Remove certificate password.
rocketnova Jun 1, 2021
7590f65
Remove console.log statements
rocketnova Jun 2, 2021
ab674ca
Refactor env vars and remove non-null assertion.
rocketnova Jun 2, 2021
5945577
Update readme with documentation of env vars.
rocketnova Jun 2, 2021
fac89ac
Update test snapshot: slightly less whitespace.
rocketnova Jun 2, 2021
5d52672
Temporarily do some nullish coalescing.
rocketnova Jun 2, 2021
fd577ab
Remove TODOs.
rocketnova Jun 7, 2021
b0b1a48
Remove extra argument.
rocketnova Jun 7, 2021
2bd6177
Apply suggestions from code review: update comments
rocketnova Jun 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ module.exports = {
rules: {
"react/react-in-jsx-scope": "off", //React is provided by NextJS instead
"react/prop-types": "off", //strict TypeScript provides the same benefits
"camelcase": ["error", {"allow": ["user_key"]}], // necessary API parameter - index.tsx
rocketnova marked this conversation as resolved.
Show resolved Hide resolved
}
}
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,30 @@ relates to weekly certification
## Running the Application

**Prerequisites:**
- Node 12+
- yarn

rocketnova marked this conversation as resolved.
Show resolved Hide resolved
- Node 12+
- yarn

**Run this app**

Clone this repo, and then run:
1. Clone this repo
2. Run `yarn install`
3. Define environment variables (see below)
4. Run `yarn dev`
5. Open [http://localhost:3000/claimstatus](http://localhost:3000/claimstatus) with your browser to see the result

```bash
yarn install
yarn dev
```
### Environment Variables

- ID_HEADER_NAME: The name of the header that contains the unique ID in the incoming request
rocketnova marked this conversation as resolved.
Show resolved Hide resolved
- API_URL: The url for the API
- API_USER_KEY: The user key for the API
- CERTIFICATE_DIR: The path to the client certificate (certificate must be in PFX/P12 format)
- P12_FILE: The name of the client certificate file

For local development:

Open [http://localhost:3000/claimstatus](http://localhost:3000/claimstatus) with your browser to see the result.
1. Create a `.env` file in the root of this repo
2. Define each of the environment variables above

## Running the test suite

Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
"dependencies": {
"@types/pino": "^6.3.8",
"bootstrap": "^4.6.0",
"dotenv": "^9.0.2",
"next": "10.0.8",
"node-fetch": "^2.6.1",
"pem": "^1.14.4",
"pino": "^6.11.3",
"react": "17.0.1",
"react-bootstrap": "^1.5.2",
Expand Down Expand Up @@ -51,6 +54,7 @@
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@types/node": "^14.14.33",
"@types/pem": "^1.9.5",
"@types/react": "^17.0.3",
"@types/react-test-renderer": "^17.0.1",
"@typescript-eslint/eslint-plugin": "^4.19.0",
Expand Down
8 changes: 8 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { AppProps } from 'next/app'
import { ReactElement } from 'react'
import { appWithTranslation } from 'next-i18next'

import { config } from 'dotenv'
import { resolve } from 'path'

// Loads .env for local development.
if (process.env.NODE_ENV === 'development') {
config({ path: resolve(process.cwd(), '..', '.env') })
}

function MyApp({ Component, pageProps }: AppProps): ReactElement {
return <Component {...pageProps} />
}
Expand Down
134 changes: 132 additions & 2 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import fs from 'fs'
import path from 'path'
import https from 'https'
import { promisify } from 'util'

import Head from 'next/head'
import Container from 'react-bootstrap/Container'
import pino from 'pino'
import { ReactElement } from 'react'
import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { GetServerSideProps } from 'next'
import pem, { Pkcs12ReadResult } from 'pem'
import fetch, { Response } from 'node-fetch'

import { Header } from '../components/Header'
import { Main } from '../components/Main'
import { Footer } from '../components/Footer'

export default function Home(): ReactElement {
export interface Claim {
ClaimType: string | 'not working'
Copy link
Contributor

@kalvinwang kalvinwang Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be ?? '' for now and error handled later?

}

export interface HomeProps {
claimData?: Claim[]
}

export default function Home({ claimData }: HomeProps): ReactElement {
const { t } = useTranslation('common')

return (
Expand All @@ -19,21 +34,136 @@ export default function Home(): ReactElement {
<title>{t('title')}</title>
<link rel="icon" href="/favicon.ico" />
</Head>

<Header />
<Main />
<Footer />
{console.dir({ claimData })} {/* @TODO: Remove. For development purposes only. */}
</Container>
)
}

export interface QueryParams {
user_key: string
uniqueNumber: string
}

function buildApiUrl(url: string, queryParams: QueryParams) {
const apiUrl = new URL(url)

for (const key in queryParams) {
apiUrl.searchParams.append(key, queryParams[key as 'user_key' | 'uniqueNumber'])
}

return apiUrl.toString()
}

export const getServerSideProps: GetServerSideProps = async ({ req, locale }) => {
const isProd = process.env.NODE_ENV === 'production'
const logger = isProd ? pino({}) : pino({ prettyPrint: true })
logger.info(req)

/*
* Load environment variables to be used for authentication & API calls.
* @TODO: Handle error case where env vars are null or undefined.
rocketnova marked this conversation as resolved.
Show resolved Hide resolved
*/
// Request fields
const ID_HEADER_NAME: string = process.env.ID_HEADER_NAME ?? ''

// API fields
const API_URL: string = process.env.API_URL ?? ''
const API_USER_KEY: string = process.env.API_USER_KEY ?? ''

// TLS Certificate fields
const CERT_DIR: string = process.env.CERTIFICATE_DIR ?? ''
const P12_FILE: string = process.env.P12_FILE ?? ''
const P12_PATH: string = path.join(CERT_DIR, P12_FILE)

let apiData: JSON | null = null

// Returns certificate object with cert, key, and ca fields.
// https://dexus.github.io/pem/jsdoc/module-pem.html#.readPkcs12
async function getCertificate() {
const pemReadPkcs12 = promisify(pem.readPkcs12)
const pfx = fs.readFileSync(P12_PATH)

// TS does not play very nicely with util.promisify
// See, e.g., https://github.com/Microsoft/TypeScript/issues/26048
// Non-MVP TODO: Consider removing this ignore & TypeScriptifying.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore -- TypeScript does not handle promisify well.
const keybundle = await pemReadPkcs12(pfx, {})
rocketnova marked this conversation as resolved.
Show resolved Hide resolved
return keybundle
}

// Takes certificate that getCertificate function returns as argument,
// makes API call, returns all API data.
async function makeRequest(certificate: Pkcs12ReadResult) {
const headers = {
Accept: 'application/json',
}

/* https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options

// TODO: Store certs in memory, instead of loading every time.
rocketnova marked this conversation as resolved.
Show resolved Hide resolved
// TODO: rejectUnauthorized - set to true or implement a `checkServerIdentity`
rocketnova marked this conversation as resolved.
Show resolved Hide resolved
// function to check that the certificate is actually
// issued by the host you're connecting to.
// https://nodejs.org/api/https.html#https_https_request_url_options_callback
*/
const options = {
cert: certificate.cert,
key: certificate.key,
rejectUnauthorized: false,
keepAlive: false,
}

// Instantiate agent to use with TLS Certificate.
// Reference: https://sebtrif.xyz/blog/2019-10-03-client-side-ssl-in-node-js-with-fetch/
const sslConfiguredAgent: https.Agent = new https.Agent(options)

// TODO: if no uniqueNumber, redirect.
rocketnova marked this conversation as resolved.
Show resolved Hide resolved
const apiUrlParams: QueryParams = {
user_key: API_USER_KEY,
uniqueNumber: req.headers[ID_HEADER_NAME] as string,
}

const apiUrl: RequestInfo = buildApiUrl(API_URL, apiUrlParams)

try {
const response: Response = await fetch(apiUrl, {
headers: headers,
agent: sslConfiguredAgent,
})

// TODO: Why does @ts-ignore not work on this line?
// TODO: Implement proper typing of responseBody if possible.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const responseBody: JSON = await response.json()

apiData = responseBody
} catch (error) {
console.log(error)
}

// Explicitly destroy agent so connection does not persist.
// https://nodejs.org/api/http.html#http_agent_destroy
// There were reports (SNAT?) of connection pool problems,
// which could be caused by testing? Either way, explicitly destroy the HTTP Agent.
sslConfiguredAgent.destroy()

return apiData
}

// The 3 steps where the above code is invoked and getServerSideProps returns props.
// Step 1: Get the certificate.
const certificate = await getCertificate()
// Step 2: Use certificate to make the API request and return the data.
const data = await makeRequest(certificate)

// Step 3: Return Props
return {
props: {
claimData: [data],
...(await serverSideTranslations(locale || 'en', ['common', 'header', 'footer'])),
},
}
Expand Down
1 change: 1 addition & 0 deletions tests/pages/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -492,5 +492,6 @@ exports[`Exemplar react-test-renderer Snapshot test renders homepage unchanged 1
</div>
</div>
</footer>

</div>
`;
50 changes: 48 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3271,6 +3271,13 @@
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109"
integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==

"@types/pem@^1.9.5":
version "1.9.5"
resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.5.tgz#cd5548b5e0acb4b41a9e21067e9fcd8c57089c99"
integrity sha512-C0txxEw8B7DCoD85Ko7SEvzUogNd5VDJ5/YBG8XUcacsOGqxr5Oo4g3OUAfdEDUbhXanwUoVh/ZkMFw77FGPQQ==
dependencies:
"@types/node" "*"

"@types/pino-pretty@*":
version "4.7.0"
resolved "https://registry.yarnpkg.com/@types/pino-pretty/-/pino-pretty-4.7.0.tgz#e4a18541f8464d1cc48216f5593cc6a0e62dc2c3"
Expand Down Expand Up @@ -4979,6 +4986,11 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==

charenc@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=

chokidar@3.5.1, "chokidar@>=2.0.0 <4.0.0", chokidar@^3.4.1, chokidar@^3.4.2:
version "3.5.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
Expand Down Expand Up @@ -5541,6 +5553,11 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"

crypt@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=

crypto-browserify@3.12.0, crypto-browserify@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
Expand Down Expand Up @@ -6068,6 +6085,11 @@ dotenv@^8.0.0, dotenv@^8.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==

dotenv@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05"
integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==

downshift@^6.0.15:
version "6.1.2"
resolved "https://registry.yarnpkg.com/downshift/-/downshift-6.1.2.tgz#99d9a03d4da4bf369df766effc3b70f7e789950e"
Expand Down Expand Up @@ -6321,6 +6343,11 @@ es5-shim@^4.5.13:
resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.15.tgz#6a26869b261854a3b045273f5583c52d390217fe"
integrity sha512-FYpuxEjMeDvU4rulKqFdukQyZSTpzhg4ScQHrAosrlVpR6GFyaw14f74yn2+4BugniIS0Frpg7TvwZocU4ZMTw==

es6-promisify@^6.0.0:
version "6.1.1"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.1.1.tgz#46837651b7b06bf6fff893d03f29393668d01621"
integrity sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==

es6-shim@^0.35.5:
version "0.35.6"
resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.6.tgz#d10578301a83af2de58b9eadb7c2c9945f7388a0"
Expand Down Expand Up @@ -8244,7 +8271,7 @@ is-boolean-object@^1.1.0:
dependencies:
call-bind "^1.0.0"

is-buffer@^1.1.5:
is-buffer@^1.1.5, is-buffer@~1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
Expand Down Expand Up @@ -9780,6 +9807,15 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"

md5@^2.2.1:
version "2.3.0"
resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
dependencies:
charenc "0.0.2"
crypt "0.0.2"
is-buffer "~1.1.6"

mdast-squeeze-paragraphs@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97"
Expand Down Expand Up @@ -10608,7 +10644,7 @@ os-browserify@^0.3.0:
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=

os-tmpdir@~1.0.2:
os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
Expand Down Expand Up @@ -10921,6 +10957,16 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"

pem@^1.14.4:
version "1.14.4"
resolved "https://registry.yarnpkg.com/pem/-/pem-1.14.4.tgz#a68c70c6e751ccc5b3b5bcd7af78b0aec1177ff9"
integrity sha512-v8lH3NpirgiEmbOqhx0vwQTxwi0ExsiWBGYh0jYNq7K6mQuO4gI6UEFlr6fLAdv9TPXRt6GqiwE37puQdIDS8g==
dependencies:
es6-promisify "^6.0.0"
md5 "^2.2.1"
os-tmpdir "^1.0.1"
which "^2.0.2"

pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
Expand Down