From 6f284ead65cf4a09944b3a0b0fbd81a4ef4c5d01 Mon Sep 17 00:00:00 2001 From: Addie Rudy Date: Tue, 28 May 2024 09:14:00 -0400 Subject: [PATCH] style(formatting): cleaned code formatting using prettier Commit changes code style only, no logic or functionality changed re: #55 --- cli/config-manage.ts | 70 +- cli/config.ts | 8 +- lib/api/functions/bundle.ts | 47 +- .../pipeline/createDataFeed/index.ts | 7 +- .../pipeline/createNewsletter/index.ts | 7 +- .../filterListByAuthorization/index.ts | 19 +- .../functions/pipeline/getDataFeed/index.ts | 5 +- .../functions/pipeline/getNewsletter/index.ts | 9 +- .../pipeline/getPublication/index.ts | 8 +- .../functions/pipeline/isAuthorized/index.ts | 11 +- .../pipeline/listDataFeedsById/index.ts | 4 +- .../listDataFeedsDiscoverable/index.ts | 11 +- .../pipeline/listDataFeedsOwned/index.ts | 11 +- .../pipeline/listDataFeedsShared/index.ts | 11 +- .../pipeline/listNewslettersById/index.ts | 21 +- .../listNewslettersDiscoverable/index.ts | 11 +- .../pipeline/listNewslettersOwned/index.ts | 11 +- .../pipeline/listNewslettersShared/index.ts | 11 +- .../pipeline/listUserSubscriptions/index.ts | 6 +- .../pipeline/subscribeToNewsletter/index.ts | 7 +- .../unsubscribeFromNewsletter/index.ts | 7 +- .../pipeline/updateDataFeed/index.ts | 18 +- .../pipeline/updateNewsletter/index.ts | 6 +- lib/api/functions/resolver-helper.ts | 46 +- .../resolver/createNewsletter/index.ts | 7 +- .../index.ts | 5 +- .../functions/resolver/listDataFeeds/index.ts | 7 +- .../resolver/listNewsletters/index.ts | 7 +- .../unsubscribeFromNewsletter/index.ts | 7 +- lib/api/resolvers.ts | 643 +++++++++++------- .../index.pre-token-generation-hook.ts | 39 +- lib/authentication/index.ts | 152 +++-- lib/authorization/authorization-helper.ts | 53 +- .../index.action-authorization.ts | 65 +- .../index.list-filter-authorization.ts | 69 +- lib/authorization/index.read-authorization.ts | 47 +- lib/authorization/index.ts | 193 +++--- lib/cdk-nag-supressions.ts | 39 +- lib/config.ts | 8 +- lib/data-feed-ingestion/index.ts | 19 +- lib/data-feed-ingestion/prompts.ts | 2 +- ...-feed-poll-step-function.get-data-feeds.ts | 3 +- .../data-feed-poll-step-function.ts | 50 +- .../index.feed-subscriber.ts | 11 +- .../rss-atom-ingestion/index.ts | 28 +- ...ngestion-step-function.article-ingestor.ts | 12 +- .../ingestion-step-function.feed-reader.ts | 4 +- ...-step-function.filter-ingested-articles.ts | 4 +- .../ingestion-step-function.ts | 11 +- lib/index.ts | 19 +- .../index.email-generator.ts | 14 +- .../index.get-newsletter.ts | 10 +- .../index.newsletter-campaign-creator.ts | 11 +- .../index.newsletter-creator.ts | 16 +- lib/newsletter-generator/index.ts | 70 +- .../index.user-subscriber.ts | 12 +- lib/newsletter-generator/pinpoint-app.ts | 15 +- .../api/schema-to-avp/codegen-auth-plugin.ts | 175 +++-- lib/shared/api/schema-to-avp/codegen.ts | 5 +- lib/shared/api/types.ts | 1 - lib/shared/common/newsletter-style.ts | 2 +- lib/shared/common/types.ts | 5 +- .../emails-preview/newsletter.tsx | 2 +- .../email-generator/emails/newsletter.tsx | 91 ++- .../email-generator/newsletter-json-data.ts | 12 +- .../prompts/newsletter-summary-prompt.ts | 5 +- lib/shared/prompts/prompt-handler.ts | 6 +- lib/shared/prompts/prompt-processing.ts | 2 +- .../genai-newsletter-ui/src/app.tsx | 10 +- .../common/helpers/graphql-client-helper.ts | 22 +- .../src/common/helpers/index.ts | 2 +- .../genai-newsletter-ui/src/common/types.ts | 20 +- .../src/components/app-configured.tsx | 33 +- .../components/auth/custom-authenticator.tsx | 24 +- .../src/components/base-content-layout.tsx | 6 +- .../components/data-feeds/article-table.tsx | 8 +- .../data-feeds/data-feed-detail.tsx | 40 +- .../data-feeds/data-feeds-table.tsx | 22 +- .../forms/data-feed-details-form.tsx | 46 +- .../src/components/global-header.tsx | 16 +- .../components/newsletters/definitions.tsx | 5 +- .../forms/data-feeds-selection-table.tsx | 28 +- .../newsletters/forms/newsletter-details.tsx | 7 +- .../newsletters/forms/newsletter-review.tsx | 44 +- .../newsletters/forms/newsletter-wizard.tsx | 69 +- .../src/components/newsletters/newsletter.tsx | 111 +-- .../newsletters/newsletters-table.tsx | 98 ++- .../src/components/newsletters/preview.tsx | 5 +- .../newsletters/publications-table.tsx | 33 +- .../newsletters/user-subscriber-data.tsx | 19 +- .../genai-newsletter-ui/src/main.tsx | 4 +- .../src/pages/data-feeds/dashboard.tsx | 11 +- .../src/pages/newsletters/create.tsx | 1 - .../src/pages/newsletters/dashboard.tsx | 10 +- .../src/pages/newsletters/detail.tsx | 61 +- .../src/pages/newsletters/my-newsletters.tsx | 11 +- .../pages/newsletters/my-subscriptions.tsx | 11 +- .../genai-newsletter-ui/src/pages/welcome.tsx | 203 +++++- .../genai-newsletter-ui/unsubscribe/main.tsx | 4 - .../unsubscribe/unsubscribe-app.tsx | 211 +++--- .../genai-newsletter-ui/vite.config.ts | 10 +- lib/user-interface/index.ts | 92 ++- 102 files changed, 2292 insertions(+), 1335 deletions(-) diff --git a/cli/config-manage.ts b/cli/config-manage.ts index 2129b9d..1584d6d 100644 --- a/cli/config-manage.ts +++ b/cli/config-manage.ts @@ -15,7 +15,12 @@ if (fs.existsSync(deployConfig)) { const configFromFile: DeployConfig = JSON.parse( fs.readFileSync(deployConfig, 'utf8') ) - console.log(formatText('A configuration already exists.', { bold: true, backgroundColor: 'bg-yellow' })) + console.log( + formatText('A configuration already exists.', { + bold: true, + backgroundColor: 'bg-yellow' + }) + ) let existingConfigChoice: string | null = null let readyToProceed = false while (!readyToProceed) { @@ -289,16 +294,30 @@ if (['UPDATE', 'NEW'].includes(configStyle)) { * Does the user want to configure a Host Name & ACM Cert for the Frontend Cloudfront */ let configHostname = false - if (config.ui?.acmCertificateArn === undefined || config.ui.hostName === undefined) { - console.log(formatText('Do you want to configure a custom hostname for the frontend?', - { textColor: 'blue' })) - console.log(formatText('Requires a hostname & a pre-existing AWS Certificate Manager public cert ARN.' + - 'For more information, visit https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html', - { italic: true })) + if ( + config.ui?.acmCertificateArn === undefined || + config.ui.hostName === undefined + ) { + console.log( + formatText( + 'Do you want to configure a custom hostname for the frontend?', + { textColor: 'blue' } + ) + ) + console.log( + formatText( + 'Requires a hostname & a pre-existing AWS Certificate Manager public cert ARN.' + + 'For more information, visit https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html', + { italic: true } + ) + ) let loopA = true while (loopA) { const response = prompter( - formatText('Do you want to proceed? (y/N):', { bold: true, textColor: 'blue' }), + formatText('Do you want to proceed? (y/N):', { + bold: true, + textColor: 'blue' + }), 'N' ) if (response.toLowerCase() === 'y') { @@ -321,11 +340,16 @@ if (['UPDATE', 'NEW'].includes(configStyle)) { if (configHostname) { let loopB = true while (loopB) { - const existingHostname = ((config.ui?.hostName) != null) ? config.ui.hostName : '' + const existingHostname = + config.ui?.hostName != null ? config.ui.hostName : '' const response = prompter( - formatText(`Enter the hostname you want to use for the frontend:(${existingHostname})`, { textColor: 'blue' }) + formatText( + `Enter the hostname you want to use for the frontend:(${existingHostname})`, + { textColor: 'blue' } + ) ) - const hostnameRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/ + const hostnameRegex = + /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/ if (response.length > 0 && hostnameRegex.test(response)) { if (config.ui === undefined) { config.ui = {} @@ -333,7 +357,11 @@ if (['UPDATE', 'NEW'].includes(configStyle)) { config.ui.hostName = response loopB = false break - } else if (response.length < 1 && config.ui?.hostName !== undefined && config.ui.hostName !== null) { + } else if ( + response.length < 1 && + config.ui?.hostName !== undefined && + config.ui.hostName !== null + ) { loopB = false break } else { @@ -348,9 +376,15 @@ if (['UPDATE', 'NEW'].includes(configStyle)) { } let loopC = true while (loopC) { - const existingAcmCert = ((config.ui?.acmCertificateArn) != null) ? config.ui.acmCertificateArn : '' + const existingAcmCert = + config.ui?.acmCertificateArn != null + ? config.ui.acmCertificateArn + : '' const response = prompter( - formatText(`Enter the ACM Certificate ARN you want to use for the frontend:(${existingAcmCert})`, { textColor: 'blue' }) + formatText( + `Enter the ACM Certificate ARN you want to use for the frontend:(${existingAcmCert})`, + { textColor: 'blue' } + ) ) const acmCertRegex = /^arn:aws:acm:\S+:\d+:\w+\/\S+$/ if (response.length > 0 && acmCertRegex.test(response)) { @@ -360,8 +394,12 @@ if (['UPDATE', 'NEW'].includes(configStyle)) { config.ui.acmCertificateArn = response loopC = false break - } else if (response.length < 1 && config.ui?.acmCertificateArn !== undefined && config.ui.acmCertificateArn !== null) { - loopC = false; + } else if ( + response.length < 1 && + config.ui?.acmCertificateArn !== undefined && + config.ui.acmCertificateArn !== null + ) { + loopC = false break } else { console.log( diff --git a/cli/config.ts b/cli/config.ts index 7c572a0..5f8e953 100644 --- a/cli/config.ts +++ b/cli/config.ts @@ -4,9 +4,11 @@ import figlet from 'figlet' const program = new Command() -console.log(figlet.textSync('GenAI Newsletter', { - font: 'Slant' -})) +console.log( + figlet.textSync('GenAI Newsletter', { + font: 'Slant' + }) +) program .name('npm run config') diff --git a/lib/api/functions/bundle.ts b/lib/api/functions/bundle.ts index 6baff3f..72fdc67 100644 --- a/lib/api/functions/bundle.ts +++ b/lib/api/functions/bundle.ts @@ -16,26 +16,29 @@ import path from 'path' const outDir = path.join(__dirname, 'out') -const resolverFunctions = fs.readdirSync(path.join(__dirname, 'resolver')).map((functionName) => { - return path.join(__dirname, 'resolver', functionName, 'index.ts') -}) -const pipelineFunctions = fs.readdirSync(path.join(__dirname, 'pipeline')).map((functionName) => { - return path.join(__dirname, 'pipeline', functionName, 'index.ts') -}) +const resolverFunctions = fs + .readdirSync(path.join(__dirname, 'resolver')) + .map((functionName) => { + return path.join(__dirname, 'resolver', functionName, 'index.ts') + }) +const pipelineFunctions = fs + .readdirSync(path.join(__dirname, 'pipeline')) + .map((functionName) => { + return path.join(__dirname, 'pipeline', functionName, 'index.ts') + }) -esbuild.build({ - bundle: true, - entryPoints: [ - ...resolverFunctions, - ...pipelineFunctions - ], - outdir: outDir, - sourcemap: 'inline', - sourcesContent: false, - external: ['@aws-appsync/utils'], - platform: 'node', - target: 'esnext', - format: 'esm', - minify: false, - logLevel: 'info' -}).catch(() => process.exit(1)) +esbuild + .build({ + bundle: true, + entryPoints: [...resolverFunctions, ...pipelineFunctions], + outdir: outDir, + sourcemap: 'inline', + sourcesContent: false, + external: ['@aws-appsync/utils'], + platform: 'node', + target: 'esnext', + format: 'esm', + minify: false, + logLevel: 'info' + }) + .catch(() => process.exit(1)) diff --git a/lib/api/functions/pipeline/createDataFeed/index.ts b/lib/api/functions/pipeline/createDataFeed/index.ts index f3b7e45..fd52d44 100644 --- a/lib/api/functions/pipeline/createDataFeed/index.ts +++ b/lib/api/functions/pipeline/createDataFeed/index.ts @@ -1,4 +1,9 @@ -import { type Context, util, type LambdaRequest, type AppSyncIdentityLambda } from '@aws-appsync/utils' +import { + type Context, + util, + type LambdaRequest, + type AppSyncIdentityLambda +} from '@aws-appsync/utils' export function request (ctx: Context): LambdaRequest { const { args } = ctx diff --git a/lib/api/functions/pipeline/createNewsletter/index.ts b/lib/api/functions/pipeline/createNewsletter/index.ts index dd53352..6d21a61 100644 --- a/lib/api/functions/pipeline/createNewsletter/index.ts +++ b/lib/api/functions/pipeline/createNewsletter/index.ts @@ -1,4 +1,9 @@ -import { type Context, util, type LambdaRequest, type AppSyncIdentityLambda } from '@aws-appsync/utils' +import { + type Context, + util, + type LambdaRequest, + type AppSyncIdentityLambda +} from '@aws-appsync/utils' export function request (ctx: Context): LambdaRequest { const { args } = ctx diff --git a/lib/api/functions/pipeline/filterListByAuthorization/index.ts b/lib/api/functions/pipeline/filterListByAuthorization/index.ts index 30c5439..955045f 100644 --- a/lib/api/functions/pipeline/filterListByAuthorization/index.ts +++ b/lib/api/functions/pipeline/filterListByAuthorization/index.ts @@ -4,11 +4,18 @@ * SPDX-License-Identifier: MIT-0 */ -import { type LambdaRequest, util, type Context, type AppSyncIdentityLambda } from '@aws-appsync/utils' +import { + type LambdaRequest, + util, + type Context, + type AppSyncIdentityLambda +} from '@aws-appsync/utils' import { convertAvpObjectsToGraphql } from '../../resolver-helper' export function request (ctx: Context): LambdaRequest { - console.log(`[Filter List by Authorization Request] request ctx ${JSON.stringify(ctx)}`) + console.log( + `[Filter List by Authorization Request] request ctx ${JSON.stringify(ctx)}` + ) const { source, args } = ctx const identity = ctx.identity as AppSyncIdentityLambda return { @@ -16,7 +23,9 @@ export function request (ctx: Context): LambdaRequest { payload: { userId: identity.resolverContext.userId, accountId: identity.resolverContext.accountId, - requestContext: JSON.parse(identity.resolverContext.requestContext as string), + requestContext: JSON.parse( + identity.resolverContext.requestContext as string + ), result: ctx.prev.result, arguments: args, source @@ -33,7 +42,9 @@ export function response (ctx: Context): any { if (result.isAuthorized !== true) { util.unauthorized() } - console.log('[IsAuthorized] response result $', { result: JSON.stringify(result) }) + console.log('[IsAuthorized] response result $', { + result: JSON.stringify(result) + }) return { isAuthorized: true, items: convertAvpObjectsToGraphql(result) diff --git a/lib/api/functions/pipeline/getDataFeed/index.ts b/lib/api/functions/pipeline/getDataFeed/index.ts index 952ff79..e529ea2 100644 --- a/lib/api/functions/pipeline/getDataFeed/index.ts +++ b/lib/api/functions/pipeline/getDataFeed/index.ts @@ -1,6 +1,9 @@ import { type DynamoDBGetItemRequest, type Context } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItem, convertFieldIdToObjectId } from '../../resolver-helper' +import { + addAccountToItem, + convertFieldIdToObjectId +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBGetItemRequest { const { id } = ctx.args.input diff --git a/lib/api/functions/pipeline/getNewsletter/index.ts b/lib/api/functions/pipeline/getNewsletter/index.ts index d66bff2..93e3974 100644 --- a/lib/api/functions/pipeline/getNewsletter/index.ts +++ b/lib/api/functions/pipeline/getNewsletter/index.ts @@ -4,11 +4,12 @@ import { type DynamoDBGetItemRequest } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItem, convertFieldIdToObjectId } from '../../resolver-helper' +import { + addAccountToItem, + convertFieldIdToObjectId +} from '../../resolver-helper' -export function request ( - ctx: Context -): DynamoDBGetItemRequest { +export function request (ctx: Context): DynamoDBGetItemRequest { console.log('getNewsletter request', { ctx }) const { id } = ctx.args.input return ddb.get({ diff --git a/lib/api/functions/pipeline/getPublication/index.ts b/lib/api/functions/pipeline/getPublication/index.ts index 6544f9c..552ac79 100644 --- a/lib/api/functions/pipeline/getPublication/index.ts +++ b/lib/api/functions/pipeline/getPublication/index.ts @@ -4,7 +4,10 @@ import { type DynamoDBGetItemRequest } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItem, convertFieldIdToObjectId } from '../../resolver-helper' +import { + addAccountToItem, + convertFieldIdToObjectId +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBGetItemRequest { const { newsletterId, publicationId } = ctx.args.input @@ -20,7 +23,8 @@ export const response = (ctx: Context): any => { if (ctx.error !== undefined && ctx.error !== null) { util.error(ctx.error.message, ctx.error.type) } - const { emailKey, createdAt, newsletterId, publicationId, accountId } = ctx.result + const { emailKey, createdAt, newsletterId, publicationId, accountId } = + ctx.result let path = '' if (emailKey !== undefined) { path = emailKey diff --git a/lib/api/functions/pipeline/isAuthorized/index.ts b/lib/api/functions/pipeline/isAuthorized/index.ts index 0017b7d..f72ea40 100644 --- a/lib/api/functions/pipeline/isAuthorized/index.ts +++ b/lib/api/functions/pipeline/isAuthorized/index.ts @@ -4,7 +4,12 @@ * SPDX-License-Identifier: MIT-0 */ -import { type LambdaRequest, util, type Context, type AppSyncIdentityLambda } from '@aws-appsync/utils' +import { + type LambdaRequest, + util, + type Context, + type AppSyncIdentityLambda +} from '@aws-appsync/utils' import { convertAvpObjectToGraphql } from '../../resolver-helper' export function request (ctx: Context): LambdaRequest { @@ -15,7 +20,9 @@ export function request (ctx: Context): LambdaRequest { payload: { userId: identity.resolverContext.userId, accountId: identity.resolverContext.accountId, - requestContext: JSON.parse(identity.resolverContext.requestContext as string), + requestContext: JSON.parse( + identity.resolverContext.requestContext as string + ), result: ctx.prev.result, arguments: args, source, diff --git a/lib/api/functions/pipeline/listDataFeedsById/index.ts b/lib/api/functions/pipeline/listDataFeedsById/index.ts index 1da531c..0dc9b13 100644 --- a/lib/api/functions/pipeline/listDataFeedsById/index.ts +++ b/lib/api/functions/pipeline/listDataFeedsById/index.ts @@ -9,7 +9,9 @@ export function request (ctx: Context): DynamoDBBatchGetItemRequest { return { operation: 'BatchGetItem', tables: { - [NEWSLETTER_TABLE]: ctx.args.dataFeedIds.map((dataFeedId: string) => util.dynamodb.toMapValues({ dataFeedId })) + [NEWSLETTER_TABLE]: ctx.args.dataFeedIds.map((dataFeedId: string) => + util.dynamodb.toMapValues({ dataFeedId }) + ) } } } diff --git a/lib/api/functions/pipeline/listDataFeedsDiscoverable/index.ts b/lib/api/functions/pipeline/listDataFeedsDiscoverable/index.ts index b2ec2b6..919c7c0 100644 --- a/lib/api/functions/pipeline/listDataFeedsDiscoverable/index.ts +++ b/lib/api/functions/pipeline/listDataFeedsDiscoverable/index.ts @@ -5,12 +5,19 @@ import { type DynamoDBQueryRequest } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItems, convertFieldIdsToObjectIds, filterForDuplicatesById } from '../../resolver-helper' +import { + addAccountToItems, + convertFieldIdsToObjectIds, + filterForDuplicatesById +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBQueryRequest { const dataFeedTypeIndex = 'type-index' // TODO - Make ENV variable const input = ctx.args.input - const includeDiscoverable = input?.includeDiscoverable !== undefined ? input.includeDiscoverable : ctx.stash.lookupDefinition.includeDiscoverable ?? false + const includeDiscoverable = + input?.includeDiscoverable !== undefined + ? input.includeDiscoverable + : ctx.stash.lookupDefinition.includeDiscoverable ?? false if (includeDiscoverable === false) { runtime.earlyReturn(ctx.prev.result) } diff --git a/lib/api/functions/pipeline/listDataFeedsOwned/index.ts b/lib/api/functions/pipeline/listDataFeedsOwned/index.ts index 3a629b0..99cf7b7 100644 --- a/lib/api/functions/pipeline/listDataFeedsOwned/index.ts +++ b/lib/api/functions/pipeline/listDataFeedsOwned/index.ts @@ -11,13 +11,20 @@ import { type AppSyncIdentityLambda } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItems, convertFieldIdsToObjectIds, filterForDuplicatesById } from '../../resolver-helper' +import { + addAccountToItems, + convertFieldIdsToObjectIds, + filterForDuplicatesById +} from '../../resolver-helper' const dataFeedTypeIndex = 'type-index' // TODO - Make ENV variable export function request (ctx: Context): DynamoDBQueryRequest { const identity = ctx.identity as AppSyncIdentityLambda const input = ctx.args.input - const includeOwned = input?.includeOwned !== undefined ? input.includeOwned : ctx.stash.lookupDefinition.includeOwned ?? true + const includeOwned = + input?.includeOwned !== undefined + ? input.includeOwned + : ctx.stash.lookupDefinition.includeOwned ?? true if (includeOwned === false) { runtime.earlyReturn(ctx.prev.result) } diff --git a/lib/api/functions/pipeline/listDataFeedsShared/index.ts b/lib/api/functions/pipeline/listDataFeedsShared/index.ts index cc9a77b..8326640 100644 --- a/lib/api/functions/pipeline/listDataFeedsShared/index.ts +++ b/lib/api/functions/pipeline/listDataFeedsShared/index.ts @@ -5,12 +5,19 @@ import { type DynamoDBQueryRequest } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItems, convertFieldIdsToObjectIds, filterForDuplicatesById } from '../../resolver-helper' +import { + addAccountToItems, + convertFieldIdsToObjectIds, + filterForDuplicatesById +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBQueryRequest { const dataFeedTypeIndex = 'type-index' // TODO - Make ENV variable const input = ctx.args.input - const includeShared = input?.includeShared !== undefined ? input.includeShared : ctx.stash.lookupDefinition.includeShared ?? false + const includeShared = + input?.includeShared !== undefined + ? input.includeShared + : ctx.stash.lookupDefinition.includeShared ?? false if (includeShared === false) { runtime.earlyReturn(ctx.prev.result) } diff --git a/lib/api/functions/pipeline/listNewslettersById/index.ts b/lib/api/functions/pipeline/listNewslettersById/index.ts index ce98018..f86b5c0 100644 --- a/lib/api/functions/pipeline/listNewslettersById/index.ts +++ b/lib/api/functions/pipeline/listNewslettersById/index.ts @@ -3,18 +3,29 @@ import { util, type DynamoDBBatchGetItemRequest } from '@aws-appsync/utils' -import { addAccountToItems, convertFieldIdsToObjectIds, filterForDuplicatesById } from '../../resolver-helper' +import { + addAccountToItems, + convertFieldIdsToObjectIds, + filterForDuplicatesById +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBBatchGetItemRequest { const { NEWSLETTER_TABLE } = ctx.env - const newsletterIds = ctx.args.newsletterIds ?? ctx.prev.result.newsletterIds ?? undefined - if (newsletterIds === undefined) { util.error('No newsletter Ids defined', 'ValidationException') } - if (newsletterIds.length === 0) { runtime.earlyReturn([]) } + const newsletterIds = + ctx.args.newsletterIds ?? ctx.prev.result.newsletterIds ?? undefined + if (newsletterIds === undefined) { + util.error('No newsletter Ids defined', 'ValidationException') + } + if (newsletterIds.length === 0) { + runtime.earlyReturn([]) + } return { operation: 'BatchGetItem', tables: { [NEWSLETTER_TABLE]: { - keys: newsletterIds.map((newsletterId: string) => util.dynamodb.toMapValues({ newsletterId, sk: 'newsletter' })) + keys: newsletterIds.map((newsletterId: string) => + util.dynamodb.toMapValues({ newsletterId, sk: 'newsletter' }) + ) } } } diff --git a/lib/api/functions/pipeline/listNewslettersDiscoverable/index.ts b/lib/api/functions/pipeline/listNewslettersDiscoverable/index.ts index f9245a7..ec096ca 100644 --- a/lib/api/functions/pipeline/listNewslettersDiscoverable/index.ts +++ b/lib/api/functions/pipeline/listNewslettersDiscoverable/index.ts @@ -5,13 +5,20 @@ import { type DynamoDBQueryRequest } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItems, convertFieldIdsToObjectIds, filterForDuplicatesById } from '../../resolver-helper' +import { + addAccountToItems, + convertFieldIdsToObjectIds, + filterForDuplicatesById +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBQueryRequest { const tableSKIndex = 'newsletter-item-type-index' // CDK doesn't have env variables yet const { nextToken, limit = 500 } = ctx.args const input = ctx.args.input - const includeDiscoverable = input?.includeDiscoverable !== undefined ? input.includeDiscoverable : ctx.stash.lookupDefinition.includeDiscoverable ?? false + const includeDiscoverable = + input?.includeDiscoverable !== undefined + ? input.includeDiscoverable + : ctx.stash.lookupDefinition.includeDiscoverable ?? false if (includeDiscoverable === true) { return ddb.query({ query: { diff --git a/lib/api/functions/pipeline/listNewslettersOwned/index.ts b/lib/api/functions/pipeline/listNewslettersOwned/index.ts index c98dcad..166f6fc 100644 --- a/lib/api/functions/pipeline/listNewslettersOwned/index.ts +++ b/lib/api/functions/pipeline/listNewslettersOwned/index.ts @@ -6,14 +6,21 @@ import { type AppSyncIdentityLambda } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItems, convertFieldIdsToObjectIds, filterForDuplicatesById } from '../../resolver-helper' +import { + addAccountToItems, + convertFieldIdsToObjectIds, + filterForDuplicatesById +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBQueryRequest { const identity = ctx.identity as AppSyncIdentityLambda const tableSKIndex = 'newsletter-item-type-index' // CDK doesn't have env variables yet const { nextToken, limit = 1000 } = ctx.args const input = ctx.args.input - const includeOwned = input?.includeOwned !== undefined ? input.includeOwned : ctx.stash.lookupDefinition.includeOwned ?? false as boolean + const includeOwned = + input?.includeOwned !== undefined + ? input.includeOwned + : ctx.stash.lookupDefinition.includeOwned ?? (false as boolean) if (includeOwned === true) { return ddb.query({ query: { diff --git a/lib/api/functions/pipeline/listNewslettersShared/index.ts b/lib/api/functions/pipeline/listNewslettersShared/index.ts index 13aade0..45b3bba 100644 --- a/lib/api/functions/pipeline/listNewslettersShared/index.ts +++ b/lib/api/functions/pipeline/listNewslettersShared/index.ts @@ -6,7 +6,11 @@ import { type AppSyncIdentityLambda } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItems, convertFieldIdsToObjectIds, filterForDuplicatesById } from '../../resolver-helper' +import { + addAccountToItems, + convertFieldIdsToObjectIds, + filterForDuplicatesById +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBQueryRequest { // const { tableSKIndex } = ctx.env @@ -14,7 +18,10 @@ export function request (ctx: Context): DynamoDBQueryRequest { const tableSKIndex = 'newsletter-item-type-index' // CDK doesn't have env variables yet const { nextToken, limit = 500 } = ctx.args const input = ctx.args.input - const includeShared = input?.includeShared !== undefined ? input.includeShared : ctx.stash.lookupDefinition.includeShared ?? false + const includeShared = + input?.includeShared !== undefined + ? input.includeShared + : ctx.stash.lookupDefinition.includeShared ?? false if (includeShared === true) { return ddb.query({ query: { diff --git a/lib/api/functions/pipeline/listUserSubscriptions/index.ts b/lib/api/functions/pipeline/listUserSubscriptions/index.ts index dafe0a3..5100582 100644 --- a/lib/api/functions/pipeline/listUserSubscriptions/index.ts +++ b/lib/api/functions/pipeline/listUserSubscriptions/index.ts @@ -4,7 +4,11 @@ import { type AppSyncIdentityLambda } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' -import { addAccountToItems, convertFieldIdsToObjectIds, filterForDuplicatesById } from '../../resolver-helper' +import { + addAccountToItems, + convertFieldIdsToObjectIds, + filterForDuplicatesById +} from '../../resolver-helper' export function request (ctx: Context): DynamoDBQueryRequest { const identity = ctx.identity as AppSyncIdentityLambda diff --git a/lib/api/functions/pipeline/subscribeToNewsletter/index.ts b/lib/api/functions/pipeline/subscribeToNewsletter/index.ts index 23b65fc..ed375a6 100644 --- a/lib/api/functions/pipeline/subscribeToNewsletter/index.ts +++ b/lib/api/functions/pipeline/subscribeToNewsletter/index.ts @@ -1,4 +1,9 @@ -import { type Context, util, type LambdaRequest, type AppSyncIdentityLambda } from '@aws-appsync/utils' +import { + type Context, + util, + type LambdaRequest, + type AppSyncIdentityLambda +} from '@aws-appsync/utils' export function request (ctx: Context): LambdaRequest { const { args } = ctx diff --git a/lib/api/functions/pipeline/unsubscribeFromNewsletter/index.ts b/lib/api/functions/pipeline/unsubscribeFromNewsletter/index.ts index b39d39f..802c984 100644 --- a/lib/api/functions/pipeline/unsubscribeFromNewsletter/index.ts +++ b/lib/api/functions/pipeline/unsubscribeFromNewsletter/index.ts @@ -1,4 +1,9 @@ -import { type Context, util, type LambdaRequest, type AppSyncIdentityLambda } from '@aws-appsync/utils' +import { + type Context, + util, + type LambdaRequest, + type AppSyncIdentityLambda +} from '@aws-appsync/utils' export function request (ctx: Context): LambdaRequest { const { args } = ctx diff --git a/lib/api/functions/pipeline/updateDataFeed/index.ts b/lib/api/functions/pipeline/updateDataFeed/index.ts index ebe14f7..e4f7aec 100644 --- a/lib/api/functions/pipeline/updateDataFeed/index.ts +++ b/lib/api/functions/pipeline/updateDataFeed/index.ts @@ -8,12 +8,20 @@ import * as ddb from '@aws-appsync/utils/dynamodb' export function request (ctx: Context): DynamoDBUpdateItemRequest { const { dataFeedId } = ctx.args.input const values: Record = {} - Object.keys(ctx.args.input as Record).forEach((key: string) => { - if (ctx.args?.input[key] !== undefined && ctx.args?.input[key] !== null && key !== 'dataFeedId') { - console.log(`UpdateDataFeed. Loop values: ${key} ---- ${ctx.args.input[key]}`) - values[key] = ctx.args.input[key] + Object.keys(ctx.args.input as Record).forEach( + (key: string) => { + if ( + ctx.args?.input[key] !== undefined && + ctx.args?.input[key] !== null && + key !== 'dataFeedId' + ) { + console.log( + `UpdateDataFeed. Loop values: ${key} ---- ${ctx.args.input[key]}` + ) + values[key] = ctx.args.input[key] + } } - }) + ) return ddb.update({ key: { diff --git a/lib/api/functions/pipeline/updateNewsletter/index.ts b/lib/api/functions/pipeline/updateNewsletter/index.ts index e6d9fe0..91802b3 100644 --- a/lib/api/functions/pipeline/updateNewsletter/index.ts +++ b/lib/api/functions/pipeline/updateNewsletter/index.ts @@ -27,9 +27,9 @@ export function request (ctx: Context): DynamoDBUpdateItemRequest { if (input.dataFeeds != null) { expression += '#dataFeedIds = :dataFeedIds, ' expressionNames['#dataFeedIds'] = 'dataFeedIds' - expressionValues[':dataFeedIds'] = util.dynamodb.toDynamoDB( - [...input.dataFeeds] - ) + expressionValues[':dataFeedIds'] = util.dynamodb.toDynamoDB([ + ...input.dataFeeds + ]) updates = updates + 1 } if (input.isPrivate !== null) { diff --git a/lib/api/functions/resolver-helper.ts b/lib/api/functions/resolver-helper.ts index f85c625..7469a9b 100644 --- a/lib/api/functions/resolver-helper.ts +++ b/lib/api/functions/resolver-helper.ts @@ -44,17 +44,20 @@ export const convertAvpObjectsToGraphql = (obj: any): any => { } /** - * Converts field id to object's "id" field and removes the provided id field - * @param item - * @param idFieldName - * @returns item - */ -export const convertFieldIdToObjectId = (obj: any, idFieldName: string): any => { + * Converts field id to object's "id" field and removes the provided id field + * @param item + * @param idFieldName + * @returns item + */ +export const convertFieldIdToObjectId = ( + obj: any, + idFieldName: string +): any => { if (obj === undefined) { return obj } obj.id = obj[idFieldName] - + delete obj[idFieldName] return obj } @@ -65,7 +68,11 @@ export const convertFieldIdToObjectId = (obj: any, idFieldName: string): any => * @param objectName * @returns */ -export const convertFieldIdToObject = (obj: any, fieldIdName: string, objectName: string): any => { +export const convertFieldIdToObject = ( + obj: any, + fieldIdName: string, + objectName: string +): any => { if (obj === undefined) { return obj } @@ -73,7 +80,7 @@ export const convertFieldIdToObject = (obj: any, fieldIdName: string, objectName __typename: objectName, id: obj[fieldIdName] } - + delete obj[fieldIdName] return obj } @@ -88,9 +95,13 @@ export const filterForDuplicatesById = (obj: any): any => { return obj } return { - items: obj.items.filter((item: { id: any }, index: any, itemArray: any[]) => { - return itemArray.findIndex((i: { id: any }) => i.id === item.id) === index - }) + items: obj.items.filter( + (item: { id: any }, index: any, itemArray: any[]) => { + return ( + itemArray.findIndex((i: { id: any }) => i.id === item.id) === index + ) + } + ) } } @@ -99,7 +110,10 @@ export const filterForDuplicatesById = (obj: any): any => { * @param idFieldName * @returns */ -export const convertFieldIdsToObjectIds = (obj: any, idFieldName: string): any => { +export const convertFieldIdsToObjectIds = ( + obj: any, + idFieldName: string +): any => { if (obj === undefined || obj.items === undefined) { return obj } @@ -117,7 +131,11 @@ export const convertFieldIdsToObjectIds = (obj: any, idFieldName: string): any = * @param objectName * @returns */ -export const convertFieldIdsToObjects = (obj: any, idFieldName: string, objectName: string): any => { +export const convertFieldIdsToObjects = ( + obj: any, + idFieldName: string, + objectName: string +): any => { if (obj === undefined || obj.items === undefined) { return obj } diff --git a/lib/api/functions/resolver/createNewsletter/index.ts b/lib/api/functions/resolver/createNewsletter/index.ts index e6ac607..b2d06f6 100644 --- a/lib/api/functions/resolver/createNewsletter/index.ts +++ b/lib/api/functions/resolver/createNewsletter/index.ts @@ -4,7 +4,12 @@ * SPDX-License-Identifier: MIT-0 */ -import { type Context, util, type LambdaRequest, type AppSyncIdentityLambda } from '@aws-appsync/utils' +import { + type Context, + util, + type LambdaRequest, + type AppSyncIdentityLambda +} from '@aws-appsync/utils' export function request (ctx: Context): LambdaRequest { ctx.stash.root = 'Newsletter' diff --git a/lib/api/functions/resolver/externalUnsubscribeFromNewsletter/index.ts b/lib/api/functions/resolver/externalUnsubscribeFromNewsletter/index.ts index f236869..ca3ab77 100644 --- a/lib/api/functions/resolver/externalUnsubscribeFromNewsletter/index.ts +++ b/lib/api/functions/resolver/externalUnsubscribeFromNewsletter/index.ts @@ -3,7 +3,10 @@ import { type Context, util, type LambdaRequest } from '@aws-appsync/utils' export function request (ctx: Context): LambdaRequest { const { newsletterId, userId } = ctx.args.input if (newsletterId === null || userId === null) { - util.error('Newsletter ID & User ID are both required', 'ValidationException') + util.error( + 'Newsletter ID & User ID are both required', + 'ValidationException' + ) } return { operation: 'Invoke', diff --git a/lib/api/functions/resolver/listDataFeeds/index.ts b/lib/api/functions/resolver/listDataFeeds/index.ts index 773f550..d699047 100644 --- a/lib/api/functions/resolver/listDataFeeds/index.ts +++ b/lib/api/functions/resolver/listDataFeeds/index.ts @@ -3,7 +3,12 @@ import { type Context, util } from '@aws-appsync/utils' export function request (ctx: Context): any { const input = ctx.args.input ctx.stash.root = 'DataFeeds' - if (input === undefined || (input.includeOwned === undefined && input.includeShared === undefined && input.includeDiscoverable === undefined)) { + if ( + input === undefined || + (input.includeOwned === undefined && + input.includeShared === undefined && + input.includeDiscoverable === undefined) + ) { ctx.stash.lookupDefinition = { includeOwned: true, includeShared: false, diff --git a/lib/api/functions/resolver/listNewsletters/index.ts b/lib/api/functions/resolver/listNewsletters/index.ts index 75d814e..ef22c51 100644 --- a/lib/api/functions/resolver/listNewsletters/index.ts +++ b/lib/api/functions/resolver/listNewsletters/index.ts @@ -10,7 +10,12 @@ export function request (ctx: Context): any { ctx.stash.root = 'Newsletters' console.log('[listNewslettersResolverRequest]', { ctx }) const input = ctx.args.input - if (input === undefined || (input.includeDiscoverable === undefined && input.includeOwned === undefined && input.includeShared === undefined)) { + if ( + input === undefined || + (input.includeDiscoverable === undefined && + input.includeOwned === undefined && + input.includeShared === undefined) + ) { ctx.stash.lookupDefinition = { includeOwned: true, includeShared: false, diff --git a/lib/api/functions/resolver/unsubscribeFromNewsletter/index.ts b/lib/api/functions/resolver/unsubscribeFromNewsletter/index.ts index bcf859e..aee08a4 100644 --- a/lib/api/functions/resolver/unsubscribeFromNewsletter/index.ts +++ b/lib/api/functions/resolver/unsubscribeFromNewsletter/index.ts @@ -4,7 +4,12 @@ * SPDX-License-Identifier: MIT-0 */ -import { type Context, util, type LambdaRequest, type AppSyncIdentityLambda } from '@aws-appsync/utils' +import { + type Context, + util, + type LambdaRequest, + type AppSyncIdentityLambda +} from '@aws-appsync/utils' export function request (ctx: Context): LambdaRequest { ctx.stash.root = 'Newsletter' diff --git a/lib/api/resolvers.ts b/lib/api/resolvers.ts index e31694f..436ce7d 100644 --- a/lib/api/resolvers.ts +++ b/lib/api/resolvers.ts @@ -15,7 +15,14 @@ import { import { Construct } from 'constructs' import * as path from 'path' import { type ApiProps } from '.' -import { Effect, Policy, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam' +import { + Effect, + Policy, + PolicyDocument, + PolicyStatement, + Role, + ServicePrincipal +} from 'aws-cdk-lib/aws-iam' interface ApiResolversProps extends ApiProps { api: GraphqlApi @@ -24,47 +31,65 @@ interface ApiResolversProps extends ApiProps { export class ApiResolvers extends Construct { constructor (scope: Construct, id: string, props: ApiResolversProps) { super(scope, id) - const { api, dataFeedTable, newsletterTable, unauthenticatedUserRole } = props + const { api, dataFeedTable, newsletterTable, unauthenticatedUserRole } = + props const functionsPath = path.join(__dirname, 'functions') - const getFunctionPath = (functionName: string, functionType: 'pipeline' | 'resolver'): string => { - return path.join(functionsPath, 'out', functionType, functionName, 'index.js') + const getFunctionPath = ( + functionName: string, + functionType: 'pipeline' | 'resolver' + ): string => { + return path.join( + functionsPath, + 'out', + functionType, + functionName, + 'index.js' + ) } /** ****** DATA SOURCES FOR AppSync ******* **/ - const newsletterTableSourceRole = new Role(this, 'NewsletterTableSourceRole', { - assumedBy: new ServicePrincipal('appsync.amazonaws.com'), - inlinePolicies: { - NewsletterTableSourceRolePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - actions: [ - 'dynamodb:GetItem', - 'dynamodb:PutItem', - 'dynamodb:UpdateItem', - 'dynamodb:DeleteItem', - 'dynamodb:Query', - 'dynamodb:Scan', - 'dynamodb:BatchGetItem' - ], - resources: [ - newsletterTable.tableArn, - `${newsletterTable.tableArn}/index/${props.newsletterTableItemTypeGSI}` - ] - }) - ] - }) + const newsletterTableSourceRole = new Role( + this, + 'NewsletterTableSourceRole', + { + assumedBy: new ServicePrincipal('appsync.amazonaws.com'), + inlinePolicies: { + NewsletterTableSourceRolePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: [ + 'dynamodb:GetItem', + 'dynamodb:PutItem', + 'dynamodb:UpdateItem', + 'dynamodb:DeleteItem', + 'dynamodb:Query', + 'dynamodb:Scan', + 'dynamodb:BatchGetItem' + ], + resources: [ + newsletterTable.tableArn, + `${newsletterTable.tableArn}/index/${props.newsletterTableItemTypeGSI}` + ] + }) + ] + }) + } } - }) + ) - const newsletterTableSource = new DynamoDbDataSource(this, 'NewsletterTableSource', { - api, - table: newsletterTable, - serviceRole: newsletterTableSourceRole.withoutPolicyUpdates(), - name: 'NewsletterTableSource', - description: 'DynamoDB data source for newsletter table' - }) + const newsletterTableSource = new DynamoDbDataSource( + this, + 'NewsletterTableSource', + { + api, + table: newsletterTable, + serviceRole: newsletterTableSourceRole.withoutPolicyUpdates(), + name: 'NewsletterTableSource', + description: 'DynamoDB data source for newsletter table' + } + ) const dataFeedTableSourceRole = new Role(this, 'DataFeedTableSourceRole', { assumedBy: new ServicePrincipal('appsync.amazonaws.com'), @@ -89,174 +114,209 @@ export class ApiResolvers extends Construct { ] }) } - } - ) - - const dataFeedTableSource = new DynamoDbDataSource(this, 'DataFeedTableSource', { - api, - table: dataFeedTable, - description: 'DynamoDB data source for Data Feed table', - serviceRole: dataFeedTableSourceRole.withoutPolicyUpdates(), - name: 'DataFeedTableSource' }) - const dataFeedSubscriberLambdaSourceRole = new Role(this, 'DataFeedSubscriberLambdaSourceRole', { - assumedBy: new ServicePrincipal('appsync.amazonaws.com'), - inlinePolicies: { - DataFeedSubscriberLambdaSourceRolePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - actions: [ - 'lambda:InvokeFunction' - ], - resources: [ - props.functions.feedSubscriberFunction.functionArn - ] - }) - ] - }) + const dataFeedTableSource = new DynamoDbDataSource( + this, + 'DataFeedTableSource', + { + api, + table: dataFeedTable, + description: 'DynamoDB data source for Data Feed table', + serviceRole: dataFeedTableSourceRole.withoutPolicyUpdates(), + name: 'DataFeedTableSource' } - } ) - const dataFeedSubscriberLambdaSource = new LambdaDataSource(this, 'DataFeedSubscriberLambdaSource', { - api, - lambdaFunction: props.functions.feedSubscriberFunction, - name: 'DataFeedSubscriberLambdaSource', - description: 'Lambda data source for feedSubscriber function', - serviceRole: dataFeedSubscriberLambdaSourceRole.withoutPolicyUpdates() - }) + const dataFeedSubscriberLambdaSourceRole = new Role( + this, + 'DataFeedSubscriberLambdaSourceRole', + { + assumedBy: new ServicePrincipal('appsync.amazonaws.com'), + inlinePolicies: { + DataFeedSubscriberLambdaSourceRolePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: [props.functions.feedSubscriberFunction.functionArn] + }) + ] + }) + } + } + ) - const newsletterCreatorLambdaSourceRole = new Role(this, 'NewsletterCreatorLambdaSourceRole', { - assumedBy: new ServicePrincipal('appsync.amazonaws.com'), - inlinePolicies: { - NewsletterCreatorLambdaSourceRolePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - actions: [ - 'lambda:InvokeFunction' - ], - resources: [ - props.functions.createNewsletterFunction.functionArn - ] - }) - ] - }) + const dataFeedSubscriberLambdaSource = new LambdaDataSource( + this, + 'DataFeedSubscriberLambdaSource', + { + api, + lambdaFunction: props.functions.feedSubscriberFunction, + name: 'DataFeedSubscriberLambdaSource', + description: 'Lambda data source for feedSubscriber function', + serviceRole: dataFeedSubscriberLambdaSourceRole.withoutPolicyUpdates() } - }) + ) - const newsletterCreatorLambdaSource = new LambdaDataSource(this, 'NewsletterCreatorLambdaSource', { - api, - lambdaFunction: props.functions.createNewsletterFunction, - name: 'NewsletterCreatorLambdaSource', - description: 'Lambda data source for createNewsletter function', - serviceRole: newsletterCreatorLambdaSourceRole.withoutPolicyUpdates() - }) + const newsletterCreatorLambdaSourceRole = new Role( + this, + 'NewsletterCreatorLambdaSourceRole', + { + assumedBy: new ServicePrincipal('appsync.amazonaws.com'), + inlinePolicies: { + NewsletterCreatorLambdaSourceRolePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: [ + props.functions.createNewsletterFunction.functionArn + ] + }) + ] + }) + } + } + ) - const userSubscriberLambdaSourceRole = new Role(this, 'UserSubscriberLambdaSourceRole', { - assumedBy: new ServicePrincipal('appsync.amazonaws.com'), - inlinePolicies: { - UserSubscriberLambdaSourceRolePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - actions: [ - 'lambda:InvokeFunction' - ], - resources: [ - props.functions.userSubscriberFunction.functionArn - ] - }) - ] - }) + const newsletterCreatorLambdaSource = new LambdaDataSource( + this, + 'NewsletterCreatorLambdaSource', + { + api, + lambdaFunction: props.functions.createNewsletterFunction, + name: 'NewsletterCreatorLambdaSource', + description: 'Lambda data source for createNewsletter function', + serviceRole: newsletterCreatorLambdaSourceRole.withoutPolicyUpdates() } - } ) - const userSubscriberLambdaSource = new LambdaDataSource(this, 'UserSubscriberLambdaSource', { - api, - lambdaFunction: props.functions.userSubscriberFunction, - name: 'UserSubscriberLambdaSource', - description: 'Lambda data source for userSubscriber function', - serviceRole: userSubscriberLambdaSourceRole.withoutPolicyUpdates() - }) + const userSubscriberLambdaSourceRole = new Role( + this, + 'UserSubscriberLambdaSourceRole', + { + assumedBy: new ServicePrincipal('appsync.amazonaws.com'), + inlinePolicies: { + UserSubscriberLambdaSourceRolePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: [props.functions.userSubscriberFunction.functionArn] + }) + ] + }) + } + } + ) - const userUnsubscriberLambdaSourceRole = new Role(this, 'UserUnsubscriberLambdaSourceRole', { - assumedBy: new ServicePrincipal('appsync.amazonaws.com'), - inlinePolicies: { - UserUnsubscriberLambdaSourceRolePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - actions: [ - 'lambda:InvokeFunction' - ], - resources: [ - props.functions.userUnsubscriberFunction.functionArn - ] - }) - ] - }) + const userSubscriberLambdaSource = new LambdaDataSource( + this, + 'UserSubscriberLambdaSource', + { + api, + lambdaFunction: props.functions.userSubscriberFunction, + name: 'UserSubscriberLambdaSource', + description: 'Lambda data source for userSubscriber function', + serviceRole: userSubscriberLambdaSourceRole.withoutPolicyUpdates() } - }) + ) - const userUnsubscriberLambdaSource = new LambdaDataSource(this, 'UserUnsubscriberLambdaSource', { - api, - lambdaFunction: props.functions.userUnsubscriberFunction, - name: 'UserUnsubscriberLambdaSource', - description: 'Lambda data source for userUnsubscriber function', - serviceRole: userUnsubscriberLambdaSourceRole.withoutPolicyUpdates() - }) + const userUnsubscriberLambdaSourceRole = new Role( + this, + 'UserUnsubscriberLambdaSourceRole', + { + assumedBy: new ServicePrincipal('appsync.amazonaws.com'), + inlinePolicies: { + UserUnsubscriberLambdaSourceRolePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: [ + props.functions.userUnsubscriberFunction.functionArn + ] + }) + ] + }) + } + } + ) - const isAuthorizedFunctionSourceRole = new Role(this, 'IsAuthorizedFunctionSourceRole', { - assumedBy: new ServicePrincipal('appsync.amazonaws.com'), - inlinePolicies: { - IsAuthInvokePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - actions: [ - 'lambda:InvokeFunction' - ], - resources: [ - props.functions.graphqlReadAuthorizerFunction.functionArn - ] - }) - ] - }) + const userUnsubscriberLambdaSource = new LambdaDataSource( + this, + 'UserUnsubscriberLambdaSource', + { + api, + lambdaFunction: props.functions.userUnsubscriberFunction, + name: 'UserUnsubscriberLambdaSource', + description: 'Lambda data source for userUnsubscriber function', + serviceRole: userUnsubscriberLambdaSourceRole.withoutPolicyUpdates() } - }) + ) - const isAuthorizedFunctionSource = new LambdaDataSource(this, 'IsAuthorizedFunctionSource', { - api, - lambdaFunction: props.functions.graphqlReadAuthorizerFunction, - name: 'isAuthorizedFunctionSource', - description: 'Lambda data source for isAuthorized function', - serviceRole: isAuthorizedFunctionSourceRole.withoutPolicyUpdates() - }) + const isAuthorizedFunctionSourceRole = new Role( + this, + 'IsAuthorizedFunctionSourceRole', + { + assumedBy: new ServicePrincipal('appsync.amazonaws.com'), + inlinePolicies: { + IsAuthInvokePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: [ + props.functions.graphqlReadAuthorizerFunction.functionArn + ] + }) + ] + }) + } + } + ) - const filterListByAuthorizationFunctionSourceRole = new Role(this, 'FilterListByAuthFunctionSourceRole', { - assumedBy: new ServicePrincipal('appsync.amazonaws.com'), - inlinePolicies: { - FilterIsAuthInvokePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - actions: [ - 'lambda:InvokeFunction' - ], - resources: [ - props.functions.graphqlFilterReadAuthorizerFunction.functionArn - ] - }) - ] - }) + const isAuthorizedFunctionSource = new LambdaDataSource( + this, + 'IsAuthorizedFunctionSource', + { + api, + lambdaFunction: props.functions.graphqlReadAuthorizerFunction, + name: 'isAuthorizedFunctionSource', + description: 'Lambda data source for isAuthorized function', + serviceRole: isAuthorizedFunctionSourceRole.withoutPolicyUpdates() } - }) + ) - const filterListByAuthorizationSource = new LambdaDataSource(this, 'FilterIsAuthorizedFunctionSource', { - api, - lambdaFunction: props.functions.graphqlFilterReadAuthorizerFunction, - name: 'filterIsAuthorizedFunctionSource', - description: 'Lambda data source for isAuthorized function', - serviceRole: filterListByAuthorizationFunctionSourceRole.withoutPolicyUpdates() - }) + const filterListByAuthorizationFunctionSourceRole = new Role( + this, + 'FilterListByAuthFunctionSourceRole', + { + assumedBy: new ServicePrincipal('appsync.amazonaws.com'), + inlinePolicies: { + FilterIsAuthInvokePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: [ + props.functions.graphqlFilterReadAuthorizerFunction + .functionArn + ] + }) + ] + }) + } + } + ) + + const filterListByAuthorizationSource = new LambdaDataSource( + this, + 'FilterIsAuthorizedFunctionSource', + { + api, + lambdaFunction: props.functions.graphqlFilterReadAuthorizerFunction, + name: 'filterIsAuthorizedFunctionSource', + description: 'Lambda data source for isAuthorized function', + serviceRole: + filterListByAuthorizationFunctionSourceRole.withoutPolicyUpdates() + } + ) /** AppSync Resolver Pipeline Functions */ @@ -279,7 +339,9 @@ export class ApiResolvers extends Construct { name: 'listNewslettersOwned', api, dataSource: newsletterTableSource, - code: AssetCode.fromAsset(getFunctionPath('listNewslettersOwned', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('listNewslettersOwned', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -292,7 +354,9 @@ export class ApiResolvers extends Construct { api, dataSource: newsletterTableSource, runtime: FunctionRuntime.JS_1_0_0, - code: AssetCode.fromAsset(getFunctionPath('listNewslettersDiscoverable', 'pipeline')) + code: AssetCode.fromAsset( + getFunctionPath('listNewslettersDiscoverable', 'pipeline') + ) } ) @@ -304,7 +368,9 @@ export class ApiResolvers extends Construct { api, dataSource: newsletterTableSource, runtime: FunctionRuntime.JS_1_0_0, - code: AssetCode.fromAsset(getFunctionPath('listNewslettersShared', 'pipeline')) + code: AssetCode.fromAsset( + getFunctionPath('listNewslettersShared', 'pipeline') + ) } ) @@ -329,7 +395,9 @@ export class ApiResolvers extends Construct { name: 'listPublications', api, dataSource: newsletterTableSource, - code: AssetCode.fromAsset(getFunctionPath('listPublications', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('listPublications', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -340,7 +408,9 @@ export class ApiResolvers extends Construct { name: 'getPublication', api, dataSource: newsletterTableSource, - code: AssetCode.fromAsset(getFunctionPath('getPublication', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('getPublication', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -352,7 +422,9 @@ export class ApiResolvers extends Construct { name: 'updateNewsletter', api, dataSource: newsletterTableSource, - code: AssetCode.fromAsset(getFunctionPath('updateNewsletter', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('updateNewsletter', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -363,7 +435,9 @@ export class ApiResolvers extends Construct { name: 'subscribeToNewsletter', api, dataSource: userSubscriberLambdaSource, - code: AssetCode.fromAsset(getFunctionPath('subscribeToNewsletter', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('subscribeToNewsletter', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -375,7 +449,9 @@ export class ApiResolvers extends Construct { name: 'unsubscribeFromNewsletter', api, dataSource: userUnsubscriberLambdaSource, - code: AssetCode.fromAsset(getFunctionPath('unsubscribeFromNewsletter', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('unsubscribeFromNewsletter', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -387,7 +463,9 @@ export class ApiResolvers extends Construct { name: 'listDataFeedsOwned', api, dataSource: dataFeedTableSource, - code: AssetCode.fromAsset(getFunctionPath('listDataFeedsOwned', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('listDataFeedsOwned', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -399,7 +477,9 @@ export class ApiResolvers extends Construct { name: 'listDataFeedsShared', api, dataSource: dataFeedTableSource, - code: AssetCode.fromAsset(getFunctionPath('listDataFeedsShared', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('listDataFeedsShared', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -411,7 +491,9 @@ export class ApiResolvers extends Construct { name: 'listDataFeedsDiscoverable', api, dataSource: dataFeedTableSource, - code: AssetCode.fromAsset(getFunctionPath('listDataFeedsDiscoverable', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('listDataFeedsDiscoverable', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -423,7 +505,9 @@ export class ApiResolvers extends Construct { name: 'createDataFeed', api, dataSource: dataFeedSubscriberLambdaSource, - code: AssetCode.fromAsset(getFunctionPath('createDataFeed', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('createDataFeed', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -446,7 +530,9 @@ export class ApiResolvers extends Construct { name: 'updateDataFeed', api, dataSource: dataFeedTableSource, - code: AssetCode.fromAsset(getFunctionPath('updateDataFeed', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('updateDataFeed', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -470,7 +556,9 @@ export class ApiResolvers extends Construct { name: 'checkSubscriptionToNewsletter', api, dataSource: newsletterTableSource, - code: AssetCode.fromAsset(getFunctionPath('checkSubscriptionToNewsletter', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('checkSubscriptionToNewsletter', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -482,7 +570,9 @@ export class ApiResolvers extends Construct { name: 'getNewsletterSubscriberStats', api, dataSource: newsletterTableSource, - code: AssetCode.fromAsset(getFunctionPath('getNewsletterSubscriberStats', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('getNewsletterSubscriberStats', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -506,7 +596,9 @@ export class ApiResolvers extends Construct { name: 'listUserSubscriptions', api, dataSource: newsletterTableSource, - code: AssetCode.fromAsset(getFunctionPath('listUserSubscriptions', 'pipeline')), + code: AssetCode.fromAsset( + getFunctionPath('listUserSubscriptions', 'pipeline') + ), runtime: FunctionRuntime.JS_1_0_0 } ) @@ -519,13 +611,19 @@ export class ApiResolvers extends Construct { code: AssetCode.fromAsset(getFunctionPath('isAuthorized', 'pipeline')) }) - const filterListByAuthorization = new AppsyncFunction(this, 'filterListByAuthorization', { - name: 'filterListByAuthorization', - api, - dataSource: filterListByAuthorizationSource, - runtime: FunctionRuntime.JS_1_0_0, - code: AssetCode.fromAsset(getFunctionPath('filterListByAuthorization', 'pipeline')) - }) + const filterListByAuthorization = new AppsyncFunction( + this, + 'filterListByAuthorization', + { + name: 'filterListByAuthorization', + api, + dataSource: filterListByAuthorizationSource, + runtime: FunctionRuntime.JS_1_0_0, + code: AssetCode.fromAsset( + getFunctionPath('filterListByAuthorization', 'pipeline') + ) + } + ) /** AppSync GraphQL API Resolvers */ @@ -544,16 +642,27 @@ export class ApiResolvers extends Construct { fieldName: 'listNewsletters', code: AssetCode.fromAsset(getFunctionPath('listNewsletters', 'resolver')), runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [listNewslettersOwned, listNewslettersDiscoverable, listNewslettersShared, filterListByAuthorization] + pipelineConfig: [ + listNewslettersOwned, + listNewslettersDiscoverable, + listNewslettersShared, + filterListByAuthorization + ] }) new Resolver(this, 'ListPublicationsResolver', { api, typeName: 'Query', fieldName: 'listPublications', - code: AssetCode.fromAsset(getFunctionPath('listPublications', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('listPublications', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [getNewsletterFunction, listPublicationsFunction, filterListByAuthorization] + pipelineConfig: [ + getNewsletterFunction, + listPublicationsFunction, + filterListByAuthorization + ] }) new Resolver(this, 'getPublicationResolverFunction', { @@ -569,9 +678,15 @@ export class ApiResolvers extends Construct { api, typeName: 'Mutation', fieldName: 'updateNewsletter', - code: AssetCode.fromAsset(getFunctionPath('updateNewsletter', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('updateNewsletter', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [getNewsletterFunction, isAuthorized, updateNewsletterResolverFunction] + pipelineConfig: [ + getNewsletterFunction, + isAuthorized, + updateNewsletterResolverFunction + ] }) new Resolver(this, 'ListDataFeedsResolver', { @@ -580,7 +695,12 @@ export class ApiResolvers extends Construct { fieldName: 'listDataFeeds', code: AssetCode.fromAsset(getFunctionPath('listDataFeeds', 'resolver')), runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [listDataFeedsOwnedFunction, listDataFeedsSharedFunction, listDataFeedsDiscoverable, filterListByAuthorization] + pipelineConfig: [ + listDataFeedsOwnedFunction, + listDataFeedsSharedFunction, + listDataFeedsDiscoverable, + filterListByAuthorization + ] }) new Resolver(this, 'GetDataFeedResolver', { @@ -598,7 +718,11 @@ export class ApiResolvers extends Construct { fieldName: 'updateDataFeed', code: AssetCode.fromAsset(getFunctionPath('updateDataFeed', 'resolver')), runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [getDataFeedFunction, isAuthorized, updateDataFeedFunction] + pipelineConfig: [ + getDataFeedFunction, + isAuthorized, + updateDataFeedFunction + ] }) new Resolver(this, 'ListArticlesResolver', { @@ -637,7 +761,9 @@ export class ApiResolvers extends Construct { dataSource: newsletterCreatorLambdaSource, typeName: 'Mutation', fieldName: 'createNewsletter', - code: AssetCode.fromAsset(getFunctionPath('createNewsletter', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('createNewsletter', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0 }) @@ -645,8 +771,14 @@ export class ApiResolvers extends Construct { api, typeName: 'Mutation', fieldName: 'subscribeToNewsletter', - code: AssetCode.fromAsset(getFunctionPath('subscribeToNewsletter', 'resolver')), - pipelineConfig: [getNewsletterFunction, isAuthorized, subscribeToNewsletterFunction], + code: AssetCode.fromAsset( + getFunctionPath('subscribeToNewsletter', 'resolver') + ), + pipelineConfig: [ + getNewsletterFunction, + isAuthorized, + subscribeToNewsletterFunction + ], runtime: FunctionRuntime.JS_1_0_0 }) @@ -654,55 +786,72 @@ export class ApiResolvers extends Construct { api, typeName: 'Mutation', fieldName: 'unsubscribeFromNewsletter', - code: AssetCode.fromAsset(getFunctionPath('unsubscribeFromNewsletter', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('unsubscribeFromNewsletter', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0, pipelineConfig: [unsubscribeFromNewsletter] }) - const externalUnsubscribeResolver = new Resolver(this, 'ExternalUnsubscriberResolver', { - api, - typeName: 'Mutation', - fieldName: 'externalUnsubscribeFromNewsletter', - code: AssetCode.fromAsset(getFunctionPath('externalUnsubscribeFromNewsletter', 'resolver')), - runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [unsubscribeFromNewsletter] - }) - unauthenticatedUserRole.attachInlinePolicy(new Policy(this, 'UnauthRoleUnsubscribe', { - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ['appsync:GraphQL'], - resources: [externalUnsubscribeResolver.arn] - }) - ] - })) + const externalUnsubscribeResolver = new Resolver( + this, + 'ExternalUnsubscriberResolver', + { + api, + typeName: 'Mutation', + fieldName: 'externalUnsubscribeFromNewsletter', + code: AssetCode.fromAsset( + getFunctionPath('externalUnsubscribeFromNewsletter', 'resolver') + ), + runtime: FunctionRuntime.JS_1_0_0, + pipelineConfig: [unsubscribeFromNewsletter] + } + ) + unauthenticatedUserRole.attachInlinePolicy( + new Policy(this, 'UnauthRoleUnsubscribe', { + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['appsync:GraphQL'], + resources: [externalUnsubscribeResolver.arn] + }) + ] + }) + ) new Resolver(this, 'CheckSubscriptionToNewsletterResolver', { api, typeName: 'Query', fieldName: 'checkSubscriptionToNewsletter', - code: AssetCode.fromAsset(getFunctionPath('checkSubscriptionToNewsletter', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('checkSubscriptionToNewsletter', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [getNewsletterFunction, isAuthorized, checkSubscriptionToNewsletterFunction] + pipelineConfig: [ + getNewsletterFunction, + isAuthorized, + checkSubscriptionToNewsletterFunction + ] }) new Resolver(this, 'ListUserSubscriptionsResolver', { api, typeName: 'Query', fieldName: 'listUserSubscriptions', - code: AssetCode.fromAsset(getFunctionPath('listUserSubscriptions', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('listUserSubscriptions', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [ - listUserSubscriptionsFunction, - filterListByAuthorization - ] + pipelineConfig: [listUserSubscriptionsFunction, filterListByAuthorization] }) new Resolver(this, 'CanUpdateNewsletterResolver', { api, typeName: 'Query', fieldName: 'canUpdateNewsletter', - code: AssetCode.fromAsset(getFunctionPath('canUpdateNewsletter', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('canUpdateNewsletter', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0, pipelineConfig: [getNewsletterFunction, isAuthorized] }) @@ -711,7 +860,9 @@ export class ApiResolvers extends Construct { api, typeName: 'Query', fieldName: 'canUpdateDataFeed', - code: AssetCode.fromAsset(getFunctionPath('canUpdateDataFeed', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('canUpdateDataFeed', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0, pipelineConfig: [getDataFeedFunction, isAuthorized] }) @@ -720,9 +871,15 @@ export class ApiResolvers extends Construct { api, typeName: 'Query', fieldName: 'getNewsletterSubscriberStats', - code: AssetCode.fromAsset(getFunctionPath('getNewsletterSubscriberStats', 'resolver')), + code: AssetCode.fromAsset( + getFunctionPath('getNewsletterSubscriberStats', 'resolver') + ), runtime: FunctionRuntime.JS_1_0_0, - pipelineConfig: [getNewsletterFunction, isAuthorized, getNewsletterSubscriberStatsFunction] + pipelineConfig: [ + getNewsletterFunction, + isAuthorized, + getNewsletterSubscriberStatsFunction + ] }) } } diff --git a/lib/authentication/index.pre-token-generation-hook.ts b/lib/authentication/index.pre-token-generation-hook.ts index b234580..4324e57 100644 --- a/lib/authentication/index.pre-token-generation-hook.ts +++ b/lib/authentication/index.pre-token-generation-hook.ts @@ -11,9 +11,20 @@ import { MetricUnits, Metrics } from '@aws-lambda-powertools/metrics' import { v4 as uuidv4 } from 'uuid' import middy from '@middy/core' import { type PreTokenGenerationAuthenticationTriggerEvent } from 'aws-lambda' -import { DynamoDBClient, type PutItemCommandInput, PutItemCommand, DynamoDBServiceException, type ScanCommandInput, ScanCommand } from '@aws-sdk/client-dynamodb' +import { + DynamoDBClient, + type PutItemCommandInput, + PutItemCommand, + DynamoDBServiceException, + type ScanCommandInput, + ScanCommand +} from '@aws-sdk/client-dynamodb' import { marshall } from '@aws-sdk/util-dynamodb' -import { AdminUpdateUserAttributesCommand, CognitoIdentityProviderClient, type AdminUpdateUserAttributesCommandInput } from '@aws-sdk/client-cognito-identity-provider' +import { + AdminUpdateUserAttributesCommand, + CognitoIdentityProviderClient, + type AdminUpdateUserAttributesCommandInput +} from '@aws-sdk/client-cognito-identity-provider' const SERVICE_NAME = 'post-authentication-hook' const ACCOUNT_TABLE = process.env.ACCOUNT_TABLE @@ -25,11 +36,17 @@ const metrics = new Metrics({ serviceName: SERVICE_NAME }) const dynamodb = tracer.captureAWSv3Client(new DynamoDBClient()) const cognito = tracer.captureAWSv3Client(new CognitoIdentityProviderClient()) -const lambdaHandler = async (event: PreTokenGenerationAuthenticationTriggerEvent): Promise => { +const lambdaHandler = async ( + event: PreTokenGenerationAuthenticationTriggerEvent +): Promise => { logger.debug('PostAuthenticationEventTriggered', { event }) metrics.addMetric('PostAuthenticationEventTriggered', MetricUnits.Count, 1) const { userAttributes } = event.request - if (userAttributes['custom:Account'] === undefined || userAttributes['custom:Account'] === null || userAttributes['custom:Account'].length < 1) { + if ( + userAttributes['custom:Account'] === undefined || + userAttributes['custom:Account'] === null || + userAttributes['custom:Account'].length < 1 + ) { logger.debug('No Account ID found for user! Creating a new one', { event }) metrics.addMetric('NewAccountCreated', MetricUnits.Count, 1) const userId = userAttributes.sub @@ -45,7 +62,9 @@ const lambdaHandler = async (event: PreTokenGenerationAuthenticationTriggerEvent } const command = new PutItemCommand(input) await dynamodb.send(command) - event.response.claimsOverrideDetails.claimsToAddOrOverride = { 'custom:Account': accountId } + event.response.claimsOverrideDetails.claimsToAddOrOverride = { + 'custom:Account': accountId + } console.log(accountId) } catch (error) { if (error instanceof DynamoDBServiceException) { @@ -61,8 +80,14 @@ const lambdaHandler = async (event: PreTokenGenerationAuthenticationTriggerEvent } const command = new ScanCommand(scanInput) const result = await dynamodb.send(command) - if (result.Items === undefined || result.Items.length !== 1 || result.Items[0].accountId.S === undefined) { - throw new Error('Account already exists for user but not found in database') + if ( + result.Items === undefined || + result.Items.length !== 1 || + result.Items[0].accountId.S === undefined + ) { + throw new Error( + 'Account already exists for user but not found in database' + ) } accountId = result.Items[0].accountId.S } diff --git a/lib/authentication/index.ts b/lib/authentication/index.ts index 63b89e7..c26208d 100644 --- a/lib/authentication/index.ts +++ b/lib/authentication/index.ts @@ -18,10 +18,25 @@ import { ClientAttributes } from 'aws-cdk-lib/aws-cognito' import { Construct } from 'constructs' -import { Role, type IRole, PolicyStatement, Effect, Policy, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam' +import { + Role, + type IRole, + PolicyStatement, + Effect, + Policy, + ServicePrincipal, + ManagedPolicy +} from 'aws-cdk-lib/aws-iam' import { AttributeType, BillingMode, Table } from 'aws-cdk-lib/aws-dynamodb' import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' -import { ApplicationLogLevel, Architecture, LambdaInsightsVersion, LogFormat, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda' +import { + ApplicationLogLevel, + Architecture, + LambdaInsightsVersion, + LogFormat, + Runtime, + Tracing +} from 'aws-cdk-lib/aws-lambda' import { NagSuppressions } from 'cdk-nag' interface AuthenticationProps { @@ -66,28 +81,42 @@ export class Authentication extends Construct { } }) this.accountTable = accountTable - const preTokenGenerationHookFunctionRole = new Role(this, 'pre-token-generation-hook-role', { - assumedBy: new ServicePrincipal('lambda.amazonaws.com') - }) - preTokenGenerationHookFunctionRole.addManagedPolicy(ManagedPolicy.fromManagedPolicyArn(this, 'PreTokenGenRoleLambdaExecution', 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole')) - const preTokenGenerationHookFunction = new NodejsFunction(this, 'pre-token-generation-hook', { - description: - 'Post Authentication, Pre-Token Generation Hook that creates a user\'s accountId', - handler: 'handler', - role: preTokenGenerationHookFunctionRole, - architecture: Architecture.ARM_64, - runtime: Runtime.NODEJS_20_X, - tracing: Tracing.ACTIVE, - logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.DEBUG, - insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, - memorySize: 128, - timeout: Duration.seconds(5), - environment: { - POWERTOOLS_LOG_LEVEL: 'DEBUG', - ACCOUNT_TABLE: accountTable.tableName + const preTokenGenerationHookFunctionRole = new Role( + this, + 'pre-token-generation-hook-role', + { + assumedBy: new ServicePrincipal('lambda.amazonaws.com') } - }) + ) + preTokenGenerationHookFunctionRole.addManagedPolicy( + ManagedPolicy.fromManagedPolicyArn( + this, + 'PreTokenGenRoleLambdaExecution', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' + ) + ) + const preTokenGenerationHookFunction = new NodejsFunction( + this, + 'pre-token-generation-hook', + { + description: + "Post Authentication, Pre-Token Generation Hook that creates a user's accountId", + handler: 'handler', + role: preTokenGenerationHookFunctionRole, + architecture: Architecture.ARM_64, + runtime: Runtime.NODEJS_20_X, + tracing: Tracing.ACTIVE, + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.DEBUG, + insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, + memorySize: 128, + timeout: Duration.seconds(5), + environment: { + POWERTOOLS_LOG_LEVEL: 'DEBUG', + ACCOUNT_TABLE: accountTable.tableName + } + } + ) if (auth === undefined || auth === null) { const selfSignUpEnabled = this.node.tryGetContext('selfSignUpEnabled') ?? false @@ -117,12 +146,14 @@ export class Authentication extends Construct { postAuthentication: preTokenGenerationHookFunction } }) - const clientWriteAttributes = new ClientAttributes().withStandardAttributes({ - familyName: true, - givenName: true, - email: true - }) - const clientReadAttributes = clientWriteAttributes.withCustomAttributes('Account') + const clientWriteAttributes = + new ClientAttributes().withStandardAttributes({ + familyName: true, + givenName: true, + email: true + }) + const clientReadAttributes = + clientWriteAttributes.withCustomAttributes('Account') const userPoolClient = userPool.addClient('UserPoolClient', { generateSecret: false, readAttributes: clientReadAttributes, @@ -227,28 +258,30 @@ export class Authentication extends Construct { }) this.identityPool = identityPool this.authenticatedUserRole = identityPool.authenticatedRole as Role - this.unauthenticatedUserRole = identityPool.unauthenticatedRole as Role + this.unauthenticatedUserRole = + identityPool.unauthenticatedRole as Role } } this.userPool = userPool } - preTokenGenerationHookFunctionRole.attachInlinePolicy(new Policy(this, 'pre-token-generation-hook-policy', { - statements: [ - new PolicyStatement({ - actions: ['dynamodb:PutItem', 'dynamodb:Scan'], - resources: [ - accountTable.tableArn - ], - effect: Effect.ALLOW - }), - new PolicyStatement({ - actions: ['cognito-idp:AdminUpdateUserAttributes'], - resources: [this.userPool.userPoolArn] - }) - ] - })) + preTokenGenerationHookFunctionRole.attachInlinePolicy( + new Policy(this, 'pre-token-generation-hook-policy', { + statements: [ + new PolicyStatement({ + actions: ['dynamodb:PutItem', 'dynamodb:Scan'], + resources: [accountTable.tableArn], + effect: Effect.ALLOW + }), + new PolicyStatement({ + actions: ['cognito-idp:AdminUpdateUserAttributes'], + resources: [this.userPool.userPoolArn] + }) + ] + }) + ) preTokenGenerationHookFunctionRole.addManagedPolicy( - ManagedPolicy.fromAwsManagedPolicyName('AWSXrayWriteOnlyAccess')) + ManagedPolicy.fromAwsManagedPolicyName('AWSXrayWriteOnlyAccess') + ) new CfnOutput(this, 'UserPoolLink', { value: `https://${Stack.of(this).region}.console.aws.amazon.com/cognito/v2/idp/user-pools/${this.userPool.userPoolId}/users?region=${Stack.of(this).region}` @@ -260,28 +293,33 @@ export class Authentication extends Construct { this.unauthenticatedUserRoleArn = this.unauthenticatedUserRole.roleArn this.userPoolClientId = this.userPoolClient.userPoolClientId /** - * Adding nag suppression to decrease sec requirements for login - */ + * Adding nag suppression to decrease sec requirements for login + */ NagSuppressions.addResourceSuppressions(this.userPool, [ { id: 'AwsSolutions-COG1', - reason: 'Skipping - Sample doesn\'t need advanced security' + reason: "Skipping - Sample doesn't need advanced security" }, { id: 'AwsSolutions-COG2', - reason: 'Skipping - Sample doesn\'t need advanced security' + reason: "Skipping - Sample doesn't need advanced security" }, { id: 'AwsSolutions-COG3', - reason: 'Skipping - Sample doesn\'t need advanced security' + reason: "Skipping - Sample doesn't need advanced security" } ]) - NagSuppressions.addResourceSuppressions(preTokenGenerationHookFunctionRole, [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allowing PreTokenGenerationHookFunctionRole to have * policies' - } - ], true) + NagSuppressions.addResourceSuppressions( + preTokenGenerationHookFunctionRole, + [ + { + id: 'AwsSolutions-IAM5', + reason: + 'Allowing PreTokenGenerationHookFunctionRole to have * policies' + } + ], + true + ) } } diff --git a/lib/authorization/authorization-helper.ts b/lib/authorization/authorization-helper.ts index 556101f..175528f 100644 --- a/lib/authorization/authorization-helper.ts +++ b/lib/authorization/authorization-helper.ts @@ -7,10 +7,19 @@ import * as schemaMap from '../shared/api/types.json' import { Kind, type OperationDefinitionNode, parse } from 'graphql' -import { type EntityItem, type AttributeValue } from '@aws-sdk/client-verifiedpermissions' +import { + type EntityItem, + type AttributeValue +} from '@aws-sdk/client-verifiedpermissions' import { type Logger } from '@aws-lambda-powertools/logger' -export const getEntityItem = (schema: Record, entityId: string, entityType: string, entityData?: Record, optionals?: { logger?: Logger }): EntityItem => { +export const getEntityItem = ( + schema: Record, + entityId: string, + entityType: string, + entityData?: Record, + optionals?: { logger?: Logger } +): EntityItem => { const { logger } = optionals ?? {} if (logger !== undefined) { logger.debug(`getEntityItem: ${entityId} ${entityType}`) @@ -27,7 +36,11 @@ export const getEntityItem = (schema: Record, entityId: string, ent return item } -export const getEntityAttributes = (schema: Record, entityType: string, entityData: Record): Record => { +export const getEntityAttributes = ( + schema: Record, + entityType: string, + entityData: Record +): Record => { const avpSchema = schema.GenAINewsletter const entityAttributes: Record = {} if (avpSchema !== undefined && avpSchema.entityTypes !== undefined) { @@ -41,7 +54,10 @@ export const getEntityAttributes = (schema: Record, entityType: str let entityDataForKey if (value.type === 'Entity') { Object.keys(entityData).forEach((dataKey) => { - if (entityData[dataKey].__typename !== undefined && entityData[dataKey].__typename === key) { + if ( + entityData[dataKey].__typename !== undefined && + entityData[dataKey].__typename === key + ) { entityDataForKey = entityData[dataKey] } }) @@ -96,14 +112,19 @@ export const lowercaseFirstLetter = (stringVal: string): string => { export const queryToActionAuth = (query: string): string => { const ast = parse(query) - const operationDefinition = ast.definitions.find(value => { + const operationDefinition = ast.definitions.find((value) => { return value.kind === Kind.OPERATION_DEFINITION }) as OperationDefinitionNode if (operationDefinition.selectionSet.kind === Kind.SELECTION_SET) { - const queryFieldSelection = operationDefinition.selectionSet.selections.find((selection) => { - return selection.kind === Kind.FIELD - }) - if (queryFieldSelection !== undefined && queryFieldSelection !== null && queryFieldSelection.kind === Kind.FIELD) { + const queryFieldSelection = + operationDefinition.selectionSet.selections.find((selection) => { + return selection.kind === Kind.FIELD + }) + if ( + queryFieldSelection !== undefined && + queryFieldSelection !== null && + queryFieldSelection.kind === Kind.FIELD + ) { return queryFieldSelection.name.value } } @@ -149,7 +170,12 @@ export const queryToResourcesEntity = (query: string): string => { const itemObject = queryFieldTypeObject?.fields?.find((fieldItem) => { return fieldItem.name === 'items' }) - if (itemObject !== undefined && itemObject.type.kind === 'LIST' && itemObject?.type?.ofType?.name !== undefined && itemObject?.type?.ofType?.name !== null) { + if ( + itemObject !== undefined && + itemObject.type.kind === 'LIST' && + itemObject?.type?.ofType?.name !== undefined && + itemObject?.type?.ofType?.name !== null + ) { return itemObject.type.ofType?.name } } @@ -167,7 +193,12 @@ export const mutationToResourceEntity = (query: string): string => { const queryField = queries.fields?.find((field) => { return field.name === action }) - if (queryField === undefined || queryField === null || queryField.type.kind !== Kind.OBJECT || queryField.type.name === null) { + if ( + queryField === undefined || + queryField === null || + queryField.type.kind !== Kind.OBJECT || + queryField.type.name === null + ) { throw new Error('Unable to locate action') } return queryField.type.name diff --git a/lib/authorization/index.action-authorization.ts b/lib/authorization/index.action-authorization.ts index e8ca5af..c7e9f9a 100644 --- a/lib/authorization/index.action-authorization.ts +++ b/lib/authorization/index.action-authorization.ts @@ -8,9 +8,19 @@ import { captureLambdaHandler } from '@aws-lambda-powertools/tracer/middleware' import { Logger, injectLambdaContext } from '@aws-lambda-powertools/logger' import { MetricUnits, Metrics } from '@aws-lambda-powertools/metrics' import { CognitoJwtVerifier } from 'aws-jwt-verify' -import { CognitoIdentityProviderClient, GetUserCommand, type GetUserCommandInput } from '@aws-sdk/client-cognito-identity-provider' // ES Modules import -import middy from '@middy/core' -import { GetSchemaCommand, VerifiedPermissionsClient, type IsAuthorizedCommandInput, IsAuthorizedCommand, Decision } from '@aws-sdk/client-verifiedpermissions' +import { + CognitoIdentityProviderClient, + GetUserCommand, + type GetUserCommandInput +} from '@aws-sdk/client-cognito-identity-provider' // ES Modules import +import middy from '@middy/core' +import { + GetSchemaCommand, + VerifiedPermissionsClient, + type IsAuthorizedCommandInput, + IsAuthorizedCommand, + Decision +} from '@aws-sdk/client-verifiedpermissions' import { queryToActionAuth } from './authorization-helper' const SERVICE_NAME = 'authorization-check' @@ -19,7 +29,8 @@ const tracer = new Tracer({ serviceName: SERVICE_NAME }) const logger = new Logger({ serviceName: SERVICE_NAME }) const metrics = new Metrics({ serviceName: SERVICE_NAME }) -const { USER_POOL_ID, USER_POOL_CLIENT_ID, POLICY_STORE_ID, VALIDATION_REGEX } = process.env +const { USER_POOL_ID, USER_POOL_CLIENT_ID, POLICY_STORE_ID, VALIDATION_REGEX } = + process.env if (VALIDATION_REGEX === undefined || VALIDATION_REGEX === null) { logger.error('VALIDATION_REGEX is not set') throw new Error('VALIDATION_REGEX is not set') @@ -39,17 +50,30 @@ const jwtVerifier = CognitoJwtVerifier.create({ tokenUse: 'access' }) -const verifiedpermissions = tracer.captureAWSv3Client(new VerifiedPermissionsClient()) -const cognitoIdp = tracer.captureAWSv3Client(new CognitoIdentityProviderClient()) +const verifiedpermissions = tracer.captureAWSv3Client( + new VerifiedPermissionsClient() +) +const cognitoIdp = tracer.captureAWSv3Client( + new CognitoIdentityProviderClient() +) let schema: Record const lambdaHandler = async (event: any): Promise => { logger.debug('AuthorizationCheckEventTriggered', { event }) - if (schema === undefined || schema === null || Object.keys(schema).length === 0) { + if ( + schema === undefined || + schema === null || + Object.keys(schema).length === 0 + ) { logger.debug('AVP Schema not yet cached. Retrieving AVP Schema') - const schemaResponse = await verifiedpermissions.send(new GetSchemaCommand({ policyStoreId: POLICY_STORE_ID })) - if (schemaResponse.schema !== undefined && schemaResponse.schema.length > 0) { + const schemaResponse = await verifiedpermissions.send( + new GetSchemaCommand({ policyStoreId: POLICY_STORE_ID }) + ) + if ( + schemaResponse.schema !== undefined && + schemaResponse.schema.length > 0 + ) { schema = JSON.parse(schemaResponse.schema) } else { metrics.addMetric('AuthCheckFailed', MetricUnits.Count, 1) @@ -84,7 +108,9 @@ const lambdaHandler = async (event: any): Promise => { }, resource: { entityType: 'GenAINewsletter::Operation', - entityId: lowercaseFirstLetter(queryToActionAuth(event.requestContext.queryString as string)) + entityId: lowercaseFirstLetter( + queryToActionAuth(event.requestContext.queryString as string) + ) }, entities: { entityList: [ @@ -105,12 +131,13 @@ const lambdaHandler = async (event: any): Promise => { { identifier: { entityType: 'GenAINewsletter::Operation', - entityId: lowercaseFirstLetter(queryToActionAuth(event.requestContext.queryString as string)) + entityId: lowercaseFirstLetter( + queryToActionAuth(event.requestContext.queryString as string) + ) } } ] } - } const command = new IsAuthorizedCommand(isAuthInput) const response = await verifiedpermissions.send(command) @@ -144,16 +171,24 @@ const lambdaHandler = async (event: any): Promise => { } } -const getUserAccountId = async (authorizationToken: string): Promise => { +const getUserAccountId = async ( + authorizationToken: string +): Promise => { logger.debug('getting accountId for authToken', { authorizationToken }) const input: GetUserCommandInput = { AccessToken: authorizationToken } const command = new GetUserCommand(input) const response = await cognitoIdp.send(command) - if (response.UserAttributes !== undefined && response.UserAttributes.length > 0) { + if ( + response.UserAttributes !== undefined && + response.UserAttributes.length > 0 + ) { for (const attribute of response.UserAttributes) { - if (attribute.Name === 'custom:Account' && attribute.Value !== undefined) { + if ( + attribute.Name === 'custom:Account' && + attribute.Value !== undefined + ) { return attribute.Value } } diff --git a/lib/authorization/index.list-filter-authorization.ts b/lib/authorization/index.list-filter-authorization.ts index f62932f..bd22d3a 100644 --- a/lib/authorization/index.list-filter-authorization.ts +++ b/lib/authorization/index.list-filter-authorization.ts @@ -9,8 +9,19 @@ import { Logger, injectLambdaContext } from '@aws-lambda-powertools/logger' import { MetricUnits, Metrics } from '@aws-lambda-powertools/metrics' import middy from '@middy/core' -import { GetSchemaCommand, VerifiedPermissionsClient, type IsAuthorizedCommandInput, IsAuthorizedCommand, Decision } from '@aws-sdk/client-verifiedpermissions' -import { getEntityItem, lowercaseFirstLetter, queryToActionAuth, queryToResourcesEntity } from './authorization-helper' +import { + GetSchemaCommand, + VerifiedPermissionsClient, + type IsAuthorizedCommandInput, + IsAuthorizedCommand, + Decision +} from '@aws-sdk/client-verifiedpermissions' +import { + getEntityItem, + lowercaseFirstLetter, + queryToActionAuth, + queryToResourcesEntity +} from './authorization-helper' const SERVICE_NAME = 'list-filter-authorization' @@ -24,17 +35,28 @@ if (POLICY_STORE_ID === undefined || POLICY_STORE_ID === null) { throw new Error('POLICY_STORE_ID is not set') } -const verifiedpermissions = tracer.captureAWSv3Client(new VerifiedPermissionsClient()) +const verifiedpermissions = tracer.captureAWSv3Client( + new VerifiedPermissionsClient() +) let schema: Record const lambdaHandler = async (event: any): Promise => { logger.debug('FilterAuthorizationCheckEventTriggered', { event }) const { userId, accountId } = event - if (schema === undefined || schema === null || Object.keys(schema).length === 0) { + if ( + schema === undefined || + schema === null || + Object.keys(schema).length === 0 + ) { logger.debug('AVP Schema not yet cached. Retrieving AVP Schema') - const schemaResponse = await verifiedpermissions.send(new GetSchemaCommand({ policyStoreId: POLICY_STORE_ID })) - if (schemaResponse.schema !== undefined && schemaResponse.schema.length > 0) { + const schemaResponse = await verifiedpermissions.send( + new GetSchemaCommand({ policyStoreId: POLICY_STORE_ID }) + ) + if ( + schemaResponse.schema !== undefined && + schemaResponse.schema.length > 0 + ) { logger.debug('AVP Schema', { schema: schemaResponse.schema }) schema = JSON.parse(schemaResponse.schema) } else { @@ -44,13 +66,25 @@ const lambdaHandler = async (event: any): Promise => { } } if (event.result.items !== undefined && event.result.items.length > 0) { - logger.debug('Checking Item Authorization for Filtering', { itemCount: event.result.items.length }) + logger.debug('Checking Item Authorization for Filtering', { + itemCount: event.result.items.length + }) const unfilteredItemPromises: Array> = [] event.result.items.forEach(async (item: any) => { - unfilteredItemPromises.push(checkItemAuthorization(item, schema, userId as string, accountId as string, event.requestContext)) + unfilteredItemPromises.push( + checkItemAuthorization( + item, + schema, + userId as string, + accountId as string, + event.requestContext + ) + ) }) const resolvedAuthItems = await Promise.all(unfilteredItemPromises) - const items = resolvedAuthItems.filter((item) => item.authorization).map((item) => item.item) + const items = resolvedAuthItems + .filter((item) => item.authorization) + .map((item) => item.item) logger.debug('Filtered Items', { itemCount: items.length }) if (items.length > 0) { return { @@ -71,7 +105,13 @@ const lambdaHandler = async (event: any): Promise => { } } -const checkItemAuthorization = async (item: any, schema: Record, userId: string, accountId: string, requestContext: any): Promise<{ item: any, authorization: boolean }> => { +const checkItemAuthorization = async ( + item: any, + schema: Record, + userId: string, + accountId: string, + requestContext: any +): Promise<{ item: any; authorization: boolean }> => { const queryString = requestContext.queryString as string const isAuthInput: IsAuthorizedCommandInput = { policyStoreId: POLICY_STORE_ID, @@ -103,10 +143,15 @@ const checkItemAuthorization = async (item: any, schema: Record, us } } }, - getEntityItem(schema, item.id as string, queryToResourcesEntity(queryString), item as Record, { logger }) + getEntityItem( + schema, + item.id as string, + queryToResourcesEntity(queryString), + item as Record, + { logger } + ) ] } - } logger.debug('AVP REQUEST', { isAuthInput diff --git a/lib/authorization/index.read-authorization.ts b/lib/authorization/index.read-authorization.ts index 92e542e..7570b2d 100644 --- a/lib/authorization/index.read-authorization.ts +++ b/lib/authorization/index.read-authorization.ts @@ -10,8 +10,19 @@ import { MetricUnits, Metrics } from '@aws-lambda-powertools/metrics' // import { getEntityItem } from '../shared/api/schema-to-avp/permission-map' import middy from '@middy/core' -import { GetSchemaCommand, VerifiedPermissionsClient, type IsAuthorizedCommandInput, IsAuthorizedCommand, Decision } from '@aws-sdk/client-verifiedpermissions' -import { getEntityItem, lowercaseFirstLetter, queryToActionAuth, queryToResourceEntity } from './authorization-helper' +import { + GetSchemaCommand, + VerifiedPermissionsClient, + type IsAuthorizedCommandInput, + IsAuthorizedCommand, + Decision +} from '@aws-sdk/client-verifiedpermissions' +import { + getEntityItem, + lowercaseFirstLetter, + queryToActionAuth, + queryToResourceEntity +} from './authorization-helper' const SERVICE_NAME = 'read-authorization' @@ -25,7 +36,9 @@ if (POLICY_STORE_ID === undefined || POLICY_STORE_ID === null) { throw new Error('POLICY_STORE_ID is not set') } -const verifiedpermissions = tracer.captureAWSv3Client(new VerifiedPermissionsClient()) +const verifiedpermissions = tracer.captureAWSv3Client( + new VerifiedPermissionsClient() +) let schema: Record @@ -33,10 +46,19 @@ const lambdaHandler = async (event: any): Promise => { logger.debug('AuthorizationCheckEventTriggered', { event }) const root = event.root as string | undefined const contingentAction = event.contingentAction as string | undefined - if (schema === undefined || schema === null || Object.keys(schema).length === 0) { + if ( + schema === undefined || + schema === null || + Object.keys(schema).length === 0 + ) { logger.debug('AVP Schema not yet cached. Retrieving AVP Schema') - const schemaResponse = await verifiedpermissions.send(new GetSchemaCommand({ policyStoreId: POLICY_STORE_ID })) - if (schemaResponse.schema !== undefined && schemaResponse.schema.length > 0) { + const schemaResponse = await verifiedpermissions.send( + new GetSchemaCommand({ policyStoreId: POLICY_STORE_ID }) + ) + if ( + schemaResponse.schema !== undefined && + schemaResponse.schema.length > 0 + ) { logger.debug('AVP Schema', { schema: schemaResponse.schema }) schema = JSON.parse(schemaResponse.schema) } else { @@ -54,7 +76,9 @@ const lambdaHandler = async (event: any): Promise => { entityType: 'GenAINewsletter::User' }, action: { - actionId: lowercaseFirstLetter(contingentAction ?? queryToActionAuth(queryString)), + actionId: lowercaseFirstLetter( + contingentAction ?? queryToActionAuth(queryString) + ), actionType: 'GenAINewsletter::Action' }, resource: { @@ -77,10 +101,15 @@ const lambdaHandler = async (event: any): Promise => { } } }, - getEntityItem(schema, event.result.id as string, root ?? queryToResourceEntity(queryString), event.result as Record, { logger }) + getEntityItem( + schema, + event.result.id as string, + root ?? queryToResourceEntity(queryString), + event.result as Record, + { logger } + ) ] } - } logger.debug('AVP REQUEST', { isAuthInput diff --git a/lib/authorization/index.ts b/lib/authorization/index.ts index 5e42912..fbe2b1b 100644 --- a/lib/authorization/index.ts +++ b/lib/authorization/index.ts @@ -3,7 +3,11 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: MIT-0 */ -import { aws_verifiedpermissions as verifiedpermissions, Duration, RemovalPolicy } from 'aws-cdk-lib' +import { + aws_verifiedpermissions as verifiedpermissions, + Duration, + RemovalPolicy +} from 'aws-cdk-lib' import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' import * as fs from 'fs' import * as path from 'path' @@ -16,7 +20,12 @@ import { Runtime, Tracing } from 'aws-cdk-lib/aws-lambda' -import { PolicyStatement, Effect, Policy, PolicyDocument } from 'aws-cdk-lib/aws-iam' +import { + PolicyStatement, + Effect, + Policy, + PolicyDocument +} from 'aws-cdk-lib/aws-iam' import { type CfnPolicy } from 'aws-cdk-lib/aws-verifiedpermissions' import { NagSuppressions } from 'cdk-nag' @@ -37,9 +46,10 @@ export class Authorization extends Construct { const { userPoolId, userPoolClientId, userPoolArn } = props super(scope, id) - const validationSettings: verifiedpermissions.CfnPolicyStore.ValidationSettingsProperty = { - mode: 'STRICT' - } + const validationSettings: verifiedpermissions.CfnPolicyStore.ValidationSettingsProperty = + { + mode: 'STRICT' + } const policyStore = new verifiedpermissions.CfnPolicyStore( this, @@ -67,29 +77,35 @@ export class Authorization extends Construct { }) const policiesFolder = path.join(__dirname, 'policies') - this.policyDefinitions = fs.readdirSync(policiesFolder).filter(p => { - const f = path.join(policiesFolder, p) - return fs.statSync(f).isFile() && p.endsWith('.cedar') - }).map((p) => { - const f = path.join(policiesFolder, p) - const policy = new verifiedpermissions.CfnPolicy(this, p, { - policyStoreId: policyStore.ref, - definition: { - static: { - description: p, - statement: fs.readFileSync(f).toString('utf-8') + this.policyDefinitions = fs + .readdirSync(policiesFolder) + .filter((p) => { + const f = path.join(policiesFolder, p) + return fs.statSync(f).isFile() && p.endsWith('.cedar') + }) + .map((p) => { + const f = path.join(policiesFolder, p) + const policy = new verifiedpermissions.CfnPolicy(this, p, { + policyStoreId: policyStore.ref, + definition: { + static: { + description: p, + statement: fs.readFileSync(f).toString('utf-8') + } } - } + }) + policy.applyRemovalPolicy(RemovalPolicy.DESTROY) + return policy }) - policy.applyRemovalPolicy(RemovalPolicy.DESTROY) - return policy - }) const avpAccessPolicy = new Policy(this, 'AuthCheckAVPAccess', { document: new PolicyDocument({ statements: [ new PolicyStatement({ - actions: ['verifiedpermissions:IsAuthorized', 'verifiedpermissions:GetSchema'], + actions: [ + 'verifiedpermissions:IsAuthorized', + 'verifiedpermissions:GetSchema' + ], resources: [policyStore.attrArn], effect: Effect.ALLOW }) @@ -97,77 +113,102 @@ export class Authorization extends Construct { }) }) - const graphqlActionAuthorizerFunction = new NodejsFunction(this, 'action-authorization', { - description: 'Function responsible for checking if requests are authorized to create items using Amazon Verified Permissions', - handler: 'handler', - architecture: Architecture.ARM_64, - runtime: Runtime.NODEJS_20_X, - tracing: Tracing.ACTIVE, - logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.DEBUG, - insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, - timeout: Duration.minutes(5), - environment: { - POWERTOOLS_LOG_LEVEL: 'DEBUG', - USER_POOL_CLIENT_ID: userPoolClientId, - USER_POOL_ID: userPoolId, - POLICY_STORE_ID: policyStore.ref, - VALIDATION_REGEX: this.avpAuthorizerValidationRegex + const graphqlActionAuthorizerFunction = new NodejsFunction( + this, + 'action-authorization', + { + description: + 'Function responsible for checking if requests are authorized to create items using Amazon Verified Permissions', + handler: 'handler', + architecture: Architecture.ARM_64, + runtime: Runtime.NODEJS_20_X, + tracing: Tracing.ACTIVE, + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.DEBUG, + insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, + timeout: Duration.minutes(5), + environment: { + POWERTOOLS_LOG_LEVEL: 'DEBUG', + USER_POOL_CLIENT_ID: userPoolClientId, + USER_POOL_ID: userPoolId, + POLICY_STORE_ID: policyStore.ref, + VALIDATION_REGEX: this.avpAuthorizerValidationRegex + } } - }) + ) graphqlActionAuthorizerFunction.role?.attachInlinePolicy(avpAccessPolicy) - const graphqlReadAuthorizerFunction = new NodejsFunction(this, 'read-authorization', { - description: 'Function responsible for checking if requests are authorized to read/view data items using Amazon Verified Permissions', - handler: 'handler', - architecture: Architecture.ARM_64, - runtime: Runtime.NODEJS_20_X, - tracing: Tracing.ACTIVE, - logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.DEBUG, - insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, - timeout: Duration.minutes(5), - environment: { - POWERTOOLS_LOG_LEVEL: 'DEBUG', - USER_POOL_CLIENT_ID: userPoolClientId, - USER_POOL_ID: userPoolId, - POLICY_STORE_ID: policyStore.ref, - VALIDATION_REGEX: this.avpAuthorizerValidationRegex + const graphqlReadAuthorizerFunction = new NodejsFunction( + this, + 'read-authorization', + { + description: + 'Function responsible for checking if requests are authorized to read/view data items using Amazon Verified Permissions', + handler: 'handler', + architecture: Architecture.ARM_64, + runtime: Runtime.NODEJS_20_X, + tracing: Tracing.ACTIVE, + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.DEBUG, + insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, + timeout: Duration.minutes(5), + environment: { + POWERTOOLS_LOG_LEVEL: 'DEBUG', + USER_POOL_CLIENT_ID: userPoolClientId, + USER_POOL_ID: userPoolId, + POLICY_STORE_ID: policyStore.ref, + VALIDATION_REGEX: this.avpAuthorizerValidationRegex + } } - }) + ) graphqlReadAuthorizerFunction.role?.attachInlinePolicy(avpAccessPolicy) - const graphqlFilterReadAuthorizerFunction = new NodejsFunction(this, 'list-filter-authorization', { - description: 'Function responsible for checking if requested resources are authorized for viewing data and filtering out unauthorized data from the list.', - handler: 'handler', - architecture: Architecture.ARM_64, - runtime: Runtime.NODEJS_20_X, - tracing: Tracing.ACTIVE, - logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.DEBUG, - insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, - timeout: Duration.minutes(5), - environment: { - POWERTOOLS_LOG_LEVEL: 'DEBUG', - USER_POOL_CLIENT_ID: userPoolClientId, - USER_POOL_ID: userPoolId, - POLICY_STORE_ID: policyStore.ref + const graphqlFilterReadAuthorizerFunction = new NodejsFunction( + this, + 'list-filter-authorization', + { + description: + 'Function responsible for checking if requested resources are authorized for viewing data and filtering out unauthorized data from the list.', + handler: 'handler', + architecture: Architecture.ARM_64, + runtime: Runtime.NODEJS_20_X, + tracing: Tracing.ACTIVE, + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.DEBUG, + insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, + timeout: Duration.minutes(5), + environment: { + POWERTOOLS_LOG_LEVEL: 'DEBUG', + USER_POOL_CLIENT_ID: userPoolClientId, + USER_POOL_ID: userPoolId, + POLICY_STORE_ID: policyStore.ref + } } - }) - graphqlFilterReadAuthorizerFunction.role?.attachInlinePolicy(avpAccessPolicy) + ) + graphqlFilterReadAuthorizerFunction.role?.attachInlinePolicy( + avpAccessPolicy + ) this.graphqlActionAuthorizerFunction = graphqlActionAuthorizerFunction this.graphqlReadAuthorizerFunction = graphqlReadAuthorizerFunction - this.graphqlFilterReadAuthorizerFunction = graphqlFilterReadAuthorizerFunction + this.graphqlFilterReadAuthorizerFunction = + graphqlFilterReadAuthorizerFunction NagSuppressions.addResourceSuppressions( - [graphqlActionAuthorizerFunction, graphqlReadAuthorizerFunction, graphqlFilterReadAuthorizerFunction], [ + [ + graphqlActionAuthorizerFunction, + graphqlReadAuthorizerFunction, + graphqlFilterReadAuthorizerFunction + ], + [ { id: 'AwsSolutions-IAM5', - reason: 'The policy is restricted to the verifiedpermissions:IsAuthorized and verifiedpermissions:GetSchema actions' + reason: + 'The policy is restricted to the verifiedpermissions:IsAuthorized and verifiedpermissions:GetSchema actions' } - ], true + ], + true ) } } diff --git a/lib/cdk-nag-supressions.ts b/lib/cdk-nag-supressions.ts index 6eae32d..23578e0 100644 --- a/lib/cdk-nag-supressions.ts +++ b/lib/cdk-nag-supressions.ts @@ -17,7 +17,9 @@ export const addNagSuppressions = (stack: Stack): void => { }, { id: 'AwsSolutions-IAM5', - appliesTo: ['Policy::arn::iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs'], + appliesTo: [ + 'Policy::arn::iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs' + ], reason: 'Allowing managed policy: AWSAppSyncPushToCloudWatchLogs' } ]) @@ -25,31 +27,38 @@ export const addNagSuppressions = (stack: Stack): void => { * This is the Stack-Level Log Retention Custom Resource. * If cdk_nag throws an IAM5 error for LogRetention, confirm the logical ID hasn't changed. */ - NagSuppressions.addResourceSuppressionsByPath(stack, - `/${stack.stackName}/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource`, [ + NagSuppressions.addResourceSuppressionsByPath( + stack, + `/${stack.stackName}/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource`, + [ { id: 'AwsSolutions-IAM5', reason: 'Allowing LogRetention to apply to any CloudWatch Resource' } - ]) + ] + ) /** * CDKBucketDeployment Resource must be referenced by Path * If cdk_nag throws errors for this resource, confirm the logical ID hasn't changed. - */ - NagSuppressions.addResourceSuppressionsByPath(stack, - `/${stack.stackName}/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allowing CDKBucketDeployment to have * policies' - } - ]) - NagSuppressions.addResourceSuppressionsByPath(stack, + */ + NagSuppressions.addResourceSuppressionsByPath( + stack, + `/${stack.stackName}/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource`, + [ + { + id: 'AwsSolutions-IAM5', + reason: 'Allowing CDKBucketDeployment to have * policies' + } + ] + ) + NagSuppressions.addResourceSuppressionsByPath( + stack, `/${stack.stackName}/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource`, [ { id: 'AwsSolutions-L1', - reason: 'Allowing CDKBucketDeployment Lambda Runtime version to be managed by CDK version' + reason: + 'Allowing CDKBucketDeployment Lambda Runtime version to be managed by CDK version' } ] ) diff --git a/lib/config.ts b/lib/config.ts index b9d5499..fa7a5be 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -9,9 +9,13 @@ import { type DeployConfig } from '../lib/shared/common/deploy-config' import { existsSync, readFileSync } from 'fs' export default function getConfig (pathValue?: string): DeployConfig { - if (existsSync(pathValue ?? path.join(__dirname, '..', 'bin', 'config.json'))) { + if ( + existsSync(pathValue ?? path.join(__dirname, '..', 'bin', 'config.json')) + ) { return JSON.parse( - readFileSync(pathValue ?? path.join(__dirname, '..', 'bin', 'config.json')).toString('utf8') + readFileSync( + pathValue ?? path.join(__dirname, '..', 'bin', 'config.json') + ).toString('utf8') ) as DeployConfig } else { throw new Error( diff --git a/lib/data-feed-ingestion/index.ts b/lib/data-feed-ingestion/index.ts index 1b7cdf5..e1d48e5 100644 --- a/lib/data-feed-ingestion/index.ts +++ b/lib/data-feed-ingestion/index.ts @@ -59,17 +59,22 @@ export class NewsSubscriptionIngestion extends Construct { } }) - const rssAtomIngestion = new RssAtomFeedConstruct(this, 'RssAtomIngestion', { - dataFeedTable, - dataFeedTableTypeIndex: this.dataFeedTableTypeIndex, - dataFeedTableLSI: this.dataFeedTableLSI, - loggingBucket - }) + const rssAtomIngestion = new RssAtomFeedConstruct( + this, + 'RssAtomIngestion', + { + dataFeedTable, + dataFeedTableTypeIndex: this.dataFeedTableTypeIndex, + dataFeedTableLSI: this.dataFeedTableLSI, + loggingBucket + } + ) this.dataFeedTable = dataFeedTable this.rssAtomDataBucket = rssAtomIngestion.rssAtomDataBucket this.feedSubscriberFunction = rssAtomIngestion.feedSubscriberFunction - this.rssAtomIngestionStepFunctionStateMachine = rssAtomIngestion.ingestionStepFunction.stateMachine + this.rssAtomIngestionStepFunctionStateMachine = + rssAtomIngestion.ingestionStepFunction.stateMachine this.dataFeedPollStepFunctionStateMachine = rssAtomIngestion.dataFeedPollStepFunction.stateMachine } diff --git a/lib/data-feed-ingestion/prompts.ts b/lib/data-feed-ingestion/prompts.ts index 8df81c4..deef6a3 100644 --- a/lib/data-feed-ingestion/prompts.ts +++ b/lib/data-feed-ingestion/prompts.ts @@ -3,7 +3,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: MIT-0 */ - + export class ArticleIngestorPromptConfiguration { private static readonly BASE_PROMPT = '\n\nHuman: ' + diff --git a/lib/data-feed-ingestion/rss-atom-ingestion/data-feed-poll-step-function.get-data-feeds.ts b/lib/data-feed-ingestion/rss-atom-ingestion/data-feed-poll-step-function.get-data-feeds.ts index 751ce9f..828d384 100644 --- a/lib/data-feed-ingestion/rss-atom-ingestion/data-feed-poll-step-function.get-data-feeds.ts +++ b/lib/data-feed-ingestion/rss-atom-ingestion/data-feed-poll-step-function.get-data-feeds.ts @@ -23,8 +23,7 @@ const metrics = new Metrics({ serviceName: SERVICE_NAME }) const dynamodb = tracer.captureAWSv3Client(new DynamoDBClient()) const DATA_FEED_TABLE = process.env.DATA_FEED_TABLE -const DATA_FEED_TABLE_TYPE_INDEX = - process.env.DATA_FEED_TABLE_TYPE_INDEX +const DATA_FEED_TABLE_TYPE_INDEX = process.env.DATA_FEED_TABLE_TYPE_INDEX interface GetDataFeedsOutput { dataFeeds: string[] diff --git a/lib/data-feed-ingestion/rss-atom-ingestion/data-feed-poll-step-function.ts b/lib/data-feed-ingestion/rss-atom-ingestion/data-feed-poll-step-function.ts index f95cae4..3a9e74d 100644 --- a/lib/data-feed-ingestion/rss-atom-ingestion/data-feed-poll-step-function.ts +++ b/lib/data-feed-ingestion/rss-atom-ingestion/data-feed-poll-step-function.ts @@ -48,28 +48,23 @@ export class DataFeedPollStepFunction extends Construct { ) { super(scope, id) - const getDataFeedsFunction = new NodejsFunction( - this, - 'get-data-feeds', - { - description: - 'Function responsible for getting all enabled data feeds to poll', - handler: 'handler', - architecture: Architecture.ARM_64, - runtime: Runtime.NODEJS_20_X, - tracing: Tracing.ACTIVE, - logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.DEBUG, - insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, - environment: { - POWERTOOLS_LOG_LEVEL: 'DEBUG', - DATA_FEED_TABLE: props.dataFeedTable.tableName, - DATA_FEED_TABLE_TYPE_INDEX: - props.dataFeedTableTypeIndex - }, - timeout: cdk.Duration.seconds(30) - } - ) + const getDataFeedsFunction = new NodejsFunction(this, 'get-data-feeds', { + description: + 'Function responsible for getting all enabled data feeds to poll', + handler: 'handler', + architecture: Architecture.ARM_64, + runtime: Runtime.NODEJS_20_X, + tracing: Tracing.ACTIVE, + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.DEBUG, + insightsVersion: LambdaInsightsVersion.VERSION_1_0_229_0, + environment: { + POWERTOOLS_LOG_LEVEL: 'DEBUG', + DATA_FEED_TABLE: props.dataFeedTable.tableName, + DATA_FEED_TABLE_TYPE_INDEX: props.dataFeedTableTypeIndex + }, + timeout: cdk.Duration.seconds(30) + }) // Step Function Tasks // Get Data Feeds from Data Feed Table @@ -128,10 +123,13 @@ export class DataFeedPollStepFunction extends Construct { */ NagSuppressions.addResourceSuppressions( [stateMachine, getDataFeedsFunction], - [{ - id: 'AwsSolutions-IAM5', - reason: 'Allowing CloudWatch & XRay' - }], true + [ + { + id: 'AwsSolutions-IAM5', + reason: 'Allowing CloudWatch & XRay' + } + ], + true ) } } diff --git a/lib/data-feed-ingestion/rss-atom-ingestion/index.feed-subscriber.ts b/lib/data-feed-ingestion/rss-atom-ingestion/index.feed-subscriber.ts index d456745..a0ebe58 100644 --- a/lib/data-feed-ingestion/rss-atom-ingestion/index.feed-subscriber.ts +++ b/lib/data-feed-ingestion/rss-atom-ingestion/index.feed-subscriber.ts @@ -47,7 +47,9 @@ const lambdaHander = async (event: { metrics.addMetric('SubscriberInvocations', MetricUnits.Count, 1) const { url, summarizationPrompt, title, description, enabled } = event.input const isPrivate: boolean = event.input.isPrivate ?? true - if (url === undefined) { throw new Error('URL is required') } + if (url === undefined) { + throw new Error('URL is required') + } const { accountId } = event try { const response = await axios.get(url) @@ -60,7 +62,8 @@ const lambdaHander = async (event: { logger.debug('Found RSS feed') feedType = DataFeedType.RSS } else if ( - $('feed').length > 0 && $('feed').attr('xmlns') === 'http://www.w3.org/2005/Atom' + $('feed').length > 0 && + $('feed').attr('xmlns') === 'http://www.w3.org/2005/Atom' ) { metrics.addMetric('ATOMFeeds', MetricUnits.Count, 1) logger.debug('Found ATOM feed') @@ -98,9 +101,7 @@ const lambdaHander = async (event: { } } -const storeDataFeed = async ( - dataFeed: DataFeed -): Promise => { +const storeDataFeed = async (dataFeed: DataFeed): Promise => { const { id: dataFeedId, url, diff --git a/lib/data-feed-ingestion/rss-atom-ingestion/index.ts b/lib/data-feed-ingestion/rss-atom-ingestion/index.ts index d6f3f69..55f1853 100644 --- a/lib/data-feed-ingestion/rss-atom-ingestion/index.ts +++ b/lib/data-feed-ingestion/rss-atom-ingestion/index.ts @@ -10,7 +10,14 @@ import { Construct } from 'constructs' import { IngestionStepFunction } from './ingestion-step-function' import { DataFeedPollStepFunction } from './data-feed-poll-step-function' import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' -import { ApplicationLogLevel, Architecture, LambdaInsightsVersion, LogFormat, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda' +import { + ApplicationLogLevel, + Architecture, + LambdaInsightsVersion, + LogFormat, + Runtime, + Tracing +} from 'aws-cdk-lib/aws-lambda' import { NagSuppressions } from 'cdk-nag' interface RssAtomFeedProps { @@ -42,7 +49,7 @@ export class RssAtomFeedConstruct extends Construct { 'IngestionStepFunction', { description: -'Step Function Responsible for ingesting data from RSS/ATOM feeds, generating summarizations and storing the information.', + 'Step Function Responsible for ingesting data from RSS/ATOM feeds, generating summarizations and storing the information.', dataFeedTable, rssAtomDataBucket } @@ -53,7 +60,7 @@ export class RssAtomFeedConstruct extends Construct { 'DataFeedPollStepFunction', { description: -'Step Function Responsible for getting enabled data feeds and starting ingestion for each one.', + 'Step Function Responsible for getting enabled data feeds and starting ingestion for each one.', dataFeedIngestionStateMachine: ingestionStepFunction.stateMachine, dataFeedTable, dataFeedTableTypeIndex @@ -62,7 +69,7 @@ export class RssAtomFeedConstruct extends Construct { const feedSubscriberFunction = new NodejsFunction(this, 'feed-subscriber', { description: -'Function responsible for subscribing to a specified RSS/ATOM feed', + 'Function responsible for subscribing to a specified RSS/ATOM feed', handler: 'handler', architecture: Architecture.ARM_64, runtime: Runtime.NODEJS_20_X, @@ -74,7 +81,7 @@ export class RssAtomFeedConstruct extends Construct { POWERTOOLS_LOG_LEVEL: 'DEBUG', DATA_FEED_TABLE: dataFeedTable.tableName, INGESTION_STEP_FUNCTION: -ingestionStepFunction.stateMachine.stateMachineArn + ingestionStepFunction.stateMachine.stateMachineArn }, timeout: Duration.minutes(1) }) @@ -97,10 +104,13 @@ ingestionStepFunction.stateMachine.stateMachineArn */ NagSuppressions.addResourceSuppressions( [feedSubscriberFunction], - [{ - id: 'AwsSolutions-IAM5', - reason: 'Allowing CloudWatch & XRay' - }], true + [ + { + id: 'AwsSolutions-IAM5', + reason: 'Allowing CloudWatch & XRay' + } + ], + true ) } } diff --git a/lib/data-feed-ingestion/rss-atom-ingestion/ingestion-step-function.article-ingestor.ts b/lib/data-feed-ingestion/rss-atom-ingestion/ingestion-step-function.article-ingestor.ts index 6ef172c..81b8aa8 100644 --- a/lib/data-feed-ingestion/rss-atom-ingestion/ingestion-step-function.article-ingestor.ts +++ b/lib/data-feed-ingestion/rss-atom-ingestion/ingestion-step-function.article-ingestor.ts @@ -226,7 +226,13 @@ const generateArticleSummarization = async ( messages: [{ role: 'user', content: prompt }] }) logger.debug('GenAI Output', { response }) - const processedResponse = summaryBuilder.getProcessedResponse(response.content.map((item) => { return item.type === 'text' ? item.text : '' }).join('\n')) + const processedResponse = summaryBuilder.getProcessedResponse( + response.content + .map((item) => { + return item.type === 'text' ? item.text : '' + }) + .join('\n') + ) logger.debug('Formatted response from Model:', { processedResponse }) if (processedResponse.error.response !== null) { logger.error('Error in processed response from LLM', { @@ -282,7 +288,9 @@ const saveArticleData = async ( const checkForRedirectFallback = ($: cheerio.Root): string | null => { logger.debug('Checking for redirect fallback in