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:
Joe Previte 2021-06-03 16:37:46 -07:00
parent 531b7c0c25
commit 8c2bb61af9
No known key found for this signature in database
GPG Key ID: 2C91590C6B742C24
8 changed files with 71 additions and 72 deletions

View File

@ -2,7 +2,6 @@
<!-- 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)
@ -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**!

View File

@ -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",

View File

@ -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,

View File

@ -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,10 +78,12 @@ 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}`)
} }
} }
}
/** /**
* Get the relative path that will get us to the root of the page. For each * Get the relative path that will get us to the root of the page. For each

View File

@ -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

View File

@ -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": {
} }
} }

View File

@ -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))
}) })
}) })

View File

@ -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.