Resolve "Back button in login not allowed - no way to cancel"

This commit is contained in:
Bart Akeley 2019-02-09 18:58:16 +00:00
parent 07ddca4e87
commit 1fc4f14944
3 changed files with 254 additions and 29 deletions

View file

@ -1,8 +1,7 @@
//@flow
// import { authFacebook, type FacebookAuth } from './apis/FacebookAuth';
import { type GoogleUser } from './apis/GoogleAuth';
import { type Auth0User, loginAuth0 } from './apis/Auth0';
import { type Auth0User, loginAuth0, createAuth0User, resetAuth0Password } from './apis/Auth0';
import moment from 'moment';
import { AsyncStorage } from 'react-native';
export const AUTH_PROVIDER_FACEBOOK: 'facebook' = 'facebook';
export const AUTH_PROVIDER_GOOGLE: 'google' = 'google';
@ -50,21 +49,49 @@ class AuthManager {
setAuth0User = (auth0User: Auth0User) => {
if (!auth0User) {
this.setUser({});
return;
}
this.user = {
this.setUser({
token: auth0User.accessToken,
tokenExpires: +moment().add(auth0User.expiresIn, 'seconds'),
};
});
};
authenticate = async () => {
this.setAuth0User(await loginAuth0());
setUser = ({ token, tokenExpires }) => {
if (!token || +moment(tokenExpires) <= +moment()) {
this.user = null;
AsyncStorage.removeItem('user');
} else {
this.user = { token, tokenExpires };
AsyncStorage.setItem('user', JSON.stringify(this.user));
}
};
checkIsAuthed = async () => {
const user = JSON.parse(await AsyncStorage.getItem('user'));
if (user) {
this.setUser(user);
return this.user;
}
};
authenticate = async ({ email, password }) => {
this.setAuth0User(await loginAuth0({ email, password }));
};
deauthenticate = () => {
this.user = null;
};
createUser = async user => {
return createAuth0User(user);
};
resetPassword = () => {
return resetAuth0Password();
};
}
export default new AuthManager();

View file

@ -18,15 +18,29 @@ const getAuth0Instance = () => {
);
};
export const loginAuth0 = async (): Promise<?Auth0User> => {
try {
const creds = await getAuth0Instance().webAuth.authorize({
scope: 'openid profile email',
audience: 'https://aretherecookies.auth0.com/userinfo',
});
return creds;
} catch (error) {
console.log(error); //eslint-disable-line no-console
return null;
}
export const loginAuth0 = async ({ email: username, password }): Promise<?Auth0User> => {
const creds = await getAuth0Instance().auth.passwordRealm({
username,
password,
realm: 'Username-Password-Authentication',
});
return creds;
};
export const createAuth0User = async ({ email, username, password }) => {
const creds = await getAuth0Instance().auth.createUser({
email,
username,
password,
connection: 'Username-Password-Authentication',
});
return creds;
};
export const resetAuth0Password = async () => {
const creds = await getAuth0Instance().webAuth.authorize({
scope: 'openid profile email',
audience: 'https://aretherecookies.auth0.com/userinfo',
});
return creds;
};

View file

@ -1,15 +1,31 @@
//@flow
import React from 'react';
import { View, Text } from 'react-native';
import { View, Text, TextInput, Button, TouchableOpacity } from 'react-native';
import { withHandlers, compose, withState, lifecycle } from 'recompose';
import { withRouterContext } from '../enhancers/routeEnhancers';
import AuthManager from '../AuthManager';
import queryString from 'query-string';
import theme from '../ui-theme';
import { Divider, Icon } from 'react-native-material-ui';
const auth0ErrToStr = message => {
if (typeof message === 'string') {
return message;
}
if (message && message.rules && message.rules.length > 0) {
return 'Password must be at least 8 characters and contain at least 1 letter, number, and symbol';
}
};
type Props = {
authUser: (provider: string) => () => Promise<void>,
authUser: () => void,
error: string,
setError: (err: string) => void,
email: string,
setEmail: string => void,
password: string,
setPassword: string => void,
router: {
route: {
location: {
@ -24,15 +40,142 @@ type Props = {
},
},
deauthUser: () => void,
createUser: () => void,
isNewUser: boolean,
setIsNewUser: boolean => void,
username: string,
setUsername: string => void,
resetPassword: () => void,
};
const LoginPageComponent = ({ deauthUser, error, router }: Props) => {
const LoginPageComponent = ({
authUser,
deauthUser,
error,
router,
email,
setEmail,
password,
setPassword,
createUser,
isNewUser,
setIsNewUser,
username,
setUsername,
resetPassword,
}: Props) => {
if (/logout/.test(router.route.location.pathname)) {
deauthUser();
return null;
} else {
const submitTitle = isNewUser ? 'Continue' : 'Sign In';
const submitAction = isNewUser ? createUser : authUser;
return (
<View style={{ flexDirection: 'column', alignItems: 'center' }}>
{error && <Text>Login Failed! {error}</Text>}
<View
style={{
flex: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',
padding: 20,
borderWidth: 1,
borderColor: 'pink',
}}>
{error && <Text style={{ fontSize: 16, color: 'red', fontWeight: 'bold' }}>{error}</Text>}
<Text style={{ fontSize: 64, fontWeight: '600' }}>Sign In</Text>
<View style={{ flexDirection: 'row', marginTop: 20 }}>
<View style={{ flex: 1, flexDirection: 'column' }}>
<TextInput
editable={true}
value={email}
placeholder="Email"
onChangeText={setEmail}
style={{
fontSize: 18,
}}
/>
<Divider />
</View>
</View>
{isNewUser && (
<View style={{ flexDirection: 'row', marginTop: 20 }}>
<View style={{ flex: 1, flexDirection: 'column' }}>
<TextInput
editable={true}
value={username}
placeholder="Username"
onChangeText={setUsername}
style={{
fontSize: 18,
}}
/>
<Divider />
</View>
</View>
)}
<View style={{ flexDirection: 'row', marginTop: 20 }}>
<View style={{ flex: 1, flexDirection: 'column' }}>
<TextInput
editable={true}
secureTextEntry={true}
value={password}
placeholder="Password"
onChangeText={setPassword}
style={{
fontSize: 18,
}}
/>
<Divider />
</View>
</View>
<View style={{ flexDirection: 'row', marginTop: 20 }}>
<View style={{ flex: 1, flexDirection: 'column', paddingTop: 20 }}>
<Button title={submitTitle} onPress={submitAction} color={theme.palette.primaryColor} />
</View>
</View>
{!isNewUser && (
<TouchableOpacity
onPress={() => setIsNewUser(true)}
style={{ flexDirection: 'row', marginTop: 20, padding: 20 }}>
<Text
style={{
flex: 1,
textAlign: 'center',
fontSize: 16,
color: theme.palette.primaryColor,
}}>
Don't have an account? Sign up!
</Text>
</TouchableOpacity>
)}
<TouchableOpacity
onPress={resetPassword}
style={{
flexDirection: 'row',
marginTop: 0,
padding: 20,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
textAlign: 'center',
fontSize: 16,
color: theme.palette.primaryColor,
marginRight: 5,
}}>
Forgot your pasword?
</Text>
<Icon name="launch" color={theme.palette.primaryColor} size={18} />
</TouchableOpacity>
</View>
);
}
@ -41,21 +184,62 @@ const LoginPageComponent = ({ deauthUser, error, router }: Props) => {
export default compose(
withRouterContext,
withState('error', 'setError', null),
withState('email', 'setEmail', ''),
withState('password', 'setPassword', ''),
withState('username', 'setUsername', ''),
withState('isNewUser', 'setIsNewUser', false),
withHandlers({
checkAuth: ({ router }) => async () => {
const isLoggedIn = await AuthManager.checkIsAuthed();
if (isLoggedIn) {
const { returnto = '/list/food' } = queryString.parse(router.route.location.search);
router.history.replace(returnto);
}
},
}),
withHandlers({
authUser: ({ email, password, setError, checkAuth }) => async () => {
try {
setError(null);
await AuthManager.authenticate({ email, password });
checkAuth();
} catch (err) {
setError('Login Failed! Please check the username and password and try again.');
}
},
}),
withHandlers({
deauthUser: ({ router: { history } }) => () => {
const { returnto = '/list/food' } = queryString.parse(history.location.search);
AuthManager.deauthenticate();
history.replace(returnto);
},
createUser: ({ email, password, username, setError, authUser }) => async () => {
if (!email || !password || !username) {
setError('Please provide username, email, and password and try again.');
return;
}
try {
setError(null);
await AuthManager.createUser({ email, password, username });
authUser({ email, password });
} catch (err) {
setError('Unable to create user: ' + auth0ErrToStr(err.message));
}
},
resetPassword: ({ setError, setIsNewUser }) => async () => {
try {
await AuthManager.resetPassword();
setIsNewUser(false);
} catch (error) {
setError('Password reset failed');
}
},
}),
lifecycle({
componentDidMount() {
const { returnto = '/list/food' } = queryString.parse(
this.props.router.route.location.search
);
if (!AuthManager.isLoggedIn()) {
AuthManager.authenticate('auth0').then(() => this.props.router.history.replace(returnto));
}
this.props.checkAuth();
},
})
)(LoginPageComponent);