added login

This commit is contained in:
2019-10-14 13:07:28 -05:00
parent 87d50bbc6c
commit 1122a37316
62 changed files with 3462 additions and 30 deletions

View File

@@ -0,0 +1,366 @@
AWSTemplateFormatVersion: 2010-09-09
Parameters:
env:
Type: String
authRoleArn:
Type: String
unauthRoleArn:
Type: String
identityPoolName:
Type: String
allowUnauthenticatedIdentities:
Type: String
resourceNameTruncated:
Type: String
userPoolName:
Type: String
autoVerifiedAttributes:
Type: CommaDelimitedList
mfaConfiguration:
Type: String
mfaTypes:
Type: CommaDelimitedList
smsAuthenticationMessage:
Type: String
smsVerificationMessage:
Type: String
emailVerificationSubject:
Type: String
emailVerificationMessage:
Type: String
defaultPasswordPolicy:
Type: String
passwordPolicyMinLength:
Type: Number
passwordPolicyCharacters:
Type: CommaDelimitedList
requiredAttributes:
Type: CommaDelimitedList
userpoolClientGenerateSecret:
Type: String
userpoolClientRefreshTokenValidity:
Type: Number
userpoolClientWriteAttributes:
Type: CommaDelimitedList
userpoolClientReadAttributes:
Type: CommaDelimitedList
userpoolClientLambdaRole:
Type: String
userpoolClientSetAttributes:
Type: String
resourceName:
Type: String
authSelections:
Type: String
useDefault:
Type: String
usernameAttributes:
Type: CommaDelimitedList
dependsOn:
Type: CommaDelimitedList
Conditions:
ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ]
Resources:
# BEGIN SNS ROLE RESOURCE
SNSRole:
# Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process
Type: AWS::IAM::Role
Properties:
RoleName: !If [ShouldNotCreateEnvResources, 'hpiotw78e5977f_sns-role', !Join ['',['hpiotw78e5977f_sns-role', '-', !Ref env]]]
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: ""
Effect: "Allow"
Principal:
Service: "cognito-idp.amazonaws.com"
Action:
- "sts:AssumeRole"
Condition:
StringEquals:
sts:ExternalId: hpiotw78e5977f_role_external_id
Policies:
-
PolicyName: hpiotw78e5977f-sns-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "sns:Publish"
Resource: "*"
# BEGIN USER POOL RESOURCES
UserPool:
# Created upon user selection
# Depends on SNS Role for Arn if MFA is enabled
Type: AWS::Cognito::UserPool
UpdateReplacePolicy: Retain
Properties:
UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]]
Schema:
-
Name: email
Required: true
Mutable: true
AutoVerifiedAttributes: !Ref autoVerifiedAttributes
EmailVerificationMessage: !Ref emailVerificationMessage
EmailVerificationSubject: !Ref emailVerificationSubject
Policies:
PasswordPolicy:
MinimumLength: !Ref passwordPolicyMinLength
RequireLowercase: false
RequireNumbers: false
RequireSymbols: false
RequireUppercase: false
UsernameAttributes: !Ref usernameAttributes
MfaConfiguration: !Ref mfaConfiguration
SmsVerificationMessage: !Ref smsVerificationMessage
SmsConfiguration:
SnsCallerArn: !GetAtt SNSRole.Arn
ExternalId: hpiotw78e5977f_role_external_id
UserPoolClientWeb:
# Created provide application access to user pool
# Depends on UserPool for ID reference
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: hpiotw78e5977f_app_clientWeb
RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity
UserPoolId: !Ref UserPool
DependsOn: UserPool
UserPoolClient:
# Created provide application access to user pool
# Depends on UserPool for ID reference
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: hpiotw78e5977f_app_client
GenerateSecret: !Ref userpoolClientGenerateSecret
RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity
UserPoolId: !Ref UserPool
DependsOn: UserPool
# BEGIN USER POOL LAMBDA RESOURCES
UserPoolClientRole:
# Created to execute Lambda which gets userpool app client config values
Type: 'AWS::IAM::Role'
Properties:
RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]]
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
DependsOn: UserPoolClient
UserPoolClientLambda:
# Lambda which gets userpool app client config values
# Depends on UserPool for id
# Depends on UserPoolClientRole for role ARN
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: !Join
- |+
- - 'const response = require(''cfn-response'');'
- 'const aws = require(''aws-sdk'');'
- 'const identity = new aws.CognitoIdentityServiceProvider();'
- 'exports.handler = (event, context, callback) => {'
- ' if (event.RequestType == ''Delete'') { '
- ' response.send(event, context, response.SUCCESS, {})'
- ' }'
- ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {'
- ' const params = {'
- ' ClientId: event.ResourceProperties.clientId,'
- ' UserPoolId: event.ResourceProperties.userpoolId'
- ' };'
- ' identity.describeUserPoolClient(params).promise()'
- ' .then((res) => {'
- ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});'
- ' })'
- ' .catch((err) => {'
- ' response.send(event, context, response.FAILED, {err});'
- ' });'
- ' }'
- '};'
Handler: index.handler
Runtime: nodejs8.10
Timeout: '300'
Role: !GetAtt
- UserPoolClientRole
- Arn
DependsOn: UserPoolClientRole
UserPoolClientLambdaPolicy:
# Sets userpool policy for the role that executes the Userpool Client Lambda
# Depends on UserPool for Arn
# Marked as depending on UserPoolClientRole for easier to understand CFN sequencing
Type: 'AWS::IAM::Policy'
Properties:
PolicyName: hpiotw78e5977f_userpoolclient_lambda_iam_policy
Roles:
- !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]]
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'cognito-idp:DescribeUserPoolClient'
Resource: !GetAtt UserPool.Arn
DependsOn: UserPoolClientLambda
UserPoolClientLogPolicy:
# Sets log policy for the role that executes the Userpool Client Lambda
# Depends on UserPool for Arn
# Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing
Type: 'AWS::IAM::Policy'
Properties:
PolicyName: hpiotw78e5977f_userpoolclient_lambda_log_policy
Roles:
- !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]]
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: !Sub
- arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*
- { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda}
DependsOn: UserPoolClientLambdaPolicy
UserPoolClientInputs:
# Values passed to Userpool client Lambda
# Depends on UserPool for Id
# Depends on UserPoolClient for Id
# Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing
Type: 'Custom::LambdaCallout'
Properties:
ServiceToken: !GetAtt UserPoolClientLambda.Arn
clientId: !Ref UserPoolClient
userpoolId: !Ref UserPool
DependsOn: UserPoolClientLogPolicy
# BEGIN IDENTITY POOL RESOURCES
IdentityPool:
# Always created
Type: AWS::Cognito::IdentityPool
Properties:
IdentityPoolName: !If [ShouldNotCreateEnvResources, 'hpiotwebapp78e5977f_identitypool_78e5977f', !Join ['',['hpiotwebapp78e5977f_identitypool_78e5977f', '__', !Ref env]]]
CognitoIdentityProviders:
- ClientId: !Ref UserPoolClient
ProviderName: !Sub
- cognito-idp.${region}.amazonaws.com/${client}
- { region: !Ref "AWS::Region", client: !Ref UserPool}
- ClientId: !Ref UserPoolClientWeb
ProviderName: !Sub
- cognito-idp.${region}.amazonaws.com/${client}
- { region: !Ref "AWS::Region", client: !Ref UserPool}
AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities
DependsOn: UserPoolClientInputs
IdentityPoolRoleMap:
# Created to map Auth and Unauth roles to the identity pool
# Depends on Identity Pool for ID ref
Type: AWS::Cognito::IdentityPoolRoleAttachment
Properties:
IdentityPoolId: !Ref IdentityPool
Roles:
unauthenticated: !Ref unauthRoleArn
authenticated: !Ref authRoleArn
DependsOn: IdentityPool
Outputs :
IdentityPoolId:
Value: !Ref 'IdentityPool'
Description: Id for the identity pool
IdentityPoolName:
Value: !GetAtt IdentityPool.Name
UserPoolId:
Value: !Ref 'UserPool'
Description: Id for the user pool
UserPoolName:
Value: !Ref userPoolName
AppClientIDWeb:
Value: !Ref 'UserPoolClientWeb'
Description: The user pool app client id for web
AppClientID:
Value: !Ref 'UserPoolClient'
Description: The user pool app client id
AppClientSecret:
Value: !GetAtt UserPoolClientInputs.appSecret

