@graphql-hive/plugin-aws-sigv4
Version:
478 lines (468 loc) • 18.6 kB
text/typescript
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 };