Skip to content

pass headers to coder api #81

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

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/coverage/
*.vsix
yarn-error.log
.vscode/settings.json
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@
},
"scope": "machine",
"default": []
},
"coder.vpnHeader":{
"markdownDescription": "key/ value pair to add to coder",
"type": "object",
"properties": {
"headerName": {
"type": "string",
"description": "header name"
},
"token": {
"type": "string",
"description": "header value (optional)"
},
"tokenFile": {
"type":"string",
"description": "optional.Path to a file to retrieve the token from, if set, it will ignore the the require on startup "

}
}

}
}
},
Expand Down
4 changes: 2 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import axios from "axios"
import axios from "axios"
import { getAuthenticatedUser, getWorkspaces, updateWorkspaceVersion } from "coder/site/src/api/api"
import { Workspace } from "coder/site/src/api/typesGenerated"
import * as vscode from "vscode"
Expand All @@ -8,8 +8,8 @@ import { Storage } from "./storage"
import { WorkspaceTreeItem } from "./workspacesProvider"

export class Commands {
public constructor(private readonly vscodeProposed: typeof vscode, private readonly storage: Storage) {}

public constructor(private readonly vscodeProposed: typeof vscode, private readonly storage: Storage) {}
public async login(...args: string[]): Promise<void> {
let url: string | undefined = args.length >= 1 ? args[0] : undefined
if (!url) {
Expand Down
98 changes: 97 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"use strict"

import axios, { AxiosResponse } from "axios"
import { getAuthenticatedUser } from "coder/site/src/api/api"
import { readFileSync } from "fs"
import * as module from "module"
import * as vscode from "vscode"
import { Commands } from "./commands"
import { Remote } from "./remote"
import { Storage } from "./storage"
import * as os from "os"
import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider"
import path from "path"

export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
const output = vscode.window.createOutputChannel("Coder")
Expand All @@ -18,7 +22,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {

vscode.window.registerTreeDataProvider("myWorkspaces", myWorkspacesProvider)
vscode.window.registerTreeDataProvider("allWorkspaces", allWorkspacesProvider)

await initGlobalVpnHeaders(storage)
addAxiosInterceptor(storage)
getAuthenticatedUser()
.then(async (user) => {
if (user) {
Expand Down Expand Up @@ -116,3 +121,94 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
})
}
}
function addAxiosInterceptor(storage: Storage): void {
axios.interceptors.response.use(
;(res) => {
if (isVpnTokenInvalid(res)) {
getVpnHeaderFromUser(
"seems like the Vpn Token provided is either invalid or expired, please provide a new token",
).then((token) => {
storage.setVpnHeaderToken(token)
})
} else {
return res
}
},
(error) => {
if (isVpnTokenInvalidOnError(error)) {
getVpnHeaderFromUser(
"seems like the Vpn Token provided is either invalid or expired, please provide a new token",
).then((token) => {
storage.setVpnHeaderToken(token)
})
// vscode.window.showErrorMessage(JSON.stringify("vpn token not valid, make sure you added a correct token"))
}
return error
},
)
}
async function initGlobalVpnHeaders(storage: Storage): Promise<void> {
//find if global vpn headers are needed
type VpnHeaderConfig = {
headerName: string
token: string
tokenFile: string
}
const vpnHeaderConf = vscode.workspace.getConfiguration("coder").get<VpnHeaderConfig>("vpnHeader")
if (!vpnHeaderConf) {
return
}
const { headerName, tokenFile, token } = vpnHeaderConf
if (!headerName) {
throw Error(
"vpn header name was not defined in extension setting, please make sure to set `coder.vpnHeader.headerName`",
)
}
const maybeVpnHeaderToken = (await storage.getVpnHeaderToken()) || token || readVpnHeaderTokenFromFile(tokenFile)
if (maybeVpnHeaderToken) {
storage.setVpnHeaderToken(maybeVpnHeaderToken)
axios.defaults.headers.common[headerName] = maybeVpnHeaderToken
} else {
//default to read global headers from user prompt
const vpnToken = await getVpnHeaderFromUser(
"you need to add your vpn access token to be able to run api calls to coder ",
)

if (vpnToken) {
storage.setVpnHeaderToken(vpnToken)
axios.defaults.headers.common[headerName] = vpnToken
} else {
throw Error(
"you must provide a vpn token, either by user prompt, path to file holding the token, or explicitly as conf argument ",
)
}
}
}

async function getVpnHeaderFromUser(message: string): Promise<string | undefined> {
return await vscode.window.showInputBox({
title: "VpnToken",
prompt: message,
placeHolder: "put your token here",
// value: ,
})
}

function readVpnHeaderTokenFromFile(filepath: string): string | undefined {
if (!filepath) {
return
}
if (filepath.startsWith("~")) {
return readFileSync(path.join(os.homedir(), filepath.slice(1)), "utf-8")
} else {
return readFileSync(filepath, "utf-8")
}
}
function isVpnTokenInvalid(res: AxiosResponse<any, any>): boolean {
//if token expired or missing the vpn will return 200 OK with the actual html page to get you to reauthenticate
// , this will result in "data" not being an object but a string containing the html
return typeof res.data !== "object"
}
function isVpnTokenInvalidOnError(error: any): boolean {
return error.isAxiosError && error.response.status === 403
}
11 changes: 9 additions & 2 deletions src/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,13 +509,20 @@ export class Remote {
}

const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"`
const vpnTokenHeaderName = this.vscodeProposed.workspace
.getConfiguration()
.get<string>("coder.vpnHeader.headerName")
const VpnHeaderToken = await this.storage.getVpnHeaderToken()
const sshValues = {
Host: `${Remote.Prefix}*`,
ProxyCommand: `${escape(binaryPath)} vscodessh --network-info-dir ${escape(
// when running vscodessh command we get the following error "find workspace: invalid character '<' looking for beginning of value" which means that the header is not proppgate currectly
ProxyCommand: `${escape(
binaryPath,
)} --header="${vpnTokenHeaderName}=${VpnHeaderToken}" vscodessh --network-info-dir ${escape(
this.storage.getNetworkInfoPath(),
)} --session-token-file ${escape(this.storage.getSessionTokenPath())} --url-file ${escape(
this.storage.getURLPath(),
)} %h`,
)} %h`,
ConnectTimeout: "0",
StrictHostKeyChecking: "no",
UserKnownHostsFile: "/dev/null",
Expand Down
8 changes: 8 additions & 0 deletions src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ export class Storage {
public async getSessionToken(): Promise<string | undefined> {
return this.secrets.get("sessionToken")
}
public async setVpnHeaderToken(headerToken?: string): Promise<void> {
if (headerToken) {
this.secrets.store("vpnHeaderToken", headerToken)
}
}
public async getVpnHeaderToken(): Promise<string | undefined> {
return await this.secrets.get("vpnHeaderToken")
}

// getRemoteSSHLogPath returns the log path for the "Remote - SSH" output panel.
// There is no VS Code API to get the contents of an output panel. We use this
Expand Down
Loading