View File

@@ -0,0 +1,52 @@
{
"identityPoolName": "hpiotwebapp78e5977f_identitypool_78e5977f",
"allowUnauthenticatedIdentities": false,
"resourceNameTruncated": "hpiotw78e5977f",
"userPoolName": "hpiotwebapp78e5977f_userpool_78e5977f",
"autoVerifiedAttributes": [
"email"
],
"mfaConfiguration": "OFF",
"mfaTypes": [
"SMS Text Message"
],
"smsAuthenticationMessage": "Your authentication code is {####}",
"smsVerificationMessage": "Your verification code is {####}",
"emailVerificationSubject": "Your verification code",
"emailVerificationMessage": "Your verification code is {####}",
"defaultPasswordPolicy": false,
"passwordPolicyMinLength": 8,
"passwordPolicyCharacters": [],
"requiredAttributes": [
"email"
],
"userpoolClientGenerateSecret": true,
"userpoolClientRefreshTokenValidity": 30,
"userpoolClientWriteAttributes": [
"email"
],
"userpoolClientReadAttributes": [
"email"
],
"userpoolClientLambdaRole": "hpiotw78e5977f_userpoolclient_lambda_role",
"userpoolClientSetAttributes": false,
"resourceName": "hpiotwebapp78e5977f",
"authSelections": "identityPoolAndUserPool",
"authRoleArn": {
"Fn::GetAtt": [
"AuthRole",
"Arn"
]
},
"unauthRoleArn": {
"Fn::GetAtt": [
"UnauthRole",
"Arn"
]
},
"useDefault": "default",
"usernameAttributes": [
"email, phone_number"
],
"dependsOn": []
}

View File

@@ -4,5 +4,12 @@
"service": "S3AndCloudFront",
"providerPlugin": "awscloudformation"
}
},
"auth": {
"hpiotwebapp78e5977f": {
"service": "Cognito",
"providerPlugin": "awscloudformation",
"dependsOn": []
}
}
}

View File

@@ -10,5 +10,22 @@
"StackName": "hpiotwebapp-dev-20191011145158",
"StackId": "arn:aws:cloudformation:us-east-1:860246592755:stack/hpiotwebapp-dev-20191011145158/95829790-ec60-11e9-a33f-1207b4ca758c"
}
},
"devs": {
"awscloudformation": {
"AuthRoleName": "hpiotwebapp-devs-20191011153744-authRole",
"UnauthRoleArn": "arn:aws:iam::860246592755:role/hpiotwebapp-devs-20191011153744-unauthRole",
"AuthRoleArn": "arn:aws:iam::860246592755:role/hpiotwebapp-devs-20191011153744-authRole",
"Region": "us-east-1",
"DeploymentBucketName": "hpiotwebapp-devs-20191011153744-deployment",
"UnauthRoleName": "hpiotwebapp-devs-20191011153744-unauthRole",
"StackName": "hpiotwebapp-devs-20191011153744",
"StackId": "arn:aws:cloudformation:us-east-1:860246592755:stack/hpiotwebapp-devs-20191011153744/fa4c3b30-ec66-11e9-8123-0e639cbb91d4"
},
"categories": {
"auth": {
"hpiotwebapp78e5977f": {}
}
}
}
}

343
package-lock.json generated
View File

