mirror of
https://gitlab.com/wheres-the-tp/ui-mobile.git
synced 2026-01-25 05:54:56 -06:00
Resolve "Back button in login not allowed - no way to cancel"
This commit is contained in:
parent
07ddca4e87
commit
1fc4f14944
3 changed files with 254 additions and 29 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue