Skip to content

Commit 6d240a0

Browse files
committed
refactor: parse options with multiple = in cli
There was a case with the hashed-password which had multiple equal signs in the value and it wasn't being parsed correctly. This uses a new function and adds a few tests.
1 parent e3171dd commit 6d240a0

File tree

7 files changed

+75
-63
lines changed

7 files changed

+75
-63
lines changed

docs/FAQ.md

+32-34
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,38 @@
22
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
33
# FAQ
44

5-
- [FAQ](#faq)
6-
- [Questions?](#questions)
7-
- [iPad Status?](#ipad-status)
8-
- [Community Projects (awesome-code-server)](#community-projects-awesome-code-server)
9-
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
10-
- [Differences compared to VS Code?](#differences-compared-to-vs-code)
11-
- [Installing an extension](#installing-an-extension)
12-
- [How can I request a missing extension?](#how-can-i-request-a-missing-extension)
13-
- [Installing an extension manually](#installing-an-extension-manually)
14-
- [How do I configure the marketplace URL?](#how-do-i-configure-the-marketplace-url)
15-
- [Where are extensions stored?](#where-are-extensions-stored)
16-
- [How is this different from VS Code Codespaces?](#how-is-this-different-from-vs-code-codespaces)
17-
- [How should I expose code-server to the internet?](#how-should-i-expose-code-server-to-the-internet)
18-
- [Can I store my password hashed?](#can-i-store-my-password-hashed)
19-
- [How do I securely access web services?](#how-do-i-securely-access-web-services)
20-
- [Sub-paths](#sub-paths)
21-
- [Sub-domains](#sub-domains)
22-
- [Why does the code-server proxy strip `/proxy/<port>` from the request path?](#why-does-the-code-server-proxy-strip-proxyport-from-the-request-path)
23-
- [Proxying to Create React App](#proxying-to-create-react-app)
24-
- [Multi-tenancy](#multi-tenancy)
25-
- [Docker in code-server container?](#docker-in-code-server-container)
26-
- [How can I disable telemetry?](#how-can-i-disable-telemetry)
27-
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
28-
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
29-
- [Heartbeat File](#heartbeat-file)
30-
- [Healthz endpoint](#healthz-endpoint)
31-
- [How does the config file work?](#how-does-the-config-file-work)
32-
- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure)
33-
- [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work)
34-
- [How do I access my Documents/Downloads/Desktop folders in code-server on OSX?](#how-do-i-access-my-documentsdownloadsdesktop-folders-in-code-server-on-osx)
35-
- [Differences compared to Theia?](#differences-compared-to-theia)
36-
- [`$HTTP_PROXY`, `$HTTPS_PROXY`, `$NO_PROXY`](#http_proxy-https_proxy-no_proxy)
37-
- [Enterprise](#enterprise)
5+
- [Questions?](#questions)
6+
- [iPad Status?](#ipad-status)
7+
- [Community Projects (awesome-code-server)](#community-projects-awesome-code-server)
8+
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
9+
- [Differences compared to VS Code?](#differences-compared-to-vs-code)
10+
- [Installing an extension](#installing-an-extension)
11+
- [How can I request a missing extension?](#how-can-i-request-a-missing-extension)
12+
- [Installing an extension manually](#installing-an-extension-manually)
13+
- [How do I configure the marketplace URL?](#how-do-i-configure-the-marketplace-url)
14+
- [Where are extensions stored?](#where-are-extensions-stored)
15+
- [How is this different from VS Code Codespaces?](#how-is-this-different-from-vs-code-codespaces)
16+
- [How should I expose code-server to the internet?](#how-should-i-expose-code-server-to-the-internet)
17+
- [Can I store my password hashed?](#can-i-store-my-password-hashed)
18+
- [How do I securely access web services?](#how-do-i-securely-access-web-services)
19+
- [Sub-paths](#sub-paths)
20+
- [Sub-domains](#sub-domains)
21+
- [Why does the code-server proxy strip `/proxy/<port>` from the request path?](#why-does-the-code-server-proxy-strip-proxyport-from-the-request-path)
22+
- [Proxying to Create React App](#proxying-to-create-react-app)
23+
- [Multi-tenancy](#multi-tenancy)
24+
- [Docker in code-server container?](#docker-in-code-server-container)
25+
- [How can I disable telemetry?](#how-can-i-disable-telemetry)
26+
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
27+
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
28+
- [Heartbeat File](#heartbeat-file)
29+
- [Healthz endpoint](#healthz-endpoint)
30+
- [How does the config file work?](#how-does-the-config-file-work)
31+
- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure)
32+
- [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work)
33+
- [How do I access my Documents/Downloads/Desktop folders in code-server on OSX?](#how-do-i-access-my-documentsdownloadsdesktop-folders-in-code-server-on-osx)
34+
- [Differences compared to Theia?](#differences-compared-to-theia)
35+
- [`$HTTP_PROXY`, `$HTTPS_PROXY`, `$NO_PROXY`](#http_proxy-https_proxy-no_proxy)
36+
- [Enterprise](#enterprise)
3837

3938
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
4039

@@ -209,7 +208,6 @@ Yes you can! Set the value of `hashed-password` instead of `password`. Generate
209208
```shell
210209
echo -n "password" | npx argon2-cli -e
211210
$argon2i$v=19$m=4096,t=3,p=1$wst5qhbgk2lu1ih4dmuxvg$ls1alrvdiwtvzhwnzcm1dugg+5dto3dt1d5v9xtlws4
212-
213211
```
214212

215213
Of course replace `thisismypassword` with your actual password and **remember to put it inside quotes**!

src/node/cli.ts

+6-13
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,8 @@ export function splitOnFirstEquals(str: string): string[] {
247247
// $argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY
248248
// 2 means return two items
249249
// Source: https://stackoverflow.com/a/4607799/3015595
250-
const split = str.split(/=(.+)/, 2)
251-
252-
// It should always return two elements
253-
// because it's used in a place where
254-
// it expected two elements
255-
if (split.length === 1) {
256-
split.push("")
257-
}
250+
// We use the ? to say the the substr after the = is optional
251+
const split = str.split(/=(.+)?/, 2)
258252

259253
return split
260254
}
@@ -289,10 +283,11 @@ export const parse = (
289283
let key: keyof Args | undefined
290284
let value: string | undefined
291285
if (arg.startsWith("--")) {
292-
// TODO fix this
293-
const split = arg.replace(/^--/, "").split("=", 2)
286+
const split = splitOnFirstEquals(arg.replace(/^--/, ""))
294287
key = split[0] as keyof Args
295288
value = split[1]
289+
290+
console.log(`Hello key: ${key}, and value: ${value}`)
296291
} else {
297292
const short = arg.replace(/^-/, "")
298293
const pair = Object.entries(options).find(([, v]) => v.short === short)
@@ -301,6 +296,7 @@ export const parse = (
301296
}
302297
}
303298

299+
console.log(`What is key: ${key} and options: ${key ? options[key] : ""}`)
304300
if (!key || !options[key]) {
305301
throw error(`Unknown option ${arg}`)
306302
}
@@ -563,7 +559,6 @@ export function parseConfigFile(configFile: string, configPath: string): ConfigA
563559
const config = yaml.load(configFile, {
564560
filename: configPath,
565561
})
566-
console.log("what is this config", config)
567562
if (!config || typeof config === "string") {
568563
throw new Error(`invalid config: ${config}`)
569564
}
@@ -576,11 +571,9 @@ export function parseConfigFile(configFile: string, configPath: string): ConfigA
576571
}
577572
return `--${optName}=${opt}`
578573
})
579-
console.log("what are the configFileArgv", configFileArgv)
580574
const args = parse(configFileArgv, {
581575
configFile: configPath,
582576
})
583-
console.log(args, "args")
584577
return {
585578
...args,
586579
config: configPath,

src/node/http.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ export const ensureAuthenticated = async (
6363
*/
6464
export const authenticated = async (req: express.Request): Promise<boolean> => {
6565
switch (req.args.auth) {
66-
case AuthType.None:
66+
case AuthType.None: {
6767
return true
68-
case AuthType.Password:
68+
}
69+
case AuthType.Password: {
6970
// The password is stored in the cookie after being hashed.
7071
const hashedPasswordFromArgs = req.args["hashed-password"]
7172
const passwordMethod = getPasswordMethod(hashedPasswordFromArgs)
@@ -77,8 +78,10 @@ export const authenticated = async (req: express.Request): Promise<boolean> => {
7778
}
7879

7980
return await isCookieValid(isCookieValidArgs)
80-
default:
81+
}
82+
default: {
8183
throw new Error(`Unsupported auth type ${req.args.auth}`)
84+
}
8285
}
8386
}
8487

src/node/util.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1+
import { logger } from "@coder/logger"
2+
import * as argon2 from "argon2"
13
import * as cp from "child_process"
24
import * as crypto from "crypto"
3-
import * as argon2 from "argon2"
45
import envPaths from "env-paths"
56
import { promises as fs } from "fs"
67
import * as net from "net"
78
import * as os from "os"
89
import * as path from "path"
10+
import safeCompare from "safe-compare"
911
import * as util from "util"
1012
import xdgBasedir from "xdg-basedir"
11-
import safeCompare from "safe-compare"
12-
import { logger } from "@coder/logger"
1313

1414
export interface Paths {
1515
data: string

test/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,5 @@
1818
"resolutions": {
1919
"@playwright/test/playwright": "^1.11.0-next-alpha-apr-13-2021"
2020
},
21-
"dependencies": {
22-
}
21+
"dependencies": {}
2322
}

test/unit/cli.test.ts

+22-7
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,21 @@ describe("parser", () => {
349349
],
350350
})
351351
})
352+
it("should parse options with double-dash and multiple equal signs ", async () => {
353+
const args = parse(
354+
[
355+
"--hashed-password=$argon2i$v=19$m=4096,t=3,p=1$0qr/o+0t00hsbjfqcksfdq$ofcm4rl6o+b7oxpua4qlxubypbbpsf+8l531u7p9hyy",
356+
],
357+
{
358+
configFile: "/pathtoconfig",
359+
},
360+
)
361+
expect(args).toEqual({
362+
_: [],
363+
"hashed-password":
364+
"$argon2i$v=19$m=4096,t=3,p=1$0qr/o+0t00hsbjfqcksfdq$ofcm4rl6o+b7oxpua4qlxubypbbpsf+8l531u7p9hyy",
365+
})
366+
})
352367
})
353368

354369
describe("cli", () => {
@@ -426,25 +441,25 @@ describe("cli", () => {
426441

427442
describe("splitOnFirstEquals", () => {
428443
it("should split on the first equals", () => {
429-
const testStr = "--enabled-proposed-api=test=value"
444+
const testStr = "enabled-proposed-api=test=value"
430445
const actual = splitOnFirstEquals(testStr)
431-
const expected = ["--enabled-proposed-api", "test=value"]
446+
const expected = ["enabled-proposed-api", "test=value"]
432447
expect(actual).toEqual(expect.arrayContaining(expected))
433448
})
434449
it("should split on first equals regardless of multiple equals signs", () => {
435450
const testStr =
436-
"--hashed-password=$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY"
451+
"hashed-password=$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY"
437452
const actual = splitOnFirstEquals(testStr)
438453
const expected = [
439-
"--hashed-password",
454+
"hashed-password",
440455
"$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY",
441456
]
442457
expect(actual).toEqual(expect.arrayContaining(expected))
443458
})
444-
it("should always return two elements", () => {
445-
const testStr = ""
459+
it("should always return the first element before an equals", () => {
460+
const testStr = "auth="
446461
const actual = splitOnFirstEquals(testStr)
447-
const expected = ["", ""]
462+
const expected = ["auth"]
448463
expect(actual).toEqual(expect.arrayContaining(expected))
449464
})
450465
})

typings/pluginapi.d.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ export const proxy: ProxyServer
145145
/**
146146
* Middleware to ensure the user is authenticated. Throws if they are not.
147147
*/
148-
export function ensureAuthenticated(req: express.Request, res?: express.Response, next?: express.NextFunction): Promise<void>
148+
export function ensureAuthenticated(
149+
req: express.Request,
150+
res?: express.Response,
151+
next?: express.NextFunction,
152+
): Promise<void>
149153

150154
/**
151155
* Returns true if the user is authenticated.

0 commit comments

Comments
 (0)