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.
This commit is contained in:
parent
531b7c0c25
commit
8c2bb61af9
66
docs/FAQ.md
66
docs/FAQ.md
|
@ -2,39 +2,38 @@
|
||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
- [FAQ](#faq)
|
- [Questions?](#questions)
|
||||||
- [Questions?](#questions)
|
- [iPad Status?](#ipad-status)
|
||||||
- [iPad Status?](#ipad-status)
|
- [Community Projects (awesome-code-server)](#community-projects-awesome-code-server)
|
||||||
- [Community Projects (awesome-code-server)](#community-projects-awesome-code-server)
|
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
|
||||||
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
|
- [Differences compared to VS Code?](#differences-compared-to-vs-code)
|
||||||
- [Differences compared to VS Code?](#differences-compared-to-vs-code)
|
- [Installing an extension](#installing-an-extension)
|
||||||
- [Installing an extension](#installing-an-extension)
|
- [How can I request a missing extension?](#how-can-i-request-a-missing-extension)
|
||||||
- [How can I request a missing extension?](#how-can-i-request-a-missing-extension)
|
- [Installing an extension manually](#installing-an-extension-manually)
|
||||||
- [Installing an extension manually](#installing-an-extension-manually)
|
- [How do I configure the marketplace URL?](#how-do-i-configure-the-marketplace-url)
|
||||||
- [How do I configure the marketplace URL?](#how-do-i-configure-the-marketplace-url)
|
- [Where are extensions stored?](#where-are-extensions-stored)
|
||||||
- [Where are extensions stored?](#where-are-extensions-stored)
|
- [How is this different from VS Code Codespaces?](#how-is-this-different-from-vs-code-codespaces)
|
||||||
- [How is this different from VS Code Codespaces?](#how-is-this-different-from-vs-code-codespaces)
|
- [How should I expose code-server to the internet?](#how-should-i-expose-code-server-to-the-internet)
|
||||||
- [How should I expose code-server to the internet?](#how-should-i-expose-code-server-to-the-internet)
|
- [Can I store my password hashed?](#can-i-store-my-password-hashed)
|
||||||
- [Can I store my password hashed?](#can-i-store-my-password-hashed)
|
- [How do I securely access web services?](#how-do-i-securely-access-web-services)
|
||||||
- [How do I securely access web services?](#how-do-i-securely-access-web-services)
|
- [Sub-paths](#sub-paths)
|
||||||
- [Sub-paths](#sub-paths)
|
- [Sub-domains](#sub-domains)
|
||||||
- [Sub-domains](#sub-domains)
|
- [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)
|
||||||
- [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)
|
- [Proxying to Create React App](#proxying-to-create-react-app)
|
||||||
- [Proxying to Create React App](#proxying-to-create-react-app)
|
- [Multi-tenancy](#multi-tenancy)
|
||||||
- [Multi-tenancy](#multi-tenancy)
|
- [Docker in code-server container?](#docker-in-code-server-container)
|
||||||
- [Docker in code-server container?](#docker-in-code-server-container)
|
- [How can I disable telemetry?](#how-can-i-disable-telemetry)
|
||||||
- [How can I disable telemetry?](#how-can-i-disable-telemetry)
|
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
|
||||||
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
|
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
|
||||||
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
|
- [Heartbeat File](#heartbeat-file)
|
||||||
- [Heartbeat File](#heartbeat-file)
|
- [Healthz endpoint](#healthz-endpoint)
|
||||||
- [Healthz endpoint](#healthz-endpoint)
|
- [How does the config file work?](#how-does-the-config-file-work)
|
||||||
- [How does the config file work?](#how-does-the-config-file-work)
|
- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure)
|
||||||
- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure)
|
- [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work)
|
||||||
- [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work)
|
- [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)
|
||||||
- [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)
|
- [Differences compared to Theia?](#differences-compared-to-theia)
|
||||||
- [Differences compared to Theia?](#differences-compared-to-theia)
|
- [`$HTTP_PROXY`, `$HTTPS_PROXY`, `$NO_PROXY`](#http_proxy-https_proxy-no_proxy)
|
||||||
- [`$HTTP_PROXY`, `$HTTPS_PROXY`, `$NO_PROXY`](#http_proxy-https_proxy-no_proxy)
|
- [Enterprise](#enterprise)
|
||||||
- [Enterprise](#enterprise)
|
|
||||||
|
|
||||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||||
|
|
||||||
|
@ -209,7 +208,6 @@ Yes you can! Set the value of `hashed-password` instead of `password`. Generate
|
||||||
```shell
|
```shell
|
||||||
echo -n "password" | npx argon2-cli -e
|
echo -n "password" | npx argon2-cli -e
|
||||||
$argon2i$v=19$m=4096,t=3,p=1$wst5qhbgk2lu1ih4dmuxvg$ls1alrvdiwtvzhwnzcm1dugg+5dto3dt1d5v9xtlws4
|
$argon2i$v=19$m=4096,t=3,p=1$wst5qhbgk2lu1ih4dmuxvg$ls1alrvdiwtvzhwnzcm1dugg+5dto3dt1d5v9xtlws4
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Of course replace `thisismypassword` with your actual password and **remember to put it inside quotes**!
|
Of course replace `thisismypassword` with your actual password and **remember to put it inside quotes**!
|
||||||
|
|
|
@ -36,7 +36,6 @@
|
||||||
"main": "out/node/entry.js",
|
"main": "out/node/entry.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@schemastore/package": "^0.0.6",
|
"@schemastore/package": "^0.0.6",
|
||||||
"@types/bcrypt": "^5.0.0",
|
|
||||||
"@types/body-parser": "^1.19.0",
|
"@types/body-parser": "^1.19.0",
|
||||||
"@types/compression": "^1.7.0",
|
"@types/compression": "^1.7.0",
|
||||||
"@types/cookie-parser": "^1.4.2",
|
"@types/cookie-parser": "^1.4.2",
|
||||||
|
@ -90,7 +89,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coder/logger": "1.1.16",
|
"@coder/logger": "1.1.16",
|
||||||
"argon2": "^0.28.0",
|
"argon2": "^0.28.0",
|
||||||
"bcrypt": "^5.0.1",
|
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
|
|
|
@ -247,14 +247,8 @@ export function splitOnFirstEquals(str: string): string[] {
|
||||||
// $argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY
|
// $argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY
|
||||||
// 2 means return two items
|
// 2 means return two items
|
||||||
// Source: https://stackoverflow.com/a/4607799/3015595
|
// Source: https://stackoverflow.com/a/4607799/3015595
|
||||||
const split = str.split(/=(.+)/, 2)
|
// We use the ? to say the the substr after the = is optional
|
||||||
|
const split = str.split(/=(.+)?/, 2)
|
||||||
// It should always return two elements
|
|
||||||
// because it's used in a place where
|
|
||||||
// it expected two elements
|
|
||||||
if (split.length === 1) {
|
|
||||||
split.push("")
|
|
||||||
}
|
|
||||||
|
|
||||||
return split
|
return split
|
||||||
}
|
}
|
||||||
|
@ -289,17 +283,9 @@ export const parse = (
|
||||||
let key: keyof Args | undefined
|
let key: keyof Args | undefined
|
||||||
let value: string | undefined
|
let value: string | undefined
|
||||||
if (arg.startsWith("--")) {
|
if (arg.startsWith("--")) {
|
||||||
// TODO fix this
|
const split = splitOnFirstEquals(arg.replace(/^--/, ""))
|
||||||
const split = arg.replace(/^--/, "").split("=", 2)
|
|
||||||
key = split[0] as keyof Args
|
key = split[0] as keyof Args
|
||||||
value = split[1]
|
value = split[1]
|
||||||
} else {
|
|
||||||
const short = arg.replace(/^-/, "")
|
|
||||||
const pair = Object.entries(options).find(([, v]) => v.short === short)
|
|
||||||
if (pair) {
|
|
||||||
key = pair[0] as keyof Args
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!key || !options[key]) {
|
if (!key || !options[key]) {
|
||||||
throw error(`Unknown option ${arg}`)
|
throw error(`Unknown option ${arg}`)
|
||||||
|
@ -563,7 +549,6 @@ export function parseConfigFile(configFile: string, configPath: string): ConfigA
|
||||||
const config = yaml.load(configFile, {
|
const config = yaml.load(configFile, {
|
||||||
filename: configPath,
|
filename: configPath,
|
||||||
})
|
})
|
||||||
console.log("what is this config", config)
|
|
||||||
if (!config || typeof config === "string") {
|
if (!config || typeof config === "string") {
|
||||||
throw new Error(`invalid config: ${config}`)
|
throw new Error(`invalid config: ${config}`)
|
||||||
}
|
}
|
||||||
|
@ -576,11 +561,9 @@ export function parseConfigFile(configFile: string, configPath: string): ConfigA
|
||||||
}
|
}
|
||||||
return `--${optName}=${opt}`
|
return `--${optName}=${opt}`
|
||||||
})
|
})
|
||||||
console.log("what are the configFileArgv", configFileArgv)
|
|
||||||
const args = parse(configFileArgv, {
|
const args = parse(configFileArgv, {
|
||||||
configFile: configPath,
|
configFile: configPath,
|
||||||
})
|
})
|
||||||
console.log(args, "args")
|
|
||||||
return {
|
return {
|
||||||
...args,
|
...args,
|
||||||
config: configPath,
|
config: configPath,
|
||||||
|
|
|
@ -63,9 +63,10 @@ export const ensureAuthenticated = async (
|
||||||
*/
|
*/
|
||||||
export const authenticated = async (req: express.Request): Promise<boolean> => {
|
export const authenticated = async (req: express.Request): Promise<boolean> => {
|
||||||
switch (req.args.auth) {
|
switch (req.args.auth) {
|
||||||
case AuthType.None:
|
case AuthType.None: {
|
||||||
return true
|
return true
|
||||||
case AuthType.Password:
|
}
|
||||||
|
case AuthType.Password: {
|
||||||
// The password is stored in the cookie after being hashed.
|
// The password is stored in the cookie after being hashed.
|
||||||
const hashedPasswordFromArgs = req.args["hashed-password"]
|
const hashedPasswordFromArgs = req.args["hashed-password"]
|
||||||
const passwordMethod = getPasswordMethod(hashedPasswordFromArgs)
|
const passwordMethod = getPasswordMethod(hashedPasswordFromArgs)
|
||||||
|
@ -77,8 +78,10 @@ export const authenticated = async (req: express.Request): Promise<boolean> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return await isCookieValid(isCookieValidArgs)
|
return await isCookieValid(isCookieValidArgs)
|
||||||
default:
|
}
|
||||||
|
default: {
|
||||||
throw new Error(`Unsupported auth type ${req.args.auth}`)
|
throw new Error(`Unsupported auth type ${req.args.auth}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
import { logger } from "@coder/logger"
|
||||||
|
import * as argon2 from "argon2"
|
||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
import * as argon2 from "argon2"
|
|
||||||
import envPaths from "env-paths"
|
import envPaths from "env-paths"
|
||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import * as net from "net"
|
import * as net from "net"
|
||||||
import * as os from "os"
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
|
import safeCompare from "safe-compare"
|
||||||
import * as util from "util"
|
import * as util from "util"
|
||||||
import xdgBasedir from "xdg-basedir"
|
import xdgBasedir from "xdg-basedir"
|
||||||
import safeCompare from "safe-compare"
|
|
||||||
import { logger } from "@coder/logger"
|
|
||||||
|
|
||||||
export interface Paths {
|
export interface Paths {
|
||||||
data: string
|
data: string
|
||||||
|
|
|
@ -17,7 +17,5 @@
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@playwright/test/playwright": "^1.11.0-next-alpha-apr-13-2021"
|
"@playwright/test/playwright": "^1.11.0-next-alpha-apr-13-2021"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,6 +349,21 @@ describe("parser", () => {
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
it("should parse options with double-dash and multiple equal signs ", async () => {
|
||||||
|
const args = parse(
|
||||||
|
[
|
||||||
|
"--hashed-password=$argon2i$v=19$m=4096,t=3,p=1$0qr/o+0t00hsbjfqcksfdq$ofcm4rl6o+b7oxpua4qlxubypbbpsf+8l531u7p9hyy",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
configFile: "/pathtoconfig",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(args).toEqual({
|
||||||
|
_: [],
|
||||||
|
"hashed-password":
|
||||||
|
"$argon2i$v=19$m=4096,t=3,p=1$0qr/o+0t00hsbjfqcksfdq$ofcm4rl6o+b7oxpua4qlxubypbbpsf+8l531u7p9hyy",
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("cli", () => {
|
describe("cli", () => {
|
||||||
|
@ -426,25 +441,25 @@ describe("cli", () => {
|
||||||
|
|
||||||
describe("splitOnFirstEquals", () => {
|
describe("splitOnFirstEquals", () => {
|
||||||
it("should split on the first equals", () => {
|
it("should split on the first equals", () => {
|
||||||
const testStr = "--enabled-proposed-api=test=value"
|
const testStr = "enabled-proposed-api=test=value"
|
||||||
const actual = splitOnFirstEquals(testStr)
|
const actual = splitOnFirstEquals(testStr)
|
||||||
const expected = ["--enabled-proposed-api", "test=value"]
|
const expected = ["enabled-proposed-api", "test=value"]
|
||||||
expect(actual).toEqual(expect.arrayContaining(expected))
|
expect(actual).toEqual(expect.arrayContaining(expected))
|
||||||
})
|
})
|
||||||
it("should split on first equals regardless of multiple equals signs", () => {
|
it("should split on first equals regardless of multiple equals signs", () => {
|
||||||
const testStr =
|
const testStr =
|
||||||
"--hashed-password=$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY"
|
"hashed-password=$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY"
|
||||||
const actual = splitOnFirstEquals(testStr)
|
const actual = splitOnFirstEquals(testStr)
|
||||||
const expected = [
|
const expected = [
|
||||||
"--hashed-password",
|
"hashed-password",
|
||||||
"$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY",
|
"$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY",
|
||||||
]
|
]
|
||||||
expect(actual).toEqual(expect.arrayContaining(expected))
|
expect(actual).toEqual(expect.arrayContaining(expected))
|
||||||
})
|
})
|
||||||
it("should always return two elements", () => {
|
it("should always return the first element before an equals", () => {
|
||||||
const testStr = ""
|
const testStr = "auth="
|
||||||
const actual = splitOnFirstEquals(testStr)
|
const actual = splitOnFirstEquals(testStr)
|
||||||
const expected = ["", ""]
|
const expected = ["auth"]
|
||||||
expect(actual).toEqual(expect.arrayContaining(expected))
|
expect(actual).toEqual(expect.arrayContaining(expected))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -145,7 +145,11 @@ export const proxy: ProxyServer
|
||||||
/**
|
/**
|
||||||
* Middleware to ensure the user is authenticated. Throws if they are not.
|
* Middleware to ensure the user is authenticated. Throws if they are not.
|
||||||
*/
|
*/
|
||||||
export function ensureAuthenticated(req: express.Request, res?: express.Response, next?: express.NextFunction): Promise<void>
|
export function ensureAuthenticated(
|
||||||
|
req: express.Request,
|
||||||
|
res?: express.Response,
|
||||||
|
next?: express.NextFunction,
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the user is authenticated.
|
* Returns true if the user is authenticated.
|
||||||
|
|
Loading…
Reference in New Issue