UNPKG

@graphql-hive/plugin-aws-sigv4

Version:
478 lines (468 loc) • 18.6 kB
import { DisposableSymbols } from '@whatwg-node/disposablestack'; import { UnifiedGraphPlugin, Instrumentation as Instrumentation$2 } from '@graphql-mesh/fusion-runtime'; import { Plugin, YogaInitialContext, Instrumentation as Instrumentation$1 } from 'graphql-yoga'; import { ExecutionRequest, MaybePromise } from '@graphql-tools/utils'; import { Tracer, Context } from '@opentelemetry/api'; import { MeshFetch, KeyValueCache, MeshFetchRequestInit, Logger as Logger$1 } from '@graphql-mesh/types'; import { FetchInstrumentation } from '@graphql-mesh/utils'; import { GraphQLResolveInfo } from 'graphql/type'; import { MaybePromise as MaybePromise$1 } from '@whatwg-node/promise-helpers'; import { ServerAdapterInitialContext } from '@whatwg-node/server'; type MaybeLazy<T> = T | (() => T); type AttributeValue = any; type Attributes = AttributeValue[] | { [key: string | number]: AttributeValue; }; interface LogWriter { write(level: LogLevel, attrs: Attributes | null | undefined, msg: string | null | undefined): void | Promise<void>; flush?(): void | Promise<void>; } type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error'; interface LoggerOptions { /** * The minimum log level to log. * * Providing `false` will disable all logging. * * Provided function will always be invoked to get the current log level. * * @default env.LOG_LEVEL || env.DEBUG ? 'debug' : 'info' */ level?: MaybeLazy<LogLevel | false>; /** A prefix to include in every log's message. */ prefix?: string; /** * The attributes to include in all logs. Is mainly used to pass the parent * attributes when creating {@link Logger.child child loggers}. */ attrs?: Attributes; /** * The log writers to use when writing logs. * * @default env.LOG_JSON ? [new JSONLogWriter()] : [new ConsoleLogWriter()] */ writers?: [LogWriter, ...LogWriter[]]; } declare class Logger implements AsyncDisposable { #private; constructor(opts?: LoggerOptions); /** The prefix that's prepended to each log message. */ get prefix(): string | undefined; /** * The attributes that are added to each log. If the log itself contains * attributes with keys existing in {@link attrs}, the log's attributes will * override. */ get attrs(): Attributes | undefined; /** The current {@link LogLevel} of the logger. You can change the level using the {@link setLevel} method. */ get level(): false | LogLevel; /** * Sets the new {@link LogLevel} of the logger. All subsequent logs, and {@link child child loggers} whose * level did not change, will respect the new level. */ setLevel(level: MaybeLazy<LogLevel | false>): void; write(level: LogLevel, attrs: Attributes | null | undefined, msg: string | null | undefined): void; flush(): Promise<void> | undefined; [DisposableSymbols.asyncDispose](): Promise<void | undefined>; child(prefix: string): Logger; child(attrs: Attributes, prefix?: string): Logger; log(level: LogLevel): void; log(level: LogLevel, attrs: MaybeLazy<Attributes>): void; log(level: LogLevel, msg: string, ...interpol: unknown[]): void; log(level: LogLevel, attrs: MaybeLazy<Attributes>, msg: string, ...interpol: unknown[]): void; trace(): void; trace(attrs: MaybeLazy<Attributes>): void; trace(msg: string, ...interpol: unknown[]): void; trace(attrs: MaybeLazy<Attributes>, msg: string, ...interpol: unknown[]): void; debug(): void; debug(attrs: MaybeLazy<Attributes>): void; debug(msg: string, ...interpol: unknown[]): void; debug(attrs: MaybeLazy<Attributes>, msg: string, ...interpol: unknown[]): void; info(): void; info(attrs: MaybeLazy<Attributes>): void; info(msg: string, ...interpol: unknown[]): void; info(attrs: MaybeLazy<Attributes>, msg: string, ...interpol: unknown[]): void; warn(): void; warn(attrs: MaybeLazy<Attributes>): void; warn(msg: string, ...interpol: unknown[]): void; warn(attrs: MaybeLazy<Attributes>, msg: string, ...interpol: unknown[]): void; error(): void; error(attrs: MaybeLazy<Attributes>): void; error(msg: string, ...interpol: unknown[]): void; error(attrs: MaybeLazy<Attributes>, msg: string, ...interpol: unknown[]): void; } type TopicDataMap = Record<string, any>; interface HivePubSub<Data extends TopicDataMap = TopicDataMap> { /** @deprecated Please use {@link subscribedTopics} if implemented instead. This method will be removed in next major release. */ getEventNames(): Iterable<keyof Data>; /** @important This method will be required starting next major release. */ subscribedTopics?(): Iterable<keyof Data>; publish<Topic extends keyof Data>(topic: Topic, data: Data[Topic]): void; subscribe<Topic extends keyof Data>(topic: Topic, listener: PubSubListener<Data, Topic>): number; unsubscribe(subId: number): void; asyncIterator<Topic extends keyof Data>(topic: Topic): AsyncIterable<Data[Topic]>; /** @important This method will be required starting next major release. */ dispose?(): void; /** @important This method will be required starting next major release. */ [DisposableSymbols.dispose]?(): void; } type PubSubListener<Data extends TopicDataMap, Topic extends keyof Data> = (data: Data[Topic]) => void; type ContextMatcher = { request?: Request; context?: any; executionRequest?: ExecutionRequest; }; type OpenTelemetryPluginUtils = { tracer?: Tracer; getActiveContext: (payload: ContextMatcher) => Context; getHttpContext: (request: Request) => Context | undefined; getOperationContext: (context: any) => Context | undefined; getExecutionRequestContext: (ExecutionRequest: ExecutionRequest) => Context | undefined; }; type OpenTelemetryContextExtension = { openTelemetry: { tracer: Tracer; getActiveContext: (payload?: ContextMatcher) => Context; getHttpContext: (request?: Request) => Context | undefined; getOperationContext: (context?: any) => Context | undefined; getExecutionRequestContext: (ExecutionRequest: ExecutionRequest) => Context | undefined; }; }; interface GatewayConfigContext { /** * WHATWG compatible Fetch implementation. */ fetch: MeshFetch; /** * The logger to use throught Hive and its plugins. */ log: Logger; /** * Current working directory. */ cwd: string; /** * Event bus for pub/sub. */ pubsub?: HivePubSub; /** * Cache Storage */ cache?: KeyValueCache; /** * OpenTelemetry API to get access to OTEL Tracer and Hive Gateway internal OTEL Contexts */ openTelemetry: OpenTelemetryPluginUtils & { register?: (plugin: OpenTelemetryPluginUtils) => void; }; } interface GatewayContext extends Omit<GatewayConfigContext, 'openTelemetry'>, OpenTelemetryContextExtension, YogaInitialContext { /** * Environment agnostic HTTP headers provided with the request. */ headers: Record<string, string>; /** * Runtime context available within WebSocket connections. */ connectionParams: Record<string, string>; } type GatewayPlugin<TPluginContext extends Record<string, any> = Record<string, any>, TContext extends Record<string, any> = Record<string, any>> = Plugin<Partial<TPluginContext> & GatewayContext & TContext> & UnifiedGraphPlugin<Partial<TPluginContext> & GatewayContext & TContext> & { onFetch?: OnFetchHook<Partial<TPluginContext> & TContext>; onCacheGet?: OnCacheGetHook; onCacheSet?: OnCacheSetHook; onCacheDelete?: OnCacheDeleteHook; /** * An Instrumentation instance that will wrap each phases of the request pipeline. * This should be used primarily as an observability tool (for monitoring, tracing, etc...). * * Note: The wrapped functions in instrumentation should always be called. Use hooks to * conditionally skip a phase. */ instrumentation?: Instrumentation<TPluginContext & TContext & GatewayContext>; }; interface OnFetchHookPayload<TContext> { url: string; setURL(url: URL | string): void; options: MeshFetchRequestInit; setOptions(options: MeshFetchRequestInit): void; /** * The context is not available in cases where "fetch" is done in * order to pull a supergraph or do some internal work. * * The logger will be available in all cases. */ context: (GatewayContext & TContext) | { log: Logger; }; /** @deprecated Please use `log` from the {@link context} instead. */ logger: Logger$1; info: GraphQLResolveInfo; fetchFn: MeshFetch; setFetchFn: (fetchFn: MeshFetch) => void; executionRequest?: ExecutionRequest; endResponse: (response$: MaybePromise<Response>) => void; } interface OnFetchHookDonePayload { response: Response; setResponse: (response: Response) => void; } type OnFetchHookDone = (payload: OnFetchHookDonePayload) => MaybePromise<void>; type OnFetchHook<TContext> = (payload: OnFetchHookPayload<TContext>) => MaybePromise<void | OnFetchHookDone>; type OnCacheGetHook = (payload: OnCacheGetHookEventPayload) => MaybePromise<OnCacheGetHookResult | void>; interface OnCacheGetHookEventPayload { cache: KeyValueCache; key: string; ttl?: number; } interface OnCacheGetHookResult { onCacheHit?: OnCacheHitHook; onCacheMiss?: OnCacheMissHook; onCacheGetError?: OnCacheErrorHook; } type OnCacheErrorHook = (payload: OnCacheErrorHookPayload) => void; interface OnCacheErrorHookPayload { error: Error; } type OnCacheHitHook = (payload: OnCacheHitHookEventPayload) => void; interface OnCacheHitHookEventPayload { value: any; } type OnCacheMissHook = () => void; type OnCacheSetHook = (payload: OnCacheSetHookEventPayload) => MaybePromise<OnCacheSetHookResult | void>; interface OnCacheSetHookResult { onCacheSetDone?: () => void; onCacheSetError?: OnCacheErrorHook; } interface OnCacheSetHookEventPayload { cache: KeyValueCache; key: string; value: any; ttl?: number; } type OnCacheDeleteHook = (payload: OnCacheDeleteHookEventPayload) => MaybePromise<OnCacheDeleteHookResult | void>; interface OnCacheDeleteHookResult { onCacheDeleteDone?: () => void; onCacheDeleteError?: OnCacheErrorHook; } interface OnCacheDeleteHookEventPayload { cache: KeyValueCache; key: string; } type Instrumentation<TContext extends Record<string, any>> = Instrumentation$1<TContext> & Instrumentation$2 & FetchInstrumentation; interface AWSSignv4PluginOptions { /** * Outgoing options for signing outgoing requests. */ outgoing?: AWSSignv4PluginOutgoingOptions | AWSSignv4PluginOutgoingOptionsFactory; /** * Incoming options for validating incoming requests. */ incoming?: AWSSignv4PluginIncomingOptions | boolean; } interface AWSSignv4PluginOutgoingOptions { /** * To sign the query instead of adding an Authorization header * @default false */ signQuery?: boolean; /** * Service name to use when signing the request. * By default, it is inferred from the hostname. */ serviceName?: string; /** * Region name to use when signing the request. * By default, it is inferred from the hostname. */ region?: string; /** * ACCESS_KEY_ID * @default env.AWS_ACCESS_KEY_ID || env.AWS_ACCESS_KEY */ accessKeyId?: string; /** * AWS_SECRET_ACCESS_KEY * @default env.AWS_SECRET_ACCESS_KEY || env.AWS_SECRET_KEY */ secretAccessKey?: string; /** * AWS_SESSION_TOKEN * @default env.AWS_SESSION_TOKEN */ sessionToken?: string; /** * An identifier for the assumed role session. * * @default env.AWS_ROLE_ARN */ roleArn?: string; /** * The Amazon Resource Names (ARNs) of the IAM managed policies that you want to use as * managed session policies. The policies must exist in the same account as the role. * * @default env.AWS_IAM_ROLE_SESSION_NAME */ roleSessionName?: string; } interface AWSSignv4PluginOutgoingOptionsFactoryOptions { url: string; options: RequestInit; subgraphName: string; } type AWSSignv4PluginOutgoingOptionsFactory = (factoryOptions: AWSSignv4PluginOutgoingOptionsFactoryOptions) => MaybePromise$1<AWSSignv4PluginOutgoingOptions | undefined | false | true>; interface AWSSignv4PluginIncomingPayload { /** * HTTP request */ request: Request; /** * Context */ serverContext: ServerAdapterInitialContext; /** * Incoming authorization headers string. Required. */ authorization: string; /** * DateTime from incoming header. Required. */ xAmzDate: string; /** * Additional header to set message exiration time even if signature message is valid. Optional. */ xAmzExpires?: number; /** * Sha256 + formated hex for body. Empty body has own bodyHash. If there is no need to sign body for performance reason you can put UNSIGNED-PAYLOAD in request headers['x-amz-content-sha256']. */ bodyHash: string; /** * accessKey used to sign this message. */ accessKey: string; /** * Date used in authorization header. */ date: string; /** * Region used in authorization header. Here can be any value. */ region: string; /** * Service used in authorization header. Here can be any value. */ service: string; /** * For aws4 will be aws4_request. Here can be any value. */ requestType: string; /** * List of signed headers separated with semicolon. */ signedHeaders: string; /** * Formated encoded header paris. */ canonicalHeaders: string; secretAccessKey?: string; } interface AssumeRolePayload { /** * Region name to use when signing the request. * By default, it is inferred from the hostname. */ region?: string; /** * An identifier for the assumed role session. * * @default env.AWS_ROLE_ARN */ roleArn?: string; /** * The Amazon Resource Names (ARNs) of the IAM managed policies that you want to use as * managed session policies. The policies must exist in the same account as the role. * * @default env.AWS_IAM_ROLE_SESSION_NAME */ roleSessionName?: string; } interface AWSSignv4PluginIncomingOptions { /** * Callback for secretKey. You have to provide process to get proper secret or return undefined secret. * By default it uses `accessKey` to get secret from `env.AWS_SECRET_ACCESS_KEY` or `env.AWS_SECRET_KEY`. * Should return secretKey on incoming parameters - but if secret is missing which it will be normal case when someone want to guess - you should return undefined; */ secretAccessKey?: (payload: AWSSignv4PluginIncomingPayload) => MaybePromise$1<string | undefined> | string | undefined; /** * An identifier for the assumed role session. * * @default env.AWS_ROLE_ARN */ assumeRole?: (payload: AWSSignv4PluginIncomingPayload) => MaybePromise$1<AssumeRolePayload | undefined> | AssumeRolePayload | undefined; /** * Callback for changes in incoming headers before it goes through parse process. Help to more sophisticated changes to preserve proper headers. */ headers?: (headers: Headers) => Headers; /** * You can skip aws signature validation. It is useful when you want to use it only for some requests. */ enabled?: (request: Request, serverContext: ServerAdapterInitialContext) => MaybePromise$1<boolean>; /** * Callback on header missing. Validation stops here. Default value `onMissingHeaders: () => { throw new GraphQLError('Headers are missing for auth', { extensions: { http: { status: 401, } } }); },` */ onMissingHeaders?: (request: Request, serverContext: ServerAdapterInitialContext) => MaybePromise$1<void>; /** * Custom response on signature mismatch. Validation stops here. Default value `onSignatureMismatch: () => { throw new GraphQLError('The signature does not match', { extensions: { http: { status: 401, } } }); },` */ onSignatureMismatch?: (request: Request, serverContext: ServerAdapterInitialContext) => MaybePromise$1<void>; /** * Custom response on exired time signature. Validation stops here. Default value `onExpired: () => { throw new GraphQLError('Request is expired', { extensions: { http: { status: 401, } } }); },` */ onExpired?: (request: Request, serverContext: ServerAdapterInitialContext) => MaybePromise$1<void>; /** * Custom callback before standard parser comes. On false validation stops here. Default value `onBeforeParse: () => true,` * * Should return true if you need to let parse request further. */ onBeforeParse?: (request: Request, serverContext: ServerAdapterInitialContext) => MaybePromise$1<boolean>; /** * Custom callback after standard parser done. On false validation stops here. Default value `onAfterParse: () => true,` * Should return true if you need to let parse request further. */ onAfterParse?: (payload: AWSSignv4PluginIncomingPayload) => MaybePromise$1<boolean>; /** * Last callback after when validation signature is done. You can even stop here process. */ onSuccess?: (payload: AWSSignv4PluginIncomingPayload) => MaybePromise$1<void>; } declare enum AWSSignV4Headers { 'Authorization' = "authorization", 'XAmzDate' = "x-amz-date", 'XAmzContentSha256' = "x-amz-content-sha256", 'XAmzExpires' = "x-amz-expires" } declare function useAWSSigv4<TContext extends Record<string, any>>(opts: AWSSignv4PluginOptions): GatewayPlugin<TContext>; export { AWSSignV4Headers, type AWSSignv4PluginIncomingOptions, type AWSSignv4PluginIncomingPayload, type AWSSignv4PluginOptions, type AWSSignv4PluginOutgoingOptions, type AWSSignv4PluginOutgoingOptionsFactory, type AWSSignv4PluginOutgoingOptionsFactoryOptions, type AssumeRolePayload, useAWSSigv4 };