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:
2026-03-19 15:24:08 -03:00
parent 31c47fe69f
commit 34cbd4a861
16 changed files with 972 additions and 220 deletions
+18 -3
View File
@@ -1,12 +1,13 @@
import React, { useState } from 'react';
import {
View, Text, TouchableOpacity, StyleSheet, Alert,
View, Text, TouchableOpacity, StyleSheet, Alert, Platform,
} from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { SafeAreaView } from 'react-native-safe-area-context';
import * as FileSystem from 'expo-file-system/legacy';
import * as Sharing from 'expo-sharing';
import * as IntentLauncher from 'expo-intent-launcher';
import { useFilamentStore } from '@store/filamentStore';
import { usePresetStore } from '@store/presetStore';
import { calcFilamentPercentage } from '@domain/Filament';
@@ -111,7 +112,14 @@ export default function LabelScreen(): React.ReactElement {
await FileSystem.writeAsStringAsync(fileUri, base64, {
encoding: FileSystem.EncodingType.Base64,
});
if (await Sharing.isAvailableAsync()) {
if (Platform.OS === 'android') {
const contentUri = await FileSystem.getContentUriAsync(fileUri);
await IntentLauncher.startActivityAsync('android.intent.action.VIEW', {
data: contentUri,
flags: 1, // FLAG_GRANT_READ_URI_PERMISSION
type: 'application/pdf',
});
} else if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(fileUri, { mimeType: 'application/pdf', UTI: 'com.adobe.pdf' });
} else {
Alert.alert('Compartilhamento indisponível', 'Este dispositivo não suporta compartilhamento de arquivos.');
@@ -123,7 +131,14 @@ export default function LabelScreen(): React.ReactElement {
);
const fileUri = `${FileSystem.cacheDirectory}meowspool-label-${id}.svg`;
await FileSystem.writeAsStringAsync(fileUri, response.data, { encoding: 'utf8' });
if (await Sharing.isAvailableAsync()) {
if (Platform.OS === 'android') {
const contentUri = await FileSystem.getContentUriAsync(fileUri);
await IntentLauncher.startActivityAsync('android.intent.action.VIEW', {
data: contentUri,
flags: 1, // FLAG_GRANT_READ_URI_PERMISSION
type: 'image/svg+xml',
});
} else if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(fileUri, { mimeType: 'image/svg+xml', UTI: 'public.svg-image' });
} else {
Alert.alert('Compartilhamento indisponível', 'Este dispositivo não suporta compartilhamento de arquivos.');