Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Steps for Heroku deployment #5

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
npm-debug.log
.DS_Store
.env
my_steps.txt
119 changes: 117 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The application requires:
- `CORP_DOMAIN` is your corporate domain (i.e.: mycompany.com) used to identify Salesforce users without corporate email
- `COOKIE_SECRET` is a secret used to sign the session cookie
- `ADMIN_TOKEN` is a secret used to edit/delete Org information such as name or description
- `ENCRYPTION_KEY` is a hex string representing 32 random bytes, used to encrypt/decrypt the Oauth refresh tokens (AES 256)
- `ENCRYPTION_KEY` is 32 random bytes (**Please Note:** MUST be hex encoded), used to encrypt/decrypt the Oauth refresh tokens (AES 256)
3. Install Node.js dependencies through Yarn, with `yarn install`
4. Run the server with `node server.js`, confirm you see the `App listening on port 3000` message in the console
5. Load `http://localhost:3000/setup`, confirm you see the `Successfully setup DB` message in the console
Expand All @@ -61,7 +61,122 @@ When ready for production deployment:
- `SAML_ENTRY_POINT`
- `SAML_ISSUER`
- `SAML_CALLBACK`
- `SAML_CERT`
- `SAML_CERT` (maps to the 'cert' configuration parameter: *"Identity Provider's public PEM-encoded X.509 signing certificate using the cert confguration key. The "BEGIN CERTIFICATE" and "END CERTIFICATE" lines should be stripped out and the certificate should be provided on a single line."*)

### Deployment to Heroku
Below are complete steps to deploy the application on Heroku. If you do not have a Heroku account head over to https://signup.heroku.com to create an account first. Once you have an account ensure you have the Heroku CLI (command line interface) installed (see https://devcenter.heroku.com/articles/heroku-cli). The below steps walks through setting up Org Monitor with a Developer Org but the steps applies equally well to a Production org.

#### Clone Git Repo and Create App
```bash
# clone repo
$ git clone [email protected]:forcedotcom/OrgMonitor.git
$ cd OrgMonitor

# create app (also sets git remote)
$ heroku apps:create --region eu

# get appname from git remote
$ APP_NAME=`git remote get-url heroku | cut -d'/' -f4 | cut -d'.' -f1`
$ echo $APP_NAME
funky-medina-23982
```

*Please Note:* Below I simply use funky-medina-23982 to refer to the app on Heroku i.e. what the APP_NAME variable contains now.

#### Create Connected App in Salesforce
1. Open Salesforce Setup
2. Search for "App Manager"
3. Click "New Connected App" and fill in
- Connected App Name
- API Name
- Contact Email
4. Check "Enable OAuth Settings"
5. Set "Callback URL" to https://funky-medina-23982.herokuapp.com/callback (replace with actual app name)
6. Select the following OAuth Scopes:
- Access and manage your data (api)
- Perform requests on your behalf at any time (refresh_token, offline_access)
7. Save to close

