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...

This commit is contained in:
bobbert committed 2026-02-23 00:12:01 +00:00
1 parent a4009c9b1c
commit 1e9d0ba24f
3 files changed
+81 -45

No files matched your search

+12
View File
@@ -20,7 +20,19 @@
"adaptiveIcon": { "adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png", "foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
},
"intentFilters": [
{
"action": "VIEW",
"data": [
{
"scheme": "com.bobthebob.massphotoapp",
"path": "/oauthredirect"
} }
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}, },
"web": { "web": {
"favicon": "./assets/favicon.png" "favicon": "./assets/favicon.png"
+1
View File
@@ -15,6 +15,7 @@
"@react-navigation/native-stack": "^6.9.26", "@react-navigation/native-stack": "^6.9.26",
"axios": "^1.7.9", "axios": "^1.7.9",
"expo": "~54.0.33", "expo": "~54.0.33",
"expo-application": "~7.0.8",
"expo-auth-session": "~7.0.10", "expo-auth-session": "~7.0.10",
"expo-blur": "~15.0.8", "expo-blur": "~15.0.8",
"expo-crypto": "~15.0.8", "expo-crypto": "~15.0.8",
+68 -45
View File
@@ -1,18 +1,21 @@
import * as AuthSession from 'expo-auth-session'; import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser'; import * as WebBrowser from 'expo-web-browser';
import * as Application from 'expo-application';
import { AuthServiceBase } from './AuthServiceBase'; import { AuthServiceBase } from './AuthServiceBase';
WebBrowser.maybeCompleteAuthSession(); WebBrowser.maybeCompleteAuthSession();
const GOOGLE_CLIENT_ID = '313090284964-rgq1u7np6ogucu9o97s134n5nc6nj7kf.apps.googleusercontent.com'; // Web client ID — used for token exchange (authorization code → access token).
// Created in Google Cloud Console as "Web application" type.
// Builds the deep link redirect URI from the scheme defined in app.json ("media-aggregation"). // No redirect URIs need to be registered for this client.
// On an Android APK this resolves to: media-aggregation:// const WEB_CLIENT_ID = '313090284964-rgq1u7np6ogucu9o97s134n5nc6nj7kf.apps.googleusercontent.com';
// Register this URI in Google Cloud Console → Authorized redirect URIs. // Android client ID — used for the authorization request on Android.
const REDIRECT_URI = AuthSession.makeRedirectUri({ scheme: 'media-aggregation' }); // Created in Google Cloud Console as "Android" type with:
// Package name: com.bobthebob.massphotoapp
//for finding what the redirect is for OAUTH: // SHA-1 fingerprint:
console.log('REDIRECT_URI:', REDIRECT_URI); //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 = { const GOOGLE_DISCOVERY = {
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
@@ -21,12 +24,18 @@ const GOOGLE_DISCOVERY = {
}; };
const SCOPES = [ const SCOPES = [
'openid',
'https://www.googleapis.com/auth/photoslibrary', 'https://www.googleapis.com/auth/photoslibrary',
'https://www.googleapis.com/auth/photoslibrary.appendonly', 'https://www.googleapis.com/auth/photoslibrary.appendonly',
'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile', '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 { class GoogleAUTH extends AuthServiceBase {
constructor() { constructor() {
super(); super();
@@ -37,11 +46,10 @@ class GoogleAUTH extends AuthServiceBase {
this.idToken = null; this.idToken = null;
} }
// Authenticate via Google OAuth; uses placeholder tokens when no real client ID is configured
async authenticate() { async authenticate() {
try { try {
if (GOOGLE_CLIENT_ID === 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com') { if (ANDROID_CLIENT_ID === 'REPLACE_WITH_ANDROID_CLIENT_ID.apps.googleusercontent.com') {
console.log('Google Auth: placeholder mode (no client ID configured)'); console.warn('Google Auth: no Android client ID — using placeholder mode');
this.accessToken = 'placeholder_google_token_' + Date.now(); this.accessToken = 'placeholder_google_token_' + Date.now();
this.refreshToken = 'placeholder_refresh_' + Date.now(); this.refreshToken = 'placeholder_refresh_' + Date.now();
this.expiresAt = Date.now() + 3600000; this.expiresAt = Date.now() + 3600000;
@@ -56,8 +64,11 @@ class GoogleAUTH extends AuthServiceBase {
console.log('OAuth redirect URI:', REDIRECT_URI); 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({ const authRequest = new AuthSession.AuthRequest({
clientId: GOOGLE_CLIENT_ID, clientId: ANDROID_CLIENT_ID,
scopes: SCOPES, scopes: SCOPES,
redirectUri: REDIRECT_URI, redirectUri: REDIRECT_URI,
responseType: AuthSession.ResponseType.Code, responseType: AuthSession.ResponseType.Code,
@@ -71,21 +82,33 @@ class GoogleAUTH extends AuthServiceBase {
throw new Error('Google authentication was cancelled or failed'); throw new Error('Google authentication was cancelled or failed');
} }
const tokenResponse = await AuthSession.exchangeCodeAsync( // Exchange authorization code for tokens using the Web client ID.
{ // Android public clients don't have a client secret — the code_verifier
clientId: GOOGLE_CLIENT_ID, // from PKCE is used to verify the exchange instead.
const tokenBody = new URLSearchParams({
client_id: WEB_CLIENT_ID,
code: result.params.code, code: result.params.code,
redirectUri: REDIRECT_URI, code_verifier: authRequest.codeVerifier,
extraParams: { code_verifier: authRequest.codeVerifier }, grant_type: 'authorization_code',
}, redirect_uri: REDIRECT_URI,
GOOGLE_DISCOVERY });
);
this.accessToken = tokenResponse.accessToken; const tokenResponse = await fetch(GOOGLE_DISCOVERY.tokenEndpoint, {
this.refreshToken = tokenResponse.refreshToken; method: 'POST',
this.expiresAt = tokenResponse.issuedAt headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
? (tokenResponse.issuedAt + (tokenResponse.expiresIn || 3600)) * 1000 body: tokenBody.toString(),
: Date.now() + 3600000; });
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(); const userInfo = await this.fetchUserInfo();
this.email = userInfo?.email || 'google-user'; this.email = userInfo?.email || 'google-user';
@@ -114,7 +137,6 @@ class GoogleAUTH extends AuthServiceBase {
} }
} }
// Return a valid access token, refreshing if expired
async getAccessToken() { async getAccessToken() {
if (this.accessToken && this.expiresAt > Date.now()) return this.accessToken; if (this.accessToken && this.expiresAt > Date.now()) return this.accessToken;
if (this.refreshToken) return await this.refreshAccessToken(); if (this.refreshToken) return await this.refreshAccessToken();
@@ -123,22 +145,23 @@ class GoogleAUTH extends AuthServiceBase {
async refreshAccessToken() { async refreshAccessToken() {
try { try {
if (GOOGLE_CLIENT_ID === 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com') { const body = new URLSearchParams({
this.accessToken = 'refreshed_placeholder_' + Date.now(); client_id: WEB_CLIENT_ID,
this.expiresAt = Date.now() + 3600000; refresh_token: this.refreshToken,
return this.accessToken; grant_type: 'refresh_token',
} });
const tokenResponse = await AuthSession.refreshAsync( const response = await fetch(GOOGLE_DISCOVERY.tokenEndpoint, {
{ clientId: GOOGLE_CLIENT_ID, refreshToken: this.refreshToken }, method: 'POST',
GOOGLE_DISCOVERY headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
); body: body.toString(),
});
this.accessToken = tokenResponse.accessToken; const data = await response.json();
this.expiresAt = tokenResponse.issuedAt if (data.error) throw new Error(data.error_description || data.error);
? (tokenResponse.issuedAt + (tokenResponse.expiresIn || 3600)) * 1000
: Date.now() + 3600000;
this.accessToken = data.access_token;
this.expiresAt = Date.now() + (data.expires_in || 3600) * 1000;
return this.accessToken; return this.accessToken;
} catch (error) { } catch (error) {
console.error('Token refresh error:', error); console.error('Token refresh error:', error);
@@ -148,8 +171,10 @@ class GoogleAUTH extends AuthServiceBase {
async revokeAccess() { async revokeAccess() {
try { try {
if (this.accessToken && GOOGLE_CLIENT_ID !== 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com') { if (this.accessToken) {
await AuthSession.revokeAsync({ token: this.accessToken }, GOOGLE_DISCOVERY); await fetch(`${GOOGLE_DISCOVERY.revocationEndpoint}?token=${this.accessToken}`, {
method: 'POST',
});
} }
} catch (err) { } catch (err) {
console.error('Revoke error:', err); console.error('Revoke error:', err);
@@ -158,6 +183,7 @@ class GoogleAUTH extends AuthServiceBase {
this.refreshToken = null; this.refreshToken = null;
this.expiresAt = null; this.expiresAt = null;
this.email = null; this.email = null;
this.idToken = null;
return { success: true }; return { success: true };
} }
@@ -166,7 +192,6 @@ class GoogleAUTH extends AuthServiceBase {
return info || { id: 'google_user', email: this.email || 'unknown', name: 'Google User' }; 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) { async getPhotos(pageSize = 50, pageToken = null) {
try { try {
const token = await this.getAccessToken(); 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) { async getAlbums(pageSize = 50, pageToken = null) {
try { try {
const token = await this.getAccessToken(); 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') { async uploadPhoto(filePath, filename, mimeType = 'image/jpeg') {
try { try {
const token = await this.getAccessToken(); const token = await this.getAccessToken();