From 1fc4f149440f7afaffe8cb9c25940198a68d69de Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sat, 9 Feb 2019 18:58:16 +0000 Subject: [PATCH] Resolve "Back button in login not allowed - no way to cancel" --- js/AuthManager.js | 41 +++++++-- js/apis/Auth0.js | 36 +++++--- js/pages/LoginPage.js | 206 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 254 insertions(+), 29 deletions(-) diff --git a/js/AuthManager.js b/js/AuthManager.js index 577b590..9d8d06d 100644 --- a/js/AuthManager.js +++ b/js/AuthManager.js @@ -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(); diff --git a/js/apis/Auth0.js b/js/apis/Auth0.js index f22bea0..44e7b52 100644 --- a/js/apis/Auth0.js +++ b/js/apis/Auth0.js @@ -18,15 +18,29 @@ const getAuth0Instance = () => { ); }; -export const loginAuth0 = async (): Promise => { - 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 => { + 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; }; diff --git a/js/pages/LoginPage.js b/js/pages/LoginPage.js index f761279..7a1655a 100644 --- a/js/pages/LoginPage.js +++ b/js/pages/LoginPage.js @@ -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, + 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 ( - - {error && Login Failed! {error}} + + {error && {error}} + + Sign In + + + + + + + + + {isNewUser && ( + + + + + + + )} + + + + + + + + + + +