0

Am creating node backend where user can upload file using the multer package, my upload.js:

const multer = require("multer");
const { google } = require("googleapis");
const fs = require("fs");
const path = require("path");
const router = require("express").Router();
const FOLDERS = require("../utils/folder.drive");
const upload = multer({ dest: "uploads/" });

// Load the service account credentials
const KEYFILEPATH = path.join(__dirname, "../json/drive-credentials.json");
const SCOPES = ["https://www.googleapis.com/auth/drive"];

let drive;

// Initialize Google Drive API
const initializeDrive = async () => {
  try {
    const auth = new google.auth.GoogleAuth({
      keyFile: KEYFILEPATH,
      scopes: SCOPES,
    });
    drive = google.drive({ version: "v3", auth });
  } catch (error) {
    console.error("Error initializing Google Drive API:", error);
    throw new Error("Failed to initialize Google Drive API");
  }
};

// Ensure the API is initialized before any route is used
initializeDrive();

// Upload file to Google Drive
const uploadFile = async (file, folderId) => {
  if (!drive) {
    throw new Error("Google Drive API not initialized");
  }

  const fileMetadata = {
    name: file.originalname,
    parents: [FOLDERS[folderId] || FOLDERS.test],
  };
  const media = {
    mimeType: file.mimetype,
    body: fs.createReadStream(file.path),
  };
  try {
    const response = await drive.files.create({
      resource: fileMetadata,
      media: media,
      fields: "id",
    });
    return response.data.id;
  } catch (error) {
    console.error("Error uploading file:", error);
    throw new Error("Failed to upload file");
  }
}

router.route("/").post(upload.single("file"), async (req, res) => {
  // Get the folder id
  const { folderId } = req.body;
  // Try uploading
  try {
    const fileId = await uploadFile(req.file, folderId);
    fs.unlinkSync(req.file.path); // Delete the file from the server after uploading
    res.status(200).send({ success: true, fileId: fileId });
  } catch (error) {
    // Send error response if something goes wrong
    res.status(500).send({ success: false, error: error.message });
  }
});

module.exports = router;

Everything was working fine, files get uploaded to my Google Drive until in installed jsonwebtoken. anytime I upload a file to the backend from my frontend: fileUpload.js

import axios from "axios";
import API from "../config/api.config";

const uploadFile = async (file, folderId) => {
  const formData = new FormData();
  formData.append("file", file);
  formData.append("folderId", folderId);

  return await axios
    .post(API.url + API.endpoints.upload, formData)
    .then((res) => res.data)
    .then((data) => {
      return { success: true, data };
    })
    .catch((error) => {
      return { success: false, error };
    });
};

export default uploadFile;

I encounter this error:

