From b854652a8549c23bda3b060ab5bc756144caa7c2 Mon Sep 17 00:00:00 2001 From: Daniel Woelfel Date: Fri, 12 Aug 2022 15:43:02 -0700 Subject: [PATCH] feat(graph): update graph helpers to work better with next.js (#354) * feat(graph): update graph helpers to work better with next.js * chore(graph): update type --- src/function/event.ts | 1 + src/function/index.ts | 10 ++++++- src/lib/graph.ts | 4 +-- src/lib/graph_token.ts | 46 ++++++++++++++++++++------------ src/lib/secrets_helper.ts | 55 +++++++++++++++++++++++++-------------- 5 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/function/event.ts b/src/function/event.ts index 4951a390..da1127c8 100644 --- a/src/function/event.ts +++ b/src/function/event.ts @@ -25,4 +25,5 @@ export interface Event { multiValueQueryStringParameters: EventMultiValueQueryStringParameters | null body: string | null isBase64Encoded: boolean + netlifyGraphToken: string | undefined } diff --git a/src/function/index.ts b/src/function/index.ts index 66bf1964..9539e6f1 100644 --- a/src/function/index.ts +++ b/src/function/index.ts @@ -2,5 +2,13 @@ export { Context as HandlerContext } from './context' export { Event as HandlerEvent } from './event' export { Handler, HandlerCallback } from './handler' export { Response as HandlerResponse } from './response' -export { getSecrets, withSecrets, getNetlifyGraphToken, GraphTokenResponse, HasHeaders } from '../lib/graph' +export { + getSecrets, + getSecretsForBuild, + withSecrets, + getNetlifyGraphToken, + getNetlifyGraphTokenForBuild, + GraphTokenResponse, + HasHeaders, +} from '../lib/graph' export { NetlifySecrets } from '../lib/secrets_helper' diff --git a/src/lib/graph.ts b/src/lib/graph.ts index 6366d4e4..0fd176ec 100644 --- a/src/lib/graph.ts +++ b/src/lib/graph.ts @@ -5,8 +5,8 @@ import { Response } from '../function/response' import { getSecrets, NetlifySecrets } from './secrets_helper' // Fine-grained control during the preview, less necessary with a more proactive OneGraph solution -export { getSecrets } from './secrets_helper' -export { getNetlifyGraphToken, GraphTokenResponse, HasHeaders } from './graph_token' +export { getSecrets, getSecretsForBuild } from './secrets_helper' +export { getNetlifyGraphToken, getNetlifyGraphTokenForBuild, GraphTokenResponse, HasHeaders } from './graph_token' export interface ContextWithSecrets extends Context { secrets: NetlifySecrets diff --git a/src/lib/graph_token.ts b/src/lib/graph_token.ts index 5a2d2ea6..40a02a62 100644 --- a/src/lib/graph_token.ts +++ b/src/lib/graph_token.ts @@ -11,6 +11,7 @@ export type GraphTokenResponse = { } const TOKEN_HEADER = 'X-Nf-Graph-Token' +const TOKEN_HEADER_NORMALIZED = 'x-nf-graph-token' // Matches Web API Headers type (https://developer.mozilla.org/en-US/docs/Web/API/Headers) interface RequestHeaders { @@ -33,21 +34,37 @@ const hasRequestStyleHeaders = function (headers: RequestHeaders | IncomingHttpH const graphTokenFromIncomingHttpStyleHeaders = function ( headers: RequestHeaders | IncomingHttpHeaders, ): string | null | undefined { - if (TOKEN_HEADER in headers) { - const header = headers[TOKEN_HEADER] - if (header == null || typeof header === 'string') { - return header + if (TOKEN_HEADER in headers || TOKEN_HEADER_NORMALIZED in headers) { + const header = headers[TOKEN_HEADER] || headers[TOKEN_HEADER_NORMALIZED] + if (Array.isArray(header)) { + return header[0] } - return header[0] + return header } } -// Backwards compatibility with older version of cli that doesn't inject header -const authlifyTokenFallback = function (event: HasHeaders): GraphTokenResponse { - const token = (event as { authlifyToken?: string | null })?.authlifyToken +const graphTokenFromEnv = function (): GraphTokenResponse { + // _NETLIFY_GRAPH_TOKEN injected by next plugin + // eslint-disable-next-line no-underscore-dangle + const token = env._NETLIFY_GRAPH_TOKEN || env.NETLIFY_GRAPH_TOKEN return { token } } +const tokenFallback = function (event: HasHeaders): GraphTokenResponse { + // Backwards compatibility with older version of cli that doesn't inject header + const token = (event as { authlifyToken?: string | null })?.authlifyToken + if (token) { + return { token } + } + + // If we're in dev-mode with next.js, the plugin won't be there to inject + // secrets, so we need to get the token from the environment + if (env.NETLIFY_DEV === 'true') { + return graphTokenFromEnv() + } + return { token: null } +} + const graphTokenFromEvent = function (event: HasHeaders): GraphTokenResponse { const { headers } = event // Check if object first in case there is a header with key `get` @@ -60,14 +77,7 @@ const graphTokenFromEvent = function (event: HasHeaders): GraphTokenResponse { return { token: headers.get(TOKEN_HEADER) } } - return authlifyTokenFallback(event) -} - -const graphTokenFromEnv = function (): GraphTokenResponse { - // _NETLIFY_GRAPH_TOKEN injected by next plugin - // eslint-disable-next-line no-underscore-dangle - const token = env._NETLIFY_GRAPH_TOKEN || env.NETLIFY_GRAPH_TOKEN - return { token } + return tokenFallback(event) } const isEventRequired = function (): boolean { @@ -125,3 +135,7 @@ export const getNetlifyGraphToken = function ( return event ? graphTokenFromEvent(event) : graphTokenFromEnv() } + +export const getNetlifyGraphTokenForBuild = function (): GraphTokenResponse { + return graphTokenFromEnv() +} diff --git a/src/lib/secrets_helper.ts b/src/lib/secrets_helper.ts index c5f32b19..95648170 100644 --- a/src/lib/secrets_helper.ts +++ b/src/lib/secrets_helper.ts @@ -1,5 +1,5 @@ import { graphRequest } from './graph_request' -import { getNetlifyGraphToken, GraphTokenResponseError, HasHeaders } from './graph_token' +import { getNetlifyGraphToken, getNetlifyGraphTokenForBuild, GraphTokenResponseError, HasHeaders } from './graph_token' const services = { gitHub: null, @@ -102,23 +102,11 @@ const logErrors = function (errors: GraphTokenResponseError[]) { } } -// Note: We may want to have configurable "sets" of secrets, -// e.g. "dev" and "prod" -export const getSecrets = async (event?: HasHeaders | null | undefined): Promise => { - const graphTokenResponse = getNetlifyGraphToken(event, true) - const graphToken = graphTokenResponse.token - if (!graphToken) { - if (graphTokenResponse.errors) { - logErrors(graphTokenResponse.errors) - } - return {} - } - - // We select for more than we typeically need here - // in order to allow for some metaprogramming for - // consumers downstream. Also, the data is typically - // static and shouldn't add any measurable overhead. - const doc = `query FindLoggedInServicesQuery { +// We select for more than we typically need here +// in order to allow for some metaprogramming for +// consumers downstream. Also, the data is typically +// static and shouldn't add any measurable overhead. +const findLoggedInServicesQuery = `query FindLoggedInServicesQuery { me { serviceMetadata { loggedInServices { @@ -143,13 +131,40 @@ export const getSecrets = async (event?: HasHeaders | null | undefined): Promise } }` - const body = JSON.stringify({ query: doc }) +const getSecretsForToken = async (token: string): Promise => { + const body = JSON.stringify({ query: findLoggedInServicesQuery }) // eslint-disable-next-line node/no-unsupported-features/node-builtins - const resultBody = await graphRequest(graphToken, new TextEncoder().encode(body)) + const resultBody = await graphRequest(token, new TextEncoder().encode(body)) const result: GraphSecretsResponse = JSON.parse(resultBody) const newSecrets = formatSecrets(result) return newSecrets } + +export const getSecrets = async (event?: HasHeaders | null | undefined): Promise => { + const graphTokenResponse = getNetlifyGraphToken(event, true) + const graphToken = graphTokenResponse.token + if (!graphToken) { + if (graphTokenResponse.errors) { + logErrors(graphTokenResponse.errors) + } + return {} + } + + return await getSecretsForToken(graphToken) +} + +export const getSecretsForBuild = async (): Promise => { + const graphTokenResponse = getNetlifyGraphTokenForBuild() + const graphToken = graphTokenResponse.token + if (!graphToken) { + if (graphTokenResponse.errors) { + logErrors(graphTokenResponse.errors) + } + return {} + } + + return await getSecretsForToken(graphToken) +}