#### Gather Info (to replace below)
1. Reopen the Connected app and note down the "Consumer Key" (`CLIENT_ID`) and "Consumer Secret" (`CLIENT_SECRET`))
2. Hex encode 32 characters of random characters (http://www.convertstring.com/EncodeDecode/HexEncode) (`ENCRYPTION_KEY`)
3. Create yourself a password for `ADMIN_TOKEN`
Copy link

Choose a reason for hiding this comment

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

Can elaborate on this further? Where does this token get input?

4. Create yourself a password for `COOKIE_SECRET`

#### Configure Heroku app, push source and open
```bash
# create addons and set config
heroku addons:create mongolab:sandbox
heroku addons:create heroku-postgresql:hobby-dev
heroku config:set CLIENT_ID=foo
heroku config:set CLIENT_SECRET=bar
heroku config:set REDIRECT_URI=https://$APP_NAME.herokuapp.com/callback
heroku config:set CORP_DOMAIN=lekkimworld.com
heroku config:set COOKIE_SECRET=baz
heroku config:set ADMIN_TOKEN=gaz
heroku config:set ENCRYPTION_KEY=3242384142324532343230334337313636384446313944453334394630334436
heroku config:set NODE_ENV=development

# push app source to Heroku
git push heroku master

# start worker dyno
heroku ps:scale -a $APP_NAME worker=1:free

# load /setup to configure app
curl https://$APP_NAME.herokuapp.com/setup

# restart app
heroku restart -a $APP_NAME

# open app in browser
open https://$APP_NAME.herokuapp.com
```

#### Test it out!
Now is a good time to ensure you can open the app in the browser. From here either follow the next section on how to configure SAML for authentication or skip it to move to adding an org to OrgMonitor.

#### My Domain and SAML
1. In Salesforce Setup enable My Domain and deploy to all users (if not enabled). Note down the custom domain you've chosen. Below I use `demoitout.my.salesforce.com` (see when I set SAML_ENTRY_POINT)
2. Search for "Identity Provider" in Setup and ensure Identity Provider is enabled
3. Search for "Single Sign-On Setings" in Setup and open
4. Ensure "SAML Enabled" is checked
5. Open the Connected App you created earlier
6. Check "Enable SAML"
7. Fill in
- Entity Id: "funky-medina-23982" (use actual app name)
- ACS URL: https://funky-medina-23982.herokuapp.com/login/callback
8. Save to close

```bash
# configure app to use SAML for login
heroku config:set SAML_CALLBACK=https://$APP_NAME.herokuapp.com/login/callback
heroku config:set SAML_ISSUER=$APP_NAME
heroku config:set SAML_CERT=MIIErDCCA...96TOK7Ph
heroku config:set SAML_ENTRY_POINT=https://demoitout.my.salesforce.com/idp/endpoint/HttpRedirect

# set NODE_ENV to production to require authentication
heroku config:set -a $APP_NAME NODE_ENV=production

# open app to ensure it requires to to authenticate
open https://$APP_NAME.herokuapp.com
```

#### Create Salesforce user and Profile
1. Open Salesforce Setup
2. Clone the "Standard User" Profile and call it "Org Monitor" (or what ever you wish) and remove all rights, CRUD access etc. Now check the following permissions:
- `API Enabled`
- `View All Users`
- `View Health Check`
- `View Setup and Configuration`
3. Ensure the Profile allows access to the Connected App you created
4. Save the Profile
5. Create a new user assigning the Profile you just created. Remove all rights. Login as the created user using the reset-link as normal.

#### Add org to OrgMonitor
1. Open the app to /add/prod to add a Production / Developer org (using https://login.salesforce.com for login) or /add/sandbox to add a Sandbox org (using https://test.salesforce.com for login)
2. Perform OAuth authorization
3. Ensure org shows up in OrgMonitor with data

## License

Expand Down
9 changes: 9 additions & 0 deletions lib/org.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ class Org {

try {
answer.a = await query(question)
console.log(`Looking for filter for question with name '${question.name}`)
if (answer.a && answer.a.records && question.filter) {
console.log(`Found filter for question with name '${question.name} - invoking it on the records (count: ${answer.a.records.length})`)
let records = answer.a.records.filter(question.filter)
console.log(`Invoking filter for question with name '${question.name} reduced records from ${answer.a.records.length} to ${records.length}`)
answer.a.records = records
answer.a.size = records.length
answer.a.totalSize = records.length
}
} catch (e) {
console.error(`[${this.orgId}] Non-blocking error while querying data:`, { query: question.q, error: JSON.stringify(e) })
answer.a = null
Expand Down
6 changes: 5 additions & 1 deletion lib/questions.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
const express = require('express')
const router = express.Router()
const jsforce = require('jsforce')
// const _ = require('lodash')
const compare = require('secure-compare')
const Org = require('../lib/org.js')
const Crypto = require('../lib/crypto.js')
Expand Down Expand Up @@ -78,7 +77,7 @@ router.get('/callback', async (req, res) => {
instanceUrl: conn.instanceUrl,
loginUrl: conn.loginUrl,
refreshToken: Crypto.encrypt(conn.refreshToken),
healthCheckScore: 'Syncing..'
healthCheckScore: 'Syncing...'
}

// Store credentials in DB
Expand Down
2 changes: 1 addition & 1 deletion worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Org = require('./lib/org.js')

agenda.define('refreshOrg', async (job, done) => {
const jobData = job.attrs.data
console.log(`[${jobData.orgId}] Syncing..`)
console.log(`[${jobData.orgId}] Syncing...`)
let org = await Org.get(jobData.orgId)
let data = await org.fetchRemoteData()
await Org.saveData(data)
Expand Down