Skip to content

Commit

Permalink
[Cloud Run] Identity Platform + Cloud SQL sample (#1984)
Browse files Browse the repository at this point in the history
  • Loading branch information
averikitsch authored Oct 21, 2020
1 parent ce1bde3 commit edae294
Show file tree
Hide file tree
Showing 25 changed files with 1,351 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .kokoro/build-with-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,13 @@ if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"release"* ]]; then
trap notify_buildcop EXIT HUP
fi

# Configure Cloud SQL variables for deploying idp-sql sample
export DB_NAME="kokoro_ci"
export DB_USER="kokoro_ci"
export DB_PASSWORD=$(cat $KOKORO_GFILE_DIR/secrets-sql-password.txt)
export CLOUD_SQL_CONNECTION_NAME=$(cat $KOKORO_GFILE_DIR/secrets-pg-connection-name.txt)

export IDP_KEY=$(gcloud secrets versions access latest --secret="nodejs-docs-samples-idp-key" --project="${GOOGLE_CLOUD_PROJECT}")

npm test
npm run --if-present system-test
7 changes: 7 additions & 0 deletions .kokoro/run/idp-sql.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Format: //devtools/kokoro/config/proto/build.proto

# Set the folder in which the tests are run
env_vars: {
key: "PROJECT"
value: "run/idp-sql"
}
1 change: 1 addition & 0 deletions run/idp-sql/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
26 changes: 26 additions & 0 deletions run/idp-sql/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2020 Google LLC. All rights reserved.
# Use of this source code is governed by the Apache 2.0
# license that can be found in the LICENSE file.

# Use the official lightweight Node.js 10 image.
# https://hub.docker.com/_/node
FROM node:12-slim

# Create and change to the app directory.
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./

# Install dependencies.
# If you add a package-lock.json speed your build by switching to 'npm ci'.
# RUN npm ci --only=production
RUN npm install --production

# Copy local code to the container image.
COPY . ./

# Run the web service on container startup.
CMD [ "node", "index.js" ]
85 changes: 85 additions & 0 deletions run/idp-sql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Cloud Run End User Authentication with PostgreSQL Database Sample

This sample integrates with the Identity Platform to authenticate users to the
application and connects to a Cloud SQL postgreSQL database for data storage.

