Skip to content

Commit 726586c

Browse files
committed
some improvement to the vpn headers
1 parent e9c94ea commit 726586c

File tree

4 files changed

+100
-31
lines changed

4 files changed

+100
-31
lines changed

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@
5151
"type": "string",
5252
"description": "header name"
5353
},
54-
"value": {
54+
"token": {
5555
"type": "string",
56-
"description": "header value optional"
56+
"description": "header value (optional)"
5757
},
58-
"requiredOnStartup":{
59-
"type":"boolean",
60-
"description": "if set to true will open a box to add the headers"
58+
"tokenFile": {
59+
"type":"string",
60+
"description": "optional.Path to a file to retrieve the token from, if set, it will ignore the the require on startup "
6161

6262
}
6363
}

src/extension.ts

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
"use strict"
22

3-
import axios from "axios"
3+
import axios, { AxiosResponse } from "axios"
44
import { getAuthenticatedUser } from "coder/site/src/api/api"
5+
import { readFileSync } from "fs"
56
import * as module from "module"
67
import * as vscode from "vscode"
78
import { Commands } from "./commands"
89
import { Remote } from "./remote"
910
import { Storage } from "./storage"
11+
import * as os from "os"
1012
import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider"
13+
import path from "path"
1114

1215
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
1316
const output = vscode.window.createOutputChannel("Coder")
@@ -19,8 +22,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
1922

2023
vscode.window.registerTreeDataProvider("myWorkspaces", myWorkspacesProvider)
2124
vscode.window.registerTreeDataProvider("allWorkspaces", allWorkspacesProvider)
22-
await addGlobalVpnHeaders()
23-
addAxiosInterceptor()
25+
await initGlobalVpnHeaders(storage)
26+
addAxiosInterceptor(storage)
2427
getAuthenticatedUser()
2528
.then(async (user) => {
2629
if (user) {
@@ -118,43 +121,94 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
118121
})
119122
}
120123
}
121-
function addAxiosInterceptor(): void {
124+
function addAxiosInterceptor(storage: Storage): void {
122125
axios.interceptors.response.use(
123-
(res) => {
124-
if (typeof res.data === "object") {
125-
return res
126+
;(res) => {
127+
if (isVpnTokenInvalid(res)) {
128+
getVpnHeaderFromUser(
129+
"seems like the Vpn Token provided is either invalid or expired, please provide a new token",
130+
).then((token) => {
131+
storage.setVpnHeaderToken(token)
132+
})
126133
} else {
127-
vscode.window.showErrorMessage(JSON.stringify("vpn token expired or missing"))
128-
}
129-
},
130-
(error) => {
131-
if (error.status === 403) {
132-
vscode.window.showErrorMessage(JSON.stringify("vpn token not valid, make sure you added a correct token"))
134+
return res
133135
}
134136
},
137+
(error) => {
138+
if (isVpnTokenInvalidOnError(error)) {
139+
getVpnHeaderFromUser(
140+
"seems like the Vpn Token provided is either invalid or expired, please provide a new token",
141+
).then((token) => {
142+
storage.setVpnHeaderToken(token)
143+
})
144+
// vscode.window.showErrorMessage(JSON.stringify("vpn token not valid, make sure you added a correct token"))
145+
}
146+
return error
147+
},
135148
)
136149
}
137-
async function addGlobalVpnHeaders(): Promise<void> {
150+
async function initGlobalVpnHeaders(storage: Storage): Promise<void> {
138151
//find if global vpn headers are needed
139152
type VpnHeaderConfig = {
140153
headerName: string
141-
value: string
142-
requiredOnStartup: boolean
154+
token: string
155+
tokenFile: string
143156
}
144-
const vpnHeaderConf = vscode.workspace.getConfiguration("coder").get<VpnHeaderConfig>("vpnHeader") || undefined
145-
if (!vpnHeaderConf || vpnHeaderConf.requiredOnStartup === false) {
157+
const vpnHeaderConf = vscode.workspace.getConfiguration("coder").get<VpnHeaderConfig>("vpnHeader")
158+
if (!vpnHeaderConf) {
146159
return
147160
}
148-
const { headerName } = vpnHeaderConf
149-
//read global headers from user prompt
150-
const vpnToken = await vscode.window.showInputBox({
161+
const { headerName, tokenFile, token } = vpnHeaderConf
162+
if (!headerName) {
163+
throw Error(
164+
"vpn header name was not defined in extension setting, please make sure to set `coder.vpnHeader.headerName`",
165+
)
166+
}
167+
const maybeVpnHeaderToken = (await storage.getVpnHeaderToken()) || token || readVpnHeaderTokenFromFile(tokenFile)
168+
if (maybeVpnHeaderToken) {
169+
storage.setVpnHeaderToken(maybeVpnHeaderToken)
170+
axios.defaults.headers.common[headerName] = maybeVpnHeaderToken
171+
} else {
172+
//default to read global headers from user prompt
173+
const vpnToken = await getVpnHeaderFromUser(
174+
"you need to add your vpn access token to be able to run api calls to coder ",
175+
)
176+
177+
if (vpnToken) {
178+
storage.setVpnHeaderToken(vpnToken)
179+
axios.defaults.headers.common[headerName] = vpnToken
180+
} else {
181+
throw Error(
182+
"you must provide a vpn token, either by user prompt, path to file holding the token, or explicitly as conf argument ",
183+
)
184+
}
185+
}
186+
}
187+
188+
async function getVpnHeaderFromUser(message: string): Promise<string | undefined> {
189+
return await vscode.window.showInputBox({
151190
title: "VpnToken",
152-
prompt: "you need to add your vpn access token to be able to run queries",
191+
prompt: message,
153192
placeHolder: "put your token here",
154193
// value: ,
155194
})
195+
}
156196

157-
if (vpnToken) {
158-
axios.defaults.headers.common[headerName] = vpnToken
197+
function readVpnHeaderTokenFromFile(filepath: string): string | undefined {
198+
if (!filepath) {
199+
return
159200
}
201+
if (filepath.startsWith("~")) {
202+
return readFileSync(path.join(os.homedir(), filepath.slice(1)), "utf-8")
203+
} else {
204+
return readFileSync(filepath, "utf-8")
205+
}
206+
}
207+
function isVpnTokenInvalid(res: AxiosResponse<any, any>): boolean {
208+
//if token expired or missing the vpn will return 200 OK with the actual html page to get you to reauthenticate
209+
// , this will result in "data" not being an object but a string containing the html
210+
return typeof res.data !== "object"
211+
}
212+
function isVpnTokenInvalidOnError(error: any): boolean {
213+
return error.isAxiosError && error.response.status === 403
160214
}

src/remote.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,20 @@ export class Remote {
509509
}
510510

511511
const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"`
512+
const vpnTokenHeaderName = this.vscodeProposed.workspace
513+
.getConfiguration()
514+
.get<string>("coder.vpnHeader.headerName")
515+
const VpnHeaderToken = await this.storage.getVpnHeaderToken()
512516
const sshValues = {
513517
Host: `${Remote.Prefix}*`,
514-
ProxyCommand: `${escape(binaryPath)} vscodessh --network-info-dir ${escape(
518+
// 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
519+
ProxyCommand: `${escape(
520+
binaryPath,
521+
)} --header="${vpnTokenHeaderName}=${VpnHeaderToken}" vscodessh --network-info-dir ${escape(
515522
this.storage.getNetworkInfoPath(),
516523
)} --session-token-file ${escape(this.storage.getSessionTokenPath())} --url-file ${escape(
517524
this.storage.getURLPath(),
518-
)} %h`,
525+
)} %h`,
519526
ConnectTimeout: "0",
520527
StrictHostKeyChecking: "no",
521528
UserKnownHostsFile: "/dev/null",

src/storage.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ export class Storage {
5454
public async getSessionToken(): Promise<string | undefined> {
5555
return this.secrets.get("sessionToken")
5656
}
57+
public async setVpnHeaderToken(headerToken?: string): Promise<void> {
58+
if (headerToken) {
59+
this.secrets.store("vpnHeaderToken", headerToken)
60+
}
61+
}
62+
public async getVpnHeaderToken(): Promise<string | undefined> {
63+
return await this.secrets.get("vpnHeaderToken")
64+
}
5765

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

0 commit comments

Comments
 (0)