appengine/pubsub/app.js (77 lines of code) (raw):
// Copyright 2016 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 express = require('express');
const {OAuth2Client} = require('google-auth-library');
const path = require('path');
const process = require('process'); // Required for mocking environment variables
// By default, the client will authenticate using the service account file
// specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable and use
// the project specified by the GOOGLE_CLOUD_PROJECT environment variable. See
// https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
// These environment variables are set automatically on Google App Engine
const {PubSub} = require('@google-cloud/pubsub');
// Instantiate a pubsub client
const authClient = new OAuth2Client();
const pubsub = new PubSub();
const app = express();
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
// This middleware is available in Express v4.16.0 onwards
const formBodyParser = express.urlencoded({extended: false});
const jsonBodyParser = express.json();
// List of all messages received by this instance
const messages = [];
const claims = [];
const tokens = [];
// The following environment variables are set by app.yaml when running on GAE,
// but will need to be manually set when running locally.
const {PUBSUB_VERIFICATION_TOKEN} = process.env;
const TOPIC = process.env.PUBSUB_TOPIC;
const topic = pubsub.topic(TOPIC);
// [START gae_flex_pubsub_index]
app.get('/', (req, res) => {
res.render('index', {messages, tokens, claims});
});
app.post('/', formBodyParser, async (req, res, next) => {
if (!req.body.payload) {
res.status(400).send('Missing payload');
return;
}
const data = Buffer.from(req.body.payload);
try {
const messageId = await topic.publishMessage({data});
res.status(200).send(`Message ${messageId} sent.`);
} catch (error) {
next(error);
}
});
// [END gae_flex_pubsub_index]
// [START gae_flex_pubsub_push]
app.post('/pubsub/push', jsonBodyParser, (req, res) => {
if (req.query.token !== PUBSUB_VERIFICATION_TOKEN) {
res.status(400).send();
return;
}
// The message is a unicode string encoded in base64.
const message = Buffer.from(req.body.message.data, 'base64').toString(
'utf-8'
);
messages.push(message);
res.status(200).send();
});
// [END gae_flex_pubsub_push]
// [START gae_flex_pubsub_auth_push]
app.post('/pubsub/authenticated-push', jsonBodyParser, async (req, res) => {
// Verify that the request originates from the application.
if (req.query.token !== PUBSUB_VERIFICATION_TOKEN) {
res.status(400).send('Invalid request');
return;
}
// Verify that the push request originates from Cloud Pub/Sub.
try {
// Get the Cloud Pub/Sub-generated JWT in the "Authorization" header.
const bearer = req.header('Authorization');
const [, token] = bearer.match(/Bearer (.*)/);
tokens.push(token);
// Verify and decode the JWT.
// Note: For high volume push requests, it would save some network
// overhead if you verify the tokens offline by decoding them using
// Google's Public Cert; caching already seen tokens works best when
// a large volume of messages have prompted a single push server to
// handle them, in which case they would all share the same token for
// a limited time window.
const ticket = await authClient.verifyIdToken({
idToken: token,
audience: 'example.com',
});
const claim = ticket.getPayload();
// IMPORTANT: you should validate claim details not covered
// by signature and audience verification above, including:
// - Ensure that `claim.email` is equal to the expected service
// account set up in the push subscription settings.
// - Ensure that `claim.email_verified` is set to true.
claims.push(claim);
} catch (e) {
res.status(400).send('Invalid token');
return;
}
// The message is a unicode string encoded in base64.
const message = Buffer.from(req.body.message.data, 'base64').toString(
'utf-8'
);
messages.push(message);
res.status(200).send();
});
// [END gae_flex_pubsub_auth_push]
// Start the server
const PORT = parseInt(process.env.PORT) || 8080;
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
module.exports = app;