feat: implement Google OAuth support for Android and iOS
- Added support for Google OAuth with separate client IDs for Android and iOS. - Updated `verify_google_id_token` to validate `aud` against both client IDs and check `email_verified`. - Modified `google_oauth_handler` to accept and process the new client IDs. - Enhanced security by enforcing explicit JWT algorithm validation. - Updated mobile app to handle Google OAuth flow using `expo-auth-session`. - Fixed API request to send `id_token` in snake_case as expected by the backend. - Added necessary environment variables for Google client IDs in mobile app. - Implemented intent filter for Google OAuth redirect in AndroidManifest.xml.
This commit is contained in:
+40
-11
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native';
|
||||
import axios from 'axios';
|
||||
import { Link, useRouter } from 'expo-router';
|
||||
@@ -6,14 +6,19 @@ import { useForm, Controller } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import * as Google from 'expo-auth-session/providers/google';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import { Screen } from '@presentation/components/layout/Screen';
|
||||
import { Input } from '@presentation/components/ui/Input';
|
||||
import { Button } from '@presentation/components/ui/Button';
|
||||
import { colors, typography, spacing } from '@shared/theme';
|
||||
import { loginUseCase } from '@infrastructure/container';
|
||||
import { loginUseCase, googleLoginUseCase } from '@infrastructure/container';
|
||||
import { useAuthStore } from '@store/authStore';
|
||||
import { CatIcon } from '@presentation/components/icons/CatIcon';
|
||||
|
||||
// Necessário para fechar o browser após redirect OAuth no Android/iOS
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('E-mail inválido'),
|
||||
password: z.string().min(1, 'Senha obrigatória'),
|
||||
@@ -31,6 +36,36 @@ export default function LoginScreen(): React.ReactElement {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
|
||||
|
||||
// Hook do expo-auth-session para Google OAuth.
|
||||
// Usa os client IDs de Android e iOS configurados via variáveis de ambiente.
|
||||
const [_request, response, promptAsync] = Google.useAuthRequest({
|
||||
androidClientId: process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID_ANDROID,
|
||||
iosClientId: process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID_IOS,
|
||||
});
|
||||
|
||||
// Reage ao resultado do fluxo OAuth assim que o browser fecha
|
||||
useEffect(() => {
|
||||
if (response?.type === 'success') {
|
||||
const idToken = response.authentication?.idToken;
|
||||
if (!idToken) {
|
||||
Alert.alert('Erro', 'Não foi possível obter o token do Google.');
|
||||
setIsGoogleLoading(false);
|
||||
return;
|
||||
}
|
||||
googleLoginUseCase
|
||||
.execute({ idToken })
|
||||
.then((session) => setSession(session))
|
||||
.then(() => router.replace('/(app)/(tabs)/home'))
|
||||
.catch(() => Alert.alert('Erro', 'Não foi possível entrar com o Google.'))
|
||||
.finally(() => setIsGoogleLoading(false));
|
||||
} else if (response?.type === 'error') {
|
||||
Alert.alert('Erro', 'Autenticação com Google cancelada ou falhou.');
|
||||
setIsGoogleLoading(false);
|
||||
} else if (response?.type === 'dismiss') {
|
||||
setIsGoogleLoading(false);
|
||||
}
|
||||
}, [response]);
|
||||
|
||||
const { control, handleSubmit, formState: { errors } } = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: { email: '', password: '' },
|
||||
@@ -57,16 +92,10 @@ export default function LoginScreen(): React.ReactElement {
|
||||
}
|
||||
}
|
||||
|
||||
async function onGoogleLogin(): Promise<void> {
|
||||
function onGoogleLogin(): void {
|
||||
setIsGoogleLoading(true);
|
||||
try {
|
||||
// TODO: Google Sign-In + GoogleLoginUseCase
|
||||
router.replace('/(app)/(tabs)/home');
|
||||
} catch {
|
||||
Alert.alert('Erro', 'Não foi possível entrar com o Google.');
|
||||
} finally {
|
||||
setIsGoogleLoading(false);
|
||||
}
|
||||
// promptAsync abre o browser; o resultado chega via useEffect no `response`
|
||||
promptAsync();
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user