quip-cli
Version:
A Command Line Interface for the Quip Live Apps platform
136 lines (135 loc) • 5.77 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.login = void 0;
const tslib_1 = require("tslib");
const command_1 = require("@oclif/command");
const fs_1 = tslib_1.__importDefault(require("fs"));
const http_1 = tslib_1.__importDefault(require("http"));
const open_1 = tslib_1.__importDefault(require("open"));
const path_1 = tslib_1.__importDefault(require("path"));
const querystring_1 = tslib_1.__importDefault(require("querystring"));
const url_1 = tslib_1.__importDefault(require("url"));
const auth_1 = require("../lib/auth");
const config_1 = require("../lib/config");
const pkce_challenge_1 = tslib_1.__importDefault(require("pkce-challenge"));
const cli_api_1 = require("../lib/cli-api");
const print_1 = require("../lib/print");
let server_;
const waitForLogin = (hostname, port, ready) => {
const pagePromise = fs_1.default.promises.readFile(path_1.default.join(__dirname, "../..", "templates", "logged-in.html"), "utf-8");
return new Promise((resolve) => {
server_ = http_1.default.createServer(async (req, res) => {
const urlInfo = url_1.default.parse(req.url || "");
const query = querystring_1.default.parse(urlInfo.query || "");
resolve(query);
if (query.next) {
res.statusCode = 302;
res.setHeader('Location', query.next);
res.end();
}
else {
res.statusCode = 200;
res.setHeader("Content-Type", "text/html");
res.end(await pagePromise);
}
server_ === null || server_ === void 0 ? void 0 : server_.close();
});
server_.listen(port, hostname, ready);
});
};
const DEFAULT_HOSTNAME = "127.0.0.1";
const DEFAULT_PORT = 9898;
exports.login = async ({ site, transparent = false, hostname = DEFAULT_HOSTNAME, port = DEFAULT_PORT, config = config_1.defaultConfigPath(), }) => {
const { code_challenge, code_verifier } = pkce_challenge_1.default(43);
const state = cli_api_1.getStateString();
const redirectURL = `http://${hostname}:${port}`;
let loginURL = `https://${site}/cli/login?client_id=quip-cli&response_type=code&redirect_uri=${encodeURIComponent(redirectURL)}&state=${state}&code_challenge=${code_challenge}&code_challenge_method=S256`;
if (transparent) {
loginURL += "&transparent=true";
}
else {
print_1.println(`opening login URL in your browser. Log in to Quip there.\n${loginURL}`);
}
let currentWindow;
const responseParams = await waitForLogin(hostname, port, async () => {
currentWindow = await open_1.default(loginURL);
});
currentWindow === null || currentWindow === void 0 ? void 0 : currentWindow.emit("close");
if (responseParams.cancelled) {
throw new Error("Login cancelled.");
}
else if (responseParams.state !== state) {
throw new Error("API returned invalid state.");
}
else if (!responseParams.code || responseParams.error) {
throw new Error(`Login Failed: ${responseParams.error ||
`no code returned, got ${JSON.stringify(responseParams, null, 2)}`}`);
}
const tokenResponse = await cli_api_1.callAPI(site, "token", "post", {
client_id: "quip-cli",
grant_type: "authorization_code",
redirect_uri: encodeURIComponent(redirectURL),
code_verifier: code_verifier,
code: responseParams.code,
});
const accessToken = tokenResponse.accessToken || tokenResponse.access_token;
if (!accessToken || tokenResponse.error) {
throw new Error(`Failed to acquire access token: ${tokenResponse.error} - response: ${JSON.stringify(tokenResponse, null, 2)}`);
}
await config_1.writeSiteConfig(config, site, { accessToken });
};
class Login extends command_1.Command {
async catch(error) {
server_ === null || server_ === void 0 ? void 0 : server_.close();
throw error;
}
async run() {
const { flags } = this.parse(Login);
const { site, force, hostname, port, config } = flags;
if (!force && (await auth_1.isLoggedIn(config, site))) {
let alt = "";
if (site === config_1.DEFAULT_SITE) {
alt = " or --site to log in to a different site";
}
this.log(`You're already logged in to ${site}. Pass --force to log in again${alt}.`);
return;
}
try {
await exports.login({ site, hostname, port, config });
this.log("Successfully logged in.");
}
catch (e) {
this.error(e);
}
}
}
exports.default = Login;
Login.description = "Logs in to Quip and stores credentials in the .quiprc file";
Login.flags = {
help: command_1.flags.help({ char: "h" }),
force: command_1.flags.boolean({
char: "f",
description: "forces a re-login even if a user is currently logged in",
}),
site: command_1.flags.string({
char: "s",
description: "use a specific quip site rather than the standard quip.com login",
default: config_1.DEFAULT_SITE,
}),
port: command_1.flags.integer({
hidden: true,
description: "Use a custom port for the OAuth redirect server (defaults to 9898)",
default: DEFAULT_PORT,
}),
hostname: command_1.flags.string({
hidden: true,
description: "Use a custom hostname for the OAuth redirect server (defaults to 127.0.0.1)",
default: DEFAULT_HOSTNAME,
}),
config: command_1.flags.string({
hidden: true,
description: "Use a custom config file (default ~/.quiprc)",
default: () => config_1.defaultConfigPath(),
}),
};
Login.args = [];