Skip to content

Commit

Permalink
Merge branch 'main' into nextjs-configurable-tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
anitarua committed Jul 27, 2023
2 parents b74e695 + e97a9d3 commit 87d5a99
Show file tree
Hide file tree
Showing 23 changed files with 10,098 additions and 13,540 deletions.
6 changes: 4 additions & 2 deletions examples/nodejs/token-vending-machine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ The primary use for the Token Vending Machine is to provide temporary, restricte

## Configuring the Token Vending Machine App

Before you deploy the token vending machine, you will need to configure the scope of permissions and the expiry duration for the tokens that it will vend. For example, you can restrict the permissions for these browser tokens so that they have read-only access or read-write access, and you can also restrict them to specific caches or topics.
Before you deploy the Token Vending Machine, you will need to configure the scope of permissions and the expiry duration for the tokens that it will vend. For example, you can restrict the permissions for these browser tokens so that they have read-only access or read-write access, and you can also restrict them to specific caches or topics.

These two required variables live in the [lambda/config.ts](./lambda/config.ts) file.
You will also need to specify the authentication method for the Token Vending Machine's API Gateway Endpoint. You may choose to leave it open, allowing the API Gateway URL to be publicly accessible, or you may choose to use Lambda Authorizer or Amazon Cognito as an authenticator. Basic examples for each of these methods is provided in the [Lambda Authorizer handler](./lambda/authorizer/authorizer.ts) and [CDK definition](./infrastructure/lib/token-vending-machine-stack.ts) files.

These three required configuration variables live in the [lambda/config.ts](./lambda/token-vending-machine/config.ts) file.

## Deploying the Token Vending Machine App

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';
import * as apig from 'aws-cdk-lib/aws-apigateway';
import * as secrets from 'aws-cdk-lib/aws-secretsmanager';
import * as config from '../../lambda/token-vending-machine/config';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';

