sample/app/server/app.js (77 lines of code) (raw):

/* * Copyright 2019 Google Inc. All Rights Reserved. * * 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. */ const request = require('request'); const IapJwtVerifier = require('./verify-iap-jwt'); const express = require('express'); const app = express(); const bodyParser = require('body-parser'); const templates = require('./templates'); const path = require('path'); // Useful links: // https://cloud.google.com/iap/docs/external-identities // https://cloud.google.com/iap/docs/signed-headers-howto // https://cloud.google.com/appengine/docs/standard/nodejs/quickstart app.enable('trust proxy'); /** Metadata service url for getting project number. */ const PROJECT_NUMBER_URL = 'http://metadata.google.internal/computeMetadata/v1/project/numeric-project-id'; /** Injected IAP JWT header name. */ const IAP_JWT_HEADER = 'x-goog-iap-jwt-assertion'; /** IAP JWT verifier. */ const jwtVerifierPromise = getProjectNumber().then((projectNumber) => { return new IapJwtVerifier(process.env.GOOGLE_CLOUD_PROJECT, projectNumber); }); /** @return {Promise<string>} A promise that resolves with the project number. */ function getProjectNumber() { return new Promise((resolve, reject) => { request({ url: PROJECT_NUMBER_URL, headers: { 'Metadata-Flavor': 'Google', }, json: true }, (error, response, body) => { if (error || response.statusCode !== 200) { reject(error || new Error('Unable to retrieve project number')); } else { resolve(body); } }); }); } /** * Renders the resource profile page and serves it in the response. * @param {function(!Object): string} template The template generating function. * @param {!Object} req The expressjs request. * @param {!Object} res The expressjs response. * @param {*} decodedClaims The decoded claims from verified IAP JWT. * @return {!Promise} A promise that resolves on success. */ function serveContentForUser(template, req, res, decodedClaims) { let gcipClaims = decodedClaims.gcip || null; res.set('Content-Type', 'text/html'); res.end(template({ sub: decodedClaims.sub, email: decodedClaims.email, emailVerifed: !!(gcipClaims && gcipClaims.email_verified), photoURL: gcipClaims && gcipClaims.picture, displayName: (gcipClaims && gcipClaims.name) || 'N/A', tenandId: (gcipClaims && gcipClaims.firebase && gcipClaims.firebase.tenant) || 'N/A', gcipClaims: JSON.stringify(gcipClaims, null, 2), iapClaims: JSON.stringify(decodedClaims, null, 2), signoutURL: '?gcp-iap-mode=GCIP_SIGNOUT', switchTenantURL: '?gcp-iap-mode=CLEAR_LOGIN_COOKIE', sessionRefreshURL: '?gcp-iap-mode=SESSION_REFRESHER', })); } /** * Checks if a user is signed in. If not, shows an error message. * @return {function()} The middleware function to run. */ function checkIfSignedIn() { return (req, res, next) => { // Allow access only if user is signed in. const iapToken = req.headers[IAP_JWT_HEADER] || ''; // Get JWT verifier. jwtVerifierPromise.then((jwtVerifier) => { jwtVerifier.verify(iapToken).then((decodedClaims) => { req.claims = decodedClaims; next(); }).catch((error) => { res.status(503).send('403: Permission denied (' + error.message + ')'); }); }); }; } // Support JSON-encoded bodies. app.use(bodyParser.json()); // Support URL-encoded bodies. app.use(bodyParser.urlencoded({ extended: true })); // Show error message if user is not signed in. app.use(checkIfSignedIn()); // Static CSS assets. app.use('/styles', express.static(path.join(__dirname, '../styles'))); app.get('/', (req, res) => { res.redirect('/resource'); }); /** * Get the resource endpoint. This will display the current authenticated user's claims. */ app.get('/resource', (req, res) => { // Serve content for signed in user. return serveContentForUser(templates.main, req, res, req.claims); }); // Start the server. const PORT = process.env.PORT || 8080; app.listen(PORT, () => { console.log(`App listening on port ${PORT}`); console.log('Press Ctrl+C to quit.'); });