From 4ffba32331f775c4b256411dd410ed23244ab931 Mon Sep 17 00:00:00 2001 From: Antonio Nuno Monteiro Date: Fri, 4 Feb 2022 02:51:27 -0800 Subject: [PATCH] fix(command-dev): inject the Authlify Token (#4167) --- package.json | 2 +- src/commands/dev/dev.js | 47 +++++++++++++++++++++++++++++++++---- src/lib/functions/server.js | 13 +++++++--- src/utils/dev.js | 23 ++++++++++++++++++ 4 files changed, 77 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index c90f55c3e4e..4aa993aa01a 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "is-plain-obj": "^3.0.0", "is-wsl": "^2.2.0", "isexe": "^2.0.0", + "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.0.0", "lambda-local": "2.0.1", "listr": "^0.14.3", @@ -197,7 +198,6 @@ "graphviz": "^0.0.9", "husky": "^7.0.4", "ini": "^2.0.0", - "jsonwebtoken": "^8.5.1", "mock-fs": "^5.1.2", "p-timeout": "^4.0.0", "proxyquire": "^2.1.3", diff --git a/src/commands/dev/dev.js b/src/commands/dev/dev.js index 60a574c3ed6..7beb34bcffb 100644 --- a/src/commands/dev/dev.js +++ b/src/commands/dev/dev.js @@ -22,6 +22,7 @@ const { detectServerSettings, error, exit, + generateAuthlifyJWT, getSiteInformation, injectEnvVariables, log, @@ -253,7 +254,29 @@ const dev = async (options, command) => { ) } - await injectEnvVariables({ env: command.netlify.cachedConfig.env, site }) + const startNetlifyGraphWatcher = Boolean(options.graph) + let authlifyJWT + + if (startNetlifyGraphWatcher) { + const netlifyToken = await command.authenticate() + authlifyJWT = generateAuthlifyJWT(netlifyToken, siteInfo.authlify_token_id, site.id) + } + + await injectEnvVariables({ + env: Object.assign( + command.netlify.cachedConfig.env, + authlifyJWT == null + ? {} + : { + ONEGRAPH_AUTHLIFY_TOKEN: { + sources: ['general'], + value: authlifyJWT, + }, + }, + ), + site, + }) + const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({ // inherited from base command --offline offline: options.offline, @@ -273,8 +296,26 @@ const dev = async (options, command) => { command.setAnalyticsPayload({ projectType: settings.framework || 'custom', live: options.live }) + let configWithAuthlify + + if (siteInfo.authlify_token_id) { + const netlifyToken = command.authenticate() + // Only inject the authlify config if a token ID exists. This prevents + // calling command.authenticate() (which opens a browser window) if the + // user hasn't enabled API Authentication + configWithAuthlify = Object.assign(config, { + authlify: { + netlifyToken, + authlifyTokenId: siteInfo.authlify_token_id, + siteId: site.id, + }, + }) + } else { + configWithAuthlify = config + } + await startFunctionsServer({ - config, + config: configWithAuthlify, settings, site, siteUrl, @@ -295,8 +336,6 @@ const dev = async (options, command) => { process.env.URL = url process.env.DEPLOY_URL = url - const startNetlifyGraphWatcher = Boolean(options.graph) - if (startNetlifyGraphWatcher && options.offline) { warn(`Unable to start Netlify Graph in offline mode`) } else if (startNetlifyGraphWatcher && !site.id) { diff --git a/src/lib/functions/server.js b/src/lib/functions/server.js index e82f9300dd6..5710ed7c070 100644 --- a/src/lib/functions/server.js +++ b/src/lib/functions/server.js @@ -6,6 +6,7 @@ const { NETLIFYDEVERR, NETLIFYDEVLOG, error: errorExit, + generateAuthlifyJWT, getInternalFunctionsDir, log, } = require('../../utils') @@ -44,7 +45,7 @@ const buildClientContext = function (headers) { } } -const createHandler = function ({ functionsRegistry }) { +const createHandler = function ({ config, functionsRegistry }) { return async function handler(request, response) { // handle proxies without path re-writes (http-servr) const cleanPath = request.path.replace(/^\/.netlify\/(functions|builders)/, '') @@ -105,6 +106,11 @@ const createHandler = function ({ functionsRegistry }) { rawQuery, } + if (config && config.authlify && config.authlify.authlifyTokenId != null) { + const { authlifyTokenId, netlifyToken, siteId } = config.authlify + event.authlifyToken = generateAuthlifyJWT(netlifyToken, authlifyTokenId, siteId) + } + const clientContext = buildClientContext(request.headers) || {} if (func.isBackground) { @@ -154,14 +160,14 @@ const createHandler = function ({ functionsRegistry }) { } } -const getFunctionsServer = function ({ buildersPrefix, functionsPrefix, functionsRegistry, siteUrl }) { +const getFunctionsServer = function ({ buildersPrefix, config, functionsPrefix, functionsRegistry, siteUrl }) { // performance optimization, load express on demand // eslint-disable-next-line node/global-require const express = require('express') // eslint-disable-next-line node/global-require const expressLogging = require('express-logging') const app = express() - const functionHandler = createHandler({ functionsRegistry }) + const functionHandler = createHandler({ config, functionsRegistry }) app.set('query parser', 'simple') @@ -218,6 +224,7 @@ const startFunctionsServer = async ({ await functionsRegistry.scan(functionsDirectories) const server = getFunctionsServer({ + config, functionsRegistry, siteUrl, functionsPrefix, diff --git a/src/utils/dev.js b/src/utils/dev.js index 6329fba27de..4f01847ff14 100644 --- a/src/utils/dev.js +++ b/src/utils/dev.js @@ -3,6 +3,7 @@ const process = require('process') const { get } = require('dot-prop') const getPort = require('get-port') +const jwt = require('jsonwebtoken') const isEmpty = require('lodash/isEmpty') const { supportsBackgroundFunctions } = require('../lib/account') @@ -193,8 +194,30 @@ const acquirePort = async ({ configuredPort, defaultPort, errorMessage }) => { return acquiredPort } +// Generates an Authlify JWT with the following claims: +// - site_id +// - netlify_token -- the bearer token for the Netlify API +// - authlify_token_id -- the authlify token ID stored for the site after +// enabling API Authentication. +const generateAuthlifyJWT = (netlifyToken, authlifyTokenId, siteId) => { + const claims = { + netlify_token: netlifyToken, + authlify_token_id: authlifyTokenId, + site_id: siteId, + } + + return jwt.sign( + { 'https://netlify.com/jwt/claims': claims }, + // doesn't matter. OneGraph doesn't check the signature. The presence of + // the Netlify API bearer token is enough because we've authenticated the + // user through `command.authenticate()` + 'NOT_SIGNED', + ) +} + module.exports = { getSiteInformation, injectEnvVariables, acquirePort, + generateAuthlifyJWT, }