├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── backend ├── .gitignore ├── .npmignore ├── README.md ├── bin │ └── provisioning.ts ├── cdk.json ├── jest.config.js ├── lambda │ └── time │ │ └── get.ts ├── lib │ ├── api.ts │ └── auth.ts ├── package-lock.json ├── package.json ├── test │ └── backend.test.ts └── tsconfig.json ├── frontend ├── provisioning │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── bin │ │ └── provisioning.ts │ ├── cdk.json │ ├── jest.config.js │ ├── lib │ │ ├── frontend-stack.ts │ │ └── web-acl-stack.ts │ ├── package-lock.json │ ├── package.json │ ├── test │ │ └── provisioning.test.ts │ └── tsconfig.json └── web │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── api.ts │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts │ └── tsconfig.json └── imgs ├── architecture.png ├── screen-cognito.png └── screen-home.png /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React SPA App with Serverless Backend and Congito Auth Demo 2 | 3 | ## Overview 4 | 5 | This sample shows how to make a SPA application with serverless backend by AWS Cloud Development Kit (CDK). You can also see from this sample how to control access to API with Amazon Cognito and attach WAF to API Gateway and CloudFront. 6 | 7 | Screenshots of this demo are shown below. 8 | 9 | ![screen-cognito](imgs/screen-cognito.png) 10 | ![screen-home](imgs/screen-home.png) 11 | 12 | ## Architecture 13 | 14 | There are three cdk stacks, which correspond to each row in the architecure below. 15 | 16 | - AuthStack 17 | - Amazon Cognito 18 | - APIStack 19 | - Amazon API Gateway, AWS WAF, AWS Lambda 20 | - FrontendStack 21 | - Amazon CloudFront, AWS WAF, Amazon S3 22 | 23 | ![Architecture](imgs/architecture.png) 24 | 25 | ## Directory Structures 26 | 27 | ```sh 28 | . 29 | ├── backend # CDK scripts for backend resources 30 | └── frontend 31 |    ├── provisioning # CDK scripts for frontend resources 32 |    └── web # React scripts 33 | ``` 34 | 35 | ## Main Libraries 36 | 37 | - @aws-amplify/ui-components 38 | - @aws-amplify/ui-react 39 | - aws-amplify 40 | - aws-cdk 41 | - aws-lambda 42 | - jest 43 | - react 44 | - react-scripts 45 | - ts-node 46 | - typescript 47 | 48 | ## Prerequisites 49 | 50 | - npm 51 | - cdk 52 | - configuration of aws profile 53 | 54 | ## Getting started 55 | 56 | ### 1. Clone the repository 57 | 58 | - Run `git clone` command to download the source code 59 | 60 | ### 2. Deploy backend resources 61 | 62 | - Run `npm install` command in the [backend](backend) directory. 63 | - Run `cdk deploy --all` to deploy backend resouces. 64 | - You can deploy each stack individually like `cdk deploy AuthStack`. 65 | - When resouces are successfully deployed, outputs such as APIStack.CognitoUserPoolId will be shown in the terminal. These values will be used to deploy frontend resouces. 66 | 67 | ```sh 68 | Outputs: 69 | APIStack.CognitoUserPoolId = xxx 70 | APIStack.CognitoUserPoolWebClientId = xxx 71 | APIStack.ExportsOutputFnGetAttUserPoolxxx = xxx 72 | ... 73 | Outputs: 74 | AuthStack.apiEndpointxxx = xxx 75 | ``` 76 | 77 | ### 3. Deploy frontend resources 78 | 79 | #### 3.1 Build React app 80 | 81 | - Run `npm install` command in the [frontend/web](frontend/web) directory. 82 | - Update `frontend/web/src/App.tsx` to use the previous outputs. 83 | - userPoolId 84 | - userPoolWebClientId 85 | - apiEndpoint 86 | - Run `npm run build` in the same directory to build react scripts. 87 | 88 | #### 3.2 Deploy frontend resources 89 | 90 | - Move to [frontend/provisioning](frontend/provisioning) directory and run `npm install` command. 91 | - Run `cdk deploy --all` to deploy frontend resouces. 92 | - When resouces are successfully deployed, FrontendStack.endpoint will be displayed in the terminal. You will access the app hosted on cloudfront/s3 by this url. 93 | 94 | ```sh 95 | Outputs: 96 | FrontendStack.endpoint = xxx.cloudfront.net 97 | ``` 98 | 99 | ### 4. Create Cognito user 100 | 101 | - In order to sign in the app, you need to create a new cognito user. You can create a user by AWS Management Console or AWS CLI. 102 | 103 | ## Security 104 | 105 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 106 | 107 | ## License 108 | 109 | This library is licensed under the MIT-0 License. See the LICENSE file. 110 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /backend/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your CDK TypeScript project! 2 | 3 | This is a blank project for TypeScript development with CDK. 4 | 5 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 6 | 7 | ## Useful commands 8 | 9 | * `npm run build` compile typescript to js 10 | * `npm run watch` watch for changes and compile 11 | * `npm run test` perform the jest unit tests 12 | * `cdk deploy` deploy this stack to your default AWS account/region 13 | * `cdk diff` compare deployed stack with current state 14 | * `cdk synth` emits the synthesized CloudFormation template 15 | -------------------------------------------------------------------------------- /backend/bin/provisioning.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import "source-map-support/register"; 3 | import * as cdk from "@aws-cdk/core"; 4 | import { APIStack } from "../lib/api"; 5 | import { AuthStack } from "../lib/auth"; 6 | 7 | const app = new cdk.App(); 8 | const env = { 9 | region: "ap-northeast-1", 10 | }; 11 | const auth = new AuthStack(app, "APIStack", { env }); 12 | new APIStack(app, "AuthStack", { 13 | userPool: auth.userPool, 14 | env, 15 | }); 16 | -------------------------------------------------------------------------------- /backend/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/provisioning.ts", 3 | "context": { 4 | "allowedIpAddressRanges": ["0.0.0.0/1", "128.0.0.0/1"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /backend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /backend/lambda/time/get.ts: -------------------------------------------------------------------------------- 1 | import { 2 | APIGatewayProxyEvent, 3 | Context, 4 | APIGatewayProxyResult, 5 | } from "aws-lambda"; 6 | 7 | const formatDate = (date: Date) => { 8 | const dt = new Date(date); 9 | const yyyy = dt.getFullYear(); 10 | const mm = ("00" + (dt.getMonth() + 1)).slice(-2); 11 | const dd = ("00" + dt.getDate()).slice(-2); 12 | const hh = ("00" + dt.getHours()).slice(-2); 13 | const min = ("00" + dt.getMinutes()).slice(-2); 14 | return yyyy + "/" + mm + "/" + dd + " " + hh + ":" + min; 15 | }; 16 | 17 | exports.handler = async ( 18 | event: APIGatewayProxyEvent, 19 | context: Context 20 | ): Promise => { 21 | let status = 200; 22 | let response = {}; 23 | try { 24 | response = { cur_date: formatDate(new Date()) }; 25 | } catch (e) { 26 | console.log(e); 27 | status = 500; 28 | } 29 | 30 | return { 31 | statusCode: status, 32 | headers: { 33 | "Content-Type": "application/json", 34 | "Access-Control-Allow-Origin": "*", 35 | }, 36 | body: JSON.stringify(response), 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /backend/lib/api.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "@aws-cdk/core"; 2 | import * as cognito from "@aws-cdk/aws-cognito"; 3 | import * as lambda from "@aws-cdk/aws-lambda"; 4 | import * as lambdaNodejs from "@aws-cdk/aws-lambda-nodejs"; 5 | import * as waf from "@aws-cdk/aws-wafv2"; 6 | import * as agw from "@aws-cdk/aws-apigateway"; 7 | 8 | interface APIStackProps extends cdk.StackProps { 9 | userPool: cognito.UserPool; 10 | } 11 | 12 | export class APIStack extends cdk.Stack { 13 | constructor(scope: cdk.Construct, id: string, props: APIStackProps) { 14 | super(scope, id, props); 15 | 16 | const authorizer = new agw.CognitoUserPoolsAuthorizer(this, "Authorizer", { 17 | cognitoUserPools: [props.userPool], 18 | }); 19 | 20 | // Definition of lambda function 21 | const getTimeFunction = new lambdaNodejs.NodejsFunction(this, "getTime", { 22 | handler: "handler", 23 | runtime: lambda.Runtime.NODEJS_14_X, 24 | timeout: cdk.Duration.seconds(30), 25 | memorySize: 512, 26 | entry: "./lambda/time/get.ts", 27 | }); 28 | 29 | // Definition of WAF 30 | const ipRanges: string[] = scope.node.tryGetContext( 31 | "allowedIpAddressRanges" 32 | ); 33 | 34 | const wafIPSet = new waf.CfnIPSet(this, `IPSet`, { 35 | name: "BackendWebAclIpSet", 36 | ipAddressVersion: "IPV4", 37 | scope: "REGIONAL", 38 | addresses: ipRanges, 39 | }); 40 | 41 | const apiWaf = new waf.CfnWebACL(this, "waf", { 42 | defaultAction: { block: {} }, 43 | scope: "REGIONAL", 44 | visibilityConfig: { 45 | cloudWatchMetricsEnabled: true, 46 | sampledRequestsEnabled: true, 47 | metricName: "ApiGatewayWAF", 48 | }, 49 | // https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/aws-managed-rule-groups-list.html 50 | rules: [ 51 | // AWSManagedRulesCommonRuleSet 52 | { 53 | priority: 1, 54 | overrideAction: { none: {} }, 55 | visibilityConfig: { 56 | sampledRequestsEnabled: true, 57 | cloudWatchMetricsEnabled: true, 58 | metricName: "AWS-AWSManagedRulesCommonRuleSet", 59 | }, 60 | name: "AWSManagedRulesCommonRuleSet", 61 | statement: { 62 | managedRuleGroupStatement: { 63 | vendorName: "AWS", 64 | name: "AWSManagedRulesCommonRuleSet", 65 | }, 66 | }, 67 | }, 68 | // AWSManagedRulesKnownBadInputsRuleSet 69 | { 70 | priority: 2, 71 | name: "BackendWebAclIpRuleSet", 72 | action: { allow: {} }, 73 | visibilityConfig: { 74 | sampledRequestsEnabled: true, 75 | cloudWatchMetricsEnabled: true, 76 | metricName: "BackendWebAclIpRuleSet", 77 | }, 78 | statement: { 79 | ipSetReferenceStatement: { 80 | arn: wafIPSet.attrArn, 81 | }, 82 | }, 83 | }, 84 | ], 85 | }); 86 | 87 | // Definition of API Gateway 88 | const api = new agw.RestApi(this, "api", { 89 | deployOptions: { 90 | stageName: "api", 91 | }, 92 | defaultCorsPreflightOptions: { 93 | allowOrigins: agw.Cors.ALL_ORIGINS, 94 | allowMethods: agw.Cors.ALL_METHODS, 95 | }, 96 | }); 97 | 98 | // Associate WAF with API Gateway 99 | const region = cdk.Stack.of(this).region; 100 | const restApiId = api.restApiId; 101 | const stageName = api.deploymentStage.stageName; 102 | new waf.CfnWebACLAssociation(this, "apply-waf-apigw", { 103 | webAclArn: apiWaf.attrArn, 104 | resourceArn: `arn:aws:apigateway:${region}::/restapis/${restApiId}/stages/${stageName}`, 105 | }); 106 | 107 | // GET: /time 108 | const userinfo = api.root.addResource("time"); 109 | userinfo.addMethod("GET", new agw.LambdaIntegration(getTimeFunction), { 110 | authorizer: authorizer, 111 | authorizationType: agw.AuthorizationType.COGNITO, 112 | }); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /backend/lib/auth.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "@aws-cdk/core"; 2 | import * as cognito from "@aws-cdk/aws-cognito"; 3 | import { CfnOutput } from "@aws-cdk/core"; 4 | 5 | export class AuthStack extends cdk.Stack { 6 | public readonly userPool: cognito.UserPool; 7 | public readonly client: cognito.UserPoolClient; 8 | 9 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 10 | super(scope, id, props); 11 | 12 | const userPool = new cognito.UserPool(this, "UserPool", { 13 | removalPolicy: cdk.RemovalPolicy.DESTROY, 14 | mfa: cognito.Mfa.REQUIRED, 15 | mfaSecondFactor: { 16 | otp: true, 17 | sms: false, 18 | }, 19 | }); 20 | 21 | const client = userPool.addClient("WebClient", { 22 | userPoolClientName: "webClient", 23 | idTokenValidity: cdk.Duration.days(1), 24 | accessTokenValidity: cdk.Duration.days(1), 25 | authFlows: { 26 | userPassword: true, 27 | userSrp: true, 28 | custom: true, 29 | }, 30 | }); 31 | 32 | this.userPool = userPool; 33 | this.client = client; 34 | 35 | new CfnOutput(this, "CognitoUserPoolId", { 36 | value: userPool.userPoolId, 37 | description: "userPoolId required for frontend settings", 38 | }); 39 | new CfnOutput(this, "CognitoUserPoolWebClientId", { 40 | value: client.userPoolClientId, 41 | description: "clientId required for frontend settings", 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "0.1.0", 4 | "bin": { 5 | "backend": "bin/backend.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@aws-cdk/assert": "^1.131.0", 15 | "@types/jest": "^26.0.10", 16 | "@types/node": "10.17.27", 17 | "aws-cdk": "^1.131.0", 18 | "jest": "^26.4.2", 19 | "ts-jest": "^26.2.0", 20 | "ts-node": "^9.0.0", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "@aws-cdk/aws-apigateway": "^1.131.0", 25 | "@aws-cdk/aws-cognito": "^1.131.0", 26 | "@aws-cdk/aws-lambda-nodejs": "^1.131.0", 27 | "@aws-cdk/aws-wafv2": "^1.131.0", 28 | "@aws-cdk/core": "^1.131.0", 29 | "@types/aws-lambda": "^8.10.85", 30 | "aws-lambda": "^1.0.7", 31 | "source-map-support": "^0.5.16" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/test/backend.test.ts: -------------------------------------------------------------------------------- 1 | import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert'; 2 | import * as cdk from '@aws-cdk/core'; 3 | import * as Backend from '../lib/backend-stack'; 4 | 5 | test('Empty Stack', () => { 6 | const app = new cdk.App(); 7 | // WHEN 8 | const stack = new Backend.BackendStack(app, 'MyTestStack'); 9 | // THEN 10 | expectCDK(stack).to(matchTemplate({ 11 | "Resources": {} 12 | }, MatchStyle.EXACT)) 13 | }); 14 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /frontend/provisioning/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /frontend/provisioning/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /frontend/provisioning/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your CDK TypeScript project! 2 | 3 | This is a blank project for TypeScript development with CDK. 4 | 5 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 6 | 7 | ## Useful commands 8 | 9 | * `npm run build` compile typescript to js 10 | * `npm run watch` watch for changes and compile 11 | * `npm run test` perform the jest unit tests 12 | * `cdk deploy` deploy this stack to your default AWS account/region 13 | * `cdk diff` compare deployed stack with current state 14 | * `cdk synth` emits the synthesized CloudFormation template 15 | -------------------------------------------------------------------------------- /frontend/provisioning/bin/provisioning.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import "source-map-support/register"; 3 | import * as cdk from "@aws-cdk/core"; 4 | import { WebAclStack } from "../lib/web-acl-stack"; 5 | import { FrontendStack } from "../lib/frontend-stack"; 6 | 7 | const app = new cdk.App(); 8 | const waf = new WebAclStack(app, "FrontendWebAclStack", { 9 | env: { 10 | region: "us-east-1", 11 | }, 12 | }); 13 | 14 | new FrontendStack(app, "FrontendStack").addDependency(waf); 15 | -------------------------------------------------------------------------------- /frontend/provisioning/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/provisioning.ts", 3 | "context": { 4 | "allowedIpAddressRanges": ["0.0.0.0/1", "128.0.0.0/1"], 5 | "allowedIpAddressRangesV6": [ 6 | "0000:0000:0000:0000:0000:0000:0000:0000/1", 7 | "8000:0000:0000:0000:0000:0000:0000:0000/1" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/provisioning/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/provisioning/lib/frontend-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "@aws-cdk/core"; 2 | import * as iam from "@aws-cdk/aws-iam"; 3 | import * as cloudfront from "@aws-cdk/aws-cloudfront"; 4 | import * as s3 from "@aws-cdk/aws-s3"; 5 | import * as s3deploy from "@aws-cdk/aws-s3-deployment"; 6 | import * as customResources from "@aws-cdk/custom-resources"; 7 | import * as path from "path"; 8 | 9 | export class FrontendStack extends cdk.Stack { 10 | constructor(scope: cdk.Construct, id: string) { 11 | super(scope, id); 12 | 13 | const websiteBucket = new s3.Bucket(this, "WebsiteBucket", { 14 | websiteErrorDocument: "index.html", 15 | websiteIndexDocument: "index.html", 16 | versioned: true, 17 | encryption: s3.BucketEncryption.S3_MANAGED, 18 | enforceSSL: true, 19 | }); 20 | 21 | const websiteIdentity = new cloudfront.OriginAccessIdentity( 22 | this, 23 | "WebsiteIdentity" 24 | ); 25 | websiteBucket.grantRead(websiteIdentity); 26 | const webAclRef = new SsmParameterReader(this, "WebAclArnParameterReader", { 27 | parameterName: "WebAclArnParameter", 28 | region: "us-east-1", 29 | }).stringValue; 30 | 31 | const websiteDistribution = new cloudfront.CloudFrontWebDistribution( 32 | this, 33 | "WebsiteDistribution", 34 | { 35 | webACLId: webAclRef, 36 | errorConfigurations: [ 37 | { 38 | errorCachingMinTtl: 300, 39 | errorCode: 404, 40 | responseCode: 200, 41 | responsePagePath: "/index.html", 42 | }, 43 | ], 44 | originConfigs: [ 45 | { 46 | s3OriginSource: { 47 | s3BucketSource: websiteBucket, 48 | originAccessIdentity: websiteIdentity, 49 | }, 50 | behaviors: [ 51 | { 52 | isDefaultBehavior: true, 53 | }, 54 | ], 55 | }, 56 | ], 57 | priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL, 58 | } 59 | ); 60 | 61 | new s3deploy.BucketDeployment(this, "WebsiteDeploy", { 62 | sources: [ 63 | s3deploy.Source.asset(`${path.resolve(__dirname)}/../../web/build`), 64 | ], 65 | destinationBucket: websiteBucket, 66 | distribution: websiteDistribution, 67 | distributionPaths: ["/*"], 68 | memoryLimit: 1024, 69 | }); 70 | 71 | new cdk.CfnOutput(this, "endpoint", { 72 | description: "Frontend Endpoint", 73 | value: websiteDistribution.distributionDomainName, 74 | }); 75 | } 76 | } 77 | 78 | interface SsmParameterReaderProps { 79 | parameterName: string; 80 | region: string; 81 | } 82 | 83 | class SsmParameterReader extends cdk.Construct { 84 | private reader: customResources.AwsCustomResource; 85 | 86 | get stringValue(): string { 87 | return this.getParameterValue(); 88 | } 89 | 90 | constructor( 91 | scope: cdk.Construct, 92 | name: string, 93 | props: SsmParameterReaderProps 94 | ) { 95 | super(scope, name); 96 | 97 | const { parameterName, region } = props; 98 | 99 | const customResource = new customResources.AwsCustomResource( 100 | scope, 101 | `${name}CustomResource`, 102 | { 103 | policy: customResources.AwsCustomResourcePolicy.fromStatements([ 104 | new iam.PolicyStatement({ 105 | effect: iam.Effect.ALLOW, 106 | actions: ["ssm:GetParameter*"], 107 | resources: [ 108 | cdk.Stack.of(this).formatArn({ 109 | service: "ssm", 110 | region, 111 | resource: "parameter", 112 | resourceName: parameterName.replace(/^\/+/, ""), 113 | }), 114 | ], 115 | }), 116 | ]), 117 | onUpdate: { 118 | service: "SSM", 119 | action: "getParameter", 120 | parameters: { 121 | Name: parameterName, 122 | }, 123 | region, 124 | physicalResourceId: customResources.PhysicalResourceId.of( 125 | Date.now().toString() 126 | ), 127 | }, 128 | } 129 | ); 130 | 131 | this.reader = customResource; 132 | } 133 | 134 | private getParameterValue(): string { 135 | return this.reader.getResponseField("Parameter.Value"); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /frontend/provisioning/lib/web-acl-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "@aws-cdk/core"; 2 | import * as ssm from "@aws-cdk/aws-ssm"; 3 | import * as waf from "@aws-cdk/aws-wafv2"; 4 | 5 | export class WebAclStack extends cdk.Stack { 6 | constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) { 7 | super(scope, id, props); 8 | const ipRanges: string[] = scope.node.tryGetContext( 9 | "allowedIpAddressRanges" 10 | ); 11 | 12 | const ipRangesV6: string[] = scope.node.tryGetContext( 13 | "allowedIpAddressRangesV6" 14 | ); 15 | 16 | const wafIPSet4 = new waf.CfnIPSet(this, `IPSet4`, { 17 | name: "FrontendWebAclIpSet4", 18 | ipAddressVersion: "IPV4", 19 | scope: "CLOUDFRONT", 20 | addresses: ipRanges, 21 | }); 22 | 23 | const wafIPSet6 = new waf.CfnIPSet(this, `IPSet6`, { 24 | name: "FrontendWebAclIpSet6", 25 | ipAddressVersion: "IPV6", 26 | scope: "CLOUDFRONT", 27 | addresses: ipRangesV6, 28 | }); 29 | 30 | const frontendWaf = new waf.CfnWebACL(this, "waf", { 31 | defaultAction: { block: {} }, 32 | scope: "CLOUDFRONT", 33 | visibilityConfig: { 34 | cloudWatchMetricsEnabled: true, 35 | sampledRequestsEnabled: true, 36 | metricName: "FrontendWAF", 37 | }, 38 | rules: [ 39 | { 40 | priority: 1, 41 | overrideAction: { none: {} }, 42 | visibilityConfig: { 43 | sampledRequestsEnabled: true, 44 | cloudWatchMetricsEnabled: true, 45 | metricName: "AWS-AWSManagedRulesCommonRuleSet", 46 | }, 47 | name: "AWSManagedRulesCommonRuleSet", 48 | statement: { 49 | managedRuleGroupStatement: { 50 | vendorName: "AWS", 51 | name: "AWSManagedRulesCommonRuleSet", 52 | }, 53 | }, 54 | }, 55 | { 56 | priority: 2, 57 | name: "FrontendWebAclIpRuleSet", 58 | action: { allow: {} }, 59 | visibilityConfig: { 60 | sampledRequestsEnabled: true, 61 | cloudWatchMetricsEnabled: true, 62 | metricName: "FrontendWebAclIpRuleSet", 63 | }, 64 | statement: { 65 | ipSetReferenceStatement: { 66 | arn: wafIPSet4.attrArn, 67 | }, 68 | }, 69 | }, 70 | { 71 | priority: 3, 72 | name: "FrontendWebAclIpV6RuleSet", 73 | action: { allow: {} }, 74 | visibilityConfig: { 75 | sampledRequestsEnabled: true, 76 | cloudWatchMetricsEnabled: true, 77 | metricName: "FrontendWebAclIpV6RuleSet", 78 | }, 79 | statement: { 80 | ipSetReferenceStatement: { 81 | arn: wafIPSet6.attrArn, 82 | }, 83 | }, 84 | }, 85 | ], 86 | }); 87 | 88 | new ssm.StringParameter(this, "WebAclArnParameter", { 89 | parameterName: "WebAclArnParameter", 90 | stringValue: frontendWaf.attrArn, 91 | }); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /frontend/provisioning/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "provisioning", 3 | "version": "0.1.0", 4 | "bin": { 5 | "provisioning": "bin/provisioning.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@aws-cdk/assertions": "1.131.0", 15 | "@types/jest": "^26.0.10", 16 | "@types/node": "10.17.27", 17 | "aws-cdk": "1.131.0", 18 | "jest": "^26.4.2", 19 | "ts-jest": "^26.2.0", 20 | "ts-node": "^9.0.0", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "@aws-cdk/aws-cloudfront": "^1.131.0", 25 | "@aws-cdk/aws-iam": "^1.131.0", 26 | "@aws-cdk/aws-s3": "^1.131.0", 27 | "@aws-cdk/aws-s3-deployment": "^1.131.0", 28 | "@aws-cdk/aws-wafv2": "^1.131.0", 29 | "@aws-cdk/core": "1.131.0", 30 | "@aws-cdk/custom-resources": "^1.131.0", 31 | "source-map-support": "^0.5.16" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/provisioning/test/provisioning.test.ts: -------------------------------------------------------------------------------- 1 | // import { Template } from '@aws-cdk/assertions'; 2 | // import * as cdk from '@aws-cdk/core'; 3 | // import * as Provisioning from '../lib/provisioning-stack'; 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/provisioning-stack.ts 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new Provisioning.ProvisioningStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | 14 | // template.hasResourceProperties('AWS::SQS::Queue', { 15 | // VisibilityTimeout: 300 16 | // }); 17 | }); 18 | -------------------------------------------------------------------------------- /frontend/provisioning/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /frontend/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/web/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /frontend/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@aws-amplify/ui-components": "^1.9.6", 7 | "@aws-amplify/ui-react": "^1.2.20", 8 | "@testing-library/jest-dom": "^5.15.0", 9 | "@testing-library/react": "^11.2.7", 10 | "@testing-library/user-event": "^12.8.3", 11 | "@types/jest": "^26.0.24", 12 | "@types/node": "^12.20.36", 13 | "@types/react": "^17.0.34", 14 | "@types/react-dom": "^17.0.11", 15 | "aws-amplify": "^4.3.8", 16 | "react": "^17.0.2", 17 | "react-dom": "^17.0.2", 18 | "react-scripts": "4.0.3", 19 | "typescript": "^4.4.4", 20 | "web-vitals": "^1.1.2" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-react-spa-with-cognito-auth/2f59eb8eca2b842b8a6affa71e3039aa82dc641a/frontend/web/public/favicon.ico -------------------------------------------------------------------------------- /frontend/web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/web/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-react-spa-with-cognito-auth/2f59eb8eca2b842b8a6affa71e3039aa82dc641a/frontend/web/public/logo192.png -------------------------------------------------------------------------------- /frontend/web/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-react-spa-with-cognito-auth/2f59eb8eca2b842b8a6affa71e3039aa82dc641a/frontend/web/public/logo512.png -------------------------------------------------------------------------------- /frontend/web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/web/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/web/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import logo from "./logo.svg"; 3 | import "./App.css"; 4 | 5 | import Amplify from "aws-amplify"; 6 | import { AuthState, onAuthUIStateChange } from "@aws-amplify/ui-components"; 7 | import { 8 | AmplifyAuthContainer, 9 | AmplifyAuthenticator, 10 | AmplifySignIn, 11 | } from "@aws-amplify/ui-react"; 12 | import { getTime } from "./api"; 13 | 14 | Amplify.configure({ 15 | Auth: { 16 | region: "ap-northeast-1", 17 | userPoolId: "ap-northeast-1_xxx", // Please change this value. 18 | userPoolWebClientId: "xxx", // Please change this value. 19 | }, 20 | }); 21 | 22 | export const apiEndpoint = "https://xxx.ap-northeast-1.amazonaws.com"; // Please change this value. (Don't include '/api') 23 | 24 | const App: React.FC = () => { 25 | const [authState, setAuthState] = useState(); 26 | const [user, setUser] = useState(); 27 | const [time, setTime] = useState(); 28 | 29 | useEffect(() => { 30 | return onAuthUIStateChange((nextAuthState, authData) => { 31 | setAuthState(nextAuthState); 32 | setUser(authData); 33 | }); 34 | }, []); 35 | 36 | useEffect(() => { 37 | const _getTime = async () => { 38 | const res = await getTime(); 39 | setTime(res.cur_date); 40 | }; 41 | 42 | _getTime(); 43 | }, []); 44 | 45 | return authState === AuthState.SignedIn && user ? ( 46 |
47 |
48 | logo 49 |

