From 1e9d0ba24f74af4b970854b77a63a40da205cf3d Mon Sep 17 00:00:00 2001 From: mudabbir-ahmad Date: Mon, 23 Feb 2026 00:12:01 +0000 Subject: [PATCH] updated google's OAUTH 2.0 to use the android client ID instead of web client ID so that OAUTH redirect works correcntly, as the old redirect was to something that i cant add to oauth... --- app.json | 14 ++++- package.json | 1 + src/AUTH/GoogleAUTH.js | 115 ++++++++++++++++++++++++----------------- 3 files changed, 83 insertions(+), 47 deletions(-) diff --git a/app.json b/app.json index d36fbab..1216a1b 100644 --- a/app.json +++ b/app.json @@ -20,7 +20,19 @@ "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" - } + }, + "intentFilters": [ + { + "action": "VIEW", + "data": [ + { + "scheme": "com.bobthebob.massphotoapp", + "path": "/oauthredirect" + } + ], + "category": ["BROWSABLE", "DEFAULT"] + } + ] }, "web": { "favicon": "./assets/favicon.png" diff --git a/package.json b/package.json index 74b0faa..8d331ba 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@react-navigation/native-stack": "^6.9.26", "axios": "^1.7.9", "expo": "~54.0.33", + "expo-application": "~7.0.8", "expo-auth-session": "~7.0.10", "expo-blur": "~15.0.8", "expo-crypto": "~15.0.8", diff --git a/src/AUTH/GoogleAUTH.js b/src/AUTH/GoogleAUTH.js index cbb5323..41fa50f 100644 --- a/src/AUTH/GoogleAUTH.js +++ b/src/AUTH/GoogleAUTH.js @@ -1,18 +1,21 @@ import * as AuthSession from 'expo-auth-session'; import * as WebBrowser from 'expo-web-browser'; +import * as Application from 'expo-application'; import { AuthServiceBase } from './AuthServiceBase'; WebBrowser.maybeCompleteAuthSession(); -const GOOGLE_CLIENT_ID = '313090284964-rgq1u7np6ogucu9o97s134n5nc6nj7kf.apps.googleusercontent.com'; - -// Builds the deep link redirect URI from the scheme defined in app.json ("media-aggregation"). -// On an Android APK this resolves to: media-aggregation:// -// Register this URI in Google Cloud Console → Authorized redirect URIs. -const REDIRECT_URI = AuthSession.makeRedirectUri({ scheme: 'media-aggregation' }); - -//for finding what the redirect is for OAUTH: -console.log('REDIRECT_URI:', REDIRECT_URI); +// Web client ID — used for token exchange (authorization code → access token). +// Created in Google Cloud Console as "Web application" type. +// No redirect URIs need to be registered for this client. +const WEB_CLIENT_ID = '313090284964-rgq1u7np6ogucu9o97s134n5nc6nj7kf.apps.googleusercontent.com'; +// Android client ID — used for the authorization request on Android. +// Created in Google Cloud Console as "Android" type with: +// Package name: com.bobthebob.massphotoapp +// SHA-1 fingerprint: +//REMOVE LATER. 59:1C:1A:B2:61:C9:8D:80:C8:E1:96:FE:CA:44:18:CE:91:5E:38:63 IS THE SHA-1 FINGERPRINT FOR THE APP +// Android clients verify by package + SHA-1, no redirect URI registration needed. +const ANDROID_CLIENT_ID = '313090284964-pa9p2rs7g60l9t8hr6haee3qbn2a7vl9.apps.googleusercontent.com'; const GOOGLE_DISCOVERY = { authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', @@ -21,12 +24,18 @@ const GOOGLE_DISCOVERY = { }; const SCOPES = [ + 'openid', 'https://www.googleapis.com/auth/photoslibrary', 'https://www.googleapis.com/auth/photoslibrary.appendonly', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', ]; +// Redirect URI uses the Android package name as scheme, matching what +// expo-auth-session/providers/google generates for installed Android apps. +// Result: com.bobthebob.massphotoapp:/oauthredirect +const REDIRECT_URI = `${Application.applicationId}:/oauthredirect`; + class GoogleAUTH extends AuthServiceBase { constructor() { super(); @@ -37,11 +46,10 @@ class GoogleAUTH extends AuthServiceBase { this.idToken = null; } - // Authenticate via Google OAuth; uses placeholder tokens when no real client ID is configured async authenticate() { try { - if (GOOGLE_CLIENT_ID === 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com') { - console.log('Google Auth: placeholder mode (no client ID configured)'); + if (ANDROID_CLIENT_ID === 'REPLACE_WITH_ANDROID_CLIENT_ID.apps.googleusercontent.com') { + console.warn('Google Auth: no Android client ID — using placeholder mode'); this.accessToken = 'placeholder_google_token_' + Date.now(); this.refreshToken = 'placeholder_refresh_' + Date.now(); this.expiresAt = Date.now() + 3600000; @@ -56,8 +64,11 @@ class GoogleAUTH extends AuthServiceBase { console.log('OAuth redirect URI:', REDIRECT_URI); + // Auth request uses the Android client ID. + // Android OAuth clients are verified by package name + SHA-1 signing cert, + // so Google accepts the package-name scheme redirect without URI registration. const authRequest = new AuthSession.AuthRequest({ - clientId: GOOGLE_CLIENT_ID, + clientId: ANDROID_CLIENT_ID, scopes: SCOPES, redirectUri: REDIRECT_URI, responseType: AuthSession.ResponseType.Code, @@ -71,21 +82,33 @@ class GoogleAUTH extends AuthServiceBase { throw new Error('Google authentication was cancelled or failed'); } - const tokenResponse = await AuthSession.exchangeCodeAsync( - { - clientId: GOOGLE_CLIENT_ID, - code: result.params.code, - redirectUri: REDIRECT_URI, - extraParams: { code_verifier: authRequest.codeVerifier }, - }, - GOOGLE_DISCOVERY - ); + // Exchange authorization code for tokens using the Web client ID. + // Android public clients don't have a client secret — the code_verifier + // from PKCE is used to verify the exchange instead. + const tokenBody = new URLSearchParams({ + client_id: WEB_CLIENT_ID, + code: result.params.code, + code_verifier: authRequest.codeVerifier, + grant_type: 'authorization_code', + redirect_uri: REDIRECT_URI, + }); - this.accessToken = tokenResponse.accessToken; - this.refreshToken = tokenResponse.refreshToken; - this.expiresAt = tokenResponse.issuedAt - ? (tokenResponse.issuedAt + (tokenResponse.expiresIn || 3600)) * 1000 - : Date.now() + 3600000; + const tokenResponse = await fetch(GOOGLE_DISCOVERY.tokenEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: tokenBody.toString(), + }); + + const tokenData = await tokenResponse.json(); + + if (tokenData.error) { + throw new Error(tokenData.error_description || tokenData.error); + } + + this.accessToken = tokenData.access_token; + this.refreshToken = tokenData.refresh_token || null; + this.idToken = tokenData.id_token || null; + this.expiresAt = Date.now() + (tokenData.expires_in || 3600) * 1000; const userInfo = await this.fetchUserInfo(); this.email = userInfo?.email || 'google-user'; @@ -114,7 +137,6 @@ class GoogleAUTH extends AuthServiceBase { } } - // Return a valid access token, refreshing if expired async getAccessToken() { if (this.accessToken && this.expiresAt > Date.now()) return this.accessToken; if (this.refreshToken) return await this.refreshAccessToken(); @@ -123,22 +145,23 @@ class GoogleAUTH extends AuthServiceBase { async refreshAccessToken() { try { - if (GOOGLE_CLIENT_ID === 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com') { - this.accessToken = 'refreshed_placeholder_' + Date.now(); - this.expiresAt = Date.now() + 3600000; - return this.accessToken; - } + const body = new URLSearchParams({ + client_id: WEB_CLIENT_ID, + refresh_token: this.refreshToken, + grant_type: 'refresh_token', + }); - const tokenResponse = await AuthSession.refreshAsync( - { clientId: GOOGLE_CLIENT_ID, refreshToken: this.refreshToken }, - GOOGLE_DISCOVERY - ); + const response = await fetch(GOOGLE_DISCOVERY.tokenEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body.toString(), + }); - this.accessToken = tokenResponse.accessToken; - this.expiresAt = tokenResponse.issuedAt - ? (tokenResponse.issuedAt + (tokenResponse.expiresIn || 3600)) * 1000 - : Date.now() + 3600000; + const data = await response.json(); + if (data.error) throw new Error(data.error_description || data.error); + this.accessToken = data.access_token; + this.expiresAt = Date.now() + (data.expires_in || 3600) * 1000; return this.accessToken; } catch (error) { console.error('Token refresh error:', error); @@ -148,8 +171,10 @@ class GoogleAUTH extends AuthServiceBase { async revokeAccess() { try { - if (this.accessToken && GOOGLE_CLIENT_ID !== 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com') { - await AuthSession.revokeAsync({ token: this.accessToken }, GOOGLE_DISCOVERY); + if (this.accessToken) { + await fetch(`${GOOGLE_DISCOVERY.revocationEndpoint}?token=${this.accessToken}`, { + method: 'POST', + }); } } catch (err) { console.error('Revoke error:', err); @@ -158,6 +183,7 @@ class GoogleAUTH extends AuthServiceBase { this.refreshToken = null; this.expiresAt = null; this.email = null; + this.idToken = null; return { success: true }; } @@ -166,7 +192,6 @@ class GoogleAUTH extends AuthServiceBase { return info || { id: 'google_user', email: this.email || 'unknown', name: 'Google User' }; } - // Fetch media items from the Google Photos Library API async getPhotos(pageSize = 50, pageToken = null) { try { const token = await this.getAccessToken(); @@ -203,7 +228,6 @@ class GoogleAUTH extends AuthServiceBase { } } - // Fetch album list from the Google Photos Library API async getAlbums(pageSize = 50, pageToken = null) { try { const token = await this.getAccessToken(); @@ -224,7 +248,6 @@ class GoogleAUTH extends AuthServiceBase { } } - // Upload a photo to Google Photos Library async uploadPhoto(filePath, filename, mimeType = 'image/jpeg') { try { const token = await this.getAccessToken();