@@ -1096,6 +1096,167 @@
"tslib": "^1.9.0"
}
},
"@aws-amplify/analytics": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/analytics/-/analytics-1.3.2.tgz",
"integrity": "sha512-RzSBZ0lIHkOnP88pRc0hjh3z6A+kSfHCTyloUSXrpMgiRMi+XvaWNqD+uH0Au5x9X3xKIZb3AOrsPX9pyCTXeA==",
"requires": {
"@aws-amplify/cache": "^1.1.2",
"@aws-amplify/core": "^1.2.2",
"uuid": "^3.2.1"
}
},
"@aws-amplify/api": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/api/-/api-1.2.2.tgz",
"integrity": "sha512-jpMf6HQO36oyZ0WVi6wIDK3EtQCjMLDSwHrLKzFGM83mtcDdC8XBIaqElWG6nKgFE8MKOmG8I9YSinTl3HpPqg==",
"requires": {
"@aws-amplify/auth": "^1.4.2",
"@aws-amplify/cache": "^1.1.2",
"@aws-amplify/core": "^1.2.2",
"axios": "^0.19.0",
"graphql": "0.13.0",
"uuid": "^3.2.1",
"zen-observable": "^0.8.6"
}
},
"@aws-amplify/auth": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/auth/-/auth-1.4.2.tgz",
"integrity": "sha512-X82t3zxAMjRQjwZ8M4xT+96DhRrb6H8FoZZDp1Pxr4p2KSKKMkEkS2AJCenLNQ37g6CUBLlltQ++iGXDLabskw==",
"requires": {
"@aws-amplify/cache": "^1.1.2",
"@aws-amplify/core": "^1.2.2",
"amazon-cognito-identity-js": "^3.1.2",
"crypto-js": "^3.1.9-1"
}
},
"@aws-amplify/cache": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/cache/-/cache-1.1.2.tgz",
"integrity": "sha512-ZK/IANuqQ0rrgI2soN6yR6YhwvftHsnmovMXzeQG+tHdnK2n3RxUciku6+Ey9k4usCD67ZFbXbb+V4KWqBf9Yw==",
"requires": {
"@aws-amplify/core": "^1.2.2"
}
},
"@aws-amplify/core": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-1.2.2.tgz",
"integrity": "sha512-mczpQr+wBP6WxL0EK9yue4p+5mQVm+u8IGiyB9HN5ibMLs1t4hPcbS/D/QzjeWSfZ16oMPvZ3Sej3Z4J9zjv9Q==",
"requires": {
"aws-sdk": "2.518.0",
"url": "^0.11.0"
}
},
"@aws-amplify/interactions": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/interactions/-/interactions-1.1.2.tgz",
"integrity": "sha512-07kHROAHk2IF87+3+yWjbagPiQLgDbas3NgqQXg0gxwd/Vt/jsUGHiJop64g4prO7NZ2ut+nx27bf0n3cxPNGA==",
"requires": {
"@aws-amplify/core": "^1.2.2"
}
},
"@aws-amplify/predictions": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/predictions/-/predictions-1.1.2.tgz",
"integrity": "sha512-vCWy8U4cWbL47C2BxqkeX2JUfH7i9XyVPgFpVorkDKpR4yH4Rt4zyHXzNXT2rP4iFAZwJCVXa1L5QA/u3AxPJg==",
"requires": {
"@aws-amplify/core": "^1.2.2",
"@aws-amplify/storage": "^1.2.2",
"@aws-sdk/eventstream-marshaller": "^0.1.0-preview.2",
"@aws-sdk/util-utf8-node": "^0.1.0-preview.1",
"uuid": "^3.2.1"
}
},
"@aws-amplify/pubsub": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/pubsub/-/pubsub-1.2.2.tgz",
"integrity": "sha512-nyWnf26ppu2VELwtTF6vF8UaDB/SiWau7atwu4MrGBSuLi3GBd5bhvLTPgDWV91YWA8rcc4Q17Cd18LE8guw3Q==",
"requires": {
"@aws-amplify/core": "^1.2.2",
"uuid": "^3.2.1",
"zen-observable": "^0.8.6"
}
},
"@aws-amplify/storage": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/storage/-/storage-1.2.2.tgz",
"integrity": "sha512-ZZgLRA2yKIfvFfOi2EKBSHE3P2kSsvRu9OX/9uLiHzNfZjF/LNebhpadh3+jMfLvF7obS/AWZYK0wzhZtcgw1Q==",
"requires": {
"@aws-amplify/core": "^1.2.2"
}
},
"@aws-amplify/ui": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/ui/-/ui-1.1.2.tgz",
"integrity": "sha512-K9wX+yYY0jp+GEDnhT2hVEB5JuLm0a5u134qC+Neg3rD5u71oOUd+jCJq48iuE2hjaNxMMOA0eGwFmsltr7h6w=="
},
"@aws-amplify/xr": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@aws-amplify/xr/-/xr-0.2.2.tgz",
"integrity": "sha512-DSyD12o0rcbvWj0DMEss50HRS3X8xja2lNpNrO8MvjdN6Y8zS+a3GBTM9vx+/kCgp2zRRmC4Ei/ZeD/Rt5N5PA==",
"requires": {
"@aws-amplify/core": "^1.2.2"
}
},
"@aws-crypto/crc32": {
"version": "0.1.0-preview.1",
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-0.1.0-preview.1.tgz",
"integrity": "sha512-GPxlpx1ezlWAYygSfyGIsQ2/imDJgOYnpqwAFlU3H4KacIqX3LHpE5/Ps+s7nxiYtEFR9GvQhsN7VNDF5/FbHg==",
"requires": {
"tslib": "^1.9.3"
}
},
"@aws-sdk/eventstream-marshaller": {
"version": "0.1.0-preview.2",
"resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-marshaller/-/eventstream-marshaller-0.1.0-preview.2.tgz",
"integrity": "sha512-StNivqLMGk+6Blp7eBYgLvidD9HEhthzNz7dBBAQPELx3Nd3imodzSvckDw5ZkuWt6ViP+aAl8HgQvJmD71M5Q==",
"requires": {
"@aws-crypto/crc32": "^0.1.0-preview.1",
"@aws-sdk/types": "^0.1.0-preview.1",
"@aws-sdk/util-hex-encoding": "^0.1.0-preview.1",
"tslib": "^1.8.0"
}
},
"@aws-sdk/is-array-buffer": {
"version": "0.1.0-preview.1",
"resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-0.1.0-preview.1.tgz",
"integrity": "sha512-9Qrr9w6sNX19N0eO7JBYjp86OPcOyjDPe580L5ISDKo7XfuzK20IC2TeGTZ77okhRTsm8rF5UgP9scBu59jwoA==",
"requires": {
"tslib": "^1.8.0"
}
},
"@aws-sdk/types": {
"version": "0.1.0-preview.1",
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-0.1.0-preview.1.tgz",
"integrity": "sha512-CcZpxyN2G0I7+Jyj0om3LafYX7d30JWJAAQ+53Ysjau7jyL/xLMMkLZgniQPV8BMV7uKLXyf4hwu8JSs0Ejb+w=="
},
"@aws-sdk/util-buffer-from": {
"version": "0.1.0-preview.1",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-0.1.0-preview.1.tgz",
"integrity": "sha512-i46iuFQA05+92L/epK7befgoxw6DM38LnaHjHNxRoJeIYUllZvpJst74FRCJ5UvVOaMDvHO3woWG+dY8/KVABw==",
"requires": {
"@aws-sdk/is-array-buffer": "^0.1.0-preview.1",
"tslib": "^1.8.0"
}
},
"@aws-sdk/util-hex-encoding": {
"version": "0.1.0-preview.1",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-0.1.0-preview.1.tgz",
"integrity": "sha512-97ZMVcJpIXwOQN2RntPimav6G5iod/QHEbqGrmaECXyjXzSrexKHRYjpQAtmJ7geZTMsofoRSdj3qZCymjn7Uw==",
"requires": {
"tslib": "^1.8.0"
}
},
"@aws-sdk/util-utf8-node": {
"version": "0.1.0-preview.1",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-0.1.0-preview.1.tgz",
"integrity": "sha512-PSUsSJ0nnMPS389f0R3kIVR0BElnEb22Ofj40iO5HCtw9gZ1ot+enFdbOmW4m1e5+ED9U/Hqxqc7QhFWWF4NUQ==",
"requires": {
"@aws-sdk/util-buffer-from": "^0.1.0-preview.1",
"tslib": "^1.8.0"
}
},
"@babel/code-frame": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
@@ -1648,6 +1809,16 @@
"integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
"dev": true
},
"amazon-cognito-identity-js": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/amazon-cognito-identity-js/-/amazon-cognito-identity-js-3.1.2.tgz",
"integrity": "sha512-NQ/MxGvzYAauHNWjWu/OMB0LtH5w3D/BKeYP9royQwT6+pR4UJifqFaETh2GQUefROKkv0rqEuvkvT/Wx2RA+Q==",
"requires": {
"buffer": "4.9.1",
"crypto-js": "^3.1.9-1",
"js-cookie": "^2.1.4"
}
},
"amdefine": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
@@ -1913,6 +2084,90 @@
"postcss-value-parser": "^4.0.0"
}
},
"aws-amplify": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/aws-amplify/-/aws-amplify-1.2.2.tgz",
"integrity": "sha512-HJ+TRwO/zow+F5OBiNaPo4xgCSgR7S4LFJTn9pMB7SqDRm0l8hKFGnlJ2nU7cBxFUf2ot1ZpdIS4l3d6RBKesw==",
"requires": {
"@aws-amplify/analytics": "^1.3.2",
"@aws-amplify/api": "^1.2.2",
"@aws-amplify/auth": "^1.4.2",
"@aws-amplify/cache": "^1.1.2",
"@aws-amplify/core": "^1.2.2",
"@aws-amplify/interactions": "^1.1.2",
"@aws-amplify/predictions": "^1.1.2",
"@aws-amplify/pubsub": "^1.2.2",
"@aws-amplify/storage": "^1.2.2",
"@aws-amplify/ui": "^1.1.2",
"@aws-amplify/xr": "^0.2.2"
}
},
"aws-sdk": {
"version": "2.518.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.518.0.tgz",
"integrity": "sha512-hwtKKf93TFyd3qugDW54ElpkUXhPe+ArPIHadre6IAFjCJiv08L8DaZKLRyclDnKfTavKe+f/PhdSEYo1QUHiA==",
"requires": {
"buffer": "4.9.1",
"events": "1.1.1",
"ieee754": "1.1.8",
"jmespath": "0.15.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"uuid": "3.3.2",
"xml2js": "0.4.19"
},
"dependencies": {
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
},
"ieee754": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
},
"punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
},
"sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
"requires": {
"punycode": "1.3.2",
"querystring": "0.2.0"
}
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
},
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
}
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
}
}
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@@ -1925,6 +2180,38 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
"integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
"requires": {
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
},
"is-buffer": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
}
}
},
"axobject-query": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
@@ -2147,8 +2434,7 @@
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
"dev": true
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"base64id": {
"version": "1.0.0",
@@ -2385,7 +2671,6 @@
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"dev": true,
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
@@ -3096,6 +3381,11 @@
"randomfill": "^1.0.3"
}
},
"crypto-js": {
"version": "3.1.9-1",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz",
"integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg="
},
"css-parse": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz",
@@ -4388,6 +4678,14 @@
"integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==",
"dev": true
},
"graphql": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-0.13.0.tgz",
"integrity": "sha512-WlO+ZJT9aY3YrBT+H5Kk+eVb3OVVehB9iRD/xqeHdmrrn4AFl5FIcOpfHz/vnBr6Y6JthGMlnFqU8XRnDjSR7A==",
"requires": {
"iterall": "1.1.x"
}
},
"hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
@@ -4743,8 +5041,7 @@
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
"dev": true
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"iferr": {
"version": "0.1.5",
@@ -5175,8 +5472,7 @@
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isbinaryfile": {
"version": "3.0.3",
@@ -5413,6 +5709,11 @@
"handlebars": "^4.1.2"
}
},
"iterall": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.4.tgz",
"integrity": "sha512-eaDsM/PY8D/X5mYQhecVc5/9xvSHED7yPON+ffQroBeTuqUVm7dfphMkK8NksXuImqZlVRoKtrNfMIVCYIqaUQ=="
},
"jasmine": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz",
@@ -5453,6 +5754,16 @@
"integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=",
"dev": true
},
"jmespath": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
},
"js-cookie": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
@@ -6962,8 +7273,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"multicast-dns": {
"version": "6.2.3",
@@ -8244,8 +8554,7 @@
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"dev": true
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
"querystring-es3": {
"version": "0.2.1",
@@ -10245,7 +10554,6 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
"dev": true,
"requires": {
"punycode": "1.3.2",
"querystring": "0.2.0"
@@ -10254,8 +10562,7 @@
"punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
"dev": true
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
}
}
},
@@ -10354,8 +10661,7 @@
"uuid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
"dev": true
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
},
"validate-npm-package-license": {
"version": "3.0.4",
@@ -12192,6 +12498,11 @@
"integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
"dev": true
},
"zen-observable": {
"version": "0.8.14",
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.14.tgz",
"integrity": "sha512-kQz39uonEjEESwh+qCi83kcC3rZJGh4mrZW7xjkSQYXkq//JZHTtKo+6yuVloTgMtzsIWOJrjIrKvk/dqm0L5g=="
},
"zone.js": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.9.1.tgz",