export class TokenVendingMachineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
Expand All @@ -25,9 +28,9 @@ export class TokenVendingMachineStack extends cdk.Stack {
const tvmLambda = new lambdaNodejs.NodejsFunction(this, 'MomentoTokenVendingMachine', {
functionName: 'MomentoTokenVendingMachine',
runtime: lambda.Runtime.NODEJS_18_X,
entry: path.join(__dirname, '../../lambda/handler.ts'),
projectRoot: path.join(__dirname, '../../lambda'),
depsLockFilePath: path.join(__dirname, '../../lambda/package-lock.json'),
entry: path.join(__dirname, '../../lambda/token-vending-machine/handler.ts'),
projectRoot: path.join(__dirname, '../../lambda/token-vending-machine'),
depsLockFilePath: path.join(__dirname, '../../lambda/token-vending-machine/package-lock.json'),
handler: 'handler',
timeout: cdk.Duration.seconds(30),
memorySize: 128,
Expand All @@ -46,6 +49,145 @@ export class TokenVendingMachineStack extends cdk.Stack {
requestTemplates: {'application/json': '{ "statusCode": "200" }'},
});

api.root.addMethod('GET', tvmIntegration);
switch (config.authenticationMethod) {
case config.AuthenticationMethod.LambdaAuthorizer: {
const authLambda = new lambdaNodejs.NodejsFunction(this, 'MomentoTokenVendingMachineAuthorizer', {
functionName: 'MomentoTokenVendingMachineAuthorizer',
runtime: lambda.Runtime.NODEJS_18_X,
entry: path.join(__dirname, '../../lambda/authorizer/authorizer.ts'),
projectRoot: path.join(__dirname, '../../lambda/authorizer'),
depsLockFilePath: path.join(__dirname, '../../lambda/authorizer/package-lock.json'),
handler: 'handler',
timeout: cdk.Duration.seconds(30),
memorySize: 128,
});

const authorizer = new apig.RequestAuthorizer(this, 'MomentoTokenVendingMachineTokenAuthorizer', {
handler: authLambda,
identitySources: [apig.IdentitySource.header('username'), apig.IdentitySource.header('password')],
resultsCacheTtl: cdk.Duration.seconds(5),
});

api.root.addMethod('GET', tvmIntegration, {
authorizer: authorizer,
});

api.root.addCorsPreflight({
allowOrigins: ['*'],
allowHeaders: ['username', 'password']
});

break;
}
case config.AuthenticationMethod.AmazonCognito: {
const userPool = new cognito.UserPool(this, 'MomentoTokenVendingMachineUserPool', {
userPoolName: 'MomentoTokenVendingMachineUserPool',
signInAliases: {
username: true
},
passwordPolicy: {
minLength: 8,
requireSymbols: true,
}
});
new cdk.CfnOutput(this, "UserPoolId", {
value: userPool.userPoolId,
});

const userPoolClient = new cognito.UserPoolClient(this, 'MomentoTokenVendingMachineUserPoolClient', {
userPool,
generateSecret: false,
authFlows: {
userPassword: true
}
});
new cdk.CfnOutput(this, "UserPoolClientId", {
value: userPoolClient.userPoolClientId,
});

const authorizer = new apig.CognitoUserPoolsAuthorizer(this, 'MomentoTokenVendingMachineTokenAuthorizer', {
cognitoUserPools: [userPool],
});

api.root.addMethod('GET', tvmIntegration, {
authorizationType: apig.AuthorizationType.COGNITO,
authorizer: authorizer,
});

api.root.addCorsPreflight({
allowOrigins: ['*'],
});

this.createCognitoUser(this, userPool, "momento", "$erverless");

break;
}
case config.AuthenticationMethod.Open: {
console.log("Warning: no authentication method provided, the Token Vending Machine URL will be publicly accessible");
api.root.addMethod('GET', tvmIntegration);
api.root.addCorsPreflight({
allowOrigins: ['*'],
});
break;
}
default: {
throw new Error("Unrecognized authentication method");
}
}
}

// reference: https://github.com/awesome-cdk/cdk-userpool-user/blob/master/lib/UserPoolUser.ts
private createCognitoUser(
scope: Construct,
userPool: cognito.IUserPool,
username: string,
password: string,
group?: string
) {
// Basically use the AWS SDK to fill in this gap in the CDK for creating a user with a password
const newUser = new AwsCustomResource(scope, 'AwsCustomResource-CreateCognitoUser', {
onCreate: {
service: 'CognitoIdentityServiceProvider',
action: 'adminCreateUser',
parameters: {
UserPoolId: userPool.userPoolId,
Username: username,
MessageAction: 'SUPPRESS',
TemporaryPassword: password
},
physicalResourceId: PhysicalResourceId.of(`AwsCustomResource-CreateCognitoUser-${username}`),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({resources: AwsCustomResourcePolicy.ANY_RESOURCE}),
installLatestAwsSdk: true,
});

const setUserPassword = new AwsCustomResource(scope, 'AwsCustomResource-SetCognitoUserPassword', {
onCreate: {
service: 'CognitoIdentityServiceProvider',
action: 'adminSetUserPassword',
parameters: {
UserPoolId: userPool.userPoolId,
Username: username,
Password: password,
Permanent: true,
},
physicalResourceId: PhysicalResourceId.of(`AwsCustomResource-SetCognitoUserPassword-${username}`),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({resources: AwsCustomResourcePolicy.ANY_RESOURCE}),
installLatestAwsSdk: true,
});

setUserPassword.node.addDependency(newUser);

if (group) {
const addUserToGroup = new cognito.CfnUserPoolUserToGroupAttachment(scope, 'AttachUserToGroup', {
userPoolId: userPool.userPoolId,
groupName: group,
username: username,
});
addUserToGroup.node.addDependency(newUser);
addUserToGroup.node.addDependency(setUserPassword);
addUserToGroup.node.addDependency(userPool);
}
}
}
Loading

0 comments on commit 87d5a99

Please sign in to comment.