Error uploading file: GaxiosError: invalid_grant: Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim.
    at Gaxios._request (C:\devfreeguy\projects\web\grajos\backend\node_modules\gaxios\build\src\gaxios.js:142:23)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async GoogleToken._GoogleToken_requestToken (C:\devfreeguy\projects\web\grajos\backend\node_modules\gtoken\build\src\index.js:241:19)
    at async GoogleToken._GoogleToken_getTokenAsync (C:\devfreeguy\projects\web\grajos\backend\node_modules\gtoken\build\src\index.js:160:16)
    at async JWT.refreshTokenNoCache (C:\devfreeguy\projects\web\grajos\backend\node_modules\google-auth-library\build\src\auth\jwtclient.js:173:23)
    at async JWT.getRequestMetadataAsync (C:\devfreeguy\projects\web\grajos\backend\node_modules\google-auth-library\build\src\auth\oauth2client.js:333:17)
    at async JWT.requestAsync (C:\devfreeguy\projects\web\grajos\backend\node_modules\google-auth-library\build\src\auth\oauth2client.js:418:23)
    at async uploadFile (C:\devfreeguy\projects\web\grajos\backend\routes\upload.js:47:22)
    at async C:\devfreeguy\projects\web\grajos\backend\routes\upload.js:64:20 {
  config: {
    method: 'POST',
    url: 'https://www.googleapis.com/oauth2/v4/token',
    data: {
      grant_type: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      assertion: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.'
    },
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'User-Agent': 'google-api-nodejs-client/9.14.0',
      'x-goog-api-client': 'gl-node/20.17.0',
      Accept: 'application/json'
    },
    responseType: 'json',
    retryConfig: {
      httpMethodsToRetry: [Array],
      currentRetryAttempt: 0,
      retry: 3,
      noResponseRetries: 2,
      retryDelayMultiplier: 2,
      timeOfFirstRequest: 1725367689228,
      totalTimeout: 9007199254740991,
      maxRetryDelay: 9007199254740991,
      statusCodesToRetry: [Array]
    },
    paramsSerializer: [Function: paramsSerializer],
    body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    validateStatus: [Function: validateStatus],
    errorRedactor: [Function: defaultErrorRedactor]
  },
  response: {
    config: {
      method: 'POST',
      url: 'https://www.googleapis.com/oauth2/v4/token',
      data: [Object],
      headers: [Object],
      responseType: 'json',
      retryConfig: [Object],
      paramsSerializer: [Function: paramsSerializer],
      body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      validateStatus: [Function: validateStatus],
      errorRedactor: [Function: defaultErrorRedactor]
    },
    data: {
      error: 'invalid_grant',
      error_description: 'Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim.'
    },
    headers: {
      'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
      'cache-control': 'private',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=UTF-8',
      date: 'Tue, 03 Sep 2024 13:55:16 GMT',
      server: 'scaffolding on HTTPServer2',
      'transfer-encoding': 'chunked',
      vary: 'Origin, X-Origin, Referer',
      'x-content-type-options': 'nosniff',
      'x-frame-options': 'SAMEORIGIN',
      'x-xss-protection': '0'
    },
    status: 400,
    statusText: 'Bad Request',
    request: { responseURL: 'https://www.googleapis.com/oauth2/v4/token' }
  },
  error: undefined,
  status: 400,
  [Symbol(gaxios-gaxios-error)]: '6.7.1'
}

when I read the error, it was related to jsonwebtoken

please help

1
  • I'd have to guess that googleapis probably already includes jsonwebtoken and at a specific version. What were you installing it for anyway?
    – Phil
    Commented Sep 4 at 1:31

1 Answer 1

0

I used this and it worked, Moved my client email and private key from the json i downloaded from google cloud api to my .env

const multer = require("multer");
const { google } = require("googleapis");
const fs = require("fs");
const path = require("path");
const router = require("express").Router();
const FOLDERS = require("../utils/folder.drive");
const upload = multer({ dest: "uploads/" });
const { JWT } = google.auth;

const auth = new JWT({
  email: process.env.DRIVE_CLIENT_EMAIL,
  key: process.env.DRIVE_PRIVATE_KEY.replace(/\\n/g, '\n'),
  scopes: ["https://www.googleapis.com/auth/drive"],
});

let drive;

// Initialize Google Drive API
const initializeDrive = async () => {
  try {
    drive = google.drive({ version: "v3", auth });
  } catch (error) {
    console.error("Error initializing Google Drive API:", error);
    throw new Error("Failed to initialize Google Drive API");
  }
};

// Ensure the API is initialized before any route is used
initializeDrive();

// Upload file to Google Drive
const uploadFile = async (file, folderId) => {
  if (!drive) {
    throw new Error("Google Drive API not initialized");
  }

  const fileMetadata = {
    name: file.originalname,
    parents: [FOLDERS[folderId] || FOLDERS.test],
  };
  const media = {
    mimeType: file.mimetype,
    body: fs.createReadStream(file.path),
  };
  try {
    const response = await drive.files.create({
      resource: fileMetadata,
      media: media,
      fields: "id",
    });
    return response.data.id;
  } catch (error) {
    console.error("Error uploading file:", error);
    throw new Error("Failed to upload file");
  }
}
1
router.route("/").post(upload.single("file"), async (req, res) => {
  // Get the folder id
  const { folderId } = req.body;
  // Try uploading
  try {
    const fileId = await uploadFile(req.file, folderId);
    fs.unlinkSync(req.file.path); // Delete the file from the server after uploading
    res.status(200).send({ success: true, fileId: fileId });
  } catch (error) {
    // Send error response if something goes wrong
    res.status(500).send({ success: false, error: error.message });
  }
});

module.exports = router;

This code above works.

Not the answer you're looking for? Browse other questions tagged or ask your own question.