Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit534be09

Browse files
committed
Add header command setting
This will be called before requests and added to the SSH config.
1 parent423360c commit534be09

File tree

8 files changed

+180
-4
lines changed

8 files changed

+180
-4
lines changed

‎flake.nix

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
description="vscode-coder";
3+
4+
inputs.flake-utils.url="github:numtide/flake-utils";
5+
6+
outputs={self,nixpkgs,flake-utils}:
7+
flake-utils.lib.eachDefaultSystem
8+
(system:
9+
letpkgs=nixpkgs.legacyPackages.${system};
10+
nodejs=pkgs.nodejs-18_x;
11+
yarn'=pkgs.yarn.override{inheritnodejs;};
12+
in{
13+
devShells.default=pkgs.mkShell{
14+
nativeBuildInputs=withpkgs;[
15+
nodejsyarn'python3pkg-configgitrsyncjqmoreutilsquiltbatsopenssl
16+
];
17+
buildInputs=withpkgs;(lib.optionals(!stdenv.isDarwin)[libsecret]
18+
++(withxorg;[libX11libxkbfile])
19+
++lib.optionalsstdenv.isDarwin(withpkgs.darwin.apple_sdk.frameworks;[
20+
AppKitCocoaCoreServicesSecurityxcbuild
21+
]));
22+
};
23+
}
24+
);
25+
}

‎package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
"markdownDescription":"If true, the extension will not verify the authenticity of the remote host. This is useful for self-signed certificates.",
4848
"type":"boolean",
4949
"default":false
50+
},
51+
"coder.headerCommand": {
52+
"markdownDescription":"An external command that outputs additional HTTP headers added to all requests. The command must output each header as `key=value` on its own line. The following environment variables will be available to the process: `CODER_URL`.",
53+
"type":"string",
54+
"default":""
5055
}
5156
}
5257
},

‎src/commands.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ export class Commands {
7070
severity:vscode.InputBoxValidationSeverity.Error,
7171
}
7272
}
73+
// This could be something like the header command erroring or an
74+
// invalid session token.
7375
return{
74-
message:"Invalid session token! ("+message+")",
76+
message:"Failed to authenticate:"+message,
7577
severity:vscode.InputBoxValidationSeverity.Error,
7678
}
7779
})

‎src/extension.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
6666
conststorage=newStorage(output,ctx.globalState,ctx.secrets,ctx.globalStorageUri,ctx.logUri)
6767
awaitstorage.init()
6868

69+
// Add headers from the header command.
70+
axios.interceptors.request.use(async(config)=>{
71+
return{
72+
...config,
73+
headers:{
74+
...(awaitstorage.getHeaders()),
75+
...creds.headers,
76+
},
77+
}
78+
})
79+
6980
constmyWorkspacesProvider=newWorkspaceProvider(WorkspaceQuery.Mine,storage)
7081
constallWorkspacesProvider=newWorkspaceProvider(WorkspaceQuery.All,storage)
7182

