mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-05-05 14:15:27 -04:00
Account deletion improvements (#2044)
* Allow the in-app browser to open users/delete from settings * When account deletion successful, sign the user out
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
useRoute
|
||||
} from "@react-navigation/native";
|
||||
import { getUserAgent } from "api/userAgent";
|
||||
import { getAPIToken } from "components/LoginSignUp/AuthenticationService.ts";
|
||||
import { getJWT } from "components/LoginSignUp/AuthenticationService.ts";
|
||||
import { ActivityIndicator, Mortal, ViewWrapper } from "components/SharedComponents";
|
||||
import { View } from "components/styledComponents";
|
||||
import React, { useEffect, useState } from "react";
|
||||
@@ -32,6 +32,16 @@ export const ALLOWED_DOMAINS = [
|
||||
"hcaptcha.com"
|
||||
];
|
||||
|
||||
const ALLOWED_ORIGINS = ["https://*", "mailto:*"];
|
||||
const ALLOWED_AUTH_DOMAINS = ["inaturalist.org"];
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
if ( __DEV__ ) {
|
||||
ALLOWED_DOMAINS.push( "localhost:3000" );
|
||||
ALLOWED_ORIGINS.push( "http://localhost:3000*" );
|
||||
ALLOWED_AUTH_DOMAINS.push( "localhost:3000" );
|
||||
}
|
||||
|
||||
// Note that you want flex-2 so it grows into the entire webview container
|
||||
const LoadingView = ( ) => (
|
||||
<View className="flex-2 justify-center items-center w-full h-full">
|
||||
@@ -40,11 +50,13 @@ const LoadingView = ( ) => (
|
||||
);
|
||||
|
||||
type FullPageWebViewParams = {
|
||||
initialUrl: string,
|
||||
blurEvent?: string,
|
||||
title?: string,
|
||||
loggedIn?: boolean,
|
||||
skipSetSourceInShouldStartLoadWithRequest?: boolean
|
||||
initialUrl: string;
|
||||
blurEvent?: string;
|
||||
title?: string;
|
||||
loggedIn?: boolean;
|
||||
skipSetSourceInShouldStartLoadWithRequest?: boolean;
|
||||
clickablePathnames?: Array<string>;
|
||||
shouldLoadUrl?: ( url: string ) => boolean;
|
||||
}
|
||||
|
||||
type ParamList = {
|
||||
@@ -69,6 +81,10 @@ export function onShouldStartLoadWithRequest(
|
||||
params: FullPageWebViewParams,
|
||||
setSource?: ( source: WebViewSource ) => void
|
||||
) {
|
||||
if ( typeof ( params.shouldLoadUrl ) === "function" ) {
|
||||
if ( !params.shouldLoadUrl( request.url ) ) return false;
|
||||
}
|
||||
|
||||
// If we're just loading the same page, that's fine
|
||||
if ( request.url === source.uri ) {
|
||||
return true;
|
||||
@@ -82,7 +98,7 @@ export function onShouldStartLoadWithRequest(
|
||||
|
||||
// This should prevent accidentally making a webview with auth for a
|
||||
// non-iNat domain
|
||||
if ( source.headers?.Authorization && sourceDomain !== "inaturalist.org" ) {
|
||||
if ( source.headers?.Authorization && ALLOWED_AUTH_DOMAINS.indexOf( sourceDomain ) < 0 ) {
|
||||
throw new Error( "Cannot send Authorization to non-iNat domain" );
|
||||
}
|
||||
|
||||
@@ -108,8 +124,11 @@ export function onShouldStartLoadWithRequest(
|
||||
// or if this is a click, i.e. even if this is an allowed domain, we want
|
||||
// to open a browser unless we were explicitly asked not to. This only
|
||||
// works in iOS.
|
||||
// TODO come up with an Android solution
|
||||
|| request.navigationType === "click"
|
||||
|| (
|
||||
// TODO come up with an Android solution
|
||||
request.navigationType === "click"
|
||||
&& ( params.clickablePathnames || [] ).indexOf( requestUrl.pathname ) < 0
|
||||
)
|
||||
) {
|
||||
// Note we can't use openExternalWebBrowser here b/c this function needs
|
||||
// to be synchronous
|
||||
@@ -120,12 +139,6 @@ export function onShouldStartLoadWithRequest(
|
||||
return false;
|
||||
}
|
||||
|
||||
// This should prevent making any request w/ auth to a non-iNat domain from
|
||||
// a web page on an iNat domain
|
||||
if ( source.headers?.Authorization && requestDomain !== "inaturalist.org" ) {
|
||||
throw new Error( "Cannot send Authorization to non-iNat domain" );
|
||||
}
|
||||
|
||||
if ( params.skipSetSourceInShouldStartLoadWithRequest || !setSource ) {
|
||||
return true;
|
||||
}
|
||||
@@ -166,11 +179,11 @@ const FullPageWebView = ( ) => {
|
||||
|
||||
// Make the WebView logged in for the current user
|
||||
if ( params.loggedIn ) {
|
||||
getAPIToken().then( token => {
|
||||
getJWT().then( jwt => {
|
||||
setSource( {
|
||||
...source,
|
||||
headers: {
|
||||
Authorization: token
|
||||
Authorization: jwt
|
||||
}
|
||||
} );
|
||||
} );
|
||||
@@ -194,7 +207,7 @@ const FullPageWebView = ( ) => {
|
||||
setSource
|
||||
)
|
||||
}
|
||||
originWhitelist={["https://*", "mailto:*"]}
|
||||
originWhitelist={ALLOWED_ORIGINS}
|
||||
renderLoading={LoadingView}
|
||||
startInLoadingState
|
||||
userAgent={getUserAgent()}
|
||||
|
||||
@@ -3,21 +3,23 @@ import {
|
||||
} from "@react-native-community/netinfo";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
// import fetchAvailableLocales from "api/translations";
|
||||
import { updateUsers } from "api/users";
|
||||
import {
|
||||
signOut
|
||||
} from "components/LoginSignUp/AuthenticationService.ts";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Body2,
|
||||
Button,
|
||||
Heading4,
|
||||
// PickerSheet,
|
||||
RadioButtonRow,
|
||||
ScrollViewWrapper
|
||||
} from "components/SharedComponents";
|
||||
import { RealmContext } from "providers/contexts.ts";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
Alert, StatusBar,
|
||||
Alert,
|
||||
StatusBar,
|
||||
View
|
||||
} from "react-native";
|
||||
import Config from "react-native-config";
|
||||
@@ -30,7 +32,6 @@ import {
|
||||
useUserMe
|
||||
} from "sharedHooks";
|
||||
import useStore from "stores/useStore";
|
||||
// import useStore, { zustandStorage } from "stores/useStore";
|
||||
|
||||
const { useRealm } = RealmContext;
|
||||
|
||||
@@ -261,7 +262,26 @@ const Settings = ( ) => {
|
||||
title: t( "SETTINGS" ),
|
||||
loggedIn: true,
|
||||
initialUrl: SETTINGS_URL,
|
||||
blurEvent: FINISHED_WEB_SETTINGS
|
||||
blurEvent: FINISHED_WEB_SETTINGS,
|
||||
clickablePathnames: ["/users/delete"],
|
||||
skipSetSourceInShouldStartLoadWithRequest: true,
|
||||
shouldLoadUrl: url => {
|
||||
async function signOutGoHome() {
|
||||
// sign out
|
||||
await signOut( { realm, clearRealm: true, queryClient } );
|
||||
// navigate to My Obs
|
||||
navigation.navigate( "ObsList" );
|
||||
Alert.alert(
|
||||
t( "Account-Deleted" ),
|
||||
t( "It-may-take-up-to-an-hour-to-remove-content" )
|
||||
);
|
||||
}
|
||||
if ( url === `${Config.OAUTH_API_URL}/?account_deleted=true` ) {
|
||||
signOutGoHome( );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} );
|
||||
}}
|
||||
accessibilityLabel={t( "INATURALIST-SETTINGS" )}
|
||||
|
||||
@@ -30,6 +30,8 @@ ABOUT-UMBRELLA-PROJECTS = ABOUT UMBRELLA PROJECTS
|
||||
accessible-comname-sciname = { $commonName } ({ $scientificName })
|
||||
# Label for a taxon when a user prefers to see or hear the scientific name first
|
||||
accessible-sciname-comname = { $scientificName } ({ $commonName })
|
||||
# Alert message shown after account deletion
|
||||
Account-Deleted = Account Deleted
|
||||
ACTIVITY = ACTIVITY
|
||||
# Label for a button that adds a vote of agreement
|
||||
Add-agreement = Add agreement
|
||||
@@ -489,6 +491,7 @@ Internet-Connection-Required = Internet Connection Required
|
||||
Intl-number = { $val }
|
||||
Introduced = Introduced
|
||||
Introduced-to-place = Introduced to { $place }
|
||||
It-may-take-up-to-an-hour-to-remove-content = It may take up to an hour to completely delete all associated content
|
||||
# Month of January
|
||||
January = January
|
||||
JOIN = JOIN
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
"comment": "Label for a taxon when a user prefers to see or hear the scientific name first",
|
||||
"val": "{ $scientificName } ({ $commonName })"
|
||||
},
|
||||
"Account-Deleted": {
|
||||
"comment": "Alert message shown after account deletion",
|
||||
"val": "Account Deleted"
|
||||
},
|
||||
"ACTIVITY": "ACTIVITY",
|
||||
"Add-agreement": {
|
||||
"comment": "Label for a button that adds a vote of agreement",
|
||||
@@ -660,6 +664,7 @@
|
||||
"Intl-number": "{ $val }",
|
||||
"Introduced": "Introduced",
|
||||
"Introduced-to-place": "Introduced to { $place }",
|
||||
"It-may-take-up-to-an-hour-to-remove-content": "It may take up to an hour to completely delete all associated content",
|
||||
"January": {
|
||||
"comment": "Month of January",
|
||||
"val": "January"
|
||||
|
||||
@@ -30,6 +30,8 @@ ABOUT-UMBRELLA-PROJECTS = ABOUT UMBRELLA PROJECTS
|
||||
accessible-comname-sciname = { $commonName } ({ $scientificName })
|
||||
# Label for a taxon when a user prefers to see or hear the scientific name first
|
||||
accessible-sciname-comname = { $scientificName } ({ $commonName })
|
||||
# Alert message shown after account deletion
|
||||
Account-Deleted = Account Deleted
|
||||
ACTIVITY = ACTIVITY
|
||||
# Label for a button that adds a vote of agreement
|
||||
Add-agreement = Add agreement
|
||||
@@ -489,6 +491,7 @@ Internet-Connection-Required = Internet Connection Required
|
||||
Intl-number = { $val }
|
||||
Introduced = Introduced
|
||||
Introduced-to-place = Introduced to { $place }
|
||||
It-may-take-up-to-an-hour-to-remove-content = It may take up to an hour to completely delete all associated content
|
||||
# Month of January
|
||||
January = January
|
||||
JOIN = JOIN
|
||||
|
||||
@@ -39,10 +39,10 @@ describe( "FullPageWebView", ( ) => {
|
||||
expect( Linking.openURL ).toHaveBeenCalledWith( request.url );
|
||||
} );
|
||||
|
||||
it( "should not try to open for any domain on the allowlist if requested", ( ) => {
|
||||
it( "should not try to open for any domain on the allowlist", ( ) => {
|
||||
const url = "https://www.inaturalist.org";
|
||||
const request = { url: "https://www.inaturalist.org/users/edit" };
|
||||
expect( ALLOWED_DOMAINS ).toContain( "inaturalist.org" );
|
||||
const request = { url: "https://www.donorbox.org" };
|
||||
expect( ALLOWED_DOMAINS ).toContain( "donorbox.org" );
|
||||
const source = { uri: url };
|
||||
const routeParams = { initialUrl: url, handleLinksForAllowedDomains: true };
|
||||
expect( onShouldStartLoadWithRequest( request, source, routeParams ) ).toBeTruthy();
|
||||
@@ -57,6 +57,16 @@ describe( "FullPageWebView", ( ) => {
|
||||
expect( onShouldStartLoadWithRequest( request, source, routeParams ) ).toBeFalsy();
|
||||
expect( Linking.openURL ).toHaveBeenCalledWith( request.url );
|
||||
} );
|
||||
|
||||
it( "should not try to open for pathnames specified in params on click", ( ) => {
|
||||
const url = "https://www.inaturalist.org";
|
||||
const request = { url: "https://www.inaturalist.org/users/delete", navigationType: "click" };
|
||||
expect( ALLOWED_DOMAINS ).toContain( "inaturalist.org" );
|
||||
const source = { uri: url };
|
||||
const routeParams = { initialUrl: url, clickablePathnames: ["/users/delete"] };
|
||||
expect( onShouldStartLoadWithRequest( request, source, routeParams ) ).toBeTruthy();
|
||||
expect( Linking.openURL ).not.toHaveBeenCalled( );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
Reference in New Issue
Block a user