Fixes and tickets
This commit is contained in:
+100
-233
@@ -1,18 +1,7 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { GoogleSigninButton } from "@react-native-google-signin/google-signin";
|
||||
import * as AuthSession from "expo-auth-session";
|
||||
import * as GoogleAuth from "expo-auth-session/providers/google";
|
||||
import Constants from "expo-constants";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import * as AppleAuthentication from "expo-apple-authentication";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { ActivityIndicator, Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import Svg, { Path } from "react-native-svg";
|
||||
import { useGlobal } from "reactn";
|
||||
import { handleFirebaseError } from "../actions/signupActions.js";
|
||||
@@ -21,11 +10,7 @@ import GradientButton from "../components/GradientButton.js";
|
||||
import { Input } from "../components/Input.js";
|
||||
import ItemContainer from "../components/ItemContainer/ItemContainer";
|
||||
import firebase from "../config/firebase";
|
||||
import {
|
||||
GOOGLE_ANDROID_CLIENT_ID,
|
||||
GOOGLE_IOS_CLIENT_ID,
|
||||
GOOGLE_WEB_CLIENT_ID,
|
||||
} from "../data/keys";
|
||||
import useSocialAuth from "../hooks/useSocialAuth";
|
||||
import { isWeb } from "../hooks/useLayoutType";
|
||||
import Page from "../layouts/Page.js";
|
||||
import { Routes } from "../navigation";
|
||||
@@ -34,37 +19,26 @@ import { FONT_FAMILY } from "../styles/Fonts.js";
|
||||
import Palette from "../styles/Palette.js";
|
||||
|
||||
export default ({ navigation }) => {
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
const [, setTooltip] = useGlobal("_tooltip");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
// Let the provider compute a compliant redirect URI for native (com.googleusercontent.apps.<client-id>:/oauth2redirect)
|
||||
// Avoid forcing a custom scheme like musicland:// which Google can reject for native apps.
|
||||
|
||||
const isExpoGo = Constants?.appOwnership === "expo";
|
||||
const expoRedirectUri = AuthSession.makeRedirectUri({ useProxy: true });
|
||||
const nativeRedirectUri = getNativeGoogleRedirectUri();
|
||||
const redirectUri =
|
||||
isWeb || isExpoGo ? expoRedirectUri : nativeRedirectUri || expoRedirectUri;
|
||||
|
||||
const [request, response, promptAsync] = GoogleAuth.useAuthRequest({
|
||||
iosClientId: GOOGLE_IOS_CLIENT_ID,
|
||||
androidClientId: GOOGLE_ANDROID_CLIENT_ID,
|
||||
webClientId: GOOGLE_WEB_CLIENT_ID,
|
||||
expoClientId: GOOGLE_WEB_CLIENT_ID,
|
||||
redirectUri,
|
||||
// Use Authorization Code + PKCE to comply with Google OAuth for native apps
|
||||
responseType: AuthSession.ResponseType.Code,
|
||||
usePKCE: true,
|
||||
scopes: ["openid", "profile", "email"],
|
||||
});
|
||||
|
||||
const afterLoginNavigate = async () => {
|
||||
const afterLoginNavigate = useCallback(async () => {
|
||||
const uid = firebase.auth().currentUser?.uid;
|
||||
if (!uid) throw new Error("Aucun utilisateur après connexion");
|
||||
navigation.reset({ index: 0, routes: [{ name: Routes.BottomTab }] });
|
||||
};
|
||||
}, [navigation]);
|
||||
|
||||
const {
|
||||
signInWithGoogle,
|
||||
signInWithApple,
|
||||
isAppleSignInAvailable,
|
||||
isLoading: socialLoading,
|
||||
} = useSocialAuth({
|
||||
onSuccess: afterLoginNavigate,
|
||||
});
|
||||
|
||||
const isBusy = loading || socialLoading;
|
||||
|
||||
const onLogin = async () => {
|
||||
try {
|
||||
@@ -83,110 +57,6 @@ export default ({ navigation }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onLoginWithGoogle = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
if (isWeb) {
|
||||
const provider = new firebase.auth.GoogleAuthProvider();
|
||||
provider.addScope("profile");
|
||||
provider.addScope("email");
|
||||
await firebase.auth().signInWithPopup(provider);
|
||||
await afterLoginNavigate();
|
||||
setLoading(false);
|
||||
} else {
|
||||
// Use defaults from the request; don't override with proxy here
|
||||
const result = await promptAsync({ useProxy: isExpoGo });
|
||||
if (result?.type !== "success") {
|
||||
// Cancelled or errored during the browser flow
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Google Login error", e?.message);
|
||||
const message =
|
||||
e?.code && e.code.startsWith("auth/")
|
||||
? handleFirebaseError(e.code)
|
||||
: e?.message;
|
||||
setTooltip({
|
||||
text: message || "Connexion Google impossible. Merci de réessayer.",
|
||||
type: "error",
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle native Google OAuth response
|
||||
useEffect(() => {
|
||||
const handleNativeGoogleResponse = async () => {
|
||||
try {
|
||||
if (response?.type === "success") {
|
||||
// If using Expo proxy, tokens can be present already.
|
||||
let idToken =
|
||||
response?.authentication?.idToken || response?.params?.id_token;
|
||||
|
||||
if (!idToken) {
|
||||
// Otherwise, exchange the authorization code for tokens using PKCE.
|
||||
const code =
|
||||
response?.params?.code || response?.authentication?.code;
|
||||
if (!code) throw new Error("Code d'autorisation Google manquant");
|
||||
|
||||
// Use the exact clientId used during the authorize request.
|
||||
const clientId = request?.clientId;
|
||||
const discovery = {
|
||||
authorizationEndpoint:
|
||||
"https://accounts.google.com/o/oauth2/v2/auth",
|
||||
tokenEndpoint: "https://oauth2.googleapis.com/token",
|
||||
revocationEndpoint: "https://oauth2.googleapis.com/revoke",
|
||||
};
|
||||
|
||||
// Light debug: helps identify redirect/client mismatches in dev.
|
||||
console.log("Google token exchange", {
|
||||
clientId,
|
||||
redirectUri: request?.redirectUri,
|
||||
hasCodeVerifier: !!request?.codeVerifier,
|
||||
});
|
||||
|
||||
const tokenResponse = await AuthSession.exchangeCodeAsync(
|
||||
{
|
||||
clientId,
|
||||
code,
|
||||
redirectUri: request?.redirectUri,
|
||||
extraParams: { code_verifier: request?.codeVerifier },
|
||||
},
|
||||
discovery
|
||||
);
|
||||
idToken = tokenResponse?.id_token;
|
||||
}
|
||||
|
||||
if (!idToken)
|
||||
throw new Error("Jeton Google manquant (échange/retour)");
|
||||
|
||||
const credential =
|
||||
firebase.auth.GoogleAuthProvider.credential(idToken);
|
||||
await firebase.auth().signInWithCredential(credential);
|
||||
await afterLoginNavigate();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Google native sign-in error", e?.message);
|
||||
const message =
|
||||
e?.code && e.code.startsWith("auth/")
|
||||
? handleFirebaseError(e.code)
|
||||
: e?.message;
|
||||
setTooltip({
|
||||
text: message || "Connexion Google impossible",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isWeb && response) {
|
||||
handleNativeGoogleResponse();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [response]);
|
||||
|
||||
return (
|
||||
<Page
|
||||
width={isWeb ? 600 : null}
|
||||
@@ -241,97 +111,95 @@ export default ({ navigation }) => {
|
||||
<GradientButton
|
||||
title="Se connecter"
|
||||
containerStyle={{
|
||||
width: "80%",
|
||||
width: "90%",
|
||||
alignSelf: "center",
|
||||
}}
|
||||
onPress={onLogin}
|
||||
disabled={loading || !email || !password}
|
||||
disabled={isBusy || !email || !password}
|
||||
/>
|
||||
<GoogleAuthButton
|
||||
onPress={onLoginWithGoogle}
|
||||
disabled={loading}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
<View style={styles.signupWrapper}>
|
||||
<Text style={styles.signupPrompt}>Pas encore de compte ?</Text>
|
||||
<Pressable
|
||||
onPress={() => navigate(Routes.Register)}
|
||||
style={({ pressed }) => [
|
||||
styles.signupPressable,
|
||||
pressed && styles.linkPressed,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.signupLink}>Créer un compte</Text>
|
||||
</Pressable>
|
||||
<View style={styles.socialWrapper}>
|
||||
{isAppleSignInAvailable ? (
|
||||
<AppleAuthButton
|
||||
onPress={signInWithApple}
|
||||
disabled={isBusy}
|
||||
loading={socialLoading}
|
||||
/>
|
||||
) : null}
|
||||
<GoogleAuthButton
|
||||
onPress={signInWithGoogle}
|
||||
disabled={isBusy}
|
||||
loading={socialLoading}
|
||||
/>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
</ItemContainer>
|
||||
<View style={styles.bottomFooter}>
|
||||
<Text style={styles.bottomFooterText}>
|
||||
Tu n'as pas de compte ?{" "}
|
||||
<Text
|
||||
style={styles.bottomFooterLink}
|
||||
onPress={() => navigate(Routes.Register)}
|
||||
>
|
||||
Créer un compte
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
const GoogleAuthButton = ({ onPress, disabled, loading }) => {
|
||||
const AppleAuthButton = ({ onPress, disabled, loading }) => {
|
||||
if (isWeb) {
|
||||
return (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
onPress={onPress}
|
||||
disabled={disabled}
|
||||
style={({ pressed }) => [
|
||||
styles.webButton,
|
||||
pressed && !disabled ? styles.webButtonPressed : null,
|
||||
disabled ? styles.webButtonDisabled : null,
|
||||
]}
|
||||
>
|
||||
<View style={styles.webContent}>
|
||||
<View style={styles.webIconContainer}>
|
||||
<GoogleLogo />
|
||||
</View>
|
||||
<Text style={styles.webText}>Continuer avec Google</Text>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color="#4285F4" />
|
||||
) : (
|
||||
<View style={styles.webRightSpacer} />
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.nativeButtonWrapper}>
|
||||
<GoogleSigninButton
|
||||
<AppleAuthentication.AppleAuthenticationButton
|
||||
buttonType={AppleAuthentication.AppleAuthenticationButtonType.CONTINUE}
|
||||
buttonStyle={
|
||||
AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||
}
|
||||
style={styles.nativeButton}
|
||||
size={GoogleSigninButton.Size.Wide}
|
||||
color={GoogleSigninButton.Color.Dark}
|
||||
cornerRadius={12}
|
||||
onPress={onPress}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{loading ? (
|
||||
<View style={styles.nativeLoadingOverlay}>
|
||||
<ActivityIndicator size="small" color={Palette.white} />
|
||||
<ActivityIndicator size="small" color={Palette.black} />
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const getNativeGoogleRedirectUri = () => {
|
||||
const clientId = Platform.select({
|
||||
ios: GOOGLE_IOS_CLIENT_ID,
|
||||
android: GOOGLE_ANDROID_CLIENT_ID,
|
||||
default: null,
|
||||
});
|
||||
|
||||
if (!clientId || !clientId.includes(".apps.googleusercontent.com")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const clientPrefix = clientId.replace(".apps.googleusercontent.com", "");
|
||||
return `com.googleusercontent.apps.${clientPrefix}:/oauth2redirect`;
|
||||
};
|
||||
const GoogleAuthButton = ({ onPress, disabled, loading }) => (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
onPress={onPress}
|
||||
disabled={disabled}
|
||||
style={({ pressed }) => [
|
||||
styles.socialButton,
|
||||
pressed && !disabled ? styles.socialButtonPressed : null,
|
||||
disabled ? styles.socialButtonDisabled : null,
|
||||
]}
|
||||
>
|
||||
<View style={styles.socialButtonContent}>
|
||||
<View style={styles.socialIconContainer}>
|
||||
<GoogleLogo />
|
||||
</View>
|
||||
<Text style={styles.socialButtonText}>Continuer avec Google</Text>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color="#4285F4" />
|
||||
) : (
|
||||
<View style={styles.socialRightSpacer} />
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: {
|
||||
@@ -347,6 +215,7 @@ const styles = StyleSheet.create({
|
||||
cardContent: {
|
||||
gap: 28,
|
||||
paddingTop: 8,
|
||||
paddingHorizontal: 5,
|
||||
width: "100%",
|
||||
},
|
||||
greetingSection: {
|
||||
@@ -384,31 +253,29 @@ const styles = StyleSheet.create({
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
textAlign: "right",
|
||||
},
|
||||
signupWrapper: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 6,
|
||||
bottomFooter: {
|
||||
marginTop: 16,
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
signupPrompt: {
|
||||
bottomFooterText: {
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontFamily: FONT_FAMILY.HelveticaNeueRegular,
|
||||
textAlign: "center",
|
||||
},
|
||||
signupPressable: {
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
signupLink: {
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
bottomFooterLink: {
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
color: Palette.white,
|
||||
},
|
||||
webButton: {
|
||||
socialWrapper: {
|
||||
width: "100%",
|
||||
gap: 16,
|
||||
alignItems: "center",
|
||||
},
|
||||
socialButton: {
|
||||
alignSelf: "center",
|
||||
borderRadius: 12,
|
||||
width: "80%",
|
||||
|
||||
width: "90%",
|
||||
backgroundColor: Palette.white,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E3E7EF",
|
||||
@@ -420,38 +287,38 @@ const styles = StyleSheet.create({
|
||||
shadowRadius: 16,
|
||||
elevation: 2,
|
||||
},
|
||||
webButtonPressed: {
|
||||
socialButtonPressed: {
|
||||
backgroundColor: "#F4F7FF",
|
||||
},
|
||||
webButtonDisabled: {
|
||||
socialButtonDisabled: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
webContent: {
|
||||
socialButtonContent: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 12,
|
||||
},
|
||||
webIconContainer: {
|
||||
socialIconContainer: {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 28,
|
||||
height: 28,
|
||||
},
|
||||
webText: {
|
||||
socialButtonText: {
|
||||
flex: 1,
|
||||
textAlign: "center",
|
||||
fontSize: 15,
|
||||
color: "#202124",
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
},
|
||||
webRightSpacer: {
|
||||
socialRightSpacer: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
nativeButtonWrapper: {
|
||||
alignSelf: "center",
|
||||
width: "80%",
|
||||
width: "90%",
|
||||
height: 50,
|
||||
justifyContent: "center",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user