Use it with the [End user Authentication for Cloud Run](http://cloud.google.com/run/docs/tutorials/identity-platform).

For more details on how to work with this sample read the [Google Cloud Run Node.js Samples README](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/master/run).

[![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run)

## Dependencies

* **express**: Web server framework
* **winston**: Logging library
* **@google-cloud/secret-manager**: Google Secret Manager client library
* **firebase-admin**: Verifying JWT token
* **knex** + **pg**: A postgreSQL query builder library
* **handlebars.js**: Template engine
* **google-auth-library-nodejs**: Access [compute metadata server](https://cloud.google.com/compute/docs/storing-retrieving-metadata) for project ID
* **Firebase JavaScript SDK**: client-side library for authentication flow

## Environment Variables

Cloud Run services can be [configured with Environment Variables](https://cloud.google.com/run/docs/configuring/environment-variables).
Required variables for this sample include:

* `CLOUD_SQL_CREDENTIALS_SECRET`: the resource ID of the secret, in format: `projects/PROJECT_ID/secrets/SECRET_ID/versions/VERSION`. See [postgres-secrets.json](postgres-secrets.json) for secret content.

OR

* `CLOUD_SQL_CONNECTION_NAME`: Cloud SQL instance name, in format: `<MY-PROJECT>:<INSTANCE-REGION>:<MY-DATABASE>`
* `DB_NAME`: Cloud SQL postgreSQL database name
* `DB_USER`: database user
* `DB_PASSWORD`: database password

Other environment variables:

* Set `TABLE` to change the postgreSQL database table name.

* Set `DB_HOST` to use the proxy with TCP. See instructions below.

* Set `DB_SOCKET_PATH` to change the directory when using the proxy with Unix sockets.
See instructions below.

## Production Considerations

* Both `postgres-secrets.json` and `static/config.js` should not be committed to
a git repository and should be added to `.gitignore`.

* Saving credentials directly as environment variables is convenient for local testing,
but not secure for production; therefore using `CLOUD_SQL_CREDENTIALS_SECRET`
in combination with the Cloud Secrets Manager is recommended.

## Running Locally

1. Set [environment variables](#environment-variables).

1. To run this application locally, download and install the `cloud_sql_proxy` by
[following the instructions](https://cloud.google.com/sql/docs/postgres/sql-proxy#install).

The proxy can be used with a TCP connection or a Unix Domain Socket. On Linux or
Mac OS you can use either option, but on Windows the proxy currently requires a TCP
connection.

* [Instructions to launch proxy with Unix Domain Socket](../../cloud-sql/postgres/knex#launch-proxy-with-unix-domain-socket)

* [Instructions to launch proxy with TCP](../../cloud-sql/postgres/knex#launch-proxy-with-tcp)

## Testing

Tests expect the Cloud SQL instance to already be created and environment Variables
to be set.

### Unit tests

```
npm run test
```

### System tests

```
npm run system-test
```
119 changes: 119 additions & 0 deletions run/idp-sql/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const { getVotes, getVoteCount, insertVote } = require('./cloud-sql');
const express = require('express');
const { buildRenderedHtml } = require('./handlebars');
const { authenticateJWT, requestLogger } = require('./middleware');

const app = express();
app.use(express.static(__dirname + '/static'));

// Automatically parse request body as form data.
app.use(express.urlencoded({extended: false}));
app.use(express.json());

// Set Content-Type for all responses for these routes.
app.use((req, res, next) => {
res.set('Content-Type', 'text/html');
next();
});

app.get('/', requestLogger, async (req, res) => {
try {
// Query the total count of "CATS" from the database.
const catsResult = await getVoteCount('CATS');
const catsTotalVotes = parseInt(catsResult[0].count);
// Query the total count of "DOGS" from the database.
const dogsResult = await getVoteCount('DOGS');
const dogsTotalVotes = parseInt(dogsResult[0].count);
// Query the last 5 votes from the database.
const votes = await getVotes();
// Calculate and set leader values.
let leadTeam = '';
let voteDiff = 0;
let leaderMessage = '';
if (catsTotalVotes !== dogsTotalVotes) {
if (catsTotalVotes > dogsTotalVotes) {
leadTeam = 'CATS';
voteDiff = catsTotalVotes - dogsTotalVotes;
} else {
leadTeam = 'DOGS';
voteDiff = dogsTotalVotes - catsTotalVotes;
}
leaderMessage = `${leadTeam} are winning by ${voteDiff} vote${voteDiff > 1 ? 's' : ''}.`;
} else {
leaderMessage = 'CATS and DOGS are evenly matched!';
}

// Add variables to Handlebars.js template
const renderedHtml = await buildRenderedHtml({
votes: votes,
catsCount: catsTotalVotes,
dogsCount: dogsTotalVotes,
leadTeam: leadTeam,
voteDiff: voteDiff,
leaderMessage: leaderMessage,
});
res.status(200).send(renderedHtml);
} catch (err) {
const message = "Error while connecting to the Cloud SQL database. " +
"Check that your username and password are correct, that the Cloud SQL " +
"proxy is running (locally), and that the database/table exists and is " +
`ready for use: ${err}`;
req.logger.error(message); // request-based logger with trace support
res
.status(500)
.send('Unable to load page; see logs for more details.')
.end();
}
});

app.post('/', requestLogger, authenticateJWT, async (req, res) => {
// Get decoded Id Platform user id
const uid = req.uid;
// Get the team from the request and record the time of the vote.
const {team} = req.body;
const timestamp = new Date();

if (!team || (team !== 'CATS' && team !== 'DOGS')) {
res.status(400).send('Invalid team specified.').end();
return;
}

// Create a vote record to be stored in the database.
const vote = {
candidate: team,
time_cast: timestamp,
uid,
};

// Save the data to the database.
try {
await insertVote(vote);
req.logger.info({message: 'vote_inserted', vote}); // request-based logger with trace support
} catch (err) {
req.logger.error(`Error while attempting to submit vote: ${err}`);
res
.status(500)
.send('Unable to cast vote; see logs for more details.')
.end();
return;
}
res.status(200).send(`Successfully voted for ${team} at ${timestamp}`).end();
});

module.exports = app;
27 changes: 27 additions & 0 deletions run/idp-sql/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "idp-sql",
"env": {
"DB_PASSWORD": {
"description": "postgreSQL password for root user"
},
"CLOUD_SQL_INSTANCE_NAME": {
"description": "Cloud SQL instance name",
"value": "idp-sql-instance"
},
"API_KEY": {
"description": "Identity Platform API key from Application Setup Details"
}
},
"hooks": {
"precreate": {
"commands": [
"./setup.sh"
]
},
"postcreate": {
"commands": [
"./postcreate.sh"
]
}
}
}
Loading

0 comments on commit edae294

Please sign in to comment.