View File

@@ -23,6 +23,7 @@
"@angular/pwa": "^0.803.9",
"@angular/router": "~8.2.0",
"@angular/service-worker": "~8.2.0",
"aws-amplify": "^1.2.2",
"hammerjs": "^2.0.8",
"rxjs": "~6.4.0",
"tslib": "^1.10.0",

View File

@@ -1,11 +1,38 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AuthComponent } from './auth/auth.component';
import { SignInComponent } from './auth/sign-in/sign-in.component';
import { SignUpComponent } from './auth/sign-up/sign-up.component';
import { UnauthGuard } from './auth/unauth.guard';
import { AuthGuard } from './auth/auth.guard';
import { ConfirmCodeComponent } from './auth/confirm-code/confirm-code.component';
import { ProfileComponent } from './auth/profile/profile.component';
const routes: Routes = [
{ path: '', component: HomeComponent}
];
{ path: 'auth', component: AuthComponent, children: [
{
path: 'signin',
component: SignInComponent,
canActivate: [UnauthGuard]
},
{
path: 'signup',
component: SignUpComponent,
canActivate: [UnauthGuard]
},
{
path: 'confirm',
component: ConfirmCodeComponent,
canActivate: [UnauthGuard]
},
{
path: 'profile',
component: ProfileComponent,
canActivate: [AuthGuard]
}
]},
{ path: '', component: HomeComponent, canActivate: [AuthGuard] }];
@NgModule({
imports: [RouterModule.forRoot(routes)],

View File

@@ -8,16 +8,16 @@ import { MatSidenav } from '@angular/material';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'Material PWA';
title = 'Henry Pump SCADA';
mobileQuery: MediaQueryList;
nav = [
{
'title': 'Home',
'path': '/'
title: 'Home',
path: '/'
},
{
'title': 'This is a second link',
'path': '/auth'
title: 'Account Settings',
path: '/auth'
}
];
private mobileQueryListener: () => void;

View File

@@ -8,20 +8,42 @@ import { MaterialModule } from './material/material.module';
import { HomeComponent } from './home/home.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { AuthComponent } from './auth/auth.component';
import { LoaderComponent } from './loader/loader.component';
import { CountryCodeSelectComponent } from './auth/country-code-select/country-code-select.component';
import { FilterPipe } from './auth/country-code-select/filter.pipe';
import { SignInComponent } from './auth/sign-in/sign-in.component';
import { SignUpComponent } from './auth/sign-up/sign-up.component';
import { ConfirmCodeComponent } from './auth/confirm-code/confirm-code.component';
import { ProfileComponent } from './auth/profile/profile.component';
import { AvatarComponent } from './auth/profile/avatar/avatar.component';
@NgModule({
declarations: [
AppComponent,
HomeComponent
HomeComponent,
AuthComponent,
LoaderComponent,
CountryCodeSelectComponent,
FilterPipe,
SignInComponent,
SignUpComponent,
ConfirmCodeComponent,
ProfileComponent,
AvatarComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MaterialModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
ReactiveFormsModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
bootstrap: [AppComponent],
entryComponents: [LoaderComponent, CountryCodeSelectComponent]
})
export class AppModule { }

View File

@@ -0,0 +1,2 @@
<router-outlet></router-outlet>

View File

@@ -0,0 +1,9 @@
:host {
height: 100%;
}
.auth-btn-signup {
position: absolute;
bottom: 1em;
right: 2em;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AuthComponent } from './auth.component';
describe('AuthComponent', () => {
let component: AuthComponent;
let fixture: ComponentFixture<AuthComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AuthComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AuthComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,37 @@
import { Component, OnInit, NgZone } from '@angular/core';
import Auth from '@aws-amplify/auth';
import { Router } from '@angular/router';
import { Hub } from '@aws-amplify/core';
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.scss']
})
export class AuthComponent implements OnInit {
constructor( private _router: Router, private _zone: NgZone ) { }
ngOnInit() {
Hub.listen('auth', ({ payload: { event, data } }) => {
switch (event) {
case 'signIn':
this._zone.run(() => {
this._router.navigate(['/']);
});
break;
case 'signOut':
this._zone.run(() => {
this._router.navigate(['/auth/signin']);
});
break;
}
});
Auth.currentAuthenticatedUser()
.then(() => {
this._router.navigate(['auth/profile']);
})
.catch(() => {});
}
}

View File

@@ -0,0 +1,15 @@
import { TestBed, async, inject } from '@angular/core/testing';
import { AuthGuard } from './auth.guard';
describe('AuthGuard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthGuard]
});
});
it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
expect(guard).toBeTruthy();
}));
});

View File