Update Time: {time}

50 |
51 |
52 | ) : ( 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | export default App; 62 | -------------------------------------------------------------------------------- /frontend/web/src/api.ts: -------------------------------------------------------------------------------- 1 | import { Auth } from "aws-amplify"; 2 | import { apiEndpoint } from "./App"; 3 | 4 | export const getToken = async () => { 5 | const session = await Auth.currentSession(); 6 | return `Bearer ${session.getIdToken().getJwtToken()}`; 7 | }; 8 | 9 | interface Time { 10 | cur_date: string; 11 | } 12 | 13 | export const getTime = async () => { 14 | const token = await getToken(); 15 | const res = await fetch(`${apiEndpoint}/api/time`, { 16 | headers: { Authorization: token }, 17 | }); 18 | 19 | return (await res.json()) as Time; 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/web/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/web/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /frontend/web/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/web/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/web/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /frontend/web/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /imgs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-react-spa-with-cognito-auth/2f59eb8eca2b842b8a6affa71e3039aa82dc641a/imgs/architecture.png -------------------------------------------------------------------------------- /imgs/screen-cognito.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-react-spa-with-cognito-auth/2f59eb8eca2b842b8a6affa71e3039aa82dc641a/imgs/screen-cognito.png -------------------------------------------------------------------------------- /imgs/screen-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-react-spa-with-cognito-auth/2f59eb8eca2b842b8a6affa71e3039aa82dc641a/imgs/screen-home.png --------------------------------------------------------------------------------