@aws-amplify/core
Version:
Core category of aws-amplify
416 lines (350 loc) • 9.99 kB
text/typescript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
/* This is because JSON schema contains keys with snake_case */
/* eslint-disable camelcase */
/* Does not like exhaustive checks */
import {
APIConfig,
APIEventsConfig,
APIGraphQLConfig,
GraphQLAuthMode,
ModelIntrospectionSchema,
} from './singleton/API/types';
import {
CognitoUserPoolConfigMfaStatus,
OAuthProvider,
} from './singleton/Auth/types';
import { NotificationsConfig } from './singleton/Notifications/types';
import {
AmplifyOutputsAnalyticsProperties,
AmplifyOutputsAuthProperties,
AmplifyOutputsCustomProperties,
AmplifyOutputsDataProperties,
AmplifyOutputsGeoProperties,
AmplifyOutputsNotificationsProperties,
AmplifyOutputsStorageBucketProperties,
AmplifyOutputsStorageProperties,
AmplifyOutputsUnknown,
} from './singleton/AmplifyOutputs/types';
import {
AnalyticsConfig,
AuthConfig,
BucketInfo,
GeoConfig,
LegacyConfig,
ResourcesConfig,
StorageConfig,
} from './singleton/types';
export function isAmplifyOutputs(
config: ResourcesConfig | LegacyConfig | AmplifyOutputsUnknown,
): config is AmplifyOutputsUnknown {
// version format initially will be '1' but is expected to be something like x.y where x is major and y minor version
const { version } = config as AmplifyOutputsUnknown;
if (!version) {
return false;
}
return version.startsWith('1');
}
function parseStorage(
amplifyOutputsStorageProperties?: AmplifyOutputsStorageProperties,
): StorageConfig | undefined {
if (!amplifyOutputsStorageProperties) {
return undefined;
}
const { bucket_name, aws_region, buckets } = amplifyOutputsStorageProperties;
return {
S3: {
bucket: bucket_name,
region: aws_region,
buckets: buckets && createBucketInfoMap(buckets),
},
};
}
function parseAuth(
amplifyOutputsAuthProperties?: AmplifyOutputsAuthProperties,
): AuthConfig | undefined {
if (!amplifyOutputsAuthProperties) {
return undefined;
}
const {
user_pool_id,
user_pool_client_id,
identity_pool_id,
password_policy,
mfa_configuration,
mfa_methods,
unauthenticated_identities_enabled,
oauth,
username_attributes,
standard_required_attributes,
groups,
} = amplifyOutputsAuthProperties;
const authConfig = {
Cognito: {
userPoolId: user_pool_id,
userPoolClientId: user_pool_client_id,
groups,
},
} as AuthConfig;
if (identity_pool_id) {
authConfig.Cognito = {
...authConfig.Cognito,
identityPoolId: identity_pool_id,
};
}
if (password_policy) {
authConfig.Cognito.passwordFormat = {
requireLowercase: password_policy.require_lowercase,
requireNumbers: password_policy.require_numbers,
requireUppercase: password_policy.require_uppercase,
requireSpecialCharacters: password_policy.require_symbols,
minLength: password_policy.min_length ?? 6,
};
}
if (mfa_configuration) {
authConfig.Cognito.mfa = {
status: getMfaStatus(mfa_configuration),
smsEnabled: mfa_methods?.includes('SMS'),
totpEnabled: mfa_methods?.includes('TOTP'),
};
}
if (unauthenticated_identities_enabled) {
authConfig.Cognito.allowGuestAccess = unauthenticated_identities_enabled;
}
if (oauth) {
authConfig.Cognito.loginWith = {
oauth: {
domain: oauth.domain,
redirectSignIn: oauth.redirect_sign_in_uri,
redirectSignOut: oauth.redirect_sign_out_uri,
responseType: oauth.response_type === 'token' ? 'token' : 'code',
scopes: oauth.scopes,
providers: getOAuthProviders(oauth.identity_providers),
},
};
}
if (username_attributes) {
authConfig.Cognito.loginWith = {
...authConfig.Cognito.loginWith,
email: username_attributes.includes('email'),
phone: username_attributes.includes('phone_number'),
// Signing in with a username is not currently supported in Gen2, this should always evaluate to false
username: username_attributes.includes('username'),
};
}
if (standard_required_attributes) {
authConfig.Cognito.userAttributes = standard_required_attributes.reduce(
(acc, curr) => ({ ...acc, [curr]: { required: true } }),
{},
);
}
return authConfig;
}
export function parseAnalytics(
amplifyOutputsAnalyticsProperties?: AmplifyOutputsAnalyticsProperties,
): AnalyticsConfig | undefined {
if (!amplifyOutputsAnalyticsProperties?.amazon_pinpoint) {
return undefined;
}
const { amazon_pinpoint } = amplifyOutputsAnalyticsProperties;
return {
Pinpoint: {
appId: amazon_pinpoint.app_id,
region: amazon_pinpoint.aws_region,
},
};
}
function parseGeo(
amplifyOutputsAnalyticsProperties?: AmplifyOutputsGeoProperties,
): GeoConfig | undefined {
if (!amplifyOutputsAnalyticsProperties) {
return undefined;
}
const { aws_region, geofence_collections, maps, search_indices } =
amplifyOutputsAnalyticsProperties;
return {
LocationService: {
region: aws_region,
searchIndices: search_indices,
geofenceCollections: geofence_collections,
maps,
},
};
}
function parseData(
amplifyOutputsDataProperties?: AmplifyOutputsDataProperties,
): APIConfig | undefined {
if (!amplifyOutputsDataProperties) {
return undefined;
}
const {
aws_region,
default_authorization_type,
url,
api_key,
model_introspection,
} = amplifyOutputsDataProperties;
const GraphQL: APIGraphQLConfig = {
endpoint: url,
defaultAuthMode: getGraphQLAuthMode(default_authorization_type),
region: aws_region,
apiKey: api_key,
modelIntrospection: model_introspection as ModelIntrospectionSchema,
};
return {
GraphQL,
};
}
function parseCustom(
amplifyOutputsCustomProperties?: AmplifyOutputsCustomProperties,
) {
if (!amplifyOutputsCustomProperties?.events) {
return undefined;
}
const { url, aws_region, api_key, default_authorization_type } =
amplifyOutputsCustomProperties.events;
const Events: APIEventsConfig = {
endpoint: url,
defaultAuthMode: getGraphQLAuthMode(default_authorization_type),
region: aws_region,
apiKey: api_key,
};
return {
Events,
};
}
function parseNotifications(
amplifyOutputsNotificationsProperties?: AmplifyOutputsNotificationsProperties,
): NotificationsConfig | undefined {
if (!amplifyOutputsNotificationsProperties) {
return undefined;
}
const { aws_region, channels, amazon_pinpoint_app_id } =
amplifyOutputsNotificationsProperties;
const hasInAppMessaging = channels.includes('IN_APP_MESSAGING');
const hasPushNotification =
channels.includes('APNS') || channels.includes('FCM');
if (!(hasInAppMessaging || hasPushNotification)) {
return undefined;
}
// At this point, we know the Amplify outputs contains at least one supported channel
const notificationsConfig: NotificationsConfig = {} as NotificationsConfig;
if (hasInAppMessaging) {
notificationsConfig.InAppMessaging = {
Pinpoint: {
appId: amazon_pinpoint_app_id,
region: aws_region,
},
};
}
if (hasPushNotification) {
notificationsConfig.PushNotification = {
Pinpoint: {
appId: amazon_pinpoint_app_id,
region: aws_region,
},
};
}
return notificationsConfig;
}
export function parseAmplifyOutputs(
amplifyOutputs: AmplifyOutputsUnknown,
): ResourcesConfig {
const resourcesConfig: ResourcesConfig = {};
if (amplifyOutputs.storage) {
resourcesConfig.Storage = parseStorage(
amplifyOutputs.storage as AmplifyOutputsStorageProperties,
);
}
if (amplifyOutputs.auth) {
resourcesConfig.Auth = parseAuth(
amplifyOutputs.auth as AmplifyOutputsAuthProperties,
);
}
if (amplifyOutputs.analytics) {
resourcesConfig.Analytics = parseAnalytics(amplifyOutputs.analytics);
}
if (amplifyOutputs.geo) {
resourcesConfig.Geo = parseGeo(
amplifyOutputs.geo as AmplifyOutputsGeoProperties,
);
}
if (amplifyOutputs.data) {
resourcesConfig.API = parseData(
amplifyOutputs.data as AmplifyOutputsDataProperties,
);
}
if (amplifyOutputs.custom) {
const customConfig = parseCustom(amplifyOutputs.custom);
if (customConfig && 'Events' in customConfig) {
resourcesConfig.API = { ...resourcesConfig.API, ...customConfig };
}
}
if (amplifyOutputs.notifications) {
resourcesConfig.Notifications = parseNotifications(
amplifyOutputs.notifications as AmplifyOutputsNotificationsProperties,
);
}
return resourcesConfig;
}
const authModeNames: Record<string, GraphQLAuthMode> = {
AMAZON_COGNITO_USER_POOLS: 'userPool',
API_KEY: 'apiKey',
AWS_IAM: 'iam',
AWS_LAMBDA: 'lambda',
OPENID_CONNECT: 'oidc',
};
function getGraphQLAuthMode(authType: string): GraphQLAuthMode {
return authModeNames[authType];
}
const providerNames: Record<string, OAuthProvider> = {
GOOGLE: 'Google',
LOGIN_WITH_AMAZON: 'Amazon',
FACEBOOK: 'Facebook',
SIGN_IN_WITH_APPLE: 'Apple',
};
function getOAuthProviders(providers: string[] = []): OAuthProvider[] {
return providers.reduce<OAuthProvider[]>((oAuthProviders, provider) => {
if (providerNames[provider] !== undefined) {
oAuthProviders.push(providerNames[provider]);
}
return oAuthProviders;
}, []);
}
function getMfaStatus(
mfaConfiguration: string,
): CognitoUserPoolConfigMfaStatus {
if (mfaConfiguration === 'OPTIONAL') return 'optional';
if (mfaConfiguration === 'REQUIRED') return 'on';
return 'off';
}
function createBucketInfoMap(
buckets: AmplifyOutputsStorageBucketProperties[],
): Record<string, BucketInfo> {
const mappedBuckets: Record<string, BucketInfo> = {};
buckets.forEach(
({ name, bucket_name: bucketName, aws_region: region, paths }) => {
if (name in mappedBuckets) {
throw new Error(
`Duplicate friendly name found: ${name}. Name must be unique.`,
);
}
const sanitizedPaths = paths
? Object.entries(paths).reduce<
Record<string, Record<string, string[] | undefined>>
>((acc, [key, value]) => {
if (value !== undefined) {
acc[key] = value;
}
return acc;
}, {})
: undefined;
mappedBuckets[name] = {
bucketName,
region,
paths: sanitizedPaths,
};
},
);
return mappedBuckets;
}