UNPKG

@aws-amplify/core

Version:

Core category of aws-amplify

137 lines (128 loc) • 6.27 kB
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { AuthClass } from '../singleton/Auth'; import { AmplifyOutputsUnknown, LegacyConfig, LibraryOptions, ResourcesConfig, } from '../singleton/types'; import { deepFreeze } from '../utils/deepFreeze'; import { parseAmplifyConfig } from '../utils/parseAmplifyConfig'; import { AmplifyContext } from './AmplifyContext'; import { AMPLIFY_CONTEXT_BRAND } from './contextBrand'; import { createAmplifyContextToken } from './contextToken'; /** * Creates a local, branded {@link AmplifyContext} from the given resource * configuration. * * Unlike `Amplify.configure()`, the returned context is **not** set as the * global context and no Hub events are dispatched. Category APIs that receive * this context (as their first positional argument) will use it instead of the * global one, enabling per-request context injection in server-side / * multi-tenant scenarios. * * The factory follows the "factory-provider" style: it instantiates a **fresh, * per-context** {@link AuthClass} (never the process-wide singleton) whose * `fetchAuthSession` / `clearCredentials` / `getTokens` closures back the * returned context's methods. The context is branded via * {@link AMPLIFY_CONTEXT_BRAND} and frozen so it can be recognized at runtime * by `isAmplifyContext()` and cannot be mutated after creation. * * @remarks * `@aws-amplify/core` sits at the bottom of the dependency graph and therefore * cannot import the Cognito token/credentials provider factories (those live in * the `aws-amplify` umbrella package and depend on `@aws-amplify/auth`). As a * result this core-level factory does **not** inject default providers: callers * that need auth must supply `libraryOptions.Auth.{tokenProvider,credentialsProvider}`. * The `aws-amplify` package wraps this factory (Phase C2) to resolve and inject * the default Cognito providers (SSR cookie storage vs. localStorage) before * delegating here. * * @param resourceConfig - Back-end resource configuration (typed config, * legacy `aws-exports`, or `amplify_outputs`). * @param libraryOptions - Optional library options (e.g. Auth token/credentials * providers, `ssr`). * @param internalOptions - Internal-only options. `skipConfigParse` lets a * trusted caller (the `aws-amplify` wrapper) that has *already* normalized the * config via `parseAmplifyConfig` opt out of a redundant re-parse. Since * `parseAmplifyConfig` is a no-op on an already-parsed `ResourcesConfig`, this * only removes duplicate work (which otherwise runs on every context creation, * and twice per SSR request through adapter-nextjs) — behavior is identical. * @returns A branded, frozen {@link AmplifyContext}. * * @example * ```ts * import { createAmplifyContext } from '@aws-amplify/core'; * * const ctx = createAmplifyContext(resourcesConfig, { * Auth: { tokenProvider, credentialsProvider }, * }); * // Pass ctx explicitly to category APIs: * await signIn(ctx, { username, password }); * ``` */ export function createAmplifyContext( resourceConfig: ResourcesConfig | LegacyConfig | AmplifyOutputsUnknown, libraryOptions?: LibraryOptions, internalOptions?: { /** @internal Skip re-parsing an already-normalized `ResourcesConfig`. */ skipConfigParse?: boolean; }, ): AmplifyContext { // `parseAmplifyConfig` returns an already-parsed `ResourcesConfig` unchanged, // so skipping it when the caller has pre-parsed keeps behavior identical while // avoiding a redundant parse. // // The resolved config is DEEP-frozen (matching `Amplify.configure()` in // Amplify.ts) on BOTH paths: a shallow `Object.freeze` would leave nested // config (e.g. `Auth.Cognito`) mutable, and on the `skipConfigParse` path the // caller's object passes through by reference — potentially SHARED between // several per-request contexts (adapter-nextjs reuses one parsed config for // every request), so any nested mutation would leak across contexts. // `deepFreeze` is idempotent, so re-freezing that shared object is safe. const resolvedResourceConfig: ResourcesConfig = deepFreeze( internalOptions?.skipConfigParse ? (resourceConfig as ResourcesConfig) : parseAmplifyConfig(resourceConfig), ); const resolvedLibraryOptions: LibraryOptions = libraryOptions ?? {}; // Fresh, per-context Auth instance (not the global singleton) so that // multiple contexts remain isolated from one another. // // `configure()` is called UNCONDITIONALLY, exactly as the pre-context // `AmplifyClass.configure()` does (see singleton/Amplify.ts). It is the only // place `AuthClass` stores `libraryOptions.Auth`, so gating it on the // presence of `resourcesConfig.Auth` would silently drop caller-supplied // token/credentials providers whenever the resource config carries no `Auth` // block — the shape `getAmplifyDataClientConfig()` produces for IAM-authed // Data access from a Lambda (`API.GraphQL` only, plus a custom // `credentialsProvider`). `AuthClass.configure()` tolerates an undefined // auth resource config. // // The `Auth!` assertion is deliberately false: in the very case this fixes, // `Auth` IS undefined at runtime. It mirrors the identical assertion in the // singleton rather than widening `AuthClass.configure()`'s parameter type. const auth = new AuthClass(); auth.configure(resolvedResourceConfig.Auth!, resolvedLibraryOptions.Auth); const ctx: AmplifyContext = { // Already deep-frozen above (both parse paths). resourcesConfig: resolvedResourceConfig, libraryOptions: resolvedLibraryOptions, // Unique, frozen per-context identity handle (see AmplifyContextToken). // Attached before the brand/freeze below so the frozen context carries it. token: createAmplifyContextToken(), fetchAuthSession: fetchOptions => auth.fetchAuthSession(fetchOptions ?? {}), clearCredentials: () => auth.clearCredentials(), getTokens: tokenOptions => auth.getTokens(tokenOptions), }; // Brand the context for runtime identification by isAmplifyContext(). Object.defineProperty(ctx, AMPLIFY_CONTEXT_BRAND, { value: true, enumerable: false, configurable: false, writable: false, }); Object.freeze(ctx); return ctx; }