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

Commit337418d

Browse files
committed
Add credentials process setting
This will be called before requests and before configuring SSH to setcustom headers.
1 parent011c1cf commit337418d

File tree

7 files changed

+153
-4
lines changed

7 files changed

+153
-4
lines changed

‎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.credentialsProcess": {
52+
"markdownDescription":"This command will be executed before each request and before configuring the Coder CLI. The command must output JSON and handle any expiration or caching on its own. Currently the JSON can contain the key `headers` which is a map of header names to values. 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 credentials process erroring
74+
// or an 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/credentials.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import{it,expect}from"vitest"
2+
import{getCredentials}from"./credentials"
3+
4+
constlogger={
5+
writeToCoderOutputChannel(){
6+
// no-op
7+
},
8+
}
9+
10+
it("should return undefined",async()=>{
11+
expect(getCredentials(undefined,undefined,logger)).resolves.toBeUndefined()
12+
expect(getCredentials("foo",undefined,logger)).resolves.toBeUndefined()
13+
expect(getCredentials(undefined,"foo",logger)).resolves.toBeUndefined()
14+
})
15+
16+
it("should return credentials",async()=>{
17+
awaitexpect(
18+
getCredentials("foo",`node -e 'process.stdout.write(JSON.stringify({headers: {foo: "bar"}}))'`,logger),
19+
).resolves.toStrictEqual({headers:{foo:"bar"}})
20+
awaitexpect(
21+
getCredentials("foo",`node -e 'process.stdout.write(JSON.stringify({headers: {}}))'`,logger),
22+
).resolves.toStrictEqual({headers:{}})
23+
awaitexpect(
24+
getCredentials("foo",`node -e 'process.stdout.write(JSON.stringify({}))'`,logger),
25+
).resolves.toStrictEqual({})
26+
})
27+
28+
it("should have access to environment variables",async()=>{
29+
constcoderUrl="dev.coder.com"
30+
awaitexpect(
31+
getCredentials(
32+
coderUrl,
33+
`node -e 'process.stdout.write(JSON.stringify({ headers: {url: process.env.CODER_URL}}))'`,
34+
logger,
35+
),
36+
).resolves.toStrictEqual({headers:{url:coderUrl}})
37+
})
38+
39+
it("should error on non-zero exit",async()=>{
40+
awaitexpect(getCredentials("foo",`node -e 'process.exit(10)'`,logger)).rejects.toMatch(
41+
/exitedunexpectedlywithcode10/,
42+
)
43+
})
44+
45+
it("should error on bad JSON",async()=>{
46+
awaitexpect(getCredentials("foo",`node -e 'process.stdout.write("baz")'`,logger)).rejects.toMatch(
47+
/malformedJSON:SyntaxError/,
48+
)
49+
})

‎src/credentials.ts

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

‎src/extension.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ 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 credentials from credentials process.
70+
axios.interceptors.request.use(async(config)=>{
71+
constcreds=awaitstorage.getCredentials()
72+
if(!creds){
73+
returnconfig
74+
}
75+
return{
76+
...config,
77+
headers:{
78+
...config.headers,
79+
...creds.headers,
80+
},
81+
}
82+
})
83+
6984
constmyWorkspacesProvider=newWorkspaceProvider(WorkspaceQuery.Mine,storage)
7085
constallWorkspacesProvider=newWorkspaceProvider(WorkspaceQuery.All,storage)
7186

@@ -81,8 +96,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
8196
}
8297
}
8398
})
84-
.catch(()=>{
85-
// Not authenticated!
99+
.catch((error)=>{
100+
// This should be a failure to make the request, like the credentials
101+
// process errored.
102+
vscodeProposed.window.showErrorMessage("Failed to check user authentication: "+error.message)
86103
})
87104
.finally(()=>{
88105
vscode.commands.executeCommand("setContext","coder.loaded",true)

‎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 credentials from credentials process.
513+
constcreds=awaitthis.storage.getCredentials()
514+
constheaders=Object.keys(creds?.headers||{}).map((header)=>{
515+
return` --header${escape(`${header}=${creds?.headers[header]}`)}`
516+
})
517+
511518
constsshValues:SSHValues={
512519
Host:`${Remote.Prefix}*`,
513-
ProxyCommand:`${escape(binaryPath)} vscodessh --network-info-dir${escape(
520+
ProxyCommand:`${escape(binaryPath)}${headers.join(" ")} 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{Credentials,getCredentials}from"./credentials"
1415

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

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

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp