├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── amplify ├── .config │ └── project-config.json └── backend │ ├── auth │ └── cognito704b3e59 │ │ ├── cognito704b3e59-cloudformation-template.yml │ │ └── parameters.json │ ├── awscloudformation │ └── nested-cloudformation-stack.yml │ └── backend-config.json ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── package.json ├── src ├── components │ ├── Details.js │ ├── Error.js │ ├── Home.js │ ├── Login.js │ ├── PrivateRoute.js │ ├── SignUp.js │ ├── header.js │ ├── layout.css │ └── layout.js ├── images │ ├── amplify-console.gif │ ├── gatby-auth.gif │ └── gatsby-icon.png ├── pages │ ├── 404.js │ ├── app.js │ └── index.js └── utils │ └── auth.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | build/** 3 | #amplify 4 | amplify/\#current-cloud-backend 5 | amplify/.config/local-* 6 | amplify/mock-data 7 | amplify/backend/amplify-meta.json 8 | amplify/backend/awscloudformation 9 | amplify/team-provider-info.json 10 | amplify.json 11 | build/ 12 | dist/ 13 | node_modules/ 14 | aws-exports.js 15 | awsconfiguration.json 16 | amplifyconfiguration.json 17 | amplify-gradle-config.json 18 | amplifyxc.config 19 | -------------------------------------------------------------------------------- /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 | The MIT License (MIT) 2 | 3 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > This repository is no longer under maintenance. 2 | 3 | # Gatsby Auth starter with AWS Amplify (ARCHIVED) 4 | 5 | This auth starter implements a basic authentication flow for signing up signing in users as well as protected client side routing using [AWS Amplify](https://amplify.aws). Auth features: 6 | - User sign up 7 | - User sign in 8 | - Multi-factor Authentication 9 | - User sign-out 10 | 11 | ![Gatsby Amplify](src/images/gatby-auth.gif) 12 | 13 | # Deploy to the Amplify console 14 | 15 | Click the button to deploy a fullstack app in your AWS account: 16 | 17 | [![amplifybutton](https://oneclick.amplifyapp.com/button.svg)](https://console.aws.amazon.com/amplify/home#/deploy?repo=https://github.com/aws-samples/aws-amplify-gatsby-auth) 18 | 19 | You can now continuously deploy changes to your frontend or backend and Amplify Console will automatically deploy those changes. 20 | 21 | 22 | ![Amplify Console](src/images/amplify-console.gif) 23 | 24 | 25 | # Run locally 26 | 27 | 1. Create the project 28 | 29 | ```sh 30 | gatsby new gatsby-amplify-auth https://github.com/dabit3/gatsby-auth-starter-aws-amplify 31 | ``` 32 | 33 | 2. Change into the new directory 34 | 35 | ```sh 36 | cd gatsby-amplify-auth 37 | ``` 38 | 39 | 3. Change into the new directory 40 | 41 | ```sh 42 | yarn 43 | # or 44 | npm install 45 | ``` 46 | 47 | 4. Install & configure the AWS Amplify CLI. 48 | 49 | ```sh 50 | npm install -g @aws-amplify/cli 51 | 52 | amplify configure 53 | ``` 54 | 55 | > To see a video of how to configure the CLI, click [here](https://www.youtube.com/watch?v=fWbM5DLh25U) 56 | 57 | 5. Create a new AWS Amplify Project 58 | 59 | ``` 60 | amplify init 61 | ``` 62 | 63 | > Here, walk through the following steps: 64 | 65 | - Enter a name for the project __YOURPROJECTNAME__ 66 | - Enter a name for the environment __master__ 67 | - Choose your default editor: __Visual Studio Code__ (or your editor of choice) 68 | - Choose the type of app that you're building __javascript__ 69 | - What javascript framework are you using __react__ 70 | - Source Directory Path: __src__ 71 | - Distribution Directory Path: __public__ 72 | - Build Command: __npm run-script build__ 73 | - Start Command: __npm run-script develop__ 74 | 75 | 6. Push the updated project configuration to AWS. It will deploy a CloudFormation template that has an Amazon Cognito resource that enables user authentication. 76 | 77 | ```sh 78 | amplify push 79 | ``` 80 | 81 | 7. Then you can run it by: 82 | ```sh 83 | gatsby develop 84 | ``` 85 | 86 | ### License 87 | 88 | This library is licensed under the MIT-0 License. See the LICENSE file. 89 | -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "my-aws-project", 3 | "version": "1.0", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "build", 10 | "BuildCommand": "npm run-script build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /amplify/backend/auth/cognito704b3e59/cognito704b3e59-cloudformation-template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | 3 | Parameters: 4 | env: 5 | Type: String 6 | authRoleName: 7 | Type: String 8 | unauthRoleName: 9 | Type: String 10 | authRoleArn: 11 | Type: String 12 | unauthRoleArn: 13 | Type: String 14 | 15 | 16 | identityPoolName: 17 | Type: String 18 | 19 | allowUnauthenticatedIdentities: 20 | Type: String 21 | 22 | thirdPartyAuth: 23 | Type: String 24 | 25 | lambdaLogPolicy: 26 | Type: String 27 | 28 | openIdLambdaRoleName: 29 | Type: String 30 | 31 | openIdRolePolicy: 32 | Type: String 33 | 34 | openIdLambdaIAMPolicy: 35 | Type: String 36 | 37 | openIdLogPolicy: 38 | Type: String 39 | 40 | userPoolName: 41 | Type: String 42 | 43 | autoVerifiedAttributes: 44 | Type: CommaDelimitedList 45 | 46 | mfaConfiguration: 47 | Type: String 48 | 49 | mfaTypes: 50 | Type: CommaDelimitedList 51 | 52 | roleName: 53 | Type: String 54 | 55 | roleExternalId: 56 | Type: String 57 | 58 | policyName: 59 | Type: String 60 | 61 | smsAuthenticationMessage: 62 | Type: String 63 | 64 | smsVerificationMessage: 65 | Type: String 66 | 67 | emailVerificationSubject: 68 | Type: String 69 | 70 | emailVerificationMessage: 71 | Type: String 72 | 73 | defaultPasswordPolicy: 74 | Type: String 75 | 76 | passwordPolicyMinLength: 77 | Type: Number 78 | 79 | passwordPolicyCharacters: 80 | Type: CommaDelimitedList 81 | 82 | requiredAttributes: 83 | Type: CommaDelimitedList 84 | 85 | userpoolClientName: 86 | Type: String 87 | 88 | userpoolClientGenerateSecret: 89 | Type: String 90 | 91 | userpoolClientRefreshTokenValidity: 92 | Type: Number 93 | 94 | userpoolClientReadAttributes: 95 | Type: CommaDelimitedList 96 | 97 | mfaLambdaRole: 98 | Type: String 99 | 100 | mfaLambdaLogPolicy: 101 | Type: String 102 | 103 | mfaPassRolePolicy: 104 | Type: String 105 | 106 | mfaLambdaIAMPolicy: 107 | Type: String 108 | 109 | userpoolClientLambdaRole: 110 | Type: String 111 | 112 | userpoolClientLogPolicy: 113 | Type: String 114 | 115 | userpoolClientLambdaPolicy: 116 | Type: String 117 | 118 | userpoolClientSetAttributes: 119 | Type: String 120 | 121 | useDefault: 122 | Type: String 123 | 124 | resourceName: 125 | Type: String 126 | 127 | authSelections: 128 | Type: String 129 | 130 | Conditions: 131 | ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ] 132 | 133 | Resources: 134 | 135 | # BEGIN SNS ROLE RESOURCE 136 | SNSRole: 137 | # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process 138 | Type: AWS::IAM::Role 139 | Properties: 140 | RoleName: !If [ShouldNotCreateEnvResources, !Ref roleName, !Join ['',[!Ref roleName, '-', !Ref env]]] 141 | AssumeRolePolicyDocument: 142 | Version: "2012-10-17" 143 | Statement: 144 | - Sid: "" 145 | Effect: "Allow" 146 | Principal: 147 | Service: "cognito-idp.amazonaws.com" 148 | Action: 149 | - "sts:AssumeRole" 150 | Condition: 151 | StringEquals: 152 | sts:ExternalId: !Ref roleExternalId 153 | Policies: 154 | - 155 | PolicyName: !Ref policyName 156 | PolicyDocument: 157 | Version: "2012-10-17" 158 | Statement: 159 | - 160 | Effect: "Allow" 161 | Action: 162 | - "sns:Publish" 163 | Resource: "*" 164 | # BEGIN USER POOL RESOURCES 165 | UserPool: 166 | # Created upon user selection 167 | # Depends on SNS Role for Arn if MFA is enabled 168 | Type: AWS::Cognito::UserPool 169 | Properties: 170 | UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]] 171 | Schema: 172 | 173 | - 174 | Name: email 175 | Required: true 176 | Mutable: true 177 | 178 | 179 | AutoVerifiedAttributes: !Ref autoVerifiedAttributes 180 | 181 | 182 | EmailVerificationMessage: !Ref emailVerificationMessage 183 | EmailVerificationSubject: !Ref emailVerificationSubject 184 | 185 | Policies: 186 | PasswordPolicy: 187 | MinimumLength: !Ref passwordPolicyMinLength 188 | RequireLowercase: true 189 | RequireNumbers: true 190 | RequireSymbols: true 191 | RequireUppercase: true 192 | MfaConfiguration: !Ref mfaConfiguration 193 | SmsVerificationMessage: !Ref smsVerificationMessage 194 | SmsConfiguration: 195 | SnsCallerArn: !GetAtt SNSRole.Arn 196 | ExternalId: !Ref roleExternalId 197 | 198 | UserPoolClientWeb: 199 | # Created provide application access to user pool 200 | # Depends on UserPool for ID reference 201 | Type: "AWS::Cognito::UserPoolClient" 202 | Properties: 203 | ClientName: cognito704b3e59_app_clientWeb 204 | 205 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 206 | UserPoolId: !Ref UserPool 207 | DependsOn: UserPool 208 | UserPoolClient: 209 | # Created provide application access to user pool 210 | # Depends on UserPool for ID reference 211 | Type: "AWS::Cognito::UserPoolClient" 212 | Properties: 213 | ClientName: !Ref userpoolClientName 214 | 215 | GenerateSecret: !Ref userpoolClientGenerateSecret 216 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 217 | UserPoolId: !Ref UserPool 218 | DependsOn: UserPool 219 | # BEGIN USER POOL LAMBDA RESOURCES 220 | UserPoolClientRole: 221 | # Created to execute Lambda which gets userpool app client config values 222 | Type: 'AWS::IAM::Role' 223 | Properties: 224 | RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 225 | AssumeRolePolicyDocument: 226 | Version: '2012-10-17' 227 | Statement: 228 | - Effect: Allow 229 | Principal: 230 | Service: 231 | - lambda.amazonaws.com 232 | Action: 233 | - 'sts:AssumeRole' 234 | DependsOn: UserPoolClient 235 | UserPoolClientLambda: 236 | # Lambda which gets userpool app client config values 237 | # Depends on UserPool for id 238 | # Depends on UserPoolClientRole for role ARN 239 | Type: 'AWS::Lambda::Function' 240 | Properties: 241 | Code: 242 | ZipFile: !Join 243 | - |+ 244 | - - 'const response = require(''cfn-response'');' 245 | - 'const aws = require(''aws-sdk'');' 246 | - 'const identity = new aws.CognitoIdentityServiceProvider();' 247 | - 'exports.handler = (event, context, callback) => {' 248 | - ' if (event.RequestType == ''Delete'') { ' 249 | - ' response.send(event, context, response.SUCCESS, {})' 250 | - ' }' 251 | - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {' 252 | - ' const params = {' 253 | - ' ClientId: event.ResourceProperties.clientId,' 254 | - ' UserPoolId: event.ResourceProperties.userpoolId' 255 | - ' };' 256 | - ' identity.describeUserPoolClient(params).promise()' 257 | - ' .then((res) => {' 258 | - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});' 259 | - ' })' 260 | - ' .catch((err) => {' 261 | - ' response.send(event, context, response.FAILURE, {err});' 262 | - ' });' 263 | - ' }' 264 | - '};' 265 | Handler: index.handler 266 | Runtime: nodejs12.x 267 | Timeout: '300' 268 | Role: !GetAtt 269 | - UserPoolClientRole 270 | - Arn 271 | DependsOn: UserPoolClientRole 272 | UserPoolClientLambdaPolicy: 273 | # Sets userpool policy for the role that executes the Userpool Client Lambda 274 | # Depends on UserPool for Arn 275 | # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing 276 | Type: 'AWS::IAM::Policy' 277 | Properties: 278 | PolicyName: !Ref userpoolClientLambdaPolicy 279 | Roles: 280 | - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 281 | PolicyDocument: 282 | Version: '2012-10-17' 283 | Statement: 284 | - Effect: Allow 285 | Action: 286 | - 'cognito-idp:DescribeUserPoolClient' 287 | Resource: !GetAtt UserPool.Arn 288 | DependsOn: UserPoolClientLambda 289 | UserPoolClientLogPolicy: 290 | # Sets log policy for the role that executes the Userpool Client Lambda 291 | # Depends on UserPool for Arn 292 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 293 | Type: 'AWS::IAM::Policy' 294 | Properties: 295 | PolicyName: !Ref userpoolClientLogPolicy 296 | Roles: 297 | - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 298 | PolicyDocument: 299 | Version: 2012-10-17 300 | Statement: 301 | - Effect: Allow 302 | Action: 303 | - 'logs:CreateLogGroup' 304 | - 'logs:CreateLogStream' 305 | - 'logs:PutLogEvents' 306 | Resource: !Sub 307 | - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:* 308 | - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda} 309 | DependsOn: UserPoolClientLambdaPolicy 310 | UserPoolClientInputs: 311 | # Values passed to Userpool client Lambda 312 | # Depends on UserPool for Id 313 | # Depends on UserPoolClient for Id 314 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 315 | Type: 'Custom::LambdaCallout' 316 | Properties: 317 | ServiceToken: !GetAtt UserPoolClientLambda.Arn 318 | clientId: !Ref UserPoolClient 319 | userpoolId: !Ref UserPool 320 | DependsOn: UserPoolClientLogPolicy 321 | 322 | 323 | # BEGIN IDENTITY POOL RESOURCES 324 | 325 | 326 | IdentityPool: 327 | # Always created 328 | Type: AWS::Cognito::IdentityPool 329 | Properties: 330 | IdentityPoolName: !If [ShouldNotCreateEnvResources, 'cognito704b3e59_identitypool_704b3e59', !Join ['',['cognito704b3e59_identitypool_704b3e59', '__', !Ref env]]] 331 | 332 | CognitoIdentityProviders: 333 | - ClientId: !Ref UserPoolClient 334 | ProviderName: !Sub 335 | - cognito-idp.${region}.amazonaws.com/${client} 336 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 337 | - ClientId: !Ref UserPoolClientWeb 338 | ProviderName: !Sub 339 | - cognito-idp.${region}.amazonaws.com/${client} 340 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 341 | 342 | AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities 343 | 344 | 345 | DependsOn: UserPoolClientInputs 346 | 347 | 348 | IdentityPoolRoleMap: 349 | # Created to map Auth and Unauth roles to the identity pool 350 | # Depends on Identity Pool for ID ref 351 | Type: AWS::Cognito::IdentityPoolRoleAttachment 352 | Properties: 353 | IdentityPoolId: !Ref IdentityPool 354 | Roles: 355 | unauthenticated: !Ref unauthRoleArn 356 | authenticated: !Ref authRoleArn 357 | DependsOn: IdentityPool 358 | 359 | 360 | Outputs : 361 | 362 | IdentityPoolId: 363 | Value: !Ref 'IdentityPool' 364 | Description: Id for the identity pool 365 | IdentityPoolName: 366 | Value: !GetAtt IdentityPool.Name 367 | 368 | 369 | UserPoolId: 370 | Value: !Ref 'UserPool' 371 | Description: Id for the user pool 372 | UserPoolName: 373 | Value: !Ref userPoolName 374 | AppClientIDWeb: 375 | Value: !Ref 'UserPoolClientWeb' 376 | Description: The user pool app client id for web 377 | AppClientID: 378 | Value: !Ref 'UserPoolClient' 379 | Description: The user pool app client id 380 | AppClientSecret: 381 | Value: !GetAtt UserPoolClientInputs.appSecret 382 | 383 | 384 | 385 | 386 | 387 | 388 | -------------------------------------------------------------------------------- /amplify/backend/auth/cognito704b3e59/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "identityPoolName": "cognito704b3e59_identitypool_704b3e59", 3 | "allowUnauthenticatedIdentities": false, 4 | "thirdPartyAuth": false, 5 | "lambdaLogPolicy": "cognito704b3e59_lambda_log_policy", 6 | "openIdLambdaRoleName": "cognito704b3e59_openid_lambda_role", 7 | "openIdRolePolicy": "cognito704b3e59_openid_pass_role_policy", 8 | "openIdLambdaIAMPolicy": "cognito704b3e59_openid_lambda_iam_policy", 9 | "openIdLogPolicy": "cognito704b3e59_openid_lambda_log_policy", 10 | "userPoolName": "cognito704b3e59_userpool_704b3e59", 11 | "autoVerifiedAttributes": [ 12 | "email" 13 | ], 14 | "mfaConfiguration": "OFF", 15 | "mfaTypes": [ 16 | "SMS Text Message" 17 | ], 18 | "roleName": "cognito704b3e59_sns-role", 19 | "roleExternalId": "cognito704b3e59_role_external_id", 20 | "policyName": "cognito704b3e59-sns-policy", 21 | "smsAuthenticationMessage": "Your authentication code is {####}", 22 | "smsVerificationMessage": "Your verification code is {####}", 23 | "emailVerificationSubject": "Your verification code", 24 | "emailVerificationMessage": "Your verification code is {####}", 25 | "defaultPasswordPolicy": false, 26 | "passwordPolicyMinLength": 8, 27 | "passwordPolicyCharacters": [ 28 | "Requires Lowercase", 29 | "Requires Uppercase", 30 | "Requires Numbers", 31 | "Requires Symbols" 32 | ], 33 | "requiredAttributes": [ 34 | "email" 35 | ], 36 | "userpoolClientName": "cognito704b3e59_app_client", 37 | "userpoolClientGenerateSecret": true, 38 | "userpoolClientRefreshTokenValidity": 30, 39 | "userpoolClientReadAttributes": [ 40 | "email" 41 | ], 42 | "mfaLambdaRole": "cognito704b3e59_totp_lambda_role", 43 | "mfaLambdaLogPolicy": "cognito704b3e59_totp_lambda_log_policy", 44 | "mfaPassRolePolicy": "cognito704b3e59_totp_pass_role_policy", 45 | "mfaLambdaIAMPolicy": "cognito704b3e59_totp_lambda_iam_policy", 46 | "userpoolClientLambdaRole": "cognito704b3e59_userpoolclient_lambda_role", 47 | "userpoolClientLogPolicy": "cognito704b3e59_userpoolclient_lambda_log_policy", 48 | "userpoolClientLambdaPolicy": "cognito704b3e59_userpoolclient_lambda_iam_policy", 49 | "userpoolClientSetAttributes": false, 50 | "useDefault": "default", 51 | "resourceName": "cognito704b3e59", 52 | "authSelections": "identityPoolAndUserPool", 53 | "authRoleName": { 54 | "Ref": "AuthRoleName" 55 | }, 56 | "unauthRoleName": { 57 | "Ref": "UnauthRoleName" 58 | }, 59 | "authRoleArn": { 60 | "Fn::GetAtt": [ 61 | "AuthRole", 62 | "Arn" 63 | ] 64 | }, 65 | "unauthRoleArn": { 66 | "Fn::GetAtt": [ 67 | "UnauthRole", 68 | "Arn" 69 | ] 70 | } 71 | } -------------------------------------------------------------------------------- /amplify/backend/awscloudformation/nested-cloudformation-stack.yml: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Root stack for the Amplify AWS CloudFormation provider", 4 | "Parameters": { 5 | "DeploymentBucketName": { 6 | "Description": "Name of the common deployment bucket provided by the parent stack", 7 | "Type": "String", 8 | "Default": "DeploymentBucket" 9 | }, 10 | "AuthRoleName": { 11 | "Type": "String", 12 | "Default": "AuthRoleName" 13 | }, 14 | "UnauthRoleName": { 15 | "Type": "String", 16 | "Default": "UnauthRoleName" 17 | } 18 | }, 19 | "Resources": { 20 | "DeploymentBucket": { 21 | "Type": "AWS::S3::Bucket", 22 | "DeletionPolicy": "Retain", 23 | "Properties": { 24 | "BucketName": { 25 | "Ref": "DeploymentBucketName" 26 | } 27 | } 28 | }, 29 | "AuthRole": { 30 | "Type": "AWS::IAM::Role", 31 | "Properties": { 32 | "RoleName": { 33 | "Ref": "AuthRoleName" 34 | }, 35 | "AssumeRolePolicyDocument": { 36 | "Version": "2012-10-17", 37 | "Statement": [ 38 | { 39 | "Sid": "", 40 | "Effect": "Allow", 41 | "Principal": { 42 | "Federated": "cognito-identity.amazonaws.com" 43 | }, 44 | "Action": "sts:AssumeRoleWithWebIdentity", 45 | "Condition": { 46 | "ForAnyValue:StringLike": { 47 | "cognito-identity.amazonaws.com:amr": "authenticated" 48 | } 49 | } 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | "UnauthRole": { 56 | "Type": "AWS::IAM::Role", 57 | "Properties": { 58 | "RoleName": { 59 | "Ref": "UnauthRoleName" 60 | }, 61 | "AssumeRolePolicyDocument": { 62 | "Version": "2012-10-17", 63 | "Statement": [ 64 | { 65 | "Sid": "", 66 | "Effect": "Allow", 67 | "Principal": { 68 | "Federated": "cognito-identity.amazonaws.com" 69 | }, 70 | "Action": "sts:AssumeRoleWithWebIdentity", 71 | "Condition": { 72 | "ForAnyValue:StringLike": { 73 | "cognito-identity.amazonaws.com:amr": "unauthenticated" 74 | } 75 | } 76 | } 77 | ] 78 | } 79 | } 80 | }, 81 | "authcognito704b3e59": { 82 | "Type": "AWS::CloudFormation::Stack", 83 | "Properties": { 84 | "TemplateURL": "https://s3.amazonaws.com/my-aws-project-20190103122546-deployment/amplify-cfn-templates/auth/cognito704b3e59-cloudformation-template.yml", 85 | "Parameters": { 86 | "identityPoolName": "cognito704b3e59_identitypool_704b3e59", 87 | "allowUnauthenticatedIdentities": false, 88 | "thirdPartyAuth": false, 89 | "lambdaLogPolicy": "cognito704b3e59_lambda_log_policy", 90 | "openIdLambdaRoleName": "cognito704b3e59_openid_lambda_role", 91 | "openIdRolePolicy": "cognito704b3e59_openid_pass_role_policy", 92 | "openIdLambdaIAMPolicy": "cognito704b3e59_openid_lambda_iam_policy", 93 | "openIdLogPolicy": "cognito704b3e59_openid_lambda_log_policy", 94 | "userPoolName": "cognito704b3e59_userpool_704b3e59", 95 | "autoVerifiedAttributes": "email", 96 | "mfaConfiguration": "OFF", 97 | "mfaTypes": "SMS Text Message", 98 | "roleName": "cognito704b3e59_sns-role", 99 | "roleExternalId": "cognito704b3e59_role_external_id", 100 | "policyName": "cognito704b3e59-sns-policy", 101 | "smsAuthenticationMessage": "Your authentication code is {####}", 102 | "smsVerificationMessage": "Your verification code is {####}", 103 | "emailVerificationSubject": "Your verification code", 104 | "emailVerificationMessage": "Your verification code is {####}", 105 | "defaultPasswordPolicy": false, 106 | "passwordPolicyMinLength": 8, 107 | "passwordPolicyCharacters": "Requires Lowercase,Requires Uppercase,Requires Numbers,Requires Symbols", 108 | "requiredAttributes": "email", 109 | "userpoolClientName": "cognito704b3e59_app_client", 110 | "userpoolClientGenerateSecret": true, 111 | "userpoolClientRefreshTokenValidity": 30, 112 | "userpoolClientReadAttributes": "email", 113 | "mfaLambdaRole": "cognito704b3e59_totp_lambda_role", 114 | "mfaLambdaLogPolicy": "cognito704b3e59_totp_lambda_log_policy", 115 | "mfaPassRolePolicy": "cognito704b3e59_totp_pass_role_policy", 116 | "mfaLambdaIAMPolicy": "cognito704b3e59_totp_lambda_iam_policy", 117 | "userpoolClientLambdaRole": "cognito704b3e59_userpoolclient_lambda_role", 118 | "userpoolClientLogPolicy": "cognito704b3e59_userpoolclient_lambda_log_policy", 119 | "userpoolClientLambdaPolicy": "cognito704b3e59_userpoolclient_lambda_iam_policy", 120 | "userpoolClientSetAttributes": false, 121 | "useDefault": "default", 122 | "resourceName": "cognito704b3e59", 123 | "authSelections": "identityPoolAndUserPool", 124 | "authRoleName": { 125 | "Ref": "AuthRoleName" 126 | }, 127 | "unauthRoleName": { 128 | "Ref": "UnauthRoleName" 129 | }, 130 | "authRoleArn": { 131 | "Fn::GetAtt": [ 132 | "AuthRole", 133 | "Arn" 134 | ] 135 | }, 136 | "unauthRoleArn": { 137 | "Fn::GetAtt": [ 138 | "UnauthRole", 139 | "Arn" 140 | ] 141 | }, 142 | "env": "prod" 143 | } 144 | } 145 | } 146 | }, 147 | "Outputs": { 148 | "Region": { 149 | "Description": "CloudFormation provider root stack Region", 150 | "Value": { 151 | "Ref": "AWS::Region" 152 | }, 153 | "Export": { 154 | "Name": { 155 | "Fn::Sub": "${AWS::StackName}-Region" 156 | } 157 | } 158 | }, 159 | "StackName": { 160 | "Description": "CloudFormation provider root stack ID", 161 | "Value": { 162 | "Ref": "AWS::StackName" 163 | }, 164 | "Export": { 165 | "Name": { 166 | "Fn::Sub": "${AWS::StackName}-StackName" 167 | } 168 | } 169 | }, 170 | "StackId": { 171 | "Description": "CloudFormation provider root stack name", 172 | "Value": { 173 | "Ref": "AWS::StackId" 174 | }, 175 | "Export": { 176 | "Name": { 177 | "Fn::Sub": "${AWS::StackName}-StackId" 178 | } 179 | } 180 | }, 181 | "DeploymentBucketName": { 182 | "Description": "CloudFormation provider root stack deployment bucket name", 183 | "Value": { 184 | "Ref": "DeploymentBucketName" 185 | }, 186 | "Export": { 187 | "Name": { 188 | "Fn::Sub": "${AWS::StackName}-DeploymentBucketName" 189 | } 190 | } 191 | }, 192 | "AuthRoleArn": { 193 | "Value": { 194 | "Fn::GetAtt": [ 195 | "AuthRole", 196 | "Arn" 197 | ] 198 | } 199 | }, 200 | "UnauthRoleArn": { 201 | "Value": { 202 | "Fn::GetAtt": [ 203 | "UnauthRole", 204 | "Arn" 205 | ] 206 | } 207 | }, 208 | "AuthRoleName": { 209 | "Value": { 210 | "Ref": "AuthRole" 211 | } 212 | }, 213 | "UnauthRoleName": { 214 | "Value": { 215 | "Ref": "UnauthRole" 216 | } 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "cognito704b3e59": { 4 | "service": "Cognito", 5 | "providerPlugin": "awscloudformation" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | 9 | import Auth from '@aws-amplify/auth' 10 | import { setUser } from './src/utils/auth' 11 | 12 | export const onRouteUpdate = (state, page, pages) => { 13 | Auth.currentAuthenticatedUser() 14 | .then(user => { 15 | const userInfo = { 16 | ...user.attributes, 17 | username: user.username 18 | } 19 | setUser(userInfo) 20 | }) 21 | .catch(err => { 22 | window.localStorage.setItem('gatsbyUser', null) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: `Gatsby Default Starter`, 4 | description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`, 5 | author: `@gatsbyjs`, 6 | }, 7 | plugins: [ 8 | `gatsby-plugin-react-helmet`, 9 | { 10 | resolve: `gatsby-source-filesystem`, 11 | options: { 12 | name: `images`, 13 | path: `${__dirname}/src/images`, 14 | }, 15 | }, 16 | `gatsby-transformer-sharp`, 17 | `gatsby-plugin-sharp`, 18 | { 19 | resolve: `gatsby-plugin-manifest`, 20 | options: { 21 | name: `gatsby-starter-default`, 22 | short_name: `starter`, 23 | start_url: `/`, 24 | background_color: `#663399`, 25 | theme_color: `#663399`, 26 | display: `minimal-ui`, 27 | icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site. 28 | }, 29 | }, 30 | // this (optional) plugin enables Progressive Web App + Offline functionality 31 | // To learn more, visit: https://gatsby.dev/offline 32 | // `gatsby-plugin-offline`, 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Node APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/node-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | exports.onCreatePage = async ({ page, actions }) => { 9 | const { createPage } = actions 10 | 11 | // page.matchPath is a special key that's used for matching pages 12 | // only on the client. 13 | if (page.path.match(/^\/app/)) { 14 | page.matchPath = `/app/*` 15 | 16 | // Update the page. 17 | createPage(page) 18 | } 19 | } -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-auth-starter-aws", 3 | "description": "Gatsby authentication starter for AWS", 4 | "version": "1.0.0", 5 | "author": "Nader Dabit ", 6 | "dependencies": { 7 | "aws-amplify": "^4.3.14", 8 | "gatsby": "^2.32.13", 9 | "gatsby-plugin-manifest": "^2.12.1", 10 | "gatsby-plugin-offline": "^2.2.10", 11 | "gatsby-plugin-react-helmet": "^3.10.0", 12 | "gatsby-plugin-sharp": "^2.14.4", 13 | "gatsby-source-filesystem": "^2.11.1", 14 | "gatsby-source-graphql": "^2.14.0", 15 | "gatsby-transformer-sharp": "^2.12.1", 16 | "prop-types": "^15.7.2", 17 | "react": "^16.14.0", 18 | "react-dom": "^16.14.0", 19 | "react-helmet": "^5.2.1" 20 | }, 21 | "keywords": [ 22 | "gatsby" 23 | ], 24 | "license": "MIT", 25 | "scripts": { 26 | "build": "gatsby build", 27 | "develop": "gatsby develop", 28 | "format": "prettier --write src/**/*.{js,jsx}", 29 | "start": "npm run develop", 30 | "serve": "gatsby serve", 31 | "test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\"" 32 | }, 33 | "devDependencies": { 34 | "prettier": "^1.18.2" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/gatsbyjs/gatsby-starter-default" 39 | }, 40 | "resolutions": { 41 | "graphql": "14.7.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Details.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 3 | import { getCurrentUser } from '../utils/auth' 4 | 5 | const Home = () => { 6 | const user = getCurrentUser() 7 | console.log('user:', user) 8 | return ( 9 |
10 |

Profile Details

11 |

Email: {user.email}

12 |

Phone: {user.phone_number}

13 |

Username: {user.username}

14 | Home 15 |
16 | ) 17 | } 18 | 19 | export default Home -------------------------------------------------------------------------------- /src/components/Error.js: -------------------------------------------------------------------------------- 1 | //https://github.com/wesbos/dump 2 | import React from 'react'; 3 | 4 | const Error = props => ( 5 |
6 | {Object.entries(props).map(([err, val]) => ( 7 |
 8 |             {err}: 
 9 |             {JSON.stringify(val, '', ' ')}
10 |             
11 | AWS Cognito User Pool documentation. 13 |
14 | ))} 15 |
16 | ); 17 | 18 | export default Error; -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 3 | 4 | const Home = () =>
5 |

Home

6 |

You are now logged in! View profile

7 |

Now go build something great and deploy it using the AWS Amplify Console

8 |
9 | 10 | export default Home -------------------------------------------------------------------------------- /src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from 'gatsby' 3 | import { navigate } from '@reach/router' 4 | import { setUser, isLoggedIn } from '../utils/auth' 5 | import Error from './Error' 6 | import { Auth } from 'aws-amplify' 7 | 8 | class Login extends React.Component { 9 | state = { 10 | username: ``, 11 | password: ``, 12 | error: `` 13 | } 14 | 15 | handleUpdate = (event) => { 16 | this.setState({ 17 | [event.target.name]: event.target.value, 18 | }) 19 | } 20 | 21 | login = async() => { 22 | const { username, password } = this.state 23 | try { 24 | await Auth.signIn(username, password) 25 | const user = await Auth.currentAuthenticatedUser() 26 | const userInfo = { 27 | ...user.attributes, 28 | username: user.username 29 | } 30 | setUser(userInfo) 31 | navigate("/app/home") 32 | } catch (err) { 33 | this.setState({ error: err }) 34 | console.log('error...: ', err) 35 | } 36 | } 37 | 38 | render() { 39 | if (isLoggedIn()) navigate('/app/profile') 40 | return ( 41 |
42 |

Sign In

43 | {this.state.error && } 44 |
45 | 52 | 60 |
61 | Sign In 62 |
63 |
64 | Sign Up
65 |
66 | ) 67 | } 68 | } 69 | 70 | const styles = { 71 | input: { 72 | height: 40, margin: '10px 0px', padding: 7 73 | }, 74 | formContainer: { 75 | display: 'flex', flexDirection: 'column' 76 | }, 77 | button: { 78 | backgroundColor: 'rebeccapurple', padding: '15px 7px', cursor: 'pointer', textAlign: 'center', marginBottom: 10 79 | }, 80 | buttonText: { 81 | color: 'white' 82 | } 83 | } 84 | 85 | export default Login -------------------------------------------------------------------------------- /src/components/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | 2 | import React from "react" 3 | import { navigate } from "@reach/router" 4 | import { isLoggedIn } from "../utils/auth" 5 | 6 | class PrivateRoute extends React.Component { 7 | render() { 8 | const { component: Component, location, ...rest } = this.props 9 | if (!isLoggedIn()) { 10 | navigate(`/app/login`) 11 | return null 12 | } 13 | return 14 | } 15 | } 16 | 17 | export default PrivateRoute -------------------------------------------------------------------------------- /src/components/SignUp.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { navigate} from "@reach/router" 3 | import { Link } from 'gatsby' 4 | import Error from './Error' 5 | import { Auth } from 'aws-amplify' 6 | 7 | const initialState = { 8 | username: ``, 9 | password: ``, 10 | email: '', 11 | phone_number: '', 12 | authCode: '', 13 | stage: 0, 14 | error: '' 15 | } 16 | 17 | 18 | class SignUp extends React.Component { 19 | state = initialState 20 | 21 | handleUpdate = (event) => { 22 | this.setState({ 23 | [event.target.name]: event.target.value, 24 | }) 25 | } 26 | 27 | signUp = async() => { 28 | const { username, password, email, phone_number } = this.state 29 | try { 30 | await Auth.signUp({ username, password, attributes: { email, phone_number }}) 31 | this.setState({ stage: 1 }) 32 | } catch (err) { 33 | this.setState({ error: err }) 34 | console.log('error signing up...', err) 35 | } 36 | } 37 | 38 | confirmSignUp = async() => { 39 | const { username, authCode } = this.state 40 | try { 41 | await Auth.confirmSignUp(username, authCode) 42 | alert('Successfully signed up!') 43 | navigate("/app/login") 44 | } catch (err) { 45 | this.setState({ error: err }) 46 | console.log('error confirming signing up...', err) 47 | } 48 | } 49 | 50 | render() { 51 | return ( 52 |
53 |

Sign Up

54 | { 55 | this.state.stage === 0 && ( 56 |
57 | {this.state.error && } 58 | 65 | 73 | 80 | 87 |
88 | Sign Up 89 |
90 |
91 | ) 92 | } 93 | { 94 | this.state.stage === 1 && ( 95 |
96 | {this.state.error && } 97 | 104 |
105 | Confirm Sign Up 106 |
107 |
108 | ) 109 | } 110 | Sign In 111 |
112 | ) 113 | } 114 | } 115 | 116 | const styles = { 117 | input: { 118 | height: 40, margin: '10px 0px', padding: 7 119 | }, 120 | formContainer: { 121 | display: 'flex', flexDirection: 'column' 122 | }, 123 | button: { 124 | backgroundColor: 'rebeccapurple', padding: '15px 7px', cursor: 'pointer', textAlign: 'center', marginBottom: 10 125 | }, 126 | buttonText: { 127 | color: 'white' 128 | } 129 | } 130 | 131 | export default SignUp -------------------------------------------------------------------------------- /src/components/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 3 | 4 | import { navigate } from '@reach/router' 5 | 6 | import { logout, isLoggedIn } from "../utils/auth" 7 | import { Auth } from 'aws-amplify' 8 | 9 | const Header = ({ siteTitle }) => ( 10 |
16 |
23 |

24 | 28 | {siteTitle} 29 | 30 |

31 | { 32 | isLoggedIn() && ( 33 |

Auth.signOut().then(logout(() => navigate('/app/login'))).catch(err => console.log('eror:', err)) 36 | } 37 | style={styles.link} 38 | >Sign Out

39 | ) 40 | } 41 |
42 |
43 | ) 44 | 45 | const styles = { 46 | headerTitle: { 47 | color: 'white', 48 | textDecoration: 'none', 49 | }, 50 | link: { 51 | cursor: 'pointer', 52 | color: 'white', 53 | textDecoration: 'underline' 54 | } 55 | } 56 | 57 | export default Header 58 | -------------------------------------------------------------------------------- /src/components/layout.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | } 9 | article, 10 | aside, 11 | details, 12 | figcaption, 13 | figure, 14 | footer, 15 | header, 16 | main, 17 | menu, 18 | nav, 19 | section, 20 | summary { 21 | display: block; 22 | } 23 | audio, 24 | canvas, 25 | progress, 26 | video { 27 | display: inline-block; 28 | } 29 | audio:not([controls]) { 30 | display: none; 31 | height: 0; 32 | } 33 | progress { 34 | vertical-align: baseline; 35 | } 36 | [hidden], 37 | template { 38 | display: none; 39 | } 40 | a { 41 | background-color: transparent; 42 | -webkit-text-decoration-skip: objects; 43 | } 44 | a:active, 45 | a:hover { 46 | outline-width: 0; 47 | } 48 | abbr[title] { 49 | border-bottom: none; 50 | text-decoration: underline; 51 | text-decoration: underline dotted; 52 | } 53 | b, 54 | strong { 55 | font-weight: inherit; 56 | font-weight: bolder; 57 | } 58 | dfn { 59 | font-style: italic; 60 | } 61 | h1 { 62 | font-size: 2em; 63 | margin: .67em 0; 64 | } 65 | mark { 66 | background-color: #ff0; 67 | color: #000; 68 | } 69 | small { 70 | font-size: 80%; 71 | } 72 | sub, 73 | sup { 74 | font-size: 75%; 75 | line-height: 0; 76 | position: relative; 77 | vertical-align: baseline; 78 | } 79 | sub { 80 | bottom: -.25em; 81 | } 82 | sup { 83 | top: -.5em; 84 | } 85 | img { 86 | border-style: none; 87 | } 88 | svg:not(:root) { 89 | overflow: hidden; 90 | } 91 | code, 92 | kbd, 93 | pre, 94 | samp { 95 | font-family: monospace, monospace; 96 | font-size: 1em; 97 | } 98 | figure { 99 | margin: 1em 40px; 100 | } 101 | hr { 102 | box-sizing: content-box; 103 | height: 0; 104 | overflow: visible; 105 | } 106 | button, 107 | input, 108 | optgroup, 109 | select, 110 | textarea { 111 | font: inherit; 112 | margin: 0; 113 | } 114 | optgroup { 115 | font-weight: 700; 116 | } 117 | button, 118 | input { 119 | overflow: visible; 120 | } 121 | button, 122 | select { 123 | text-transform: none; 124 | } 125 | [type=reset], 126 | [type=submit], 127 | button, 128 | html [type=button] { 129 | -webkit-appearance: button; 130 | } 131 | [type=button]::-moz-focus-inner, 132 | [type=reset]::-moz-focus-inner, 133 | [type=submit]::-moz-focus-inner, 134 | button::-moz-focus-inner { 135 | border-style: none; 136 | padding: 0; 137 | } 138 | [type=button]:-moz-focusring, 139 | [type=reset]:-moz-focusring, 140 | [type=submit]:-moz-focusring, 141 | button:-moz-focusring { 142 | outline: 1px dotted ButtonText; 143 | } 144 | fieldset { 145 | border: 1px solid silver; 146 | margin: 0 2px; 147 | padding: .35em .625em .75em; 148 | } 149 | legend { 150 | box-sizing: border-box; 151 | color: inherit; 152 | display: table; 153 | max-width: 100%; 154 | padding: 0; 155 | white-space: normal; 156 | } 157 | textarea { 158 | overflow: auto; 159 | } 160 | [type=checkbox], 161 | [type=radio] { 162 | box-sizing: border-box; 163 | padding: 0; 164 | } 165 | [type=number]::-webkit-inner-spin-button, 166 | [type=number]::-webkit-outer-spin-button { 167 | height: auto; 168 | } 169 | [type=search] { 170 | -webkit-appearance: textfield; 171 | outline-offset: -2px; 172 | } 173 | [type=search]::-webkit-search-cancel-button, 174 | [type=search]::-webkit-search-decoration { 175 | -webkit-appearance: none; 176 | } 177 | ::-webkit-input-placeholder { 178 | color: inherit; 179 | opacity: .54; 180 | } 181 | ::-webkit-file-upload-button { 182 | -webkit-appearance: button; 183 | font: inherit; 184 | } 185 | html { 186 | font: 112.5%/1.45em georgia, serif; 187 | box-sizing: border-box; 188 | overflow-y: scroll; 189 | } 190 | * { 191 | box-sizing: inherit; 192 | } 193 | *:before { 194 | box-sizing: inherit; 195 | } 196 | *:after { 197 | box-sizing: inherit; 198 | } 199 | body { 200 | color: hsla(0, 0%, 0%, 0.8); 201 | font-family: georgia, serif; 202 | font-weight: normal; 203 | word-wrap: break-word; 204 | font-kerning: normal; 205 | -moz-font-feature-settings: "kern", "liga", "clig", "calt"; 206 | -ms-font-feature-settings: "kern", "liga", "clig", "calt"; 207 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; 208 | font-feature-settings: "kern", "liga", "clig", "calt"; 209 | } 210 | img { 211 | max-width: 100%; 212 | margin-left: 0; 213 | margin-right: 0; 214 | margin-top: 0; 215 | padding-bottom: 0; 216 | padding-left: 0; 217 | padding-right: 0; 218 | padding-top: 0; 219 | margin-bottom: 1.45rem; 220 | } 221 | h1 { 222 | margin-left: 0; 223 | margin-right: 0; 224 | margin-top: 0; 225 | padding-bottom: 0; 226 | padding-left: 0; 227 | padding-right: 0; 228 | padding-top: 0; 229 | margin-bottom: 1.45rem; 230 | color: inherit; 231 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 232 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 233 | font-weight: bold; 234 | text-rendering: optimizeLegibility; 235 | font-size: 2.25rem; 236 | line-height: 1.1; 237 | } 238 | h2 { 239 | margin-left: 0; 240 | margin-right: 0; 241 | margin-top: 0; 242 | padding-bottom: 0; 243 | padding-left: 0; 244 | padding-right: 0; 245 | padding-top: 0; 246 | margin-bottom: 1.45rem; 247 | color: inherit; 248 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 249 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 250 | font-weight: bold; 251 | text-rendering: optimizeLegibility; 252 | font-size: 1.62671rem; 253 | line-height: 1.1; 254 | } 255 | h3 { 256 | margin-left: 0; 257 | margin-right: 0; 258 | margin-top: 0; 259 | padding-bottom: 0; 260 | padding-left: 0; 261 | padding-right: 0; 262 | padding-top: 0; 263 | margin-bottom: 1.45rem; 264 | color: inherit; 265 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 266 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 267 | font-weight: bold; 268 | text-rendering: optimizeLegibility; 269 | font-size: 1.38316rem; 270 | line-height: 1.1; 271 | } 272 | h4 { 273 | margin-left: 0; 274 | margin-right: 0; 275 | margin-top: 0; 276 | padding-bottom: 0; 277 | padding-left: 0; 278 | padding-right: 0; 279 | padding-top: 0; 280 | margin-bottom: 1.45rem; 281 | color: inherit; 282 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 283 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 284 | font-weight: bold; 285 | text-rendering: optimizeLegibility; 286 | font-size: 1rem; 287 | line-height: 1.1; 288 | } 289 | h5 { 290 | margin-left: 0; 291 | margin-right: 0; 292 | margin-top: 0; 293 | padding-bottom: 0; 294 | padding-left: 0; 295 | padding-right: 0; 296 | padding-top: 0; 297 | margin-bottom: 1.45rem; 298 | color: inherit; 299 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 300 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 301 | font-weight: bold; 302 | text-rendering: optimizeLegibility; 303 | font-size: 0.85028rem; 304 | line-height: 1.1; 305 | } 306 | h6 { 307 | margin-left: 0; 308 | margin-right: 0; 309 | margin-top: 0; 310 | padding-bottom: 0; 311 | padding-left: 0; 312 | padding-right: 0; 313 | padding-top: 0; 314 | margin-bottom: 1.45rem; 315 | color: inherit; 316 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 317 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 318 | font-weight: bold; 319 | text-rendering: optimizeLegibility; 320 | font-size: 0.78405rem; 321 | line-height: 1.1; 322 | } 323 | hgroup { 324 | margin-left: 0; 325 | margin-right: 0; 326 | margin-top: 0; 327 | padding-bottom: 0; 328 | padding-left: 0; 329 | padding-right: 0; 330 | padding-top: 0; 331 | margin-bottom: 1.45rem; 332 | } 333 | ul { 334 | margin-left: 1.45rem; 335 | margin-right: 0; 336 | margin-top: 0; 337 | padding-bottom: 0; 338 | padding-left: 0; 339 | padding-right: 0; 340 | padding-top: 0; 341 | margin-bottom: 1.45rem; 342 | list-style-position: outside; 343 | list-style-image: none; 344 | } 345 | ol { 346 | margin-left: 1.45rem; 347 | margin-right: 0; 348 | margin-top: 0; 349 | padding-bottom: 0; 350 | padding-left: 0; 351 | padding-right: 0; 352 | padding-top: 0; 353 | margin-bottom: 1.45rem; 354 | list-style-position: outside; 355 | list-style-image: none; 356 | } 357 | dl { 358 | margin-left: 0; 359 | margin-right: 0; 360 | margin-top: 0; 361 | padding-bottom: 0; 362 | padding-left: 0; 363 | padding-right: 0; 364 | padding-top: 0; 365 | margin-bottom: 1.45rem; 366 | } 367 | dd { 368 | margin-left: 0; 369 | margin-right: 0; 370 | margin-top: 0; 371 | padding-bottom: 0; 372 | padding-left: 0; 373 | padding-right: 0; 374 | padding-top: 0; 375 | margin-bottom: 1.45rem; 376 | } 377 | p { 378 | margin-left: 0; 379 | margin-right: 0; 380 | margin-top: 0; 381 | padding-bottom: 0; 382 | padding-left: 0; 383 | padding-right: 0; 384 | padding-top: 0; 385 | margin-bottom: 1.45rem; 386 | } 387 | figure { 388 | margin-left: 0; 389 | margin-right: 0; 390 | margin-top: 0; 391 | padding-bottom: 0; 392 | padding-left: 0; 393 | padding-right: 0; 394 | padding-top: 0; 395 | margin-bottom: 1.45rem; 396 | } 397 | pre { 398 | margin-left: 0; 399 | margin-right: 0; 400 | margin-top: 0; 401 | padding-bottom: 0; 402 | padding-left: 0; 403 | padding-right: 0; 404 | padding-top: 0; 405 | margin-bottom: 1.45rem; 406 | font-size: 0.85rem; 407 | line-height: 1.42; 408 | background: hsla(0, 0%, 0%, 0.04); 409 | border-radius: 3px; 410 | overflow: auto; 411 | word-wrap: normal; 412 | padding: 1.45rem; 413 | } 414 | table { 415 | margin-left: 0; 416 | margin-right: 0; 417 | margin-top: 0; 418 | padding-bottom: 0; 419 | padding-left: 0; 420 | padding-right: 0; 421 | padding-top: 0; 422 | margin-bottom: 1.45rem; 423 | font-size: 1rem; 424 | line-height: 1.45rem; 425 | border-collapse: collapse; 426 | width: 100%; 427 | } 428 | fieldset { 429 | margin-left: 0; 430 | margin-right: 0; 431 | margin-top: 0; 432 | padding-bottom: 0; 433 | padding-left: 0; 434 | padding-right: 0; 435 | padding-top: 0; 436 | margin-bottom: 1.45rem; 437 | } 438 | blockquote { 439 | margin-left: 1.45rem; 440 | margin-right: 1.45rem; 441 | margin-top: 0; 442 | padding-bottom: 0; 443 | padding-left: 0; 444 | padding-right: 0; 445 | padding-top: 0; 446 | margin-bottom: 1.45rem; 447 | } 448 | form { 449 | margin-left: 0; 450 | margin-right: 0; 451 | margin-top: 0; 452 | padding-bottom: 0; 453 | padding-left: 0; 454 | padding-right: 0; 455 | padding-top: 0; 456 | margin-bottom: 1.45rem; 457 | } 458 | noscript { 459 | margin-left: 0; 460 | margin-right: 0; 461 | margin-top: 0; 462 | padding-bottom: 0; 463 | padding-left: 0; 464 | padding-right: 0; 465 | padding-top: 0; 466 | margin-bottom: 1.45rem; 467 | } 468 | iframe { 469 | margin-left: 0; 470 | margin-right: 0; 471 | margin-top: 0; 472 | padding-bottom: 0; 473 | padding-left: 0; 474 | padding-right: 0; 475 | padding-top: 0; 476 | margin-bottom: 1.45rem; 477 | } 478 | hr { 479 | margin-left: 0; 480 | margin-right: 0; 481 | margin-top: 0; 482 | padding-bottom: 0; 483 | padding-left: 0; 484 | padding-right: 0; 485 | padding-top: 0; 486 | margin-bottom: calc(1.45rem - 1px); 487 | background: hsla(0, 0%, 0%, 0.2); 488 | border: none; 489 | height: 1px; 490 | } 491 | address { 492 | margin-left: 0; 493 | margin-right: 0; 494 | margin-top: 0; 495 | padding-bottom: 0; 496 | padding-left: 0; 497 | padding-right: 0; 498 | padding-top: 0; 499 | margin-bottom: 1.45rem; 500 | } 501 | b { 502 | font-weight: bold; 503 | } 504 | strong { 505 | font-weight: bold; 506 | } 507 | dt { 508 | font-weight: bold; 509 | } 510 | th { 511 | font-weight: bold; 512 | } 513 | li { 514 | margin-bottom: calc(1.45rem / 2); 515 | } 516 | ol li { 517 | padding-left: 0; 518 | } 519 | ul li { 520 | padding-left: 0; 521 | } 522 | li > ol { 523 | margin-left: 1.45rem; 524 | margin-bottom: calc(1.45rem / 2); 525 | margin-top: calc(1.45rem / 2); 526 | } 527 | li > ul { 528 | margin-left: 1.45rem; 529 | margin-bottom: calc(1.45rem / 2); 530 | margin-top: calc(1.45rem / 2); 531 | } 532 | blockquote *:last-child { 533 | margin-bottom: 0; 534 | } 535 | li *:last-child { 536 | margin-bottom: 0; 537 | } 538 | p *:last-child { 539 | margin-bottom: 0; 540 | } 541 | li > p { 542 | margin-bottom: calc(1.45rem / 2); 543 | } 544 | code { 545 | font-size: 0.85rem; 546 | line-height: 1.45rem; 547 | } 548 | kbd { 549 | font-size: 0.85rem; 550 | line-height: 1.45rem; 551 | } 552 | samp { 553 | font-size: 0.85rem; 554 | line-height: 1.45rem; 555 | } 556 | abbr { 557 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 558 | cursor: help; 559 | } 560 | acronym { 561 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 562 | cursor: help; 563 | } 564 | abbr[title] { 565 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 566 | cursor: help; 567 | text-decoration: none; 568 | } 569 | thead { 570 | text-align: left; 571 | } 572 | td, 573 | th { 574 | text-align: left; 575 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 576 | font-feature-settings: "tnum"; 577 | -moz-font-feature-settings: "tnum"; 578 | -ms-font-feature-settings: "tnum"; 579 | -webkit-font-feature-settings: "tnum"; 580 | padding-left: 0.96667rem; 581 | padding-right: 0.96667rem; 582 | padding-top: 0.725rem; 583 | padding-bottom: calc(0.725rem - 1px); 584 | } 585 | th:first-child, 586 | td:first-child { 587 | padding-left: 0; 588 | } 589 | th:last-child, 590 | td:last-child { 591 | padding-right: 0; 592 | } 593 | tt, 594 | code { 595 | background-color: hsla(0, 0%, 0%, 0.04); 596 | border-radius: 3px; 597 | font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", 598 | "Liberation Mono", Menlo, Courier, monospace; 599 | padding: 0; 600 | padding-top: 0.2em; 601 | padding-bottom: 0.2em; 602 | } 603 | pre code { 604 | background: none; 605 | line-height: 1.42; 606 | } 607 | code:before, 608 | code:after, 609 | tt:before, 610 | tt:after { 611 | letter-spacing: -0.2em; 612 | content: " "; 613 | } 614 | pre code:before, 615 | pre code:after, 616 | pre tt:before, 617 | pre tt:after { 618 | content: ""; 619 | } 620 | @media only screen and (max-width: 480px) { 621 | html { 622 | font-size: 100%; 623 | } 624 | } 625 | -------------------------------------------------------------------------------- /src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Helmet from 'react-helmet' 4 | import { StaticQuery, graphql } from 'gatsby' 5 | 6 | import Header from './header' 7 | import './layout.css' 8 | 9 | const Layout = ({ children, data }) => ( 10 | ( 21 | <> 22 | 29 | 30 | 31 |
32 |
40 | {children} 41 |
42 | 43 | )} 44 | /> 45 | ) 46 | 47 | Layout.propTypes = { 48 | children: PropTypes.node.isRequired, 49 | } 50 | 51 | export default Layout 52 | -------------------------------------------------------------------------------- /src/images/amplify-console.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-amplify-gatsby-auth/6ab2c9a3f5346774d00da6ba77eeb49353c519c0/src/images/amplify-console.gif -------------------------------------------------------------------------------- /src/images/gatby-auth.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-amplify-gatsby-auth/6ab2c9a3f5346774d00da6ba77eeb49353c519c0/src/images/gatby-auth.gif -------------------------------------------------------------------------------- /src/images/gatsby-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-amplify-gatsby-auth/6ab2c9a3f5346774d00da6ba77eeb49353c519c0/src/images/gatsby-icon.png -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from '../components/layout' 3 | 4 | const NotFoundPage = () => ( 5 | 6 |

NOT FOUND

7 |

You just hit a route that doesn't exist... the sadness.

8 |
9 | ) 10 | 11 | export default NotFoundPage 12 | -------------------------------------------------------------------------------- /src/pages/app.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Router } from "@reach/router" 3 | import Layout from "../components/layout" 4 | import Details from "../components/Details" 5 | import Home from "../components/Home" 6 | import Login from "../components/Login" 7 | import SignUp from "../components/SignUp" 8 | import PrivateRoute from "../components/PrivateRoute" 9 | 10 | 11 | const App = () => ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | 22 | export default App -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 3 | 4 | import Layout from '../components/layout' 5 | 6 | import Amplify from 'aws-amplify' 7 | import config from '../aws-exports' 8 | Amplify.configure(config) 9 | 10 | const IndexPage = () => ( 11 | 12 |

Hi people

13 |

Welcome to your new Gatsby site with multi-user authentication powered by AWS Amplify

14 |

Create a new account: Sign Up

15 | Sign In
16 | Home
17 | Your profile 18 |
19 | ) 20 | 21 | export default IndexPage 22 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | const isBrowser = typeof window !== `undefined` 2 | 3 | export const setUser = user => (window.localStorage.gatsbyUser = JSON.stringify(user)) 4 | 5 | const getUser = () => { 6 | if (window.localStorage.gatsbyUser) { 7 | let user = JSON.parse(window.localStorage.gatsbyUser) 8 | return user ? user : {} 9 | } 10 | return {} 11 | } 12 | 13 | export const isLoggedIn = () => { 14 | if (!isBrowser) return false 15 | 16 | const user = getUser() 17 | if (user) return !!user.username 18 | } 19 | 20 | export const getCurrentUser = () => isBrowser && getUser() 21 | 22 | export const logout = callback => { 23 | if (!isBrowser) return 24 | setUser({}) 25 | callback() 26 | } 27 | --------------------------------------------------------------------------------