|
@@ -1,16 +1,17 @@
|
|
-import axios, { AxiosRequestConfig } from "axios"
|
|
|
|
|
|
+import axios, { AxiosRequestConfig, AxiosError } from "axios"
|
|
import { loadAsync, makeRedirectUri, ResponseType } from "expo-auth-session"
|
|
import { loadAsync, makeRedirectUri, ResponseType } from "expo-auth-session"
|
|
import { deleteItemAsync, getItemAsync, setItemAsync } from "expo-secure-store"
|
|
import { deleteItemAsync, getItemAsync, setItemAsync } from "expo-secure-store"
|
|
import jwt_decode from 'jwt-decode'
|
|
import jwt_decode from 'jwt-decode'
|
|
-import React, { createContext, useContext, useEffect, useMemo, useState } from "react"
|
|
|
|
|
|
+import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react"
|
|
import { useQueryClient } from "react-query"
|
|
import { useQueryClient } from "react-query"
|
|
-import { API_URL, reportAxiosError } from "../../utils/RequestUtils"
|
|
|
|
|
|
+import { API_URL } from "../../utils/RequestUtils"
|
|
import { Landmark, } from "../landmarks"
|
|
import { Landmark, } from "../landmarks"
|
|
import { queryKeys } from "../query-keys"
|
|
import { queryKeys } from "../query-keys"
|
|
import {v4} from 'uuid'
|
|
import {v4} from 'uuid'
|
|
import { ErrorMessage } from "formik"
|
|
import { ErrorMessage } from "formik"
|
|
-import { Alert } from "react-native"
|
|
|
|
|
|
+import { Alert, AppState } from "react-native"
|
|
import { navigate } from "../../navigation/RootNavigator"
|
|
import { navigate } from "../../navigation/RootNavigator"
|
|
|
|
+import { LogCategory, LOGGING } from "../../utils/logging"
|
|
|
|
|
|
export const SECURESTORE_ACCESSTOKEN = "access"
|
|
export const SECURESTORE_ACCESSTOKEN = "access"
|
|
export const SECURESTORE_REFRESHTOKEN = "refresh"
|
|
export const SECURESTORE_REFRESHTOKEN = "refresh"
|
|
@@ -26,18 +27,15 @@ interface AuthState {
|
|
setRefreshTokenAsync: (token: string) => Promise<void>,
|
|
setRefreshTokenAsync: (token: string) => Promise<void>,
|
|
setUserIdAsync: (id: string) => Promise<void>,
|
|
setUserIdAsync: (id: string) => Promise<void>,
|
|
clearAuthStorage: () => Promise<void>,
|
|
clearAuthStorage: () => Promise<void>,
|
|
|
|
+ setAlert: (alert: GlobalAlert) => void,
|
|
refreshToken: string,
|
|
refreshToken: string,
|
|
userId: string,
|
|
userId: string,
|
|
anonUserId: string,
|
|
anonUserId: string,
|
|
- loading: boolean,
|
|
|
|
- error: string,
|
|
|
|
- setLoading: (state: boolean) => void,
|
|
|
|
- setError: (error: string) => void,
|
|
|
|
|
|
+ authStateLoading: boolean,
|
|
|
|
+ setAuthStateLoading: (state: boolean) => void,
|
|
sendApiRequestAsync: (config: RequestConfig) => Promise<any>,
|
|
sendApiRequestAsync: (config: RequestConfig) => Promise<any>,
|
|
- login: () => Promise<void>,
|
|
|
|
|
|
+ login: () => Promise<AuthenticationResult>,
|
|
logout: () => Promise<void>,
|
|
logout: () => Promise<void>,
|
|
- refreshAccessToken: () => Promise<void>,
|
|
|
|
- getNotificationTokenFromServer: () => Promise<string>,
|
|
|
|
landmarkOwnedByUser: (landmark: Landmark) => boolean,
|
|
landmarkOwnedByUser: (landmark: Landmark) => boolean,
|
|
}
|
|
}
|
|
|
|
|
|
@@ -45,6 +43,7 @@ interface RequestConfig {
|
|
axiosConfig: AxiosRequestConfig,
|
|
axiosConfig: AxiosRequestConfig,
|
|
authorized: boolean
|
|
authorized: boolean
|
|
errorMessage: string
|
|
errorMessage: string
|
|
|
|
+ loggingCategory: LogCategory
|
|
}
|
|
}
|
|
|
|
|
|
export interface IdToken {
|
|
export interface IdToken {
|
|
@@ -60,14 +59,17 @@ interface GlobalAlert {
|
|
title: string
|
|
title: string
|
|
message: string
|
|
message: string
|
|
type: 'success' | 'error' | 'warning'
|
|
type: 'success' | 'error' | 'warning'
|
|
- callback: () => void
|
|
|
|
|
|
+ callback?: () => void,
|
|
|
|
+ callbackButtonText?: string
|
|
}
|
|
}
|
|
|
|
|
|
const setStorageItem = async (key: string, value: string) => {
|
|
const setStorageItem = async (key: string, value: string) => {
|
|
if (value) {
|
|
if (value) {
|
|
|
|
+ LOGGING.log('SYSTEM', 'info', "Setting storage item for: " + key)
|
|
await setItemAsync(key, value)
|
|
await setItemAsync(key, value)
|
|
}
|
|
}
|
|
else {
|
|
else {
|
|
|
|
+ LOGGING.log('SYSTEM', 'info', "Deleting storage item: " + key)
|
|
await deleteItemAsync(key)
|
|
await deleteItemAsync(key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -93,21 +95,31 @@ const AuthContext = createContext(null)
|
|
});
|
|
});
|
|
|
|
|
|
export const AuthContextProvider: React.FC = ({children}) => {
|
|
export const AuthContextProvider: React.FC = ({children}) => {
|
|
- const [accessToken, setAccessToken] = useState<string>()
|
|
|
|
|
|
+ const [accessToken, _setAccessToken] = useState<string>()
|
|
const [refreshToken, setRefreshToken] = useState<string>()
|
|
const [refreshToken, setRefreshToken] = useState<string>()
|
|
const [notificationToken, setNotificationToken] = useState<string>()
|
|
const [notificationToken, setNotificationToken] = useState<string>()
|
|
const [userId, setUserId] = useState<string>()
|
|
const [userId, setUserId] = useState<string>()
|
|
const [anonUserId, setAnonUserId] = useState<string>()
|
|
const [anonUserId, setAnonUserId] = useState<string>()
|
|
- const [loading, setLoading] = useState<boolean>(false)
|
|
|
|
|
|
+ const [authStateLoading, setAuthStateLoading] = useState<boolean>(false)
|
|
const [alert, setAlert] = useState<GlobalAlert>()
|
|
const [alert, setAlert] = useState<GlobalAlert>()
|
|
|
|
|
|
|
|
+ const refreshingToken = useRef<boolean>(false)
|
|
|
|
+ const accessTokenRef = useRef<string>(accessToken)
|
|
|
|
+
|
|
|
|
+ const setAccessToken = (token: string) => {
|
|
|
|
+ accessTokenRef.current = token
|
|
|
|
+ _setAccessToken(token)
|
|
|
|
+ }
|
|
|
|
+
|
|
const queryClient = useQueryClient()
|
|
const queryClient = useQueryClient()
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
const loadAuthStateFromStorageOnAppLoad = async () => {
|
|
const loadAuthStateFromStorageOnAppLoad = async () => {
|
|
|
|
+ LOGGING.log("AUTH", 'info', "App started, loading existing auth state from storage...")
|
|
const accessTokenFromStorage = await getItemAsync(SECURESTORE_ACCESSTOKEN)
|
|
const accessTokenFromStorage = await getItemAsync(SECURESTORE_ACCESSTOKEN)
|
|
|
|
|
|
if (accessTokenFromStorage) {
|
|
if (accessTokenFromStorage) {
|
|
|
|
+ LOGGING.log("AUTH", 'info', "Access token found in storage, testing if it is valid...")
|
|
try {
|
|
try {
|
|
const response = await sendApiRequestAsync({
|
|
const response = await sendApiRequestAsync({
|
|
axiosConfig: {
|
|
axiosConfig: {
|
|
@@ -116,23 +128,28 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
headers: {Authorization: 'Bearer ' + accessTokenFromStorage}
|
|
headers: {Authorization: 'Bearer ' + accessTokenFromStorage}
|
|
},
|
|
},
|
|
authorized: false,
|
|
authorized: false,
|
|
- errorMessage: 'Failed to retrieve user data from server'})
|
|
|
|
|
|
+ errorMessage: 'Failed to retrieve user data from server',
|
|
|
|
+ loggingCategory: 'AUTH'
|
|
|
|
+ })
|
|
|
|
|
|
if (response.status == 200) {
|
|
if (response.status == 200) {
|
|
setAccessToken(accessTokenFromStorage)
|
|
setAccessToken(accessTokenFromStorage)
|
|
setRefreshToken(await getItemAsync(SECURESTORE_REFRESHTOKEN))
|
|
setRefreshToken(await getItemAsync(SECURESTORE_REFRESHTOKEN))
|
|
setNotificationToken(await getItemAsync(SECURESTORE_NOTIFTOKEN))
|
|
setNotificationToken(await getItemAsync(SECURESTORE_NOTIFTOKEN))
|
|
setUserId(await getItemAsync(SECURESTORE_ID))
|
|
setUserId(await getItemAsync(SECURESTORE_ID))
|
|
|
|
+ LOGGING.log("AUTH", 'info', "Access token is valid, auth state has been loaded...")
|
|
return
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- catch {}
|
|
|
|
|
|
+ catch {
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
await setAccessTokenAsync("")
|
|
await setAccessTokenAsync("")
|
|
await setRefreshTokenAsync("")
|
|
await setRefreshTokenAsync("")
|
|
await setNotificationTokenAsync('')
|
|
await setNotificationTokenAsync('')
|
|
await setUserIdAsync('')
|
|
await setUserIdAsync('')
|
|
|
|
+ LOGGING.log("AUTH", 'info', "No auth state found in storage, starting with an empty auth state...")
|
|
|
|
|
|
let anonUserId = await getItemAsync(SECURESTORE_ANONID)
|
|
let anonUserId = await getItemAsync(SECURESTORE_ANONID)
|
|
if (anonUserId) {
|
|
if (anonUserId) {
|
|
@@ -143,14 +160,53 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
await setItemAsync(SECURESTORE_ANONID, anonUserId)
|
|
await setItemAsync(SECURESTORE_ANONID, anonUserId)
|
|
setAnonUserId(anonUserId)
|
|
setAnonUserId(anonUserId)
|
|
}
|
|
}
|
|
|
|
+ LOGGING.log("AUTH", 'info', "Created anonymous id for non account user: " + anonUserId)
|
|
}
|
|
}
|
|
loadAuthStateFromStorageOnAppLoad()
|
|
loadAuthStateFromStorageOnAppLoad()
|
|
}, [])
|
|
}, [])
|
|
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
+ const checkAuthStateOnAppForeground = async (state) => {
|
|
|
|
+ let currentAccessToken = accessTokenRef.current
|
|
|
|
+ if (currentAccessToken === undefined) {
|
|
|
|
+ currentAccessToken = await getItemAsync(SECURESTORE_ACCESSTOKEN)
|
|
|
|
+ console.log("access token found in storage:" + currentAccessToken)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (currentAccessToken && state == 'active') {
|
|
|
|
+ LOGGING.log("AUTH", 'info', "App was foregrounded and access token found, checking if it is still valid...")
|
|
|
|
+ try {
|
|
|
|
+ const response = await sendApiRequestAsync({
|
|
|
|
+ axiosConfig: {
|
|
|
|
+ method: 'GET',
|
|
|
|
+ url: '/api/me/',
|
|
|
|
+ headers: {Authorization: 'Bearer ' + accessTokenRef.current}
|
|
|
|
+ },
|
|
|
|
+ authorized: false,
|
|
|
|
+ errorMessage: 'Failed to retrieve user data from server while checking access token',
|
|
|
|
+ loggingCategory: 'AUTH'
|
|
|
|
+ })
|
|
|
|
+ if (response.status == 200) {
|
|
|
|
+ LOGGING.log("AUTH", 'info', "Access token is valid, no action required")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ AppState.addEventListener('change', checkAuthStateOnAppForeground)
|
|
|
|
+
|
|
|
|
+ return () => {
|
|
|
|
+ AppState.removeEventListener('change', checkAuthStateOnAppForeground)
|
|
|
|
+ }
|
|
|
|
+ }, [])
|
|
|
|
+
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
if (alert) {
|
|
if (alert) {
|
|
|
|
+ LOGGING.log("SYSTEM", 'info', "Showing alert")
|
|
|
|
+ let buttons = [{text: alert.callbackButtonText ? alert.callbackButtonText : 'OK', onPress: alert.callback}, alert.callbackButtonText ? {text: 'Cancel'} : null]
|
|
const alertTitle = alert.title
|
|
const alertTitle = alert.title
|
|
- Alert.alert(alertTitle, alert.message, [{text: 'OK', onPress: alert.callback}])
|
|
|
|
|
|
+ Alert.alert(alertTitle, alert.message, buttons)
|
|
setAlert(undefined)
|
|
setAlert(undefined)
|
|
}
|
|
}
|
|
}, [alert])
|
|
}, [alert])
|
|
@@ -166,22 +222,22 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
|
|
|
|
const setAccessTokenAsync = async (token: string) => {
|
|
const setAccessTokenAsync = async (token: string) => {
|
|
setAccessToken(token)
|
|
setAccessToken(token)
|
|
- setStorageItem(SECURESTORE_ACCESSTOKEN, token)
|
|
|
|
|
|
+ await setStorageItem(SECURESTORE_ACCESSTOKEN, token)
|
|
}
|
|
}
|
|
|
|
|
|
const setRefreshTokenAsync = async (token: string) => {
|
|
const setRefreshTokenAsync = async (token: string) => {
|
|
setRefreshToken(token)
|
|
setRefreshToken(token)
|
|
- setStorageItem(SECURESTORE_REFRESHTOKEN, token)
|
|
|
|
|
|
+ await setStorageItem(SECURESTORE_REFRESHTOKEN, token)
|
|
}
|
|
}
|
|
|
|
|
|
const setUserIdAsync = async (id: string) => {
|
|
const setUserIdAsync = async (id: string) => {
|
|
setUserId(id)
|
|
setUserId(id)
|
|
- setStorageItem(SECURESTORE_ID, id)
|
|
|
|
|
|
+ await setStorageItem(SECURESTORE_ID, id)
|
|
}
|
|
}
|
|
|
|
|
|
const setNotificationTokenAsync = async (token: string) => {
|
|
const setNotificationTokenAsync = async (token: string) => {
|
|
setNotificationToken(token)
|
|
setNotificationToken(token)
|
|
- setStorageItem(SECURESTORE_NOTIFTOKEN, token)
|
|
|
|
|
|
+ await setStorageItem(SECURESTORE_NOTIFTOKEN, token)
|
|
}
|
|
}
|
|
|
|
|
|
const clearAuthStorage = async () => {
|
|
const clearAuthStorage = async () => {
|
|
@@ -193,7 +249,7 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
])
|
|
])
|
|
}
|
|
}
|
|
|
|
|
|
- const sendApiRequestAsync = async ({axiosConfig, authorized = false, errorMessage = 'An error occured'}: RequestConfig) => {
|
|
|
|
|
|
+ const sendApiRequestAsync = async ({axiosConfig, authorized = false, errorMessage = 'An error occured', loggingCategory = "SYSTEM"}: RequestConfig) => {
|
|
if (authorized && !axiosConfig?.headers?.Authorization) {
|
|
if (authorized && !axiosConfig?.headers?.Authorization) {
|
|
axiosConfig.headers = {
|
|
axiosConfig.headers = {
|
|
...axiosConfig.headers,
|
|
...axiosConfig.headers,
|
|
@@ -206,78 +262,92 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
try {
|
|
try {
|
|
return await axios(axiosConfig)
|
|
return await axios(axiosConfig)
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- console.log(error.response.request._headers)
|
|
|
|
- reportAxiosError(errorMessage, error)
|
|
|
|
|
|
+ const axiosError = error as AxiosError
|
|
|
|
+ if (axiosError.response.status == 401 || axiosError.response.status == 403) {
|
|
|
|
+ if (!refreshingToken.current) {
|
|
|
|
+ await refreshAccessToken()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ LOGGING.log("AUTH", 'error', errorMessage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
const login = async (): Promise<AuthenticationResult> => {
|
|
const login = async (): Promise<AuthenticationResult> => {
|
|
- setLoading(true)
|
|
|
|
- console.log('[Authentication]: User is attempting to login, opening web portal login...')
|
|
|
|
-
|
|
|
|
- // initiate authentication request to the server
|
|
|
|
- const request = await loadAsync({
|
|
|
|
- clientId: "atlas.mobile",
|
|
|
|
- responseType: ResponseType.Code,
|
|
|
|
- redirectUri,
|
|
|
|
- usePKCE: true,
|
|
|
|
- scopes: ['openid'],
|
|
|
|
-
|
|
|
|
- }, discovery)
|
|
|
|
-
|
|
|
|
- // handle authentication response from the server
|
|
|
|
- const response = await request.promptAsync(discovery);
|
|
|
|
-
|
|
|
|
- // if succesful, prepare a request for an access/id token
|
|
|
|
- if (response.type == "success" && request.codeVerifier) {
|
|
|
|
- console.log('[Authentication]: User authentication was successful.')
|
|
|
|
- const tokenData = new URLSearchParams();
|
|
|
|
- tokenData.append('grant_type', 'authorization_code');
|
|
|
|
- tokenData.append('client_id', 'atlas.mobile');
|
|
|
|
- tokenData.append('code', response.params.code);
|
|
|
|
- tokenData.append('redirect_uri', request.redirectUri);
|
|
|
|
- tokenData.append('code_verifier', request.codeVerifier);
|
|
|
|
- console.log('[Authentication]: Attempting to retrieve access token...')
|
|
|
|
-
|
|
|
|
- // send the token request
|
|
|
|
- try {
|
|
|
|
- const response = await axios.post(API_URL + `/o/token/`, tokenData, {
|
|
|
|
- headers: {
|
|
|
|
- 'Content-Type': "application/x-www-form-urlencoded"
|
|
|
|
- },
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- const tokenResponse = response.data;
|
|
|
|
-
|
|
|
|
- // if its a successful response, decode the jwt id token, and store the tokens in the corresponding stores
|
|
|
|
- const idToken = jwt_decode(tokenResponse.id_token) as IdToken;
|
|
|
|
-
|
|
|
|
- setLoading(false)
|
|
|
|
-
|
|
|
|
- await setAccessTokenAsync(tokenResponse.access_token);
|
|
|
|
- await setRefreshTokenAsync(tokenResponse.refresh_token);
|
|
|
|
- await setUserIdAsync(idToken.sub)
|
|
|
|
-
|
|
|
|
- console.log('[Authentication]: Tokens successfully retrieved.')
|
|
|
|
|
|
+ try {
|
|
|
|
+ setAuthStateLoading(true)
|
|
|
|
+ LOGGING.log('AUTH', 'info', "Starting login process...")
|
|
|
|
+
|
|
|
|
+ // initiate authentication request to the server
|
|
|
|
+ const request = await loadAsync({
|
|
|
|
+ clientId: "atlas.mobile",
|
|
|
|
+ responseType: ResponseType.Code,
|
|
|
|
+ redirectUri,
|
|
|
|
+ usePKCE: true,
|
|
|
|
+ scopes: ['openid'],
|
|
|
|
|
|
-
|
|
|
|
- return {success: true}
|
|
|
|
- } catch (error) {
|
|
|
|
- reportAxiosError("Something went wrong when retrieving access token", error);
|
|
|
|
- setLoading(false)
|
|
|
|
- setAlert({title:"Error", message: "Something went wrong when retrieving access token", callback: () => {}, type: 'error'})
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- else if (response.type == "cancel") {
|
|
|
|
- setLoading(false)
|
|
|
|
|
|
+ }, discovery)
|
|
|
|
+
|
|
|
|
+ // handle authentication response from the server
|
|
|
|
+ LOGGING.log('AUTH', 'info', "Prompting user with web browser...")
|
|
|
|
+ const response = await request.promptAsync(discovery);
|
|
|
|
+
|
|
|
|
+ // if succesful, prepare a request for an access/id token
|
|
|
|
+ if (response.type == "success" && request.codeVerifier) {
|
|
|
|
+ const tokenData = new URLSearchParams();
|
|
|
|
+ tokenData.append('grant_type', 'authorization_code');
|
|
|
|
+ tokenData.append('client_id', 'atlas.mobile');
|
|
|
|
+ tokenData.append('code', response.params.code);
|
|
|
|
+ tokenData.append('redirect_uri', request.redirectUri);
|
|
|
|
+ tokenData.append('code_verifier', request.codeVerifier);
|
|
|
|
+ LOGGING.log('AUTH', 'info', "User successfully authenticated, attempting to get access token...")
|
|
|
|
+
|
|
|
|
+ // send the token request
|
|
|
|
+ try {
|
|
|
|
+ const response = await axios.post(API_URL + `/o/token/`, tokenData, {
|
|
|
|
+ headers: {
|
|
|
|
+ 'Content-Type': "application/x-www-form-urlencoded"
|
|
|
|
+ },
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const tokenResponse = response.data;
|
|
|
|
+
|
|
|
|
+ // if its a successful response, decode the jwt id token, and store the tokens in the corresponding stores
|
|
|
|
+ const idToken = jwt_decode(tokenResponse.id_token) as IdToken;
|
|
|
|
+
|
|
|
|
+ setAuthStateLoading(false)
|
|
|
|
+
|
|
|
|
+ await setAccessTokenAsync(tokenResponse.access_token);
|
|
|
|
+ await setRefreshTokenAsync(tokenResponse.refresh_token);
|
|
|
|
+ await setUserIdAsync(idToken.sub)
|
|
|
|
+
|
|
|
|
+ LOGGING.log('AUTH', 'info', "Successfully retrieved access token, login process completed")
|
|
|
|
+
|
|
|
|
+ return {success: true}
|
|
|
|
+ } catch (error) {
|
|
|
|
+ LOGGING.log('AUTH', 'error', "An error occured when trying to retrieve access token: " + error)
|
|
|
|
+ setAuthStateLoading(false)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (response.type == "cancel") {
|
|
|
|
+ LOGGING.log('AUTH', 'info', "User canceled login")
|
|
|
|
+ setAuthStateLoading(false)
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ LOGGING.log('AUTH', 'error', "An error occured when trying to authenticate user: " + response.type)
|
|
|
|
+ setAlert({title: 'Login error', message: "Something went wrong while logging in. Please try again.", callback: () => {}, type: 'error'})
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- else {
|
|
|
|
- setAlert({title: 'Error', message: "Something went wrong while logging in. Please try again.", callback: () => {}, type: 'error'})
|
|
|
|
|
|
+ catch (error) {
|
|
|
|
+ LOGGING.log('AUTH', 'error', "An error occured when trying to authenticate user: " + error)
|
|
|
|
+ setAlert({title: 'Login error', message: "Something went wrong while logging in. Please try again.", callback: () => {}, type: 'error'})
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return {success: false}
|
|
}
|
|
}
|
|
|
|
|
|
const logout = async () => {
|
|
const logout = async () => {
|
|
- setLoading(true)
|
|
|
|
|
|
+ LOGGING.log('AUTH', 'info', "Starting logout process...")
|
|
|
|
+ setAuthStateLoading(true)
|
|
try {
|
|
try {
|
|
const tokenParams = new URLSearchParams();
|
|
const tokenParams = new URLSearchParams();
|
|
tokenParams.append('client_id', 'atlas.mobile');
|
|
tokenParams.append('client_id', 'atlas.mobile');
|
|
@@ -292,16 +362,18 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
queryClient.setQueryData(queryKeys.getOwnedProfile, null)
|
|
queryClient.setQueryData(queryKeys.getOwnedProfile, null)
|
|
await setAnonUserId(await getItemAsync(SECURESTORE_ANONID))
|
|
await setAnonUserId(await getItemAsync(SECURESTORE_ANONID))
|
|
await clearAuthStorage()
|
|
await clearAuthStorage()
|
|
|
|
+ LOGGING.log('AUTH', 'info', "Successfully logged out. Local data has been cleared, and the device's anonymous id has been reloaded.")
|
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- reportAxiosError("Something went wrong when logging out", error);
|
|
|
|
|
|
+ LOGGING.log('AUTH', 'info', "Something when wrong while logging out: " + error)
|
|
setAlert({title: 'Error', message: "Something went wrong while logging out. Please try again.", callback: () => {}, type: 'error'})
|
|
setAlert({title: 'Error', message: "Something went wrong while logging out. Please try again.", callback: () => {}, type: 'error'})
|
|
}
|
|
}
|
|
- setLoading(false)
|
|
|
|
|
|
+ setAuthStateLoading(false)
|
|
}
|
|
}
|
|
|
|
|
|
const refreshAccessToken = async () => {
|
|
const refreshAccessToken = async () => {
|
|
- setLoading(true)
|
|
|
|
|
|
+ let success = true;
|
|
|
|
+ refreshingToken.current = true
|
|
let currentRefreshToken = refreshToken
|
|
let currentRefreshToken = refreshToken
|
|
if (!currentRefreshToken) {
|
|
if (!currentRefreshToken) {
|
|
currentRefreshToken = await getItemAsync(SECURESTORE_REFRESHTOKEN);
|
|
currentRefreshToken = await getItemAsync(SECURESTORE_REFRESHTOKEN);
|
|
@@ -323,41 +395,58 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
|
|
|
|
console.info('Successfully refreshed access token.')
|
|
console.info('Successfully refreshed access token.')
|
|
|
|
|
|
|
|
+ setAlert({title: 'Login timeout', message: "It looks like you've been away for awhile! We were able to log you back in.", callback: () => {}, type: 'success'})
|
|
}
|
|
}
|
|
catch (error) {
|
|
catch (error) {
|
|
- reportAxiosError("[Authentication]: Error when trying to refresh access token", error);
|
|
|
|
|
|
+ setAlert({
|
|
|
|
+ title: 'Login timeout',
|
|
|
|
+ message: "It looks like you've been away for awhile! Unfortunately we weren't able to log you back in, you'll have to do that manually.",
|
|
|
|
+ callback: () => {navigate('Account')},
|
|
|
|
+ type: 'error',
|
|
|
|
+ callbackButtonText: "Go to login"
|
|
|
|
+ })
|
|
|
|
+ success = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- setLoading(false)
|
|
|
|
|
|
+ refreshingToken.current = false
|
|
|
|
+ return success
|
|
}
|
|
}
|
|
|
|
|
|
const landmarkOwnedByUser = (landmark: Landmark) => {
|
|
const landmarkOwnedByUser = (landmark: Landmark) => {
|
|
- const owned = landmark?.user == userId || landmark?.anonymous == anonUserId
|
|
|
|
|
|
+ console.log(`owned by logged in user: ${landmark?.user == userId}`)
|
|
|
|
+ console.log(`owned by anon user: ${landmark?.anonymous == anonUserId}`)
|
|
|
|
+ const owned = landmark?.user && landmark?.user == userId || landmark?.anonymous && landmark?.anonymous == anonUserId
|
|
return owned
|
|
return owned
|
|
}
|
|
}
|
|
|
|
|
|
const convertExistingAnonymousLandmarks = async () => {
|
|
const convertExistingAnonymousLandmarks = async () => {
|
|
try {
|
|
try {
|
|
|
|
+ LOGGING.log("AUTH", 'info', "Checking server to see if new logged in user has any anonymous landmarks...")
|
|
const response = await sendApiRequestAsync({
|
|
const response = await sendApiRequestAsync({
|
|
axiosConfig: {
|
|
axiosConfig: {
|
|
method: 'GET',
|
|
method: 'GET',
|
|
url: `/api/landmarks/anon/${anonUserId}/`
|
|
url: `/api/landmarks/anon/${anonUserId}/`
|
|
},
|
|
},
|
|
authorized: true,
|
|
authorized: true,
|
|
- errorMessage: 'An error occured while checking for anonymous landmarks'
|
|
|
|
|
|
+ errorMessage: 'An error occured while checking for anonymous landmarks',
|
|
|
|
+ loggingCategory: "AUTH",
|
|
})
|
|
})
|
|
|
|
|
|
if (response?.data?.has_landmark) {
|
|
if (response?.data?.has_landmark) {
|
|
// send request to convert landarks
|
|
// send request to convert landarks
|
|
|
|
+ LOGGING.log("AUTH", 'info', "Anonymous landmarks found belonging to user. Converting to owned landmarks...")
|
|
await sendApiRequestAsync({
|
|
await sendApiRequestAsync({
|
|
axiosConfig: {
|
|
axiosConfig: {
|
|
method: 'POST',
|
|
method: 'POST',
|
|
url: `/api/landmarks/convert/${anonUserId}/`
|
|
url: `/api/landmarks/convert/${anonUserId}/`
|
|
},
|
|
},
|
|
authorized: true,
|
|
authorized: true,
|
|
- errorMessage: 'Something went wrong when converting anonymous landmarks'
|
|
|
|
|
|
+ errorMessage: 'Something went wrong when converting anonymous landmarks',
|
|
|
|
+ loggingCategory: "AUTH",
|
|
})
|
|
})
|
|
|
|
+
|
|
|
|
+ LOGGING.log("AUTH", 'info', "Successfully converted anonymous landmarks to owned landmarks.")
|
|
|
|
|
|
setAlert({
|
|
setAlert({
|
|
title: 'Heads up',
|
|
title: 'Heads up',
|
|
@@ -369,18 +458,20 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
|
|
|
|
setAnonUserId('')
|
|
setAnonUserId('')
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- reportAxiosError("[Authentication]: Error when checking for anonymous landmarks", error);
|
|
|
|
- setAlert({
|
|
|
|
- title: 'Error',
|
|
|
|
- message: "Failed to convert your old anonymous landmarks to your account. Please try again manually by going to Account -> Information -> Transfer anonymous landmarks.",
|
|
|
|
- callback: () => navigate("Account"),
|
|
|
|
- type: 'error'
|
|
|
|
- })
|
|
|
|
|
|
+ LOGGING.log("AUTH", 'error', "An error occured while converting anonymous landmarks: " + error)
|
|
|
|
+
|
|
|
|
+ // TODO: implement transfer anonymus landmarks in accounts
|
|
|
|
+ // setAlert({
|
|
|
|
+ // title: 'Error',
|
|
|
|
+ // message: "Failed to convert your old anonymous landmarks to your account. Please try again manually by going to Account -> Information -> Transfer anonymous landmarks.",
|
|
|
|
+ // callback: () => navigate("Account"),
|
|
|
|
+ // type: 'error'
|
|
|
|
+ // })
|
|
return false
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- const authState = useMemo(() => ({
|
|
|
|
|
|
+ const authState = useMemo<AuthState>(() => ({
|
|
accessToken,
|
|
accessToken,
|
|
notificationToken,
|
|
notificationToken,
|
|
setNotificationTokenAsync,
|
|
setNotificationTokenAsync,
|
|
@@ -391,15 +482,14 @@ export const AuthContextProvider: React.FC = ({children}) => {
|
|
landmarkOwnedByUser,
|
|
landmarkOwnedByUser,
|
|
refreshToken,
|
|
refreshToken,
|
|
userId,
|
|
userId,
|
|
- loading,
|
|
|
|
|
|
+ authStateLoading,
|
|
anonUserId,
|
|
anonUserId,
|
|
- setLoading,
|
|
|
|
|
|
+ setAuthStateLoading,
|
|
setAlert,
|
|
setAlert,
|
|
sendApiRequestAsync,
|
|
sendApiRequestAsync,
|
|
login,
|
|
login,
|
|
logout,
|
|
logout,
|
|
- refreshAccessToken,
|
|
|
|
- }), [accessToken, refreshToken, userId, loading, anonUserId])
|
|
|
|
|
|
+ }), [accessToken, refreshToken, userId, authStateLoading, anonUserId])
|
|
|
|
|
|
return (
|
|
return (
|
|
<AuthContext.Provider value={authState}>
|
|
<AuthContext.Provider value={authState}>
|