Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(graph): update graph helpers to work better with next.js #354

Merged
merged 2 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/function/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ export interface Event {
multiValueQueryStringParameters: EventMultiValueQueryStringParameters | null
body: string | null
isBase64Encoded: boolean
netlifyGraphToken: string | undefined
}
10 changes: 9 additions & 1 deletion src/function/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
4 changes: 2 additions & 2 deletions src/lib/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 30 additions & 16 deletions src/lib/graph_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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`
Expand All @@ -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 {
Expand Down Expand Up @@ -125,3 +135,7 @@ export const getNetlifyGraphToken = function (

return event ? graphTokenFromEvent(event) : graphTokenFromEnv()
}

export const getNetlifyGraphTokenForBuild = function (): GraphTokenResponse {
return graphTokenFromEnv()
}
55 changes: 35 additions & 20 deletions src/lib/secrets_helper.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<NetlifySecrets> => {
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 {
Expand All @@ -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<NetlifySecrets> => {
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<NetlifySecrets> => {
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<NetlifySecrets> => {
const graphTokenResponse = getNetlifyGraphTokenForBuild()
const graphToken = graphTokenResponse.token
if (!graphToken) {
if (graphTokenResponse.errors) {
logErrors(graphTokenResponse.errors)
}
return {}
}

return await getSecretsForToken(graphToken)
}