aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShravya Ramesh <37447613+shravyaramesh@users.noreply.github.com>2020-10-07 23:06:32 -0700
committerGitHub <noreply@github.com>2020-10-08 02:06:32 -0400
commit45e435dbb4c43cb890eb360413784d0b2e331bc5 (patch)
treecaa1df04c7b5fcc70ba2c48fa780a4cf2d8e5e0d /src
parent0f332655d2b64700623f25912d2610517fb954b6 (diff)
[TMA 68] Frontend Token Security (#43)
* frontend tma-68 token security * removed: try catch while storing token to async, unnecessary console.log * login/registration exception handling and relocation * Modified promises, applied fetch restriction
Diffstat (limited to 'src')
-rw-r--r--src/components/search/SearchResult.tsx16
-rw-r--r--src/routes/authentication/AuthProvider.tsx66
-rw-r--r--src/screens/onboarding/Login.tsx24
-rw-r--r--src/screens/onboarding/ProfileOnboarding.tsx4
-rw-r--r--src/screens/onboarding/RegistrationThree.tsx14
-rw-r--r--src/screens/onboarding/RegistrationTwo.tsx1
-rw-r--r--src/screens/search/SearchScreen.tsx14
7 files changed, 116 insertions, 23 deletions
diff --git a/src/components/search/SearchResult.tsx b/src/components/search/SearchResult.tsx
index e65be1f4..952f08f7 100644
--- a/src/components/search/SearchResult.tsx
+++ b/src/components/search/SearchResult.tsx
@@ -11,6 +11,11 @@ import {
import RNFetchBlob from 'rn-fetch-blob';
import AsyncStorage from '@react-native-community/async-storage';
import {AVATAR_PHOTO_ENDPOINT} from '../../constants';
+import {UserType} from '../../types';
+const NO_USER: UserType = {
+ userId: '',
+ username: '',
+};
interface SearchResultProps extends ViewProps {
profilePreview: ProfilePreviewType;
@@ -20,15 +25,22 @@ const SearchResult: React.FC<SearchResultProps> = ({
style,
}) => {
const [avatarURI, setAvatarURI] = useState<string | null>(null);
-
+ const [user, setUser] = useState<UserType>(NO_USER);
useEffect(() => {
let mounted = true;
const loadAvatar = async () => {
try {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) {
+ setUser(NO_USER);
+ return;
+ }
const response = await RNFetchBlob.config({
fileCache: true,
appendExt: 'jpg',
- }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${id}`);
+ }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${id}`, {
+ Authorization: 'Token ' + token,
+ });
const status = response.info().status;
if (status === 200) {
if (mounted) {
diff --git a/src/routes/authentication/AuthProvider.tsx b/src/routes/authentication/AuthProvider.tsx
index 589cb051..e5956eb2 100644
--- a/src/routes/authentication/AuthProvider.tsx
+++ b/src/routes/authentication/AuthProvider.tsx
@@ -14,6 +14,7 @@ import {
COVER_PHOTO_ENDPOINT,
GET_IG_POSTS_ENDPOINT,
} from '../../constants';
+import {Alert} from 'react-native';
interface AuthContextProps {
user: UserType;
@@ -57,16 +58,18 @@ const AuthProvider: React.FC = ({children}) => {
const [recentSearches, setRecentSearches] = useState<
Array<ProfilePreviewType>
>([]);
-
const {userId} = user;
useEffect(() => {
if (!userId) {
return;
}
- const loadProfileInfo = async () => {
+ const loadProfileInfo = async (token: string) => {
try {
const response = await fetch(PROFILE_INFO_ENDPOINT + `${userId}/`, {
method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
});
const status = response.status;
if (status === 200) {
@@ -75,15 +78,20 @@ const AuthProvider: React.FC = ({children}) => {
setProfile({name, biography, website});
}
} catch (error) {
- console.log(error);
+ Alert.alert(
+ 'Something went wrong! 😭',
+ "Would you believe me if I told you that I don't know what happened?",
+ );
}
};
- const loadAvatar = async () => {
+ const loadAvatar = async (token: string) => {
try {
const response = await RNFetchBlob.config({
fileCache: true,
appendExt: 'jpg',
- }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${userId}/`);
+ }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${userId}/`, {
+ Authorization: 'Token ' + token,
+ });
const status = response.info().status;
if (status === 200) {
setAvatar(response.path());
@@ -94,12 +102,14 @@ const AuthProvider: React.FC = ({children}) => {
console.log(error);
}
};
- const loadCover = async () => {
+ const loadCover = async (token: string) => {
try {
let response = await RNFetchBlob.config({
fileCache: true,
appendExt: 'jpg',
- }).fetch('GET', COVER_PHOTO_ENDPOINT + `${userId}/`);
+ }).fetch('GET', COVER_PHOTO_ENDPOINT + `${userId}/`, {
+ Authorization: 'Token ' + token,
+ });
const status = response.info().status;
if (status === 200) {
setCover(response.path());
@@ -110,10 +120,13 @@ const AuthProvider: React.FC = ({children}) => {
console.log(error);
}
};
- const loadInstaPosts = async () => {
+ const loadInstaPosts = async (token: string) => {
try {
const response = await fetch(GET_IG_POSTS_ENDPOINT + `${userId}/`, {
method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
});
const status = response.status;
if (status === 200) {
@@ -124,6 +137,10 @@ const AuthProvider: React.FC = ({children}) => {
}
} catch (error) {
console.log(error);
+ Alert.alert(
+ 'Something went wrong! 😭',
+ "Would you believe me if I told you that I don't know what happened?",
+ );
}
};
const loadRecentlySearchedUsers = async () => {
@@ -136,11 +153,24 @@ const AuthProvider: React.FC = ({children}) => {
console.log(e);
}
};
- loadProfileInfo();
- loadAvatar();
- loadCover();
- loadInstaPosts();
- loadRecentlySearchedUsers();
+
+ const loadData = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) {
+ setUser(NO_USER);
+ return;
+ }
+ loadProfileInfo(token);
+ loadAvatar(token);
+ loadCover(token);
+ loadInstaPosts(token);
+ loadRecentlySearchedUsers();
+ } catch (err) {
+ console.log(err);
+ }
+ };
+ loadData();
}, [userId]);
return (
@@ -155,7 +185,15 @@ const AuthProvider: React.FC = ({children}) => {
setUser({...user, userId: id, username});
},
logout: () => {
- setUser(NO_USER);
+ try {
+ new Promise(() => {
+ AsyncStorage.removeItem('token');
+ }).then(() => {
+ setUser(NO_USER);
+ });
+ } catch (err) {
+ console.log(err);
+ }
},
recentSearches,
}}>
diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx
index 5c569ec3..c0dc14b7 100644
--- a/src/screens/onboarding/Login.tsx
+++ b/src/screens/onboarding/Login.tsx
@@ -1,4 +1,4 @@
-import React, {useRef} from 'react';
+import React, {useRef, useState} from 'react';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
import {
@@ -17,6 +17,8 @@ import {OnboardingStackParams} from '../../routes/onboarding';
import {AuthContext} from '../../routes/authentication';
import {Background, TaggInput, SubmitButton} from '../../components';
import {usernameRegex, LOGIN_ENDPOINT} from '../../constants';
+import AsyncStorage from '@react-native-community/async-storage';
+import {UserType} from '../../types';
type VerificationScreenRouteProp = RouteProp<OnboardingStackParams, 'Login'>;
type VerificationScreenNavigationProp = StackNavigationProp<
@@ -34,6 +36,12 @@ interface LoginProps {
const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => {
// ref for focusing on input fields
const inputRef = useRef();
+
+ const NO_USER: UserType = {
+ userId: '',
+ username: '',
+ };
+
// login form state
const [form, setForm] = React.useState({
username: '',
@@ -41,10 +49,11 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => {
isValidUser: false,
isValidPassword: false,
attemptedSubmit: false,
+ token: '',
});
// determines if user is logged in
const {login} = React.useContext(AuthContext);
-
+ const [user, setUser] = useState<UserType>(NO_USER);
/**
* Updates the state of username. Also verifies the input of the username field by ensuring proper length and appropriate characters.
*/
@@ -101,6 +110,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => {
/**
* Handler for the Let's Start button or the Go button on the keyboard.
Makes a POST request to the Django login API and presents Alerts based on the status codes that the backend returns.
+ * Stores token received in the response, into client's AsynStorage
*/
const handleLogin = async () => {
if (!form.attemptedSubmit) {
@@ -122,8 +132,16 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => {
let statusCode = response.status;
let data = await response.json();
+
if (statusCode === 200) {
- login(data.UserID, username);
+ //Stores token received in the response into client's AsynStorage
+ try {
+ await AsyncStorage.setItem('token', data.token);
+ login(data.UserID, username);
+ } catch (err) {
+ setUser(NO_USER);
+ Alert.alert('Auth token storage failed', 'Please login again!');
+ }
} else if (statusCode === 401) {
Alert.alert(
'Login failed 😔',
diff --git a/src/screens/onboarding/ProfileOnboarding.tsx b/src/screens/onboarding/ProfileOnboarding.tsx
index 814cd82e..506d5f63 100644
--- a/src/screens/onboarding/ProfileOnboarding.tsx
+++ b/src/screens/onboarding/ProfileOnboarding.tsx
@@ -27,6 +27,7 @@ import {
genderRegex,
} from '../../constants';
import moment from 'moment';
+import AsyncStorage from '@react-native-community/async-storage';
type ProfileOnboardingScreenRouteProp = RouteProp<
OnboardingStackParams,
@@ -59,6 +60,7 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => {
isValidBio: true,
isValidGender: true,
attemptedSubmit: false,
+ token: '',
});
const [customGender, setCustomGender] = React.useState();
@@ -314,10 +316,12 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => {
const endpoint = EDIT_PROFILE_ENDPOINT + `${userId}/`;
try {
+ const token = await AsyncStorage.getItem('token');
let response = await fetch(endpoint, {
method: 'PATCH',
headers: {
'Content-Type': 'multipart/form-data',
+ Authorization: 'Token ' + token,
},
body: request,
});
diff --git a/src/screens/onboarding/RegistrationThree.tsx b/src/screens/onboarding/RegistrationThree.tsx
index f8daaf71..5b8f52b3 100644
--- a/src/screens/onboarding/RegistrationThree.tsx
+++ b/src/screens/onboarding/RegistrationThree.tsx
@@ -28,6 +28,7 @@ import {
Background,
} from '../../components';
import {passwordRegex, usernameRegex, REGISTER_ENDPOINT} from '../../constants';
+import AsyncStorage from '@react-native-community/async-storage';
type RegistrationScreenThreeRouteProp = RouteProp<
OnboardingStackParams,
@@ -170,10 +171,15 @@ const RegistrationThree: React.FC<RegistrationThreeProps> = ({
let data = await registerResponse.json();
const userId: string = data.UserID;
if (statusCode === 201) {
- navigation.navigate('Checkpoint', {
- userId: userId,
- username: form.username,
- });
+ try {
+ await AsyncStorage.setItem('token', data.token);
+ navigation.navigate('Checkpoint', {
+ userId: userId,
+ username: form.username,
+ });
+ } catch (err) {
+ console.log(err);
+ }
} else if (statusCode === 409) {
Alert.alert('Registration failed 😔', `${data}`);
} else {
diff --git a/src/screens/onboarding/RegistrationTwo.tsx b/src/screens/onboarding/RegistrationTwo.tsx
index 0ce4f410..d28fb197 100644
--- a/src/screens/onboarding/RegistrationTwo.tsx
+++ b/src/screens/onboarding/RegistrationTwo.tsx
@@ -70,6 +70,7 @@ const RegistrationTwo: React.FC<RegistrationTwoProps> = ({
isValidFname: false,
isValidLname: false,
attemptedSubmit: false,
+ token: '',
});
/*
diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx
index 2a2a5a4a..da83ddef 100644
--- a/src/screens/search/SearchScreen.tsx
+++ b/src/screens/search/SearchScreen.tsx
@@ -16,6 +16,11 @@ import AsyncStorage from '@react-native-community/async-storage';
import {ProfilePreviewType} from '../../types';
import {SEARCH_ENDPOINT} from '../../constants';
import {AuthContext} from '../../routes/authentication';
+import {UserType} from '../../types';
+const NO_USER: UserType = {
+ userId: '',
+ username: '',
+};
/**
* Search Screen for user recommendations and a search
@@ -31,6 +36,7 @@ const SearchScreen: React.FC = () => {
);
const [searching, setSearching] = useState(false);
const top = Animated.useValue(-SCREEN_HEIGHT);
+ const [user, setUser] = useState<UserType>(NO_USER);
useEffect(() => {
if (query.length < 3) {
setResults([]);
@@ -38,8 +44,16 @@ const SearchScreen: React.FC = () => {
}
const loadResults = async (q: string) => {
try {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) {
+ setUser(NO_USER);
+ return;
+ }
const response = await fetch(`${SEARCH_ENDPOINT}?query=${q}`, {
method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
});
const status = response.status;
if (status === 200) {