Skip to content

Commit

Permalink
Merge pull request #9 from checkr/encryption-upgrade
Browse files Browse the repository at this point in the history
Use encryption library and fix tests
  • Loading branch information
rosatolen authored Jul 22, 2022
2 parents b30cb5b + 3a7ce3e commit 51b13e3
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 139 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ CHECKR_API_URL='https://api.checkr-staging.com/v1'
CHECKR_OAUTH_URL='https://api.checkr-staging.com/oauth'
CHECKR_OAUTH_CLIENT_ID=your_partner_application_client_id
CHECKR_OAUTH_CLIENT_SECRET=your_partner_application_client_secret

# Encryption key for local test environment
ENCRYPTION_SECRET_KEY_FROM_SECURE_VAULT='65520b062cff37a7b7632d0da163025dc39b17497bb16de6c42c3820da88c825'
34 changes: 5 additions & 29 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ jobs:
- run: yarn setup
- run: yarn lint:js
- run: yarn lint:style
macos_test_frontend:
macos_test:
runs-on: macos-latest
strategy:
matrix:
node-version: [16.x, 18.x]
node-version: [16.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -27,24 +27,12 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: yarn setup
- run: yarn test:frontend:ci
macos_test_backend:
runs-on: macos-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: yarn setup
- run: yarn test:backend
windows_test_frontend:
windows_test:
runs-on: windows-latest
strategy:
matrix:
node-version: [16.x, 18.x]
node-version: [16.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -53,23 +41,11 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: yarn setup
- run: yarn test:frontend:ci
windows_test_backend:
runs-on: windows-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: yarn setup
- run: yarn test:backend
deploy_to_heroku:
if: github.ref_name == 'main'
runs-on: ubuntu-latest
needs: [lint, macos_test_frontend, macos_test_backend, windows_test_frontend, windows_test_backend]
needs: [lint, macos_test, windows_test]
steps:
- uses: actions/checkout@v3
- name: Deploy to Heroku
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ CHECKR_API_URL='https://api.checkr-staging.com/v1'
CHECKR_OAUTH_URL='https://api.checkr-staging.com/oauth'
CHECKR_OAUTH_CLIENT_ID=your_partner_application_client_id
CHECKR_OAUTH_CLIENT_SECRET=your_partner_application_client_secret
ENCRYPTION_SECRET_KEY_FROM_SECURE_VAULT='65520b062cff37a7b7632d0da163025dc39b17497bb16de6c42c3820da88c825'
```

3. Run tests locally:

Backend tests:

```shell
yarn test:backend
```

Watch frontend tests:

```shell
yarn test:frontend
```

3. Run it locally:
Expand Down
47 changes: 19 additions & 28 deletions __tests__/checkr.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ import checkrRouter from '../routes/checkr.js'
import {faker} from '@faker-js/faker'
import {
createAccountWithCheckrAccountId,
//createAccountWithCheckrAccessToken,
createAccountWithCheckrAccessToken,
findAccountWithCheckrId,
findAccountWithId,
} from './testSupport/helpers/accountHelper.js'
import {getValidSignature} from './testSupport/helpers/webhooksHelper.js'
import {
// encrypt,
decrypt,
clearDB,
} from './testSupport/helpers/dbHelper.js'
import {clearDB} from './testSupport/helpers/dbHelper.js'
import {encrypt, decrypt} from '../encryption.js'
import testSeedData from './testSupport/testSeedData.js'
import mockBackend from '../client/src/__tests__/testSupport/helpers/mockBackend.js'

Expand Down Expand Up @@ -43,7 +40,7 @@ describe('/api/checkr', () => {

const updatedAccount = await findAccountWithId(existingAccountId)
expect(updatedAccount.checkrAccount.id).toEqual(expectedCheckrAccountId)
expect(decrypt(updatedAccount.checkrAccount.accessToken)).toEqual(
expect(await decrypt(updatedAccount.checkrAccount.accessToken)).toEqual(
expectedAccessToken,
)
})
Expand All @@ -66,29 +63,23 @@ describe('/api/checkr', () => {
expect(account.checkrAccount.credentialed).toEqual(true)
})

// TODO: for DX-377
// This test is failing because it does not stub the token value with a key, iv, and encryptedData
//
//it('should deauthorize a Checkr account', async () => {
// const existingAccessToken = encrypt(faker.lorem.slug())
// const account = await createAccountWithCheckrAccessToken(
// existingAccessToken,
// )
it('should deauthorize a Checkr account', async () => {
const plaintextToken = faker.lorem.slug()
const encryptedToken = await encrypt(plaintextToken)
const account = await createAccountWithCheckrAccessToken(encryptedToken)

// checkrApiMock.stubHttpPost(`${process.env.CHECKR_OAUTH_URL}/deauthorize`, {
// token: existingAccessToken,
// })
checkrApiMock.stubHttpPost(`${process.env.CHECKR_OAUTH_URL}/deauthorize`)

// const disconnectBody = {
// token: existingAccessToken,
// }
const disconnectBody = {
encryptedToken: encryptedToken,
}

// const disconnect = await checkrApi
// .post('/api/checkr/disconnect')
// .send(disconnectBody)
const disconnect = await checkrApi
.post('/api/checkr/disconnect')
.send(disconnectBody)

// expect(disconnect.status).toEqual(204)
// const updatedAccount = await findAccountWithId(account.id)
// expect(updatedAccount.checkrAccount).toBe(undefined)
//})
expect(disconnect.status).toEqual(204)
const updatedAccount = await findAccountWithId(account.id)
expect(updatedAccount.deauthorized).toBe(true)
})
})
7 changes: 4 additions & 3 deletions __tests__/testSupport/helpers/accountHelper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import database from '../../../db.js'
import {v4 as uuidv4} from 'uuid'
import {faker} from '@faker-js/faker'
import {encrypt} from '../../../encryption.js'

const createAccountWithCheckrAccountId = async id => {
const db = await database()
Expand All @@ -12,7 +13,7 @@ const createAccountWithCheckrAccountId = async id => {
updatedAt: now,
checkrAccount: {
id: id,
accessToken: faker.lorem.slug(),
accessToken: await encrypt(faker.lorem.slug()),
},
})
await db.write()
Expand Down Expand Up @@ -44,7 +45,7 @@ const findAccountWithCheckrId = async id => {
)
}

const createAccountWithCheckrAccessToken = async token => {
const createAccountWithCheckrAccessToken = async encryptedToken => {
const db = await database()
const now = new Date()
const newAccount = {
Expand All @@ -54,7 +55,7 @@ const createAccountWithCheckrAccessToken = async token => {
updatedAt: now,
checkrAccount: {
id: faker.lorem.slug(),
accessToken: token,
accessToken: encryptedToken,
},
}
db.data.accounts.push(newAccount)
Expand Down
2 changes: 2 additions & 0 deletions __tests__/testSupport/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ export default async function () {
process.env.NODE_ENV = 'test'
process.env.NODE_OPTIONS = '--experimental-vm-modules'
process.env.NODE_NO_WARNINGS = 1 // Mute experimental warning logs in console.
process.env.ENCRYPTION_SECRET_KEY =
'65520b062cff37a7b7632d0da163025dc39b17497bb16de6c42c3820da88c825'
}
2 changes: 1 addition & 1 deletion client/src/components/Navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function Settings({createToast, account}) {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({token: account.checkrAccount.accessToken}),
body: JSON.stringify({encryptedToken: account.checkrAccount.accessToken}),
}

return (
Expand Down
32 changes: 32 additions & 0 deletions encryption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import sodium from 'libsodium-wrappers'

const key = process.env.ENCRYPTION_SECRET_KEY

const encrypt = async plaintext => {
await sodium.ready
const nonce = Buffer.from(sodium.randombytes_buf(24))
const ciphertext = Buffer.from(
sodium.crypto_secretbox_easy(plaintext, nonce, Buffer.from(key, 'hex')),
)
return {ciphertext: ciphertext.toString('hex'), nonce: nonce.toString('hex')}
}

const decrypt = async ({ciphertext, nonce}) => {
await sodium.ready
let decrypted = Buffer.from(
sodium.crypto_secretbox_open_easy(
Buffer.from(ciphertext, 'hex'),
Buffer.from(nonce, 'hex'),
Buffer.from(key, 'hex'),
),
)
return sodium.to_string(decrypted)
}

const keygen = async () => {
await sodium.ready
const key = Buffer.from(sodium.crypto_secretbox_keygen())
return key.toString('hex')
}

export {encrypt, decrypt, keygen}
51 changes: 20 additions & 31 deletions helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,8 @@
const {
randomBytes,
createCipheriv,
createDecipheriv,
createHmac,
timingSafeEqual,
} = await import('node:crypto')
import {decrypt} from '../encryption.js'
const {createHmac, timingSafeEqual} = await import('node:crypto')

const key = randomBytes(32)
const iv = randomBytes(16)
const checkrClientSecret = process.env.CHECKR_OAUTH_CLIENT_SECRET

function encrypt(text) {
let cipher = createCipheriv('aes-256-cbc', Buffer.from(key), iv)
let encrypted = cipher.update(text)
encrypted = Buffer.concat([encrypted, cipher.final()])
return {
key: key.toString('hex'),
iv: iv.toString('hex'),
encryptedData: encrypted.toString('hex'),
}
}

function decrypt(text) {
let key = Buffer.from(text.key, 'hex')
let iv = Buffer.from(text.iv, 'hex')
let encryptedText = Buffer.from(text.encryptedData, 'hex')
let decipher = createDecipheriv('aes-256-cbc', key, iv)
let decrypted = decipher.update(encryptedText)
decrypted = Buffer.concat([decrypted, decipher.final()])
return decrypted.toString()
}

const validCheckrSignature = (signature, payload) => {
const expectedMac = createHmac('sha256', checkrClientSecret)
.update(JSON.stringify(payload))
Expand All @@ -49,4 +21,21 @@ const parseJSON = async response => {
}
}

export {encrypt, decrypt, parseJSON, validCheckrSignature}
const findAccountWithMatchingToken = async (
accounts,
expectedPlaintextToken,
) => {
for (const account of accounts) {
if (account.checkrAccount) {
const existingPlaintextToken = await decrypt(
account.checkrAccount.accessToken,
)
if (existingPlaintextToken === expectedPlaintextToken) {
return account
}
}
}
return null
}

export {parseJSON, validCheckrSignature, findAccountWithMatchingToken}
12 changes: 0 additions & 12 deletions jest.config.js

This file was deleted.

28 changes: 25 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"dev:docs": "watch 'yarn docs' ./routes",
"dev:frontend": "cd client/ && yarn dev:frontend",
"docs": "docco routes/checkr.js routes/session-tokens.js -o client/public/docs",
"test:backend": "jest",
"test:backend": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"test:frontend": "cd client/ && yarn test",
"test:frontend:ci": "cd client/ && yarn test:ci",
"start": "node server.js",
Expand All @@ -32,15 +32,16 @@
"express": "^4.18.1",
"express-bearer-token": "^2.4.0",
"jsonwebtoken": "^8.5.1",
"libsodium-wrappers": "^0.7.10",
"lowdb": "^3.0.0",
"morgan": "^1.10.0",
"msw": "^0.42.3",
"node-fetch": "^3.2.6",
"supertest": "^6.2.3",
"uuid": "^8.3.2"
},
"devDependencies": {
"@faker-js/faker": "^7.3.0",
"cross-env": "^7.0.3",
"docco": "^0.9.1",
"eslint": "^8.20.0",
"eslint-config-prettier": "^8.5.0",
Expand All @@ -54,7 +55,7 @@
"jest": "^28.1.1",
"lint-staged": "^13.0.3",
"mocha": "^10.0.0",
"msw": "^0.42.3",
"msw": "^0.44.2",
"nodemon": "^2.0.18",
"prettier": "^2.7.1",
"stylelint": "^14.9.1",
Expand All @@ -64,6 +65,27 @@
"supertest": "^6.2.3",
"watch": "^1.0.2"
},
"jest": {
"transform": {},
"testEnvironment": "node",
"testPathIgnorePatterns": [
"node_modules/",
"client/",
"__tests__/testSupport"
],
"globalSetup": "./__tests__/testSupport/setupTests.js",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"moduleNameMapper": {
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2|svg)$": "identity-obj-proxy"
}
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx,json}": [
"eslint --fix",
Expand Down
Loading

0 comments on commit 51b13e3

Please sign in to comment.