/*
* Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
import React, { FC, ReactNode } from 'react';
import { Auth, Analytics, Logger, Hub } from 'aws-amplify';
import { isEmpty } from '@aws-amplify/core';
import AmplifyTheme, { AmplifyThemeType } from '../AmplifyTheme';
import AmplifyMessageMap from '../AmplifyMessageMap';
import { Container } from '../AmplifyUI';
import Loading from './Loading';
import SignIn from './SignIn';
import ConfirmSignIn from './ConfirmSignIn';
import VerifyContact from './VerifyContact';
import SignUp from './SignUp';
import ConfirmSignUp from './ConfirmSignUp';
import ForgotPassword from './ForgotPassword';
import RequireNewPassword from './RequireNewPassword';
import Greetings from './Greetings';
import { HubCapsule, OnStateChangeType, ISignUpConfig, UsernameAttributesType } from '../../types';
const logger = new Logger('Authenticator');
const EmptyContainer: FC<{}> = ({ children }) => {children};
class AuthDecorator {
onStateChange: (state: string) => void;
constructor(onStateChange: OnStateChangeType) {
this.onStateChange = onStateChange;
}
signIn(username: string, password: string) {
const that = this;
return Auth.signIn(username, password).then((data) => {
that.onStateChange('signedIn');
return data;
});
}
signOut() {
const that = this;
return Auth.signOut().then(() => {
that.onStateChange('signedOut');
});
}
}
interface IAuthenticatorProps {
authData?: any;
authState?: string;
container?: ReactNode;
errorMessage?: string;
hideDefault?: boolean;
signUpConfig?: ISignUpConfig;
usernameAttributes?: UsernameAttributesType;
onStateChange?: OnStateChangeType;
theme?: AmplifyThemeType;
}
interface IAuthenticatorState {
authData?: any;
authState: string;
error?: string;
}
export default class Authenticator extends React.Component {
_initialAuthState: string;
_isMounted: boolean;
constructor(props: IAuthenticatorProps) {
super(props);
this._initialAuthState = this.props.authState || 'signIn';
this.state = {
authState: props.authState || 'loading',
authData: props.authData,
};
this.handleStateChange = this.handleStateChange.bind(this);
this.checkUser = this.checkUser.bind(this);
this.onHubCapsule = this.onHubCapsule.bind(this);
this.checkContact = this.checkContact.bind(this);
Hub.listen('auth', this.onHubCapsule);
}
componentDidMount() {
this._isMounted = true;
this.checkUser();
}
componentWillUnmount() {
this._isMounted = false;
}
onHubCapsule(capsule: HubCapsule) {
const {
payload: { event, data },
} = capsule;
switch (event) {
case 'cognitoHostedUI':
case 'signIn':
this.checkContact(data);
break;
case 'cognitoHostedUI_failure':
case 'parsingUrl_failure':
case 'signOut':
case 'customGreetingSignOut':
return this.handleStateChange('signIn', null);
}
}
handleStateChange(state: string, data?: any) {
if (state === undefined) return logger.info('Auth state cannot be undefined');
logger.info('Inside handleStateChange method current authState:', this.state.authState);
const nextAuthState = state === 'signedOut' ? this._initialAuthState : state;
const nextAuthData = data !== undefined ? data : this.state.authData;
if (this._isMounted) {
this.setState({
authState: nextAuthState,
authData: nextAuthData,
error: null,
});
logger.log('Auth Data was set:', nextAuthData);
logger.info(`authState has been updated to ${nextAuthState}`);
}
if (this.props.onStateChange) {
this.props.onStateChange(state, data);
}
// @ts-ignore
if (Analytics._config && Object.entries(Analytics._config).length > 0) {
switch (state) {
case 'signedIn':
Analytics.record('_userauth.sign_in');
break;
case 'signedUp':
Analytics.record('_userauth.sign_up');
break;
}
}
}
async checkContact(user: any) {
try {
const data = await Auth.verifiedContact(user);
logger.debug('verified user attributes', data);
if (!isEmpty(data.verified)) {
this.handleStateChange('signedIn', user);
} else {
user = Object.assign(user, data);
this.handleStateChange('verifyContact', user);
}
} catch (e) {
logger.warn('Failed to verify contact', e);
this.handleStateChange('signedIn', user);
}
}
checkUser() {
const { authState } = this.state;
const statesJumpToSignIn = ['signedIn', 'signedOut', 'loading'];
Auth.currentAuthenticatedUser()
.then((user) => {
if (!this._isMounted) return;
if (user) {
this.checkContact(user);
} else {
if (statesJumpToSignIn.includes(authState)) {
this.handleStateChange(this._initialAuthState, null);
}
}
})
.catch((err) => {
if (!this._isMounted) return;
logger.debug(err);
if (statesJumpToSignIn.includes(authState)) {
Auth.signOut()
.then(() => {
this.handleStateChange(this._initialAuthState, null);
})
.catch((err) => logger.warn('Failed to sign out', err));
}
});
}
render() {
const { authState, authData } = this.state;
const theme = this.props.theme || AmplifyTheme;
const messageMap = this.props.errorMessage || AmplifyMessageMap;
// If container prop is undefined, default to AWS Amplify UI Container (SafeAreaView)
// otherwise if truthy, use the supplied render prop
// otherwise if falsey, use EmptyContainer
const ContainerWrapper: any =
this.props.container === undefined ? Container : this.props.container || EmptyContainer;
const { hideDefault, signUpConfig, usernameAttributes = 'username' } = this.props;
const props_children: any = this.props.children || [];
const default_children = [
,
,
,
,
,
,
,
,
,
];
const children = (hideDefault ? [] : default_children).concat(props_children).map((child, index) => {
return React.cloneElement(child, {
key: 'auth_piece_' + index,
theme: theme,
messageMap: messageMap,
authState: authState,
authData: authData,
onStateChange: this.handleStateChange,
Auth: new AuthDecorator(this.handleStateChange),
usernameAttributes,
});
});
return {children};
}
}