mirror of
https://github.com/Nezumi-2711/google-drive-s3.git
synced 2026-09-22 13:38:30 +00:00
161 lines
6.5 KiB
JavaScript
161 lines
6.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// Local alternative to `rclone config` for obtaining a Google Drive OAuth
|
|
// refresh token. Runs the standard "installed app" / loopback OAuth flow
|
|
// (RFC 8252): opens the consent screen, catches the redirect on a local
|
|
// HTTP server, and exchanges the code for tokens.
|
|
//
|
|
// You still need your own OAuth client (Google requires this per-app, it
|
|
// cannot be generated by a script):
|
|
// 1. https://console.cloud.google.com/ -> create/select a project.
|
|
// 2. APIs & Services > Library -> enable "Google Drive API".
|
|
// 3. APIs & Services > OAuth consent screen -> External, Testing is fine;
|
|
// add your own Google account under "Test users".
|
|
// 4. APIs & Services > Credentials -> Create credentials > OAuth client ID
|
|
// -> Application type: "Desktop app". Copy the Client ID and Client secret.
|
|
//
|
|
// Usage:
|
|
// node scripts/get-google-refresh-token.mjs --client-id ID --client-secret SECRET
|
|
// (or set GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET env vars instead of flags)
|
|
//
|
|
// Optional: --port 8976 (must not be in use; Desktop-app clients accept any
|
|
// loopback port automatically, no need to register it in Google Cloud Console).
|
|
|
|
import { randomBytes } from "node:crypto";
|
|
import http from "node:http";
|
|
import { exec } from "node:child_process";
|
|
import { URL } from "node:url";
|
|
|
|
const SCOPE = "https://www.googleapis.com/auth/drive";
|
|
const AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
const TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
|
|
function parseArgs() {
|
|
const args = process.argv.slice(2);
|
|
const opts = { port: 8976 };
|
|
for (let i = 0; i < args.length; i++) {
|
|
const a = args[i];
|
|
if (a === "--client-id") opts.clientId = args[++i];
|
|
else if (a === "--client-secret") opts.clientSecret = args[++i];
|
|
else if (a === "--port") opts.port = Number(args[++i]);
|
|
else if (a === "--help" || a === "-h") opts.help = true;
|
|
}
|
|
opts.clientId ??= process.env.GOOGLE_CLIENT_ID;
|
|
opts.clientSecret ??= process.env.GOOGLE_CLIENT_SECRET;
|
|
return opts;
|
|
}
|
|
|
|
function printHelp() {
|
|
console.log(`
|
|
Usage: node scripts/get-google-refresh-token.mjs --client-id <ID> --client-secret <SECRET> [--port 8976]
|
|
|
|
Before running this script, create a Desktop-app OAuth client in the Google
|
|
Cloud Console (see the comment block at the top of this file for the exact
|
|
steps) and enable the Google Drive API for that project.
|
|
|
|
Env vars GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET are accepted instead of flags.
|
|
`);
|
|
}
|
|
|
|
function openBrowser(url) {
|
|
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
exec(cmd, () => {});
|
|
}
|
|
|
|
async function main() {
|
|
const opts = parseArgs();
|
|
if (opts.help || !opts.clientId || !opts.clientSecret) {
|
|
printHelp();
|
|
process.exit(opts.help ? 0 : 1);
|
|
}
|
|
|
|
const redirectUri = `http://127.0.0.1:${opts.port}/`;
|
|
const state = randomBytes(16).toString("hex");
|
|
|
|
const authUrl = new URL(AUTH_ENDPOINT);
|
|
authUrl.searchParams.set("client_id", opts.clientId);
|
|
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
authUrl.searchParams.set("response_type", "code");
|
|
authUrl.searchParams.set("scope", SCOPE);
|
|
authUrl.searchParams.set("access_type", "offline");
|
|
authUrl.searchParams.set("prompt", "consent"); // force refresh_token even on repeat auth
|
|
authUrl.searchParams.set("state", state);
|
|
|
|
const code = await new Promise((resolve, reject) => {
|
|
const server = http.createServer((req, res) => {
|
|
const reqUrl = new URL(req.url, redirectUri);
|
|
if (reqUrl.pathname !== "/") {
|
|
res.writeHead(404).end();
|
|
return;
|
|
}
|
|
|
|
const returnedState = reqUrl.searchParams.get("state");
|
|
const err = reqUrl.searchParams.get("error");
|
|
const authCode = reqUrl.searchParams.get("code");
|
|
|
|
if (err) {
|
|
res.writeHead(400, { "Content-Type": "text/plain" }).end(`Authorization failed: ${err}`);
|
|
server.close();
|
|
reject(new Error(`Google returned error: ${err}`));
|
|
return;
|
|
}
|
|
if (returnedState !== state || !authCode) {
|
|
res.writeHead(400, { "Content-Type": "text/plain" }).end("Invalid state or missing code.");
|
|
server.close();
|
|
reject(new Error("State mismatch or missing authorization code."));
|
|
return;
|
|
}
|
|
|
|
res.writeHead(200, { "Content-Type": "text/plain" }).end("Authorization complete. You can close this tab and return to the terminal.");
|
|
server.close();
|
|
resolve(authCode);
|
|
});
|
|
|
|
server.listen(opts.port, "127.0.0.1", () => {
|
|
console.log(`\nOpen this URL in your browser if it doesn't open automatically:\n\n${authUrl.toString()}\n`);
|
|
openBrowser(authUrl.toString());
|
|
console.log("Waiting for authorization...");
|
|
});
|
|
|
|
server.on("error", reject);
|
|
});
|
|
|
|
const tokenRes = await fetch(TOKEN_ENDPOINT, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body: new URLSearchParams({
|
|
code,
|
|
client_id: opts.clientId,
|
|
client_secret: opts.clientSecret,
|
|
redirect_uri: redirectUri,
|
|
grant_type: "authorization_code",
|
|
}),
|
|
});
|
|
|
|
const tokenData = await tokenRes.json();
|
|
if (!tokenRes.ok) {
|
|
console.error("Token exchange failed:", tokenData);
|
|
process.exit(1);
|
|
}
|
|
if (!tokenData.refresh_token) {
|
|
console.error(
|
|
"No refresh_token was returned. This usually means the account already has an active grant.\n" +
|
|
"Revoke it at https://myaccount.google.com/permissions and re-run this script.",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("\nSuccess. Set these as your Worker secrets:\n");
|
|
console.log(`GOOGLE_CLIENT_ID=${opts.clientId}`);
|
|
console.log(`GOOGLE_CLIENT_SECRET=${opts.clientSecret}`);
|
|
console.log(`GOOGLE_REFRESH_TOKEN=${tokenData.refresh_token}`);
|
|
console.log("\nExample (from the repo root):\n");
|
|
console.log(` echo -n "${opts.clientId}" | npx wrangler secret put GOOGLE_CLIENT_ID`);
|
|
console.log(` echo -n "${opts.clientSecret}" | npx wrangler secret put GOOGLE_CLIENT_SECRET`);
|
|
console.log(` echo -n "${tokenData.refresh_token}" | npx wrangler secret put GOOGLE_REFRESH_TOKEN`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err.message ?? err);
|
|
process.exit(1);
|
|
});
|