Skip to content

Commit 6448408

Browse files
committed
Support hashed password for basic auth and match style
1 parent d911eac commit 6448408

File tree

5 files changed

+48
-22
lines changed

5 files changed

+48
-22
lines changed

src/node/cli.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ export type Options<T> = {
140140
export const options: Options<Required<UserProvidedArgs>> = {
141141
auth: { type: AuthType, description: "The type of authentication to use." },
142142
"auth-user": {
143-
type: "string",
144-
description: "The username for http-basic authentication."
143+
type: "string",
144+
description: "The username for http-basic authentication.",
145145
},
146146
password: {
147147
type: "string",
@@ -486,6 +486,7 @@ export interface DefaultedArgs extends ConfigArgs {
486486
"proxy-domain": string[]
487487
verbose: boolean
488488
usingEnvPassword: boolean
489+
usingEnvAuthUser: boolean
489490
usingEnvHashedPassword: boolean
490491
"extensions-dir": string
491492
"user-data-dir": string
@@ -575,9 +576,13 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
575576
if (process.env.PASSWORD) {
576577
args.password = process.env.PASSWORD
577578
}
579+
580+
const usingEnvAuthUser = !!process.env.AUTH_USER
578581
if (process.env.AUTH_USER) {
579582
args["auth"] = AuthType.HttpBasic
580583
args["auth-user"] = process.env.AUTH_USER
584+
} else if (args["auth-user"]) {
585+
args["auth"] = AuthType.HttpBasic
581586
}
582587

583588
if (process.env.CS_DISABLE_FILE_DOWNLOADS?.match(/^(1|true)$/)) {
@@ -631,6 +636,7 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
631636
return {
632637
...args,
633638
usingEnvPassword,
639+
usingEnvAuthUser,
634640
usingEnvHashedPassword,
635641
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
636642
}

src/node/http.ts

+28-11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as expressCore from "express-serve-static-core"
44
import * as http from "http"
55
import * as net from "net"
66
import * as qs from "qs"
7+
import safeCompare from "safe-compare"
78
import { Disposable } from "../common/emitter"
89
import { CookieKeys, HttpCode, HttpError } from "../common/http"
910
import { normalize } from "../common/util"
@@ -20,6 +21,7 @@ import {
2021
escapeHtml,
2122
escapeJSON,
2223
splitOnFirstEquals,
24+
isHashMatch,
2325
} from "./util"
2426

2527
/**
@@ -114,21 +116,31 @@ export const ensureAuthenticated = async (
114116
/**
115117
* Validate basic auth credentials.
116118
*/
117-
const validateBasicAuth = (authHeader: string | undefined, authUser: string | undefined, authPassword: string | undefined): boolean => {
118-
if (!authHeader?.startsWith('Basic ')) {
119-
return false;
119+
const validateBasicAuth = async (
120+
authHeader: string | undefined,
121+
authUser: string | undefined,
122+
authPassword: string | undefined,
123+
hashedPassword: string | undefined,
124+
): Promise<boolean> => {
125+
if (!authHeader?.startsWith("Basic ")) {
126+
return false
120127
}
121128

122129
try {
123-
const base64Credentials = authHeader.split(' ')[1];
124-
const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8');
125-
const [username, password] = credentials.split(':');
126-
return username === authUser && password === authPassword;
130+
const base64Credentials = authHeader.split(" ")[1]
131+
const credentials = Buffer.from(base64Credentials, "base64").toString("utf-8")
132+
const [username, password] = credentials.split(":")
133+
if (username !== authUser) return false
134+
if (hashedPassword) {
135+
return await isHashMatch(password, hashedPassword)
136+
} else {
137+
return safeCompare(password, authPassword || "")
138+
}
127139
} catch (error) {
128-
logger.error('Error validating basic auth:' + error);
129-
return false;
140+
logger.error("Error validating basic auth:" + error)
141+
return false
130142
}
131-
};
143+
}
132144

133145
/**
134146
* Return true if authenticated via cookies.
@@ -152,7 +164,12 @@ export const authenticated = async (req: express.Request): Promise<boolean> => {
152164
return await isCookieValid(isCookieValidArgs)
153165
}
154166
case AuthType.HttpBasic: {
155-
return validateBasicAuth(req.headers.authorization, req.args["auth-user"], req.args.password);
167+
return await validateBasicAuth(
168+
req.headers.authorization,
169+
req.args["auth-user"],
170+
req.args.password,
171+
req.args["hashed-password"],
172+
)
156173
}
157174
default: {
158175
throw new Error(`Unsupported auth type ${req.args.auth}`)

src/node/main.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export const runCodeServer = async (
133133

134134
logger.info(`Using config file ${args.config}`)
135135
logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
136-
if (args.auth === AuthType.Password) {
136+
if (args.auth === AuthType.Password || args.auth === AuthType.HttpBasic) {
137137
logger.info(" - Authentication is enabled")
138138
if (args.usingEnvPassword) {
139139
logger.info(" - Using password from $PASSWORD")
@@ -142,10 +142,13 @@ export const runCodeServer = async (
142142
} else {
143143
logger.info(` - Using password from ${args.config}`)
144144
}
145-
} else if (args.auth === AuthType.HttpBasic) {
146-
logger.info(" - HTTP basic authentication is enabled")
147-
logger.info(" - Using user from $AUTH_USER")
148-
logger.info(" - Using password from $PASSWORD")
145+
if (args.auth === AuthType.HttpBasic) {
146+
if (args.usingEnvAuthUser) {
147+
logger.info(" - Using user from $AUTH_USER")
148+
} else {
149+
logger.info(` - With user ${args["auth-user"]}`)
150+
}
151+
}
149152
} else {
150153
logger.info(" - Authentication is disabled")
151154
}

src/node/routes/domainProxy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Request, Router } from "express"
22
import { HttpCode, HttpError } from "../../common/http"
3+
import { AuthType } from "../cli"
34
import { getHost, ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
45
import { proxy } from "../proxy"
56
import { Router as WsRouter } from "../wsRouter"
6-
import { AuthType } from "../cli"
77

88
export const router = Router()
99

src/node/routes/vscode.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import * as http from "http"
66
import * as net from "net"
77
import * as path from "path"
88
import { WebsocketRequest } from "../../../typings/pluginapi"
9+
import { HttpCode, HttpError } from "../../common/http"
910
import { logError } from "../../common/util"
1011
import { AuthType, CodeArgs, toCodeArgs } from "../cli"
1112
import { isDevMode, vsRootPath } from "../constants"
1213
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, replaceTemplates, self } from "../http"
1314
import { SocketProxyProvider } from "../socket"
1415
import { isFile } from "../util"
1516
import { Router as WsRouter } from "../wsRouter"
16-
import { HttpCode, HttpError } from "../../common/http"
1717

1818
export const router = express.Router()
1919

@@ -121,9 +121,9 @@ router.get("/", ensureVSCodeLoaded, async (req, res, next) => {
121121
if (!isAuthenticated) {
122122
// If auth is HttpBasic, return a 401.
123123
if (req.args.auth === AuthType.HttpBasic) {
124-
res.setHeader('WWW-Authenticate', 'Basic realm="Access to the site"')
124+
res.setHeader("WWW-Authenticate", `Basic realm="${req.args["app-name"] || "code-server"}"`)
125125
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
126-
};
126+
}
127127
const to = self(req)
128128
return redirect(req, res, "login", {
129129
to: to !== "/" ? to : undefined,

0 commit comments

Comments
 (0)