Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
itcon-pty-au committed Aug 30, 2024
1 parent 133e23f commit 7604e89
Showing 1 changed file with 127 additions and 99 deletions.
226 changes: 127 additions & 99 deletions gdrive.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,112 +247,24 @@ if (cloudButtonDiv) {
startBackupInterval();
});
}

async function signWithPrivateKey(data, privateKeyPEM) {
// Sanitize PEM key
const pemContents = privateKeyPEM
.replace(/\n/g, '')
.replace('-----BEGIN PRIVATE KEY-----', '')
.replace('-----END PRIVATE KEY-----', '');
const binaryDerString = atob(pemContents);
const binaryDer = str2ab(binaryDerString);

// Import the private key
const key = await window.crypto.subtle.importKey(
'pkcs8',
binaryDer,
{
name: 'RSASSA-PKCS1-v1_5', // Updated to correct algorithm
hash: { name: 'SHA-256' }
},
true,
['sign']
);

// Sign the data
const enc = new TextEncoder();
const signature = await window.crypto.subtle.sign(
{
name: 'RSASSA-PKCS1-v1_5'
},
key,
enc.encode(data)
);

// Convert signature to Base64URL format
return arrayBufferToBase64Url(signature);
}

async function getGoogleAccessToken(serviceAccountKey) {
const scope = 'https://www.googleapis.com/auth/drive';

// Create the header
const header = {
alg: "RS256",
typ: "JWT"
};

const now = Math.floor(Date.now() / 1000);
const expiryTime = now + 3600; // 1 hour expiry time

// Create the claims
const claims = {
iss: serviceAccountKey.client_email,
scope: scope,
aud: 'https://oauth2.googleapis.com/token',
exp: expiryTime,
iat: now,
};

// Encode the header and claims as Base64URL
const base64UrlHeader = btoa(JSON.stringify(header)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
const base64UrlClaims = btoa(JSON.stringify(claims)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');

// Create the signature
const signatureInput = `${base64UrlHeader}.${base64UrlClaims}`;
const signature = await signWithPrivateKey(signatureInput, serviceAccountKey.private_key);

const jwt = `${signatureInput}.${signature}`;

// Get the access token
const params = new URLSearchParams();
params.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
params.append('assertion', jwt);

const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});

const tokenData = await response.json();
if ('error' in tokenData) {
throw new Error(tokenData.error);
}

return tokenData.access_token;
}

async function exportToGoogleDrive() {
console.log("Starting export to Google Drive...");
console.log("Starting export to Google Drive..."); // Log start of function execution

const remoteFilename = localStorage.getItem('remote-filename');
const serviceAccountKey = JSON.parse(localStorage.getItem('service-account-key'));

const serviceAccountKey = JSON.parse(localStorage.getItem('service-account-key')); // Retrieve stored Service Account Key from localStorage
let googleAccessToken;
try {
googleAccessToken = await getGoogleAccessToken(serviceAccountKey);
console.log("Successfully retrieved Google Access Token.");
console.log("Successfully retrieved Google Access Token."); // Log token retrieval success
} catch (error) {
console.error("Failed to get Google Access Token:", error);
console.error("Failed to get Google Access Token:", error); // Log error if token retrieval fails
displayMessage('AppData sync to Google Drive failed!', 'white');
return;
}

try {
const exportData = await exportBackupData();
const exportData = await exportBackupData(); // Export the local data
const metadata = {
name: remoteFilename,
mimeType: 'application/json',
Expand All @@ -368,7 +280,7 @@ async function exportToGoogleDrive() {
});

const location = initResponse.headers.get('Location');
console.log("Location for upload:", location);
console.log("Location for upload:", location); // Log the location URL for the resumable upload

if (!location) {
throw new Error('Failed to initiate resumable upload session. Location header not found.');
Expand All @@ -383,7 +295,7 @@ async function exportToGoogleDrive() {
body: JSON.stringify(exportData),
});

console.log("Upload response status:", uploadResponse.status);
console.log("Upload response status:", uploadResponse.status); // Log the upload response status

if (uploadResponse.ok) {
const currentTime = new Date().toLocaleString('en-AU', {
Expand All @@ -395,16 +307,132 @@ async function exportToGoogleDrive() {
hour12: true,
});
localStorage.setItem('last-cloud-sync', currentTime);
console.log("AppData synced to Google Drive successfully at", currentTime);
console.log("AppData synced to Google Drive successfully at", currentTime); // Log successful sync and time
displayMessage('AppData synced to Google Drive successfully!', 'white');
} else {
throw new Error(`Upload failed with status: ${uploadResponse.status} - ${uploadResponse.statusText}`);
}
} catch (error) {
console.error("Export to Google Drive failed:", error);
console.error("Export to Google Drive failed:", error); // Log any error caught in the export process
displayMessage('AppData sync to Google Drive failed!', 'white');
}
}
// Fetches the access token using JWT
async function getGoogleAccessToken(serviceAccountKey) {
const scope = 'https://www.googleapis.com/auth/drive';

const header = {
alg: "RS256",
typ: "JWT"
};

const now = Math.floor(Date.now() / 1000);
const expiryTime = now + 3600; // 1 hour expiry time

const claims = {
iss: serviceAccountKey.client_email,
scope: scope,
aud: 'https://oauth2.googleapis.com/token',
exp: expiryTime,
iat: now,
};

const base64UrlHeader = btoa(JSON.stringify(header)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
const base64UrlClaims = btoa(JSON.stringify(claims)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');

const signatureInput = `${base64UrlHeader}.${base64UrlClaims}`;
const signature = await signWithPrivateKey(signatureInput, serviceAccountKey.private_key);

const jwt = `${signatureInput}.${signature}`;

const params = new URLSearchParams();
params.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
params.append('assertion', jwt);

const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});

const tokenData = await response.json();
if ('error' in tokenData) {
throw new Error(tokenData.error);
}

return tokenData.access_token;
}
// Signs data with provided PEM private key
async function signWithPrivateKey(data, privateKeyPEM) {
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
const pemContents = privateKeyPEM.substring(pemHeader.length, privateKeyPEM.length - pemFooter.length);
const binaryDerString = window.atob(pemContents);
const binaryDer = str2ab(binaryDerString);

const key = await window.crypto.subtle.importKey(
'pkcs8',
binaryDer,
{
name: 'RSASSA-PKCS1-v1_5',
hash: { name: 'SHA-256' }
},
true,
['sign']
);

const enc = new TextEncoder();
const signature = await window.crypto.subtle.sign(
{
name: 'RSASSA-PKCS1-v1_5'
},
key,
enc.encode(data)
);

return arrayBufferToBase64Url(signature);
}

function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}

function arrayBufferToBase64Url(buffer) {
const byteArray = new Uint8Array(buffer);
let binaryString = '';
for (let i = 0; i < byteArray.byteLength; i++) {
binaryString += String.fromCharCode(byteArray[i]);
}
const base64String = window.btoa(binaryString);
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}

function arrayBufferToBase64Url(buffer) {
const byteArray = new Uint8Array(buffer);
let binaryString = '';
for (let i = 0; i < byteArray.byteLength; i++) {
binaryString += String.fromCharCode(byteArray[i]);
}
const base64String = window.btoa(binaryString);
// Replace characters according to Base64URL specs
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

async function importFromGoogleDrive() {
console.log("Starting import from Google Drive..."); // Log start of function execution
Expand Down Expand Up @@ -538,4 +566,4 @@ function displayMessage(message, color) {
cloudActionMsg.textContent = '';
}, 3000);
}
checkDocumentReady();
checkDocumentReady();

0 comments on commit 7604e89

Please sign in to comment.