Skip to content

Commit

Permalink
Merge pull request #4 from aarondill/dev
Browse files Browse the repository at this point in the history
feat!: Add Oauth login!
  • Loading branch information
aarondill authored Feb 8, 2024
2 parents 4708aad + 3c1e611 commit 5902f42
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 18 deletions.
36 changes: 19 additions & 17 deletions src/commands/LogIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@ import * as vscode from "vscode";

import type { Command } from "../CommandManager";
import type { VercelManager } from "../features/VercelManager";
const viewSidebar = () =>
vscode.commands.executeCommand("workbench.view.extension.vercel-sidebar");

/** @return HasAccessToken */
async function warnDeprecatedConfig() {
const config = vscode.workspace.getConfiguration("vercel");
const apiToken = config.get<string>("AccessToken");
if (!apiToken) return;
const msg =
"The vercel.AccessToken configuration has been removed. Auto-removing from configuration.";
void vscode.window.showWarningMessage(msg);
await config.update(
"AccessToken",
undefined,
vscode.ConfigurationTarget.Global
);
}
export class LogIn implements Command {
public readonly id = "vercel.logIn";
constructor(private readonly vercel: VercelManager) {}
async execute() {
const apiToken = vscode.workspace
.getConfiguration("vercel")
.get("AccessToken") as string;
//TODO Add support for signing in through website
if (apiToken) {
await this.vercel
.logIn(apiToken)
.then(() =>
vscode.commands.executeCommand(
"workbench.view.extension.vercel-sidebar"
)
);
} else {
await vscode.window.showErrorMessage(
"Please provide vscode-vercel.AccessToken in settings.json."
);
}
await warnDeprecatedConfig();
await this.vercel.logIn();
await viewSidebar();
}
}
6 changes: 5 additions & 1 deletion src/features/VercelManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
VercelEnvironmentInformation,
VercelResponse,
} from "./models";
import { getTokenOauth } from "../utils/oauth";

export class VercelManager {
public onDidEnvironmentsUpdated: () => void = () => {};
Expand Down Expand Up @@ -41,11 +42,14 @@ export class VercelManager {
};
}

async logIn(apiToken: string): Promise<void> {
async logIn(): Promise<boolean> {
const apiToken = await getTokenOauth();
if (!apiToken) return false;
await this.token.setAuth(apiToken);
this.onDidDeploymentsUpdated();
this.onDidEnvironmentsUpdated();
await this.token.onDidLogIn();
return true;
}

/**
Expand Down
80 changes: 80 additions & 0 deletions src/utils/oauth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as vscode from "vscode";
import http from "http";
const OAUTH_PORT = 9615;
const OAUTH_PATH = "/oauth-complete";
const OAUTH_URL = `http://localhost:${OAUTH_PORT}${OAUTH_PATH}`;
const CLIENT_ID = "oac_dJy0AdgVEITrkrnYF5Y4nSlo";
const CLIENT_SEC = "NPBb5J2ZNrlhX3W98DCPS1o1";

const vercelSlug = "vercel-project-manager";
const link = `https://vercel.com/integrations/${vercelSlug}/new`;

/** successMessage is html */
async function serveResponse(
port: number,
pathname?: string // If present, will ignore all other paths
): Promise<URL> {
return await new Promise((resolve, reject) => {
const server = http.createServer(async function (req, res) {
const url = req.url && new URL(req.url, `http://${req.headers.host}`);
if (!url || url.pathname !== pathname) {
res.writeHead(404);
res.end();
if (!url) reject(new Error("No URL provided"));
return;
}
res.writeHead(200, { "Content-Type": "text/html" });
res.end(
"<html><body>Authentication successful! You can now close this window.</body></html>"
);
server.close();
return resolve(url);
});
server.on("error", e => {
if (!("code" in e) || e.code !== "EADDRINUSE") return;
// console.error("Address in use, retrying...");
let tries = 0;
setTimeout(() => {
if (tries++ > 5) return reject(e); // Retry up to 5 times
server.close();
server.listen(port);
}, 1000);
});
server.listen(port);
});
}
async function getTokenFromCode(code: string): Promise<string | undefined> {
const url = new URL("https://api.vercel.com/v2/oauth/access_token");
const headers = {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
};
const params = new URLSearchParams({
code: code,
redirect_uri: OAUTH_URL, // eslint-disable-line @typescript-eslint/naming-convention
client_id: CLIENT_ID, // eslint-disable-line @typescript-eslint/naming-convention
client_secret: CLIENT_SEC, // eslint-disable-line @typescript-eslint/naming-convention
});
const res = await fetch(url, { headers, method: "POST", body: params });
if (!res.ok) {
return;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const jsonRes = (await res.json()) as { access_token: string };
const accessToken = jsonRes.access_token;
return accessToken;
}
export async function getTokenOauth() {
const resUrl = serveResponse(OAUTH_PORT, OAUTH_PATH); // don't await it here! We need to open the url before it's meaningful.
await vscode.env.openExternal(vscode.Uri.parse(link)); // open in a browser
const code = (await resUrl).searchParams.get("code");
if (!code) {
const msg = "Failed to authenticate with Vercel (Couldn't get code).";
return await vscode.window.showErrorMessage(msg);
}
const accessToken = await getTokenFromCode(code);
if (!accessToken) {
const msg = `Failed to authenticate with Vercel. (Couldn't get access token)`;
return await vscode.window.showErrorMessage(msg);
}
return accessToken;
}

0 comments on commit 5902f42

Please sign in to comment.