@@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import Auth from '@aws-amplify/auth';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor( private _router: Router ) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return Auth.currentAuthenticatedUser().then(() => { return true; })
.catch(() => {
this._router.navigate(['auth/signin']);
return false;
});
}
}

View File

@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: AuthService = TestBed.get(AuthService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,72 @@
import { Injectable } from '@angular/core';
import Auth, { CognitoHostedUIIdentityProvider } from '@aws-amplify/auth';
import { Hub, ICredentials } from '@aws-amplify/core';
import { Subject, Observable } from 'rxjs';
import { CognitoUser } from 'amazon-cognito-identity-js';
export interface NewUser {
email: string;
phone: string;
password: string;
firstName: string;
lastName: string;
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
public loggedIn: boolean;
private _authState: Subject<CognitoUser|any> = new Subject<CognitoUser|any>();
authState: Observable<CognitoUser|any> = this._authState.asObservable();
public static SIGN_IN = 'signIn';
public static SIGN_OUT = 'signOut';
public static FACEBOOK = CognitoHostedUIIdentityProvider.Facebook;
public static GOOGLE = CognitoHostedUIIdentityProvider.Google;
constructor() {
Hub.listen('auth', (data) => {
const { channel, payload } = data;
if (channel === 'auth') {
this._authState.next(payload.event);
}
});
}
signUp(user: NewUser): Promise<CognitoUser|any> {
return Auth.signUp({
"username": user.email,
"password": user.password,
"attributes": {
"email": user.email,
"given_name": user.firstName,
"family_name": user.lastName,
"phone_number": user.phone
}
});
}
signIn(username: string, password: string):Promise<CognitoUser|any> {
return new Promise((resolve, reject) => {
Auth.signIn(username, password)
.then((user: CognitoUser|any) => {
this.loggedIn = true;
resolve(user);
}).catch((error: any) => reject(error));
});
}
signOut(): Promise<any> {
return Auth.signOut()
.then(() => this.loggedIn = false);
}
socialSignIn(provider:CognitoHostedUIIdentityProvider): Promise<ICredentials> {
return Auth.federatedSignIn({
'provider': provider
});
}
}

View File

@@ -0,0 +1,12 @@
<form [formGroup]="confirmForm">
<h2>Confirm your email address</h2>
<mat-form-field>
<input matInput placeholder="Email" type="email" formControlName="email">
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Confirmation Code" type="text" formControlName="code" required>
<mat-hint *ngIf="!codeInput.value">Enter the confirmation code that was emailed to you</mat-hint>
</mat-form-field>
<button mat-raised-button color="primary" (click)="confirmCode()">Confirm</button>
<p>Didn't receive a code? <a href="javascript:void(null)" (click)="sendAgain()">Send again</a></p>
</form>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmCodeComponent } from './confirm-code.component';
describe('ConfirmCodeComponent', () => {
let component: ConfirmCodeComponent;
let fixture: ComponentFixture<ConfirmCodeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ConfirmCodeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ConfirmCodeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,60 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup, Validators, FormControl } from '@angular/forms';
import { environment } from 'src/environments/environment';
import { Router } from '@angular/router';
import Auth from '@aws-amplify/auth';
import { NotificationService } from 'src/app/services/notification.service';
@Component({
selector: 'app-confirm-code',
templateUrl: './confirm-code.component.html',
styleUrls: ['./confirm-code.component.scss']
})
export class ConfirmCodeComponent implements OnInit {
email = environment.confirm.email;
confirmForm: FormGroup = new FormGroup({
email: new FormControl({value: this.email, disabled: true}),
code: new FormControl('', [ Validators.required, Validators.min(3) ])
});
get codeInput() { return this.confirmForm.get('code'); }
constructor( private _router: Router, private _notification: NotificationService ) { }
ngOnInit() {
if (!this.email) {
this._router.navigate(['auth/signup']);
} else {
Auth.resendSignUp(this.email);
}
}
sendAgain() {
Auth.resendSignUp(this.email)
.then(() => this._notification.show('A code has been emailed to you'))
.catch(() => this._notification.show('An error occurred'));
}
confirmCode() {
Auth.confirmSignUp(this.email, this.codeInput.value)
.then((data: any) => {
console.log(data);
if (data === 'SUCCESS' &&
environment.confirm.email &&
environment.confirm.password) {
Auth.signIn(this.email, environment.confirm.password)
.then(() => {
this._router.navigate(['']);
}).catch((error: any) => {
this._router.navigate(['auth/signin']);
})
}
})
.catch((error: any) => {
console.log(error);
this._notification.show(error.message);
})
}
}

View File

@@ -0,0 +1,10 @@
<mat-form-field class="full-width country-filter">
<input matInput [(ngModel)]="searchText" placeholder="Filter Countries">
</mat-form-field>
<mat-nav-list class="country-list">
<a mat-list-item *ngFor="let code of countryCodes | filter : searchText" (click)="selectCountry(code)">
<span mat-line>{{ code.name }}</span>
<span mat-line>{{ code.dial_code }}</span>
</a>
</mat-nav-list>

View File

@@ -0,0 +1,10 @@
.country-list {
margin-top: 5px;
height: 300px;
overflow-y: scroll;
}
.country-filter {
position: sticky;
top: 0em;
margin-top:0.5em;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CountryCodeSelectComponent } from './country-code-select.component';
describe('CountryCodeSelectComponent', () => {
let component: CountryCodeSelectComponent;
let fixture: ComponentFixture<CountryCodeSelectComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CountryCodeSelectComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CountryCodeSelectComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { CountryCode, CountryCodes } from './country-codes';
import { MatBottomSheetRef } from '@angular/material';
@Component({
selector: 'app-country-code-select',
templateUrl: './country-code-select.component.html',
styleUrls: ['./country-code-select.component.scss']
})
export class CountryCodeSelectComponent implements OnInit {
countryCodes: Array<CountryCode> = CountryCodes;
constructor(private bottomSheetRef: MatBottomSheetRef<CountryCodeSelectComponent>) { }
ngOnInit() {
}
selectCountry(code: CountryCode) {
this.bottomSheetRef.dismiss(code);
}
}

View File

@@ -0,0 +1,7 @@
import { CountryCodes } from './country-codes';
describe('CountryCodes', () => {
it('should create an instance', () => {
expect(new CountryCodes()).toBeTruthy();
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
import { FilterPipe } from './filter.pipe';
describe('FilterPipe', () => {
it('create an instance', () => {
const pipe = new FilterPipe();
expect(pipe).toBeTruthy();
});
});

View File

@@ -0,0 +1,19 @@
import { Pipe, PipeTransform } from '@angular/core';
import { CountryCode } from './country-codes';
@Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(items: CountryCode[], searchText: string): CountryCode[] {
if (!items) {
return [];
}
if (!searchText) {
return items;
}
searchText = searchText.toLowerCase();
return items.filter( code => {
return code.name.toLowerCase().includes(searchText);
});
}
}

View File

@@ -0,0 +1,33 @@
<div class="app-avatar">
<div class="app-avatar-container">
<div
[class]="previewClass"
*ngIf="!hasPhoto"
(drop)="onDrop($event)"
(dragleave)="onDragout($event)"
(dragover)="onDragover($event)"></div>
<div class="app-avatar-preview">
<img class="app-avatar-preview"
src="{{photoUrl}}"
*ngIf="hasPhoto"
alt="Avatar Preview"
(error)="onPhotoError()"
/>
<button mat-mini-fab (click)="this.removed.emit()" *ngIf="hasPhoto && !uploading" class="avatar-remove" title="Delete avatar" aria-label="Delete avatar"><mat-icon>close</mat-icon></button>
<small *ngIf="uploading"><br/>Processing Avatar...</small>
</div>
<div class="app-upload-input" *ngIf="!uploading && !hasPhoto">
<input type="file"
accept="image/*"
(change)="pick($event)"/>
</div>
</div>
<div class="app-alert" *ngIf="errorMessage">
<div class="app-alert-body">
<span class="app-alert-icon">&#9888;</span>
<div class="app-alert-message">{{ errorMessage }}</div>
<a class="app-alert-close" (click)="onAlertClose()">&times;</a>
</div>
</div>
</div>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AvatarComponent } from './avatar.component';
describe('AvatarComponent', () => {
let component: AvatarComponent;
let fixture: ComponentFixture<AvatarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AvatarComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AvatarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,160 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import Storage from '@aws-amplify/storage';
import { NotificationService } from 'src/app/services/notification.service';
import { CompressorService } from 'src/app/services/compressor.service';
@Component({
selector: 'app-avatar',
templateUrl: './avatar.component.html',
styleUrls: ['./avatar.component.scss']
})
export class AvatarComponent {
photoUrl: string;
hasPhoto: boolean = false;
uploading: boolean = false;
s3ImageFile: any = null;
s3ImagePath: string = "avatar";
errorMessage: string;
previewClass = "app-avatar-upload";
private _storageOptions: any = { 'level': 'private' };
private _previewClassIdle = "app-avatar-upload";
private _previewClassOver = "app-avatar-upload-dragover"
@Input()
set url(url: string) {
this.photoUrl = url;
this.hasPhoto = true;
}
@Input()
set storageOptions(storageOptions: any){
this._storageOptions = Object.assign(this._storageOptions, storageOptions);
}
@Input()
set path(path: string){
this.s3ImagePath = path;
}
@Input()
set data(data: any) {
this.photoUrl = data.url;
this.s3ImagePath = data.path;
this._storageOptions = Object.assign(this._storageOptions, data.storageOptions);
this.hasPhoto = true;
}
@Output()
picked: EventEmitter<string> = new EventEmitter<string>();
@Output()
loaded: EventEmitter<string> = new EventEmitter<string>();
@Output()
uploaded: EventEmitter<Object> = new EventEmitter<Object>();
@Output()
removed: EventEmitter<null> = new EventEmitter<null>();
constructor( private notification: NotificationService,
private compressor: CompressorService ) {}
pick(evt) {
let file = null;
if (evt.target.files) {
file = evt.target.files[0];
}
if (!file && evt.dataTransfer.files) {
file = evt.dataTransfer.files[0];
}
if (!file) { return; }
const isImage = file.type.split('/')[0] === 'image';
if (!isImage) {
this.previewClass = this._previewClassIdle;
return this.notification.show('Only images are allowed.');
}
if (!this._storageOptions.contentType) {
this._storageOptions.contentType = file.type;
}
// console.log('file size: ', file.size);
this.picked.emit(file);
this.compressor.compress(file).subscribe(
(file: File) => {
const { name, size, type } = file;
// console.log('compressed size: ', size);
const fileName = file.name.split('.');
const fileExt = fileName[fileName.length - 1];
this.s3ImagePath = `${this.s3ImagePath}/picture.${fileExt}`
this.s3ImageFile = file;
const that = this;
const reader = new FileReader();
reader.onload = function(e) {
const target: any = e.target;
const url = target.result;
that.photoUrl = url;
that.hasPhoto = true;
that.loaded.emit(url);
that.uploadFile();
};
reader.readAsDataURL(file);
}
);
}
uploadFile() {
this.uploading = true;
Storage.put(
this.s3ImagePath,
this.s3ImageFile, this._storageOptions)
.then ( (result:any) => {
this.uploaded.emit(result);
this.completeFileUpload();
})
.catch( error => {
this.completeFileUpload(error);
});
}
completeFileUpload(error?:any) {
if (error) {
return this._setError(error);
}
this.uploading = false;
}
onPhotoError() {
this.hasPhoto = false;
}
onAlertClose() {
this._setError(null);
}
onDrop(event) {
event.preventDefault();
this.pick(event);
}
onDragover(event: DragEvent) {
event.stopPropagation();
event.preventDefault();
this.previewClass = this._previewClassOver;
}
onDragout(event: DragEvent) {
event.stopPropagation();
event.preventDefault();
this.previewClass = this._previewClassIdle;
}
_setError(err) {
if (!err) {
this.errorMessage = null;
return;
}
this.errorMessage = err.message || err;
}
}

View File

@@ -0,0 +1,31 @@
<div class="container">
<h2>My Account</h2>
<form [formGroup]="profileForm" autocomplete="on">
<mat-form-field>
<input matInput placeholder="First Name" type="text" formControlName="fname" autocomplete="family-name" required>
<mat-hint *ngIf="!fnameInput.value">Enter your first name</mat-hint>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Last Name" type="text" formControlName="lname" autocomplete="given-name" required>
<mat-hint *ngIf="!lnameInput.value">Enter your last name</mat-hint>
</mat-form-field>
<mat-form-field>
<input type="tel" matInput placeholder="Telephone" formControlName="phone" autocomplete="tel">
</mat-form-field>
<div class="profile-picture">
<label>Profile Picture</label>
<app-avatar
[url]="currentAvatarUrl"
(loaded)="this.loading.show()"
(uploaded)="onAvatarUploadComplete($event)"
(removed)="onAvatarRemove()">
</app-avatar>
</div>
<button mat-raised-button color="primary" (click)="editProfile()" [disabled]="loading.loading || !profileForm.valid">Save</button>
<button mat-raised-button (click)="signOut()" class="signout">Sign Out</button>
</form>
</div>

View File

@@ -0,0 +1,26 @@
.container {
width: 100%;
}
form {
width: 100%;
}
form > * {
margin-bottom: 1.5em;
width: 100%;
}
.cursor-pointer {
cursor: pointer;
}
.signout {
float: right;
}
@media (min-width: 900px) {
.container {
margin: 0 auto;
width: 75%;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileComponent } from './profile.component';
describe('ProfileComponent', () => {
let component: ProfileComponent;
let fixture: ComponentFixture<ProfileComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ProfileComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,106 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import Auth, { CognitoUser } from '@aws-amplify/auth';
import Storage from '@aws-amplify/storage';
import { NotificationService } from 'src/app/services/notification.service';
import { LoaderService } from 'src/app/loader/loader.service';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss']
})
export class ProfileComponent implements OnInit {
profileForm: FormGroup = new FormGroup({
email: new FormControl('',[ Validators.email ]),
phone: new FormControl('', [ Validators.min(10) ]),
fname: new FormControl('', [ Validators.min(2) ]),
lname: new FormControl('', [ Validators.min(2) ])
});
currentAvatarUrl: string;
avatar: string;
deleteAvatar = false;
profile:any = {};
user: CognitoUser;
get emailInput() { return this.profileForm.get('email'); }
get fnameInput() { return this.profileForm.get('fname'); }
get lnameInput() { return this.profileForm.get('lname'); }
get phoneInput() { return this.profileForm.get('phone'); }
constructor(
private _authService: AuthService,
private _router: Router,
private _notification: NotificationService,
public loading: LoaderService ) { }
ngOnInit() {
this.loading.show();
this.getUserInfo();
}
async getUserInfo() {
this.profile = await Auth.currentUserInfo();
this.user = await Auth.currentAuthenticatedUser();
if ( this.profile.attributes['profile'] ) {
this.avatar = this.profile.attributes['profile'];
this.currentAvatarUrl = await Storage.vault.get(this.avatar) as string;
}
this.fnameInput.setValue(this.profile.attributes['given_name']);
this.lnameInput.setValue(this.profile.attributes['family_name']);
this.phoneInput.setValue(this.profile.attributes['phone_number']);
this.loading.hide();
}
getEmailInputError() {
if (this.emailInput.hasError('email')) {
return 'Please enter a valid email address.';
}
if (this.emailInput.hasError('required')) {
return 'An Email is required.';
}
}
signOut() {
this._authService.signOut()
.then(() => this._router.navigate(['auth/signin']))
}
onAvatarUploadComplete(data: any) {
this.avatar = data.key;
this.loading.hide();
}
onAvatarRemove() {
this.avatar = undefined;
this.currentAvatarUrl = undefined;
this.deleteAvatar = true;
}
async editProfile() {
try {
let attributes = {
'given_name': this.fnameInput.value,
'family_name': this.lnameInput.value,
'phone_number': this.phoneInput.value
};
if (this.avatar) {
attributes['profile'] = this.avatar;
}
await Auth.updateUserAttributes(this.user,attributes);
if (!this.avatar && this.deleteAvatar) {
this.user.deleteAttributes(["profile"],(error) => {
if (error) console.log(error);
this._notification.show('Your profile information has been updated.');
});
} else {
this._notification.show('Your profile information has been updated.');
}
} catch (error) {
console.log(error);
}
}
}

View File

@@ -0,0 +1,23 @@
<form [formGroup]="signinForm">
<h2>Sign in to your account</h2>
<mat-form-field>
<input matInput placeholder="Email" type="email" formControlName="email" autocomplete="email" required>
<mat-hint *ngIf="!emailInput.value">Enter your email address</mat-hint>
<mat-error *ngIf="!emailInput.valid">
{{getEmailInputError()}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Password" [type]="hide ? 'password' : 'text'" formControlName="password" required>
<mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
<mat-hint *ngIf="!passwordInput.value">Enter your password</mat-hint>
<mat-error *ngIf="!passwordInput.valid">
{{getPasswordInputError()}}
</mat-error>
</mat-form-field>
<button mat-raised-button color="primary" (click)="signIn()" [disabled]="!signinForm.valid">Sign In</button>
<p style="text-align:center;">or</p>
<button mat-raised-button color="primary" (click)="signInWithFacebook()">Sign In with Facebook</button><br/><br/>
<button mat-raised-button color="primary" (click)="signInWithGoogle()">Sign In with Google</button>
<p>Don't have an account? <a routerLink="/auth/signup">Sign up</a></p>
</form>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SignInComponent } from './sign-in.component';
describe('SignInComponent', () => {
let component: SignInComponent;
let fixture: ComponentFixture<SignInComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SignInComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SignInComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,80 @@
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { AuthService } from '../auth.service';
import { CognitoUser } from '@aws-amplify/auth';
import { NotificationService } from 'src/app/services/notification.service';
import { Router } from '@angular/router';
import { environment } from 'src/environments/environment';
import { LoaderService } from 'src/app/loader/loader.service';
@Component({
selector: 'app-sign-in',
templateUrl: './sign-in.component.html',
styleUrls: ['./sign-in.component.scss']
})
export class SignInComponent {
signinForm: FormGroup = new FormGroup({
email: new FormControl('',[ Validators.email, Validators.required ]),
password: new FormControl('', [ Validators.required, Validators.min(6) ])
});
hide = true;
get emailInput() { return this.signinForm.get('email'); }
get passwordInput() { return this.signinForm.get('password'); }
constructor(
public auth: AuthService,
private _notification: NotificationService,
private _router: Router,
private _loader: LoaderService ) { }
getEmailInputError() {
if (this.emailInput.hasError('email')) {
return 'Please enter a valid email address.';
}
if (this.emailInput.hasError('required')) {
return 'An Email is required.';
}
}
getPasswordInputError() {
if (this.passwordInput.hasError('required')) {
return 'A password is required.';
}
}
signIn() {
this._loader.show();
this.auth.signIn(this.emailInput.value, this.passwordInput.value)
.then((user: CognitoUser|any) => {
this._loader.hide();
this._router.navigate(['']);
})
.catch((error: any) => {
this._loader.hide();
this._notification.show(error.message);
switch (error.code) {
case "UserNotConfirmedException":
environment.confirm.email = this.emailInput.value;
environment.confirm.password = this.passwordInput.value;
this._router.navigate(['auth/confirm']);
break;
case "UsernameExistsException":
this._router.navigate(['auth/signin']);
break;
}
})
}
async signInWithFacebook() {
const socialResult = await this.auth.socialSignIn(AuthService.FACEBOOK);
console.log('fb Result:', socialResult);
}
async signInWithGoogle() {
const socialResult = await this.auth.socialSignIn(AuthService.GOOGLE);
console.log('google Result:', socialResult);
}
}

View File

@@ -0,0 +1,27 @@
<form [formGroup]="signupForm">
<h2>Create a new account</h2>
<mat-form-field>
<input matInput placeholder="Email" type="email" formControlName="email" autocomplete="email" required>
<mat-hint *ngIf="!emailInput.value">Enter your email address</mat-hint>
</mat-form-field>
<mat-form-field>
<span matPrefix>{{ countryCode }} &nbsp;</span>
<input type="tel" matInput placeholder="Telephone" formControlName="phone" autocomplete="tel">
<mat-icon matSuffix class="cursor-pointer" (click)="selectCountryCode()">mode_edit</mat-icon>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="First Name" type="text" formControlName="fname" autocomplete="given-name" required>
<mat-hint *ngIf="!fnameInput.value">Enter your first name</mat-hint>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Last Name" type="text" formControlName="lname" autocomplete="family-name" required>
<mat-hint *ngIf="!lnameInput.value">Enter your last name</mat-hint>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Password" [type]="hide ? 'password' : 'text'" formControlName="password" required>
<mat-icon matSuffix class="cursor-pointer" (click)="hide = !hide">{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
<mat-hint *ngIf="!passwordInput.value">Enter your password</mat-hint>
</mat-form-field>
<button mat-raised-button color="primary" (click)="signUp()" [disabled]="shouldEnableSubmit()">Sign up</button>
<p class="center">Already have an account? <a routerLink="/auth/signin">Sign In</a></p>
</form>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SignUpComponent } from './sign-up.component';
describe('SignUpComponent', () => {
let component: SignUpComponent;
let fixture: ComponentFixture<SignUpComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SignUpComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SignUpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,92 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { MatBottomSheet } from '@angular/material';
import { CountryCodeSelectComponent } from '../country-code-select/country-code-select.component';
import { CountryCode } from '../country-code-select/country-codes';
import { AuthService } from '../auth.service';
import { environment } from 'src/environments/environment';
import { Router } from '@angular/router';
@Component({
selector: 'app-sign-up',
templateUrl: './sign-up.component.html',
styleUrls: ['./sign-up.component.scss']
})
export class SignUpComponent implements OnInit {
hide = true;
signupForm: FormGroup = new FormGroup({
email: new FormControl('', [ Validators.email, Validators.required ]),
password: new FormControl('', [ Validators.required ]),
phone: new FormControl('', [ Validators.min(10) ]),
fname: new FormControl('', [ Validators.min(2) ]),
lname: new FormControl('', [ Validators.min(2) ])
});
countryCode = '+1';
get emailInput() { return this.signupForm.get('email'); }
get passwordInput() { return this.signupForm.get('password'); }
get fnameInput() { return this.signupForm.get('fname'); }
get lnameInput() { return this.signupForm.get('lname'); }
get phoneInput() { return this.signupForm.get('phone'); }
constructor(
private _bottomSheet: MatBottomSheet,
private _authService: AuthService,
private _router: Router ) {
}
ngOnInit() {
}
selectCountryCode() {
this._bottomSheet.open(CountryCodeSelectComponent)
.afterDismissed()
.subscribe(
(data: CountryCode) => {
this.countryCode = (data) ? data.dial_code : this.countryCode;
}
);
}
getEmailInputError() {
if (this.emailInput.hasError('email')) {
return 'Please enter a valid email address.';
}
if (this.emailInput.hasError('required')) {
return 'An Email is required.';
}
}
getPasswordInputError() {
if (this.passwordInput.hasError('required')) {
return 'A password is required.';
}
}
shouldEnableSubmit() {
return !this.emailInput.valid ||
!this.passwordInput.valid ||
!this.fnameInput.valid ||
!this.lnameInput.valid ||
!this.phoneInput.valid;
}
signUp() {
this._authService.signUp({
"email": this.emailInput.value,
"password": this.passwordInput.value,
"firstName": this.fnameInput.value,
"lastName": this.lnameInput.value,
"phone": this.countryCode + this.phoneInput.value
})
.then((data) => {
environment.confirm.email = this.emailInput.value;
environment.confirm.password = this.passwordInput.value;
this._router.navigate(['auth/confirm']);
})
.catch((error) => console.log(error));
}
}

View File

@@ -0,0 +1,15 @@
import { TestBed, async, inject } from '@angular/core/testing';
import { UnauthGuard } from './unauth.guard';
describe('UnauthGuard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UnauthGuard]
});
});
it('should ...', inject([UnauthGuard], (guard: UnauthGuard) => {
expect(guard).toBeTruthy();
}));
});

View File

@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
Router } from '@angular/router';
import { Observable } from 'rxjs';
import Auth from '@aws-amplify/auth';
@Injectable({
providedIn: 'root'
})
export class UnauthGuard implements CanActivate {
constructor( private _router: Router ) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return Auth.currentAuthenticatedUser()
.then(() => {
this._router.navigate(['auth/profile']);
return false;
})
.catch(() => {
return true;
});
}
}

View File

@@ -0,0 +1,4 @@
<div class="loader">
<mat-spinner></mat-spinner>
<p> {{message}} </p>
</div>

View File

@@ -0,0 +1,7 @@
.loader > * {
text-align: center;
margin: 0 auto;
}
.loader p {
margin-top: 1em;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoaderComponent } from './loader.component';
describe('LoaderComponent', () => {
let component: LoaderComponent;
let fixture: ComponentFixture<LoaderComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoaderComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,24 @@
import { Component, OnInit, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material';
@Component({
selector: 'app-loader',
templateUrl: './loader.component.html',
styleUrls: ['./loader.component.scss']
})
export class LoaderComponent implements OnInit {
message = 'Please wait...';
constructor(
@Inject(MAT_DIALOG_DATA) public data: any
) {
if (data.message) {
this.message = data.message;
}
}
ngOnInit() {
}
}

View File

@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { LoaderService } from './loader.service';
describe('LoaderService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: LoaderService = TestBed.get(LoaderService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material';
import { LoaderComponent } from './loader.component';
@Injectable({
providedIn: 'root'
})
export class LoaderService {
loading: boolean;
dialogRef: MatDialogRef<LoaderComponent>;
constructor( private _dialog: MatDialog ) { }
show(message: string = "Please wait..."): void {
setTimeout(() => {
this.loading = true;
this.dialogRef = this._dialog.open(LoaderComponent, {
width: '80%',
data: { 'message': message },
closeOnNavigation: false
});
});
}
hide() {
if (this.dialogRef) {
this.dialogRef.close();
this.loading = false;
}
}
}

View File

@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { CompressorService } from './compressor.service';
describe('CompressorService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: CompressorService = TestBed.get(CompressorService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,41 @@
import { Injectable } from '@angular/core';
import { Observable} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CompressorService {
constructor() { }
compress(file: File): Observable<any> {
const width = 128; // For scaling relative to width
const reader = new FileReader();
reader.readAsDataURL(file);
return new Observable(observer => {
reader.onload = ev => {
const img = new Image();
img.src = (ev.target as any).result;
(img.onload = () => {
const elem = document.createElement('canvas'); // Use Angular's Renderer2 method
const scaleFactor = width / img.width;
elem.width = width;
elem.height = img.height * scaleFactor;
const ctx = elem.getContext('2d') as CanvasRenderingContext2D;
ctx.drawImage(img, 0, 0, width, img.height * scaleFactor);
ctx.canvas.toBlob(
blob => {
observer.next(
new File([blob], file.name, {
type: file.type,
lastModified: Date.now(),
}),
);
},
file.type,
1,
);
}),
(reader.onerror = error => observer.error(error));
};
});
}
}

View File

@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { NotificationService } from './notification.service';
describe('NotificationService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: NotificationService = TestBed.get(NotificationService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,57 @@
import { Injectable, OnDestroy } from '@angular/core';
import { MatSnackBar, MatSnackBarRef } from '@angular/material';
import { Subscription } from 'rxjs';
/**
* Provides an abstract wrapper around showing a MatSnackbar
* notification based on global environment or API provided
* configuration.
*
* This class Listens for the authentication state to change.
* Once the state becomes authenticated, retrieve the startup
* configuration from the API service. Once de-authenticated
* set the _params to undefined and unsubscribe.
*/
@Injectable({
providedIn: 'root'
})
export class NotificationService implements OnDestroy {
// Configuration api subscription
private _configState: Subscription;
/**
* Constructor
* @param toast {MatSnackBar}
* @param configService {ConfigurationService}
*/
constructor(
private toast: MatSnackBar) {}
/**
* Unsubscribe from the config state
* when the component is destroyed, and remove
* the in-memory parameters.
*/
ngOnDestroy() {
this._configState.unsubscribe();
}
/**
* Display a MatSnackbar notification and return the reference.
* Will set the duration to the global configuration if present.
* @param message {string}
* @param buttonLabel {string}
* @returns {MatSnackBarRef}
*/
show(message: string, buttonLabel: string = 'OK'): MatSnackBarRef<any> {
const toastTimeout = 8000;
if (toastTimeout > 0) {
return this.toast.open(message, buttonLabel, {
duration: toastTimeout
});
} else {
return this.toast.open(message, buttonLabel, {
});
}
}
}

View File

@@ -3,7 +3,11 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
production: false,
confirm: {
email: '',
password: ''
}
};
/*

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>HPIoTWebApp</title>
<title>Henry Pump SCADA</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">

View File

@@ -5,6 +5,13 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
// Amplify Config
import Auth from '@aws-amplify/auth';
import AWSConfig from './aws-exports';
Auth.configure(AWSConfig);
if (environment.production) {
enableProdMode();
}

View File

@@ -55,6 +55,7 @@
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
(window as any).global = window;
import 'zone.js/dist/zone'; // Included with Angular CLI.

View File

@@ -2,7 +2,7 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
"types": ["node"]
},
"files": [
"src/main.ts",