From a5e8a77d6e1ceb09408455cf2f6d1f3faabe7533 Mon Sep 17 00:00:00 2001 From: Avinash Lingaloo <> Date: Fri, 14 May 2021 17:48:40 +0400 Subject: [PATCH] MM_35115: Login screen - email - login api call [IN PROGRESS] --- .eslintrc.json | 3 +- app/client/rest/base.ts | 5 +- app/components/error_text/index.tsx | 2 +- app/requests/remote/user.ts | 40 +++++++++++++ app/screens/login/index.tsx | 87 ++++++++++++++--------------- app/screens/server/index.tsx | 4 +- package.json | 1 + types/api/client4.d.ts | 14 ++--- 8 files changed, 99 insertions(+), 57 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index b7d9dbed4c..191b60b9ad 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,7 +2,8 @@ "extends": [ "plugin:mattermost/react", "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended" ], "parser": "@typescript-eslint/parser", "plugins": [ diff --git a/app/client/rest/base.ts b/app/client/rest/base.ts index 0b57fcec17..14c4125e91 100644 --- a/app/client/rest/base.ts +++ b/app/client/rest/base.ts @@ -4,6 +4,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {ClientOptions} from '@typings/api/client4'; import urlParse from 'url-parse'; import {Analytics, create} from '@init/analytics'; @@ -12,7 +13,7 @@ import * as ClientConstants from './constants'; import ClientError from './error'; export default class ClientBase { - analitics: Analytics|undefined; + analytics: Analytics|undefined; clusterId = ''; csrf = ''; defaultHeaders: {[x: string]: string} = {}; @@ -127,7 +128,7 @@ export default class ClientBase { setUrl(url: string) { this.url = url.replace(/\/+$/, ''); - this.analitics = create(this.url); + this.analytics = create(this.url); } // Routes diff --git a/app/components/error_text/index.tsx b/app/components/error_text/index.tsx index 944987c1d3..48e3c7c71f 100644 --- a/app/components/error_text/index.tsx +++ b/app/components/error_text/index.tsx @@ -47,7 +47,7 @@ const ErrorText = ({error, testID, textStyle, theme}: ErrorProps) => { const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { return { errorLabel: { - color: (theme.errorTextColor || '#DA4A4A'), + color: (theme?.errorTextColor || '#DA4A4A'), marginTop: 15, marginBottom: 15, fontSize: 12, diff --git a/app/requests/remote/user.ts b/app/requests/remote/user.ts index ae3764618c..c4afff0180 100644 --- a/app/requests/remote/user.ts +++ b/app/requests/remote/user.ts @@ -7,10 +7,12 @@ import DatabaseConnectionException from '@database/exceptions/database_connectio import DatabaseManager from '@database/manager'; import {Q} from '@nozbe/watermelondb'; import {Client4Error} from '@typings/api/client4'; +import Global from '@typings/database/global'; const HTTP_UNAUTHORIZED = 401; //fixme: this file needs to be finalized +//todo: retrieve deviceToken from default database - Global entity export const logout = async (skipServerLogout = false) => { return async () => { @@ -42,3 +44,41 @@ export const forceLogoutIfNecessary = async (err: Client4Error) => { logout(false); } }; + +type LoginArgs = {loginId: string, password: string, mfaToken?: string, ldapOnly?: boolean} +export const login = async ({loginId, password, mfaToken, ldapOnly = false}: LoginArgs) => { + const database = await DatabaseManager.getDefaultDatabase(); + + if (!database) { + throw new DatabaseConnectionException('DatabaseManager.getActiveServerDatabase returned undefined'); + } + + let deviceToken; + let user; + + try { + const tokens = await database.collections.get(MM_TABLES.DEFAULT.GLOBAL).query(Q.where('name', 'deviceToken')).fetch() as Global[]; + + console.log('called login api method with ', loginId, password); + deviceToken = tokens?.[0]?.value ?? ''; + user = await Client4.login(loginId, password, mfaToken, deviceToken, ldapOnly); + console.log('user =>> ', user); + + //todo : setCSRFFromCookie + // await setCSRFFromCookie(Client4.getUrl()); + } catch (error) { + return {error}; + } + + //todo : loadMe + // const result = await dispatch(loadMe(user)); + + // if (!result.error) { + // //todo: completeLogin + // // dispatch(completeLogin(user, deviceToken)); + // } + // return user; + // return result; + + return user; +}; diff --git a/app/screens/login/index.tsx b/app/screens/login/index.tsx index e689c87485..e6f254a92b 100644 --- a/app/screens/login/index.tsx +++ b/app/screens/login/index.tsx @@ -1,11 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {login} from '@requests/remote/user'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {useIntl} from 'react-intl'; import { ActivityIndicator, - Dimensions, Image, InteractionManager, Keyboard, @@ -53,14 +53,22 @@ const Login: NavigationFunctionComponent = ({config, license, theme}: LoginProps const managedConfig = useManagedConfig(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); - const [loginId, setLoginId] = useState(''); - const [password, setPassword] = useState(''); + //fixme: remove hardcoded value for loginId and password + const [loginId, setLoginId] = useState('avinash.lingaloo@mattermost.com'); + const [password, setPassword] = useState('AluminiumZ545*'); + + //fixme: is this necessary ? // useEffect for orientation change - useEffect(() => { - Dimensions.addEventListener('change', handleOrientationDidChange); - return () => Dimensions.removeEventListener('change', handleOrientationDidChange); - }, []); + // useEffect(() => { + // const handleOrientationDidChange = () => { + // if (this.scroll.current) { + // this.scroll.current.scrollTo({x: 0, y: 0, animated: true}); + // } + // }; + // Dimensions.addEventListener('change', handleOrientationDidChange); + // return () => Dimensions.removeEventListener('change', handleOrientationDidChange); + // }, []); // useEffect to set userName for EMM useEffect(() => { @@ -74,14 +82,10 @@ const Login: NavigationFunctionComponent = ({config, license, theme}: LoginProps setEmmUsernameIfAvailable(); }, []); - const handleOrientationDidChange = () => { - if (this.scroll.current) { - this.scroll.current.scrollTo({x: 0, y: 0, animated: true}); - } - }; - const preSignIn = preventDoubleTap(() => { + const preSignIn = preventDoubleTap(async () => { setIsLoading(true); setError(null); + Keyboard.dismiss(); InteractionManager.runAfterInteractions(async () => { if (!loginId) { @@ -111,43 +115,35 @@ const Login: NavigationFunctionComponent = ({config, license, theme}: LoginProps }); setIsLoading(false); - setError( - intl.formatMessage( - { - id: msgId, - defaultMessage: '', - }, - { - ldapUsername: config.LdapLoginFieldName || ldapUsername, - }, - ), - ); + setError(intl.formatMessage( + { + id: msgId, + defaultMessage: '', + }, + { + ldapUsername: config.LdapLoginFieldName || ldapUsername, + }, + )); return; } if (!password) { setIsLoading(false); - setError( - intl.formatMessage( - { - id: t('login.noPassword'), - defaultMessage: 'Please enter your password', - }, - ), - ); + setError(intl.formatMessage({ + id: t('login.noPassword'), + defaultMessage: 'Please enter your password', + })); + return; } - signIn(); }); }); const signIn = async () => { - if (isLoading) { - const result = await login(loginId.toLowerCase(), password); - if (checkLoginResponse(result)) { - goToChannel(); - } + const result = await login({loginId: loginId.toLowerCase(), password}); + if (checkLoginResponse(result)) { + goToChannel(); } }; @@ -160,10 +156,9 @@ const Login: NavigationFunctionComponent = ({config, license, theme}: LoginProps scheduleExpiredNotification(intl); }; - const checkLoginResponse = (data) => { + const checkLoginResponse = (data: any) => { if (MFA_EXPECTED_ERRORS.includes(data?.error?.server_error_id)) { // eslint-disable-line camelcase goToMfa(); - setIsLoading(false); return false; } @@ -184,11 +179,11 @@ const Login: NavigationFunctionComponent = ({config, license, theme}: LoginProps goToScreen(screen, title, {goToChannel, loginId, password}); }; - const getLoginErrorMessage = (loginError) => { + const getLoginErrorMessage = (loginError: any) => { return (getServerErrorForLogin(loginError) || loginError); }; - const getServerErrorForLogin = (serverError) => { + const getServerErrorForLogin = (serverError: any) => { if (!serverError) { return null; } @@ -338,14 +333,14 @@ const Login: NavigationFunctionComponent = ({config, license, theme}: LoginProps source={require('@assets/images/logo.png')} style={{height: 72, resizeMode: 'contain'}} /> - - {config.SiteName} + {config?.SiteName && ( + {config?.SiteName} - + )} {error && ( {getProceed()} {(config.EnableSignInWithEmail === 'true' || config.EnableSignInWithUsername === 'true') && ( diff --git a/app/screens/server/index.tsx b/app/screens/server/index.tsx index 94711e705c..e9c8e79c9d 100644 --- a/app/screens/server/index.tsx +++ b/app/screens/server/index.tsx @@ -37,7 +37,9 @@ const Server: NavigationFunctionComponent = ({theme}: ServerProps) => { const input = useRef(null); const [connecting, setConnecting] = useState(false); const [error, setError] = useState(); - const [url, setUrl] = useState(''); + + //fixme: remove hardcoded url + const [url, setUrl] = useState('https://rc.test.mattermost.com'); const styles = getStyleSheet(theme); const {formatMessage} = intl; diff --git a/package.json b/package.json index c63a68cd96..676553e43d 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "eslint-plugin-jest": "24.3.5", "eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee", "eslint-plugin-react": "7.23.2", + "eslint-plugin-react-hooks": "4.2.0", "husky": "6.0.0", "isomorphic-fetch": "3.0.0", "jest": "26.6.3", diff --git a/types/api/client4.d.ts b/types/api/client4.d.ts index 4bb3f85e68..16b4ca89d0 100644 --- a/types/api/client4.d.ts +++ b/types/api/client4.d.ts @@ -1,30 +1,30 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -declare type logLevel = 'ERROR' | 'WARNING' | 'INFO'; -declare type GenericClientResponse = { +export type logLevel = 'ERROR' | 'WARNING' | 'INFO'; +export type GenericClientResponse = { response: any; headers: Map; data: any; }; -declare type ErrorOffline = { +export type ErrorOffline = { message: string; url: string; }; -declare type ErrorInvalidResponse = { +export type ErrorInvalidResponse = { intl: { id: string; defaultMessage: string; }; }; -declare type ErrorApi = { +export type ErrorApi = { message: string; server_error_id: string; status_code: number; url: string; }; -declare type Client4Error = ErrorOffline | ErrorInvalidResponse | ErrorApi; -declare type ClientOptions = { +export type Client4Error = ErrorOffline | ErrorInvalidResponse | ErrorApi; +export type ClientOptions = { headers?: { [x: string]: string; };