From cc894ff4f43ad7200f872f9d17c4d015ac6f1e23 Mon Sep 17 00:00:00 2001 From: tapframe Date: Sat, 26 Apr 2025 15:45:49 +0530 Subject: [PATCH] Update package dependencies and enhance Trakt authentication flow; add base64-js and react-native-url-polyfill to package.json, improve expo-auth-session integration with PKCE support in TraktSettingsScreen, and refactor token exchange method in traktService to include code verifier for enhanced security. --- package-lock.json | 16 +++++++++++++++- package.json | 8 +++++--- src/screens/TraktSettingsScreen.tsx | 28 ++++++++++++++-------------- src/services/traktService.ts | 7 +++++-- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5acc3d71..f3f5b58f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@types/lodash": "^4.17.16", "@types/react-native-video": "^5.0.20", "axios": "^1.8.4", + "base64-js": "^1.5.1", "date-fns": "^4.1.0", "eventemitter3": "^5.0.1", "expo": "~52.0.43", @@ -40,7 +41,7 @@ "expo-screen-orientation": "~8.0.4", "expo-status-bar": "~2.0.1", "expo-system-ui": "^4.0.9", - "expo-web-browser": "^14.0.2", + "expo-web-browser": "~14.0.2", "lodash": "^4.17.21", "react": "18.3.1", "react-native": "0.76.9", @@ -56,6 +57,7 @@ "react-native-screens": "~4.4.0", "react-native-svg": "^15.11.2", "react-native-tab-view": "^4.0.10", + "react-native-url-polyfill": "^2.0.0", "react-native-video": "^6.12.0", "react-native-web": "~0.19.13", "subsrt": "^1.1.1" @@ -11047,6 +11049,18 @@ "react-native-pager-view": ">= 6.0.0" } }, + "node_modules/react-native-url-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz", + "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==", + "license": "MIT", + "dependencies": { + "whatwg-url-without-unicode": "8.0.0-3" + }, + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/react-native-vector-icons": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz", diff --git a/package.json b/package.json index 9011b3bf..a18105d2 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,13 @@ "@types/lodash": "^4.17.16", "@types/react-native-video": "^5.0.20", "axios": "^1.8.4", + "base64-js": "^1.5.1", "date-fns": "^4.1.0", "eventemitter3": "^5.0.1", "expo": "~52.0.43", "expo-auth-session": "^6.0.3", "expo-blur": "^14.0.3", + "expo-dev-client": "~5.0.20", "expo-file-system": "^18.0.12", "expo-haptics": "~14.0.1", "expo-image": "~2.0.7", @@ -40,7 +42,7 @@ "expo-screen-orientation": "~8.0.4", "expo-status-bar": "~2.0.1", "expo-system-ui": "^4.0.9", - "expo-web-browser": "^14.0.2", + "expo-web-browser": "~14.0.2", "lodash": "^4.17.21", "react": "18.3.1", "react-native": "0.76.9", @@ -56,10 +58,10 @@ "react-native-screens": "~4.4.0", "react-native-svg": "^15.11.2", "react-native-tab-view": "^4.0.10", + "react-native-url-polyfill": "^2.0.0", "react-native-video": "^6.12.0", "react-native-web": "~0.19.13", - "subsrt": "^1.1.1", - "expo-dev-client": "~5.0.20" + "subsrt": "^1.1.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/src/screens/TraktSettingsScreen.tsx b/src/screens/TraktSettingsScreen.tsx index 81214448..8c87af76 100644 --- a/src/screens/TraktSettingsScreen.tsx +++ b/src/screens/TraktSettingsScreen.tsx @@ -13,7 +13,7 @@ import { Platform, } from 'react-native'; import { useNavigation } from '@react-navigation/native'; -import { makeRedirectUri, useAuthRequest, ResponseType } from 'expo-auth-session'; +import { makeRedirectUri, useAuthRequest, ResponseType, Prompt, CodeChallengeMethod } from 'expo-auth-session'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import { traktService, TraktUser } from '../services/traktService'; import { colors } from '../styles/colors'; @@ -23,12 +23,11 @@ import TraktIcon from '../../assets/rating-icons/trakt.svg'; const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; -// Trakt configuration (replace with your actual Client ID if different) +// Trakt configuration const TRAKT_CLIENT_ID = 'd7271f7dd57d8aeff63e99408610091a6b1ceac3b3a541d1031a48f429b7942c'; const discovery = { authorizationEndpoint: 'https://trakt.tv/oauth/authorize', - // Note: Trakt doesn't use a standard token endpoint for the auth code flow - // We'll handle the code exchange manually in `traktService` + tokenEndpoint: 'https://api.trakt.tv/oauth/token', }; // For use with deep linking @@ -55,7 +54,7 @@ const TraktSettingsScreen: React.FC = () => { const profile = await traktService.getUserProfile(); setUserProfile(profile); } else { - setUserProfile(null); // Ensure profile is cleared if not authenticated + setUserProfile(null); } } catch (error) { logger.error('[TraktSettingsScreen] Error checking auth status:', error); @@ -68,13 +67,15 @@ const TraktSettingsScreen: React.FC = () => { checkAuthStatus(); }, [checkAuthStatus]); - // Setup expo-auth-session hook + // Setup expo-auth-session hook with PKCE const [request, response, promptAsync] = useAuthRequest( { clientId: TRAKT_CLIENT_ID, - scopes: [], // Trakt doesn't use scopes for standard auth code flow + scopes: [], redirectUri: redirectUri, - responseType: ResponseType.Code, // Ask for the authorization code + responseType: ResponseType.Code, + usePKCE: true, + codeChallengeMethod: CodeChallengeMethod.S256, }, discovery ); @@ -84,15 +85,15 @@ const TraktSettingsScreen: React.FC = () => { // Handle the response from the auth request useEffect(() => { if (response) { - setIsExchangingCode(true); // Indicate we're processing the response - if (response.type === 'success') { + setIsExchangingCode(true); + if (response.type === 'success' && request?.codeVerifier) { const { code } = response.params; logger.log('[TraktSettingsScreen] Auth code received:', code); - traktService.exchangeCodeForToken(code) + traktService.exchangeCodeForToken(code, request.codeVerifier) .then(success => { if (success) { logger.log('[TraktSettingsScreen] Token exchange successful'); - checkAuthStatus(); // Re-check auth status and fetch profile + checkAuthStatus(); } else { logger.error('[TraktSettingsScreen] Token exchange failed'); Alert.alert('Authentication Error', 'Failed to complete authentication with Trakt.'); @@ -110,12 +111,11 @@ const TraktSettingsScreen: React.FC = () => { Alert.alert('Authentication Error', response.error?.message || 'An error occurred during authentication.'); setIsExchangingCode(false); } else { - // Handle other response types like 'cancel', 'dismiss' if needed logger.log('[TraktSettingsScreen] Auth response type:', response.type); setIsExchangingCode(false); } } - }, [response, checkAuthStatus]); + }, [response, checkAuthStatus, request?.codeVerifier]); const handleSignIn = () => { promptAsync(); // Trigger the authentication flow diff --git a/src/services/traktService.ts b/src/services/traktService.ts index 3030040e..64a7f6af 100644 --- a/src/services/traktService.ts +++ b/src/services/traktService.ts @@ -125,7 +125,7 @@ export class TraktService { /** * Exchange the authorization code for an access token */ - public async exchangeCodeForToken(code: string): Promise { + public async exchangeCodeForToken(code: string, codeVerifier: string): Promise { await this.ensureInitialized(); try { @@ -139,11 +139,14 @@ export class TraktService { client_id: TRAKT_CLIENT_ID, client_secret: TRAKT_CLIENT_SECRET, redirect_uri: TRAKT_REDIRECT_URI, - grant_type: 'authorization_code' + grant_type: 'authorization_code', + code_verifier: codeVerifier }) }); if (!response.ok) { + const errorBody = await response.text(); + logger.error('[TraktService] Token exchange error response:', errorBody); throw new Error(`Failed to exchange code: ${response.status}`); }