Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure OAuth workflow #70

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions src/_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { dirname } from "node:path";
import { pathToFileURL } from "node:url";

import chalk from "chalk";
import { GoogleAuth } from "google-auth-library";
import { GoogleAuth, OAuth2Client } from "google-auth-library";
import { findUp } from "find-up";

const _is_js_config = (filename) => {
Expand Down Expand Up @@ -65,10 +65,36 @@ export const get_auth = (path, scopes) => {
const file = path.startsWith("~") ? path.replace("~", homedir()) : path;
if (!existsSync(file)) {
fatal_error(`
Could not open service account credentials at ${file}.
Reconfigure the "auth" properties in your configuration file or download the credentials file.
Could not open account credentials at ${file}.
Reconfigure the "auth" properties in your configuration file or create a credentials file.
`);
}

return new GoogleAuth({ keyFile: file, scopes });
const auth = JSON.parse(readFileSync(file).toString());

if (auth.type === "service_account") {
return new GoogleAuth({ keyFile: file, scopes });
} else if (auth.type === "oauth") {
if (!existsSync(auth.clientPath)) {
fatal_error(`Could not open OAuth client file at ${auth.clientPath}.`)
} else {
const { web: { client_id, client_secret, redirect_uris }} = JSON.parse(readFileSync(auth.clientPath).toString());

const client = new OAuth2Client(
client_id,
client_secret,
redirect_uris[0]
);

const token = structuredClone(auth);
delete token.type;
delete token.clientPath;

client.setCredentials(token);

return client;
}
} else {
fatal_error(`Could not parse authentication file type ${auth.type} at ${file}`);
}
};
85 changes: 85 additions & 0 deletions src/sink-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { fileURLToPath } from "node:url";
import { homedir } from "node:os";
import { readFileSync, writeFileSync } from "node:fs";
import { createServer } from "node:http";

import { program } from "commander";
import { OAuth2Client } from "google-auth-library";
import chalk from "chalk";
import { success } from "./_utils.js";

const self = fileURLToPath(import.meta.url);

const main = async () => {
const authPath = "~/.sink-google-auth-oauth-client.json";
const absoluteAuthPath = authPath.startsWith("~") ? authPath.replace("~", homedir()) : authPath;
const auth = JSON.parse(readFileSync(absoluteAuthPath).toString());

const client = new OAuth2Client(
auth.web.client_id,
auth.web.client_secret,
auth.web.redirect_uris[0]
);

const authorizationUrl = client.generateAuthUrl({
access_type: "offline",
scope: "https://www.googleapis.com/auth/drive.readonly"
});

console.log(
`Start the OAuth workflow at ${chalk.yellow(authorizationUrl)}.`
);

console.log(
"Note that you are expected to see an error screen after getting redirected to a localhost URL."
)

let server;
const sockets = new Set();

const token = await new Promise((resolve) => {
server = createServer(async (req) => {
const queryParams = new URL(req.url, "http://localhost:3000").searchParams;
const code = queryParams.get("code");
const { tokens } = await client.getToken(code);
resolve(tokens);
});

server.listen(3000);

server.on("connection", (socket) => {
sockets.add(socket);

server.once("close", () => {
sockets.delete(socket);
})
})
});

for (const socket of sockets) {
socket.destroy();
sockets.delete(socket);
}

server.close();

const tokenPath = `${homedir()}/.sink-google-auth-oauth-token.json`
writeFileSync(
tokenPath,
JSON.stringify({
type: "oauth",
clientPath: absoluteAuthPath,
...token
})
);

success(`A Google OAuth token has been generated at ${tokenPath}.`);
}

if (process.argv[1] === self) {
program
.version("2.7.3")
.parse();

main();
}
1 change: 1 addition & 0 deletions src/sink-text.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
get_auth,
write_file,
has_filled_props,
fatal_error
} from "./_utils.js";

export const fetchText = async ({ id, output, auth }) => {
Expand Down
3 changes: 2 additions & 1 deletion src/sink.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ program
.command("json", "fetch JSON files from Google Drive")
.command("text", "fetch text files from Google Drive")
.command("fetch", "fetch all Google Docs and Sheets")
.command("deploy", "deploy a build directory to AWS S3");
.command("deploy", "deploy a build directory to AWS S3")
.command("auth", "authenticate with Google OAuth");

program.parse(process.argv);