@@ -81,8 +92,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
8192
}
8293
}
8394
})
84-
.catch(()=>{
85-
// Not authenticated!
95+
.catch((error)=>{
96+
// This should be a failure to make the request, like the header command
97+
// errored.
98+
vscodeProposed.window.showErrorMessage("Failed to check user authentication: "+error.message)
8699
})
87100
.finally(()=>{
88101
vscode.commands.executeCommand("setContext","coder.loaded",true)

‎src/headers.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import*asosfrom"os"
2+
import{it,expect}from"vitest"
3+
import{getHeaders}from"./headers"
4+
5+
constlogger={
6+
writeToCoderOutputChannel(){
7+
// no-op
8+
},
9+
}
10+
11+
it("should return undefined",async()=>{
12+
awaitexpect(getHeaders(undefined,undefined,logger)).resolves.toStrictEqual({})
13+
awaitexpect(getHeaders("foo",undefined,logger)).resolves.toStrictEqual({})
14+
awaitexpect(getHeaders(undefined,"foo",logger)).resolves.toStrictEqual({})
15+
})
16+
17+
it("should return headers",async()=>{
18+
awaitexpect(getHeaders("foo","printf foo=bar'\n'baz=qux",logger)).resolves.toStrictEqual({
19+
foo:"bar",
20+
baz:"qux",
21+
})
22+
awaitexpect(getHeaders("foo","printf foo=bar'\r\n'baz=qux",logger)).resolves.toStrictEqual({
23+
foo:"bar",
24+
baz:"qux",
25+
})
26+
awaitexpect(getHeaders("foo","printf foo=bar'\r\n'",logger)).resolves.toStrictEqual({foo:"bar"})
27+
awaitexpect(getHeaders("foo","printf foo=bar",logger)).resolves.toStrictEqual({foo:"bar"})
28+
awaitexpect(getHeaders("foo","printf foo=bar=",logger)).resolves.toStrictEqual({foo:"bar="})
29+
awaitexpect(getHeaders("foo","printf foo=bar=baz",logger)).resolves.toStrictEqual({foo:"bar=baz"})
30+
awaitexpect(getHeaders("foo","printf foo=",logger)).resolves.toStrictEqual({foo:""})
31+
})
32+
33+
it("should error on malformed or empty lines",async()=>{
34+
awaitexpect(getHeaders("foo","printf foo=bar'\r\n\r\n'",logger)).rejects.toMatch(/Malformed/)
35+
awaitexpect(getHeaders("foo","printf '\r\n'foo=bar",logger)).rejects.toMatch(/Malformed/)
36+
awaitexpect(getHeaders("foo","printf =foo",logger)).rejects.toMatch(/Malformed/)
37+
awaitexpect(getHeaders("foo","printf foo",logger)).rejects.toMatch(/Malformed/)
38+
awaitexpect(getHeaders("foo","printf ''",logger)).rejects.toMatch(/Malformed/)
39+
})
40+
41+
it("should have access to environment variables",async()=>{
42+
constcoderUrl="dev.coder.com"
43+
awaitexpect(
44+
getHeaders(
45+
coderUrl,
46+
os.platform()==="win32" ?"printf url=%CODER_URL" :"printf url=$CODER_URL",
47+
logger,
48+
),
49+
).resolves.toStrictEqual({url:coderUrl})
50+
})
51+
52+
it("should error on non-zero exit",async()=>{
53+
awaitexpect(getHeaders("foo","exit 10",logger)).rejects.toMatch(
54+
/exitedunexpectedlywithcode10/,
55+
)
56+
})

‎src/headers.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import*ascpfrom"child_process"
2+
import*asutilfrom"util"
3+
4+
exportinterfaceLogger{
5+
writeToCoderOutputChannel(message:string):void
6+
}
7+
8+
interfaceExecException{
9+
code?:number
10+
stderr?:string
11+
stdout?:string
12+
}
13+
14+
functionisExecException(err:unknown):err isExecException{
15+
returntypeof(errasExecException).code!=="undefined"
16+
}
17+
18+
// TODO: getHeaders might make more sense to directly implement on Storage
19+
// but it is difficult to test Storage right now since we use vitest instead of
20+
// the standard extension testing framework which would give us access to vscode
21+
// APIs. We should revert the testing framework then consider moving this.
22+
23+
// getHeaders executes the header command and parses the headers from stdout.
24+
// Both stdout and stderr are logged on error but stderr is otherwise ignored.
25+
// Throws an error if the process exits with non-zero or the JSON is invalid.
26+
// Returns undefined if there is no header command set. No effort is made to
27+
// validate the JSON other than making sure it can be parsed.
28+
exportasyncfunctiongetHeaders(
29+
url:string|undefined,
30+
command:string|undefined,
31+
logger:Logger,
32+
):Promise<Record<string,string>>{
33+
constheaders:Record<string,string>={}
34+
if(typeofurl==="string"&&url.trim().length>0&&typeofcommand==="string"&&command.trim().length>0){
35+
letresult:{stdout:string,stderr:string}
36+
try{
37+
result=awaitutil.promisify(cp.exec)(command,{
38+
env:{
39+
...process.env,
40+
CODER_URL:url,
41+
},
42+
})
43+
}catch(error){
44+
if(isExecException(error)){
45+
logger.writeToCoderOutputChannel(`Header command exited unexpectedly with code${error.code}`)
46+
logger.writeToCoderOutputChannel(`stdout:${error.stdout}`)
47+
logger.writeToCoderOutputChannel(`stderr:${error.stderr}`)
48+
thrownewError(`Header command exited unexpectedly with code${error.code}`)
49+
}
50+
thrownewError(`Header command exited unexpectedly:${error}`)
51+
}
52+
// This should imitate or be a subset of the Coder CLI's behavior.
53+
constlines=result.stdout.replace(/\r?\n$/,"").split(/\r?\n/)
54+
for(leti=0;i<lines.length;++i){
55+
const[key,value]=lines[i].split(/=(.*)/)
56+
if(key.length===0||typeofvalue==="undefined"){
57+
thrownewError(`Malformed line from header command: [${lines[i]}] (out:${result.stdout})`)
58+
}
59+
headers[key]=value
60+
}
61+
}
62+
returnheaders
63+
}

‎src/remote.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,9 +508,16 @@ export class Remote {
508508
}
509509

510510
constescape=(str:string):string=>`"${str.replace(/"/g,'\\"')}"`
511+
512+
// Add headers from the header command.
513+
letheaderCommand=vscode.workspace.getConfiguration().get("coder.headerCommand")
514+
if(headerCommand){
515+
headerCommand=` --header-command${headerCommand}`
516+
}
517+
511518
constsshValues:SSHValues={
512519
Host:`${Remote.Prefix}*`,
513-
ProxyCommand:`${escape(binaryPath)} vscodessh --network-info-dir${escape(
520+
ProxyCommand:`${escape(binaryPath)}${headerCommand} vscodessh --network-info-dir${escape(
514521
this.storage.getNetworkInfoPath(),
515522
)} --session-token-file${escape(this.storage.getSessionTokenPath())} --url-file${escape(
516523
this.storage.getURLPath(),

‎src/storage.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import os from "os"
1111
importpathfrom"path"
1212
importprettyBytesfrom"pretty-bytes"
1313
import*asvscodefrom"vscode"
14+
import{getHeaders}from"./headers"
1415

1516
exportclassStorage{
1617
publicworkspace?:Workspace
@@ -382,6 +383,10 @@ export class Storage {
382383
awaitfs.rm(this.getSessionTokenPath(),{force:true})
383384
}
384385
}
386+
387+
publicasyncgetHeaders(url=this.getURL()):Promise<Record<string,string>|undefined>{
388+
returngetHeaders(url,vscode.workspace.getConfiguration().get("coder.headerCommand"),this)
389+
}
385390
}
386391

387392
// goos returns the Go format for the current platform.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp