├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── lambda ├── index.js ├── sam.yml └── test-event.json └── web ├── .amplifyrc ├── .gitignore ├── README.md ├── amplify-init-options.png ├── create-react-app-README.md ├── package-lock.json ├── package.json ├── public ├── index.html └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── aws-exports.js ├── index.css ├── index.js ├── serviceWorker.js └── setupTests.js /.gitignore: -------------------------------------------------------------------------------- 1 | lambda/packaged.yml 2 | generate-archive.sh 3 | .DS_Store 4 | web/amplify 5 | *.pem 6 | token* 7 | -------------------------------------------------------------------------------- /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 *master* 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 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /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 | # AWS IoT Enhanced Custom Authorizer Demo 2 | 3 | This example demonstrates the necessary steps to use Enhanced Custom Authentication and Configurable Endpoints with AWS IoT Core. 4 | 5 | One major disclaimer at this time: This demo was built taking the path of least resistance; a production quality implementation is pending using a more suitable approach to token validation in the Custom Authorizer AWS Lambda function, and using token signing. 6 | 7 | Another goal in building this demo was to illustrate the use of AWS Amplify to build browser based web applications to integrate with AWS IoT Core using new enhanced custom authorizers. Example code can be found under the [web](./web) directory of this repository. 8 | 9 | ## MQTT/TLS username/password auth 10 | 11 | If you want to test the Custom Authorizer for MQTT/TLS connections using username and password authentication, please checkout the [mqtt-username-password](https://github.com/aws-samples/aws-iot-enhanced-custom-authorizer-demo/tree/mqtt-username-password) branch 12 | 13 | ## Prerequisites 14 | 15 | * An AWS Account 16 | * [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) 17 | * [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) 18 | * [Amplify CLI](https://aws-amplify.github.io/docs/cli-toolchain/quickstart#installation) 19 | 20 | ## Deploy the Enhanced Custom Authorizer Lambda function 21 | 22 | The SAM CLI requires an S3 Bucket for uploading the Enhanced Custom Authorizer Lambda function artifacts. Creation of the S3 bucket is assumed prior to executing the following commands: 23 | 24 | ```bash 25 | DEPLOYMENT_BUCKET=mybucketname 26 | 27 | # Package SAM template 28 | sam package --template-file ./lambda/sam.yml \ 29 | --s3-bucket $DEPLOYMENT_BUCKET \ 30 | --output-template-file ./lambda/packaged.yml 31 | 32 | # Deploy packaged SAM template 33 | sam deploy --template-file ./lambda/packaged.yml \ 34 | --stack-name iot-enhanced-custom-authorizer-lambda-stack \ 35 | --capabilities CAPABILITY_IAM 36 | 37 | # Get the ARN for the deployed Lambda function 38 | aws cloudformation describe-stacks \ 39 | --stack-name iot-enhanced-custom-authorizer-lambda-stack \ 40 | --query 'Stacks[0].Outputs[?OutputKey==`IoTEnhancedCustomAuthorizerArn`].OutputValue' \ 41 | --output text 42 | ``` 43 | 44 | ## Configure your custom authorizer with AWS IoT 45 | 46 | 1. We will register the Lambda function created above as an authorizer with AWS IoT Core: 47 | 48 | ```bash 49 | aws iot create-authorizer \ 50 | --authorizer-name "CustomAuthorizer" \ 51 | --authorizer-function-arn "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:iot-enhanced-custom-autho-xxxx" \ 52 | --status ACTIVE \ 53 | --signing-disabled 54 | ``` 55 | 56 | The output of the above command, if successful, will contain the ARN for your custom authorizer: 57 | 58 | ```javascript 59 | { 60 | "authorizerName": "CustomAuthorizer", 61 | "authorizerArn": "arn:aws:iot:us-east-1:xxxxxxxxxxxx:authorizer/CustomAuthorizer" 62 | } 63 | ``` 64 | 65 | 66 | 2. Next, you need to grant the AWS IoT Core service principal access to invoke your custom authorizer Lambda function: 67 | 68 | ```bash 69 | aws lambda add-permission \ 70 | --function-name "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:iot-enhanced-custom-autho-xxxx" \ 71 | --principal iot.amazonaws.com \ 72 | --source-arn "arn:aws:iot:us-east-1:xxxxxxxxxxxx:authorizer/CustomAuthorizer" \ 73 | --statement-id Id-123 \ 74 | --action "lambda:InvokeFunction" 75 | ``` 76 | 77 | Now you should test your custom authorizer using the aws cli: 78 | 79 | ```bash 80 | aws iot test-invoke-authorizer \ 81 | --authorizer-name CustomAuthorizer \ 82 | --http-context '{"headers":{}, "queryString": "?token=allow"}' 83 | ``` 84 | 85 | 3. Enhanced custom authorizers must be registered as part of configurable endpoints for AWS IoT Core. In this example, we will create a custom endpoint for an AWS-managed domain. 86 | 87 | ```bash 88 | aws iot create-domain-configuration \ 89 | --domain-configuration-name "customAuthorizerDomainConfiguration" \ 90 | --service-type "DATA" 91 | 92 | aws iot describe-domain-configuration \ 93 | --domain-configuration-name "customAuthorizerDomainConfiguration" 94 | ``` 95 | The output of the `describe-domain-configuration` command above contains the Fully Qualified Domain Name (FQDN) you will need to use to connect your devices or applications to AWS IoT Core: 96 | 97 | ```javascript 98 | { 99 | "domainConfigurationName": "customAuthorizerDomainConfiguration", 100 | "domainConfigurationArn": "arn:aws:iot:us-east-1:xxxxxxxxxxxx:domainconfiguration/testDomainConfiguration/abcd", 101 | "domainName": "xxxxxxxxxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com", 102 | "serverCertificates": [], 103 | "authorizerConfig": { 104 | "defaultAuthorizerName": "CustomAuthorizer", 105 | "allowAuthorizerOverride": true 106 | }, 107 | "domainConfigurationStatus": "ENABLED", 108 | "serviceType": "DATA", 109 | "domainType": "AWS_MANAGED" 110 | } 111 | ``` 112 | 113 | 4. Finally, you need to update your domain configuration to use your custom authorizer: 114 | 115 | ```bash 116 | aws iot update-domain-configuration \ 117 | --domain-configuration-name "customAuthorizerDomainConfiguration" \ 118 | --authorizer-config '{"allowAuthorizerOverride": true,"defaultAuthorizerName": "CustomAuthorizer"}' 119 | ``` 120 | 121 | 122 | 123 | ## Resources 124 | 125 | * https://aws.amazon.com/blogs/security/how-to-use-your-own-identity-and-access-management-systems-to-control-access-to-aws-iot-resources/ 126 | * https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-authentication.html 127 | * https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-auth-create.html 128 | * https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html 129 | * https://docs.aws.amazon.com/iot/latest/apireference/API_CreateAuthorizer.html 130 | * https://docs.aws.amazon.com/cli/latest/reference/iot/create-authorizer.html 131 | * https://docs.aws.amazon.com/iot/latest/developerguide/iot-custom-endpoints-configurable-aws.html 132 | 133 | 134 | ## TODO 135 | 136 | * Generate restrictive policies based on principal and grants in tokens 137 | * Demo: Username / password based authentication example 138 | * Demo: JWT based authorization example 139 | * Demo: Query string authentication example using token signing 140 | * Demo: vanilla javascript example 141 | 142 | ## License 143 | 144 | This library is licensed under the MIT-0 License. See the LICENSE file. 145 | -------------------------------------------------------------------------------- /lambda/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | // A simple authorizer Lambda function demonstrating 20 | // how to parse auth token and generate response 21 | 22 | exports.handler = async (event, context, callback) => { 23 | console.log(`event: ${JSON.stringify(event)}\n`) 24 | 25 | 26 | const queryString = event.protocolData.http.queryString 27 | console.log(`queryString ${JSON.stringify(queryString)}\n`) 28 | 29 | const params = new URLSearchParams(queryString); 30 | 31 | // Log the values 32 | params.forEach((value, key) => { 33 | console.log(`${key} = ${value}\n`); 34 | }); 35 | 36 | let token = params.get("token") 37 | 38 | if(!token){ 39 | console.log('token not found in query string; attempting to use token in event payload') 40 | token = event.token 41 | } 42 | 43 | console.log(`token: ${token}\n`) 44 | 45 | switch (token.toLowerCase()) { 46 | case 'allow': 47 | callback(null, generateAuthResponse(token, 'Allow')); 48 | case 'deny': 49 | callback(null, generateAuthResponse(token, 'Deny')); 50 | default: 51 | callback("Error: Invalid token"); 52 | } 53 | }; 54 | 55 | // Helper function to generate authorization response 56 | var generateAuthResponse = function(token, effect) { 57 | // Invoke your preferred identity provider 58 | // to get the authN and authZ response. 59 | // Following is just for simplicity sake 60 | 61 | var authResponse = {}; 62 | authResponse.isAuthenticated = true; 63 | authResponse.principalId = 'principalId'; 64 | 65 | var policyDocument = {}; 66 | policyDocument.Version = '2012-10-17'; 67 | policyDocument.Statement = []; 68 | var statement = {}; 69 | statement.Action = 'iot:*'; 70 | statement.Effect = effect; 71 | statement.Resource = "arn:aws:iot:us-east-1:0000000000:*"; 72 | policyDocument.Statement[0] = statement; 73 | authResponse.policyDocuments = [policyDocument]; 74 | authResponse.disconnectAfterInSeconds = 3600; 75 | authResponse.refreshAfterInSeconds = 300; 76 | 77 | return authResponse; 78 | } 79 | -------------------------------------------------------------------------------- /lambda/sam.yml: -------------------------------------------------------------------------------- 1 | ## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | ## SPDX-License-Identifier: MIT-0 3 | 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | Transform: AWS::Serverless-2016-10-31 6 | Resources: 7 | IoTEnhancedCustomAuthorizer: 8 | Type: AWS::Serverless::Function 9 | Properties: 10 | Handler: index.handler 11 | Runtime: nodejs12.x 12 | Timeout: 5 13 | Outputs: 14 | IoTEnhancedCustomAuthorizerArn: 15 | Description: ARN of the Lambda function 16 | Value: !GetAtt IoTEnhancedCustomAuthorizer.Arn 17 | -------------------------------------------------------------------------------- /lambda/test-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "allow", 3 | "signatureVerified": false, 4 | "protocols": [ 5 | "tls", 6 | "http", 7 | "mqtt" 8 | ], 9 | "protocolData": { 10 | "tls": { 11 | "serverName": "serverName" 12 | }, 13 | "http": { 14 | "headers": { 15 | "header1": "lorem-ipsum" 16 | }, 17 | "queryString": "?token=allow" 18 | }, 19 | "mqtt": { 20 | "username": "myUserName", 21 | "password": "myPassword", 22 | "clientId": "myClientId" 23 | } 24 | }, 25 | "connectionMetadata": { 26 | "id": 1234567890 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/.amplifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "providers": { 3 | "awscloudformation": { 4 | "AuthRoleName": "test-20191218085803-authRole", 5 | "UnauthRoleArn": "arn:aws:iam::068311527115:role/test-20191218085803-unauthRole", 6 | "AuthRoleArn": "arn:aws:iam::068311527115:role/test-20191218085803-authRole", 7 | "Region": "us-east-1", 8 | "DeploymentBucketName": "test-20191218085803-deployment", 9 | "UnauthRoleName": "test-20191218085803-unauthRole", 10 | "StackName": "test-20191218085803", 11 | "StackId": "arn:aws:cloudformation:us-east-1:068311527115:stack/test-20191218085803/693b8840-219e-11ea-b14b-0e167b726915" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /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 | 25 | #amplify 26 | amplify/\#current-cloud-backend 27 | amplify/.config/local-* 28 | amplify/mock-data 29 | amplify/backend/amplify-meta.json 30 | amplify/backend/awscloudformation 31 | build/ 32 | dist/ 33 | node_modules/ 34 | aws-exports.js 35 | awsconfiguration.json 36 | amplifyconfiguration.json 37 | amplify-gradle-config.json 38 | amplifyxc.config -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # AWS Amplify SPA Demo 2 | 3 | This is a rudimentary sample application built using AWS Amplify which subscribes to MQTT topics using AWS IoT Core Enhanced Custom Authorizers. 4 | 5 | ## Setup 6 | 7 | You have two options to run this demo: 8 | - without an AWS Amplify backend 9 | - with an AWS Amplify backend 10 | 11 | This also assumes that you have completed all of the necessary steps to configure your Enhanced Custom Authorizer with AWS IoT Core. 12 | 13 | ### Option 1 - no AWS Amplify backend 14 | If you want to only test the connection to AWS IoT Core without deploying an Amplify backend, you can run: 15 | 16 | ```bash 17 | npm install 18 | npm start 19 | ``` 20 | 21 | > NOTE: AWS Amplify libraries requires an `aws-exports.js` file to be present. For this option to work we are providing the file with dummy values. 22 | 23 | ### Option 2 - with AWS Amplify backend 24 | If you plan to add other AWS Amplify functionalities to this sample app, you should first initialize the backend. You can do this by running: 25 | 26 | ```bash 27 | npm install 28 | amplify configure # optional if you already have amplify configured with an AWS access key pair 29 | amplify init # example of options to select in screenshot below 30 | npm start 31 | ``` 32 | 33 | ![Amplify Init Options](amplify-init-options.png "Amplify Init Options") 34 | 35 | > NOTE: If you run this option, a new `aws-exports.js` file will be generated. 36 | 37 | ## Configuring the app to subscribe to AWS IoT Core 38 | 39 | Using the FQDN from your configurable AWS IoT endpoint, you can now build applications using Amplify's PubSub support by passing Tokens through as query string parameters. 40 | 41 | This allows you to use any IdP, assuming you correctly validate your tokens within the Custom Authorizer and generate sensible IoT policies with least privileged access (instead of the allow all policy the example in this codebase currently generates!). 42 | 43 | The following is example Amplify component built in React, which also exists under [App.js](./src/App.js): 44 | 45 | ```javascript 46 | import React from 'react'; 47 | import Amplify, { PubSub } from 'aws-amplify'; 48 | import { MqttOverWSProvider } from "@aws-amplify/pubsub/lib/Providers"; 49 | 50 | // TODO - you will replace this with your own ATS-specific custom domain endpoint for AWS IoT Core 51 | const mqtt_host = 'xxxxxxxxxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com' 52 | 53 | Amplify.addPluggable(new MqttOverWSProvider({ 54 | //here you would include your token as the query string parameter use to initialize the connection 55 | aws_pubsub_endpoint: `wss://${mqtt_host}/mqtt?token=allow`, 56 | })); 57 | 58 | function MessageList(props){ 59 | const messages = props.messages 60 | const listItems = messages.map((data, i) => 61 |
  • {data.client_received_at.toString()} - {data.message}
  • 62 | ) 63 | 64 | return( 65 | 66 | ) 67 | } 68 | 69 | export default class App extends React.Component { 70 | 71 | constructor(props){ 72 | super(props) 73 | this.state = {messages:[]} 74 | } 75 | 76 | componentDidMount(){ 77 | PubSub.subscribe('#').subscribe({ 78 | next: data => { 79 | data.value.client_received_at = new Date() 80 | console.log(`Message received: ${JSON.stringify(data.value)}`) 81 | this.setState(prevState => ({ 82 | messages: [...prevState.messages, data.value] 83 | })) 84 | }, 85 | error: error => console.error(error), 86 | close: () => console.log('Done'), 87 | }) 88 | } 89 | 90 | render(){ 91 | const messages = this.state.messages 92 | 93 | return ( 94 |
    95 | 96 |
    97 | ) 98 | } 99 | } 100 | ``` 101 | 102 | ## Cleanup 103 | 104 | If you do not plan on using this amplify project, execute the following command to cleanup both the AWS Cloud resources generated by Amplify, as well as local Amplify resource files: 105 | 106 | ```bash 107 | amplify delete 108 | ``` 109 | -------------------------------------------------------------------------------- /web/amplify-init-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-iot-enhanced-custom-authorizer-demo/2c83a3bbf31ba02d34f6a7e2b5586af0c675903b/web/amplify-init-options.png -------------------------------------------------------------------------------- /web/create-react-app-README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
    10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
    13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
    18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
    23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
    26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | 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. 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@aws-amplify/ui-react": "^3.0.2", 7 | "@testing-library/jest-dom": "^5.12.0", 8 | "@testing-library/react": "^13.3.0", 9 | "@testing-library/user-event": "^14.2.0", 10 | "aws-amplify": "^4.3.26", 11 | "normalize-url": ">=4.5.1", 12 | "nth-check": "^2.1.1", 13 | "postcss": ">=8.4.31", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-scripts": "^5.0.0", 17 | "web-vitals": "^1.1.2" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 41 | AWS IoT Enhanced Custom Authorizer Demo 42 | 43 | 44 | 45 |
    46 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/src/App.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | import React from 'react'; 20 | import Amplify, { PubSub } from 'aws-amplify'; 21 | import { MqttOverWSProvider } from "@aws-amplify/pubsub/lib/Providers"; 22 | 23 | //TODO - add your ATS-specific custom domain here 24 | const mqtt_host = 'xxxxxxxxxxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com' 25 | const mqtt_topic = '#' 26 | 27 | Amplify.addPluggable(new MqttOverWSProvider({ 28 | aws_pubsub_endpoint: `wss://${mqtt_host}/mqtt?token=allow`, 29 | })); 30 | 31 | function MessageList(props){ 32 | const messages = props.messages 33 | const listItems = messages.map((data, i) => 34 |
  • {JSON.stringify(data)}
  • 35 | ) 36 | 37 | return( 38 | 39 | ) 40 | } 41 | 42 | export default class App extends React.Component { 43 | 44 | constructor(props){ 45 | super(props) 46 | this.state = { 47 | messages:[], 48 | connectionState: 'Initializing Connection...', 49 | } 50 | } 51 | 52 | componentDidMount(){ 53 | PubSub.subscribe(mqtt_topic).subscribe({ 54 | next: data => { 55 | data.value.client_received_at = new Date() 56 | console.log(`Message received: ${JSON.stringify(data.value)} \nRaw data: ${JSON.stringify(data)}`) 57 | 58 | this.setState(prevState => ({ 59 | connectionState: `Connected and Subscribed to topic '${mqtt_topic}'; Messages Received`, 60 | messages: [...prevState.messages, data.value] 61 | })) 62 | }, 63 | error: error => { 64 | console.log(JSON.stringify(error, null, 2)) 65 | this.setState(prevState => ({ 66 | connectionState: `Failed to subscribe to topic '${mqtt_topic}' on endpoint ${mqtt_host}: ${error.error.errorMessage}` 67 | })) 68 | }, 69 | close: () => { 70 | this.setState({ connectionState: `Connection Closed` }) 71 | }, 72 | }) 73 | 74 | 75 | this.setState({ connectionState: `Connected and Subscribed to topic '${mqtt_topic}'; Awaiting Messages...` }) 76 | } 77 | 78 | render(){ 79 | const messages = this.state.messages 80 | const connectionState = this.state.connectionState 81 | 82 | return ( 83 |
    84 |

    {connectionState}

    85 | 86 |
    87 | ) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /web/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /web/src/aws-exports.js: -------------------------------------------------------------------------------- 1 | // WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten. 2 | 3 | const awsmobile = { 4 | "aws_project_region": "us-east-1", 5 | "aws_cognito_identity_pool_id": "us-east-1:9629b815-be64-4863-a8e3-0011b0c52dbd", 6 | "aws_cognito_region": "us-east-1", 7 | "aws_user_pools_id": "us-east-1_1lQ8XXWQW", 8 | "aws_user_pools_web_client_id": "v4bn0sq3mehuljkcu3rmnkaaa", 9 | "oauth": {} 10 | }; 11 | 12 | 13 | export default awsmobile; 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | import React from 'react'; 20 | import ReactDOM from 'react-dom'; 21 | import './index.css'; 22 | import App from './App'; 23 | import * as serviceWorker from './serviceWorker'; 24 | 25 | import Amplify from 'aws-amplify' 26 | import config from './aws-exports' 27 | Amplify.configure(config); 28 | ReactDOM.render(, document.getElementById('root')); 29 | 30 | // If you want your app to work offline and load faster, you can change 31 | // unregister() to register() below. Note this comes with some pitfalls. 32 | // Learn more about service workers: https://bit.ly/CRA-PWA 33 | serviceWorker.unregister(); 34 | -------------------------------------------------------------------------------- /web/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' } 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready.then(registration => { 134 | registration.unregister(); 135 | }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /web/src/setupTests.js: -------------------------------------------------------------------------------- 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/extend-expect'; 6 | --------------------------------------------------------------------------------