-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Cloud Run] Identity Platform + Cloud SQL sample (#1984)
- Loading branch information
1 parent
ce1bde3
commit edae294
Showing
25 changed files
with
1,351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] | ||
} | ||
} | ||
} |
Oops, something went wrong.