feat: github-auth flag (#4926)
* feat: github-auth flag This will allow injecting credentials into code-server if you already have them. * Update Code Contains the GitHub auth changes. * Add e2e test for GitHub token
This commit is contained in:
parent
3f3a489f33
commit
0e78a147b6
@ -87,6 +87,7 @@ export interface UserProvidedArgs {
|
|||||||
"locate-extension"?: string[]
|
"locate-extension"?: string[]
|
||||||
"show-versions"?: boolean
|
"show-versions"?: boolean
|
||||||
category?: string
|
category?: string
|
||||||
|
"github-auth"?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option<T> {
|
interface Option<T> {
|
||||||
@ -205,6 +206,10 @@ const options: Options<Required<UserProvidedArgs>> = {
|
|||||||
},
|
},
|
||||||
"uninstall-extension": { type: "string[]", description: "Uninstall a VS Code extension by id." },
|
"uninstall-extension": { type: "string[]", description: "Uninstall a VS Code extension by id." },
|
||||||
"show-versions": { type: "boolean", description: "Show VS Code extension versions." },
|
"show-versions": { type: "boolean", description: "Show VS Code extension versions." },
|
||||||
|
"github-auth": {
|
||||||
|
type: "string",
|
||||||
|
description: "GitHub authentication token (can only be passed in via $GITHUB_TOKEN or the config file).",
|
||||||
|
},
|
||||||
"proxy-domain": { type: "string[]", description: "Domain used for proxying ports." },
|
"proxy-domain": { type: "string[]", description: "Domain used for proxying ports." },
|
||||||
"ignore-last-opened": {
|
"ignore-last-opened": {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
@ -336,6 +341,10 @@ export const parse = (
|
|||||||
throw new Error("--hashed-password can only be set in the config file or passed in via $HASHED_PASSWORD")
|
throw new Error("--hashed-password can only be set in the config file or passed in via $HASHED_PASSWORD")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key === "github-auth" && !opts?.configFile) {
|
||||||
|
throw new Error("--github-auth can only be set in the config file or passed in via $GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
|
||||||
const option = options[key]
|
const option = options[key]
|
||||||
if (option.type === "boolean") {
|
if (option.type === "boolean") {
|
||||||
;(args[key] as boolean) = true
|
;(args[key] as boolean) = true
|
||||||
@ -409,7 +418,12 @@ export const parse = (
|
|||||||
|
|
||||||
logger.debug(() => [
|
logger.debug(() => [
|
||||||
`parsed ${opts?.configFile ? "config" : "command line"}`,
|
`parsed ${opts?.configFile ? "config" : "command line"}`,
|
||||||
field("args", { ...args, password: undefined }),
|
field("args", {
|
||||||
|
...args,
|
||||||
|
password: args.password ? "<redacted>" : undefined,
|
||||||
|
"hashed-password": args["hashed-password"] ? "<redacted>" : undefined,
|
||||||
|
"github-auth": args["github-auth"] ? "<redacted>" : undefined,
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
return args
|
return args
|
||||||
@ -530,9 +544,14 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
|||||||
usingEnvPassword = false
|
usingEnvPassword = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.GITHUB_TOKEN) {
|
||||||
|
args["github-auth"] = process.env.GITHUB_TOKEN
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure they're not readable by child processes.
|
// Ensure they're not readable by child processes.
|
||||||
delete process.env.PASSWORD
|
delete process.env.PASSWORD
|
||||||
delete process.env.HASHED_PASSWORD
|
delete process.env.HASHED_PASSWORD
|
||||||
|
delete process.env.GITHUB_TOKEN
|
||||||
|
|
||||||
// Filter duplicate proxy domains and remove any leading `*.`.
|
// Filter duplicate proxy domains and remove any leading `*.`.
|
||||||
const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, "")))
|
const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, "")))
|
||||||
|
@ -13,11 +13,12 @@ export const describe = (
|
|||||||
name: string,
|
name: string,
|
||||||
includeCredentials: boolean,
|
includeCredentials: boolean,
|
||||||
codeServerArgs: string[],
|
codeServerArgs: string[],
|
||||||
|
codeServerEnv: NodeJS.ProcessEnv,
|
||||||
fn: (codeServer: CodeServer) => void,
|
fn: (codeServer: CodeServer) => void,
|
||||||
) => {
|
) => {
|
||||||
test.describe(name, () => {
|
test.describe(name, () => {
|
||||||
// This will spawn on demand so nothing is necessary on before.
|
// This will spawn on demand so nothing is necessary on before.
|
||||||
const codeServer = new CodeServer(name, codeServerArgs)
|
const codeServer = new CodeServer(name, codeServerArgs, codeServerEnv)
|
||||||
|
|
||||||
// Kill code-server after the suite has ended. This may happen even without
|
// Kill code-server after the suite has ended. This may happen even without
|
||||||
// doing it explicitly but it seems prudent to be sure.
|
// doing it explicitly but it seems prudent to be sure.
|
||||||
|
@ -2,7 +2,7 @@ import { promises as fs } from "fs"
|
|||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { describe, test, expect } from "./baseFixture"
|
import { describe, test, expect } from "./baseFixture"
|
||||||
|
|
||||||
describe("CodeServer", true, [], () => {
|
describe("CodeServer", true, [], {}, () => {
|
||||||
test("should navigate to home page", async ({ codeServerPage }) => {
|
test("should navigate to home page", async ({ codeServerPage }) => {
|
||||||
// We navigate codeServer before each test
|
// We navigate codeServer before each test
|
||||||
// and we start the test with a storage state
|
// and we start the test with a storage state
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import * as path from "path"
|
||||||
import { describe, test } from "./baseFixture"
|
import { describe, test } from "./baseFixture"
|
||||||
|
|
||||||
function runTestExtensionTests() {
|
function runTestExtensionTests() {
|
||||||
@ -11,10 +12,12 @@ function runTestExtensionTests() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Extensions", true, [], () => {
|
const flags = ["--extensions-dir", path.join(__dirname, "./extensions")]
|
||||||
|
|
||||||
|
describe("Extensions", true, flags, {}, () => {
|
||||||
runTestExtensionTests()
|
runTestExtensionTests()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Extensions with --cert", true, ["--cert"], () => {
|
describe("Extensions with --cert", true, [...flags, "--cert"], {}, () => {
|
||||||
runTestExtensionTests()
|
runTestExtensionTests()
|
||||||
})
|
})
|
||||||
|
38
test/e2e/github.test.ts
Normal file
38
test/e2e/github.test.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { test as base } from "@playwright/test"
|
||||||
|
import { describe, expect, test } from "./baseFixture"
|
||||||
|
|
||||||
|
if (process.env.GITHUB_TOKEN) {
|
||||||
|
describe("GitHub token", true, [], {}, () => {
|
||||||
|
test("should be logged in to pull requests extension", async ({ codeServerPage }) => {
|
||||||
|
await codeServerPage.exec("git init")
|
||||||
|
await codeServerPage.exec("git remote add origin https://github.com/coder/code-server")
|
||||||
|
await codeServerPage.installExtension("GitHub.vscode-pull-request-github")
|
||||||
|
await codeServerPage.executeCommandViaMenus("View: Show Github")
|
||||||
|
await codeServerPage.page.click("text=Sign in")
|
||||||
|
await codeServerPage.page.click("text=Allow")
|
||||||
|
// It should ask to select an account, one of which will be the one we
|
||||||
|
// pre-injected.
|
||||||
|
expect(await codeServerPage.page.isVisible("text=Select an account")).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("No GitHub token", true, [], { GITHUB_TOKEN: "" }, () => {
|
||||||
|
test("should not be logged in to pull requests extension", async ({ codeServerPage }) => {
|
||||||
|
await codeServerPage.exec("git init")
|
||||||
|
await codeServerPage.exec("git remote add origin https://github.com/coder/code-server")
|
||||||
|
await codeServerPage.installExtension("GitHub.vscode-pull-request-github")
|
||||||
|
await codeServerPage.executeCommandViaMenus("View: Show Github")
|
||||||
|
await codeServerPage.page.click("text=Sign in")
|
||||||
|
await codeServerPage.page.click("text=Allow")
|
||||||
|
// Since there is no account it will ask directly for the token (because
|
||||||
|
// we are on localhost; otherwise it would initiate the oauth flow).
|
||||||
|
expect(await codeServerPage.page.isVisible("text=GitHub Personal Access Token")).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
base.describe("GitHub token", () => {
|
||||||
|
base.skip("skipped because GITHUB_TOKEN is not set", () => {
|
||||||
|
// Playwright will not show this without a function.
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -2,7 +2,7 @@ import { describe, test, expect } from "./baseFixture"
|
|||||||
|
|
||||||
// This test is to make sure the globalSetup works as expected
|
// This test is to make sure the globalSetup works as expected
|
||||||
// meaning globalSetup ran and stored the storageState
|
// meaning globalSetup ran and stored the storageState
|
||||||
describe("globalSetup", true, [], () => {
|
describe("globalSetup", true, [], {}, () => {
|
||||||
test("should keep us logged in using the storageState", async ({ codeServerPage }) => {
|
test("should keep us logged in using the storageState", async ({ codeServerPage }) => {
|
||||||
// Make sure the editor actually loaded
|
// Make sure the editor actually loaded
|
||||||
expect(await codeServerPage.isEditorVisible()).toBe(true)
|
expect(await codeServerPage.isEditorVisible()).toBe(true)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { PASSWORD } from "../utils/constants"
|
import { PASSWORD } from "../utils/constants"
|
||||||
import { describe, test, expect } from "./baseFixture"
|
import { describe, test, expect } from "./baseFixture"
|
||||||
|
|
||||||
describe("login", false, [], () => {
|
describe("login", false, [], {}, () => {
|
||||||
test("should see the login page", async ({ codeServerPage }) => {
|
test("should see the login page", async ({ codeServerPage }) => {
|
||||||
// It should send us to the login page
|
// It should send us to the login page
|
||||||
expect(await codeServerPage.page.title()).toBe("code-server login")
|
expect(await codeServerPage.page.title()).toBe("code-server login")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// NOTE@jsjoeio commenting out until we can figure out what's wrong
|
// NOTE@jsjoeio commenting out until we can figure out what's wrong
|
||||||
// import { describe, test, expect } from "./baseFixture"
|
// import { describe, test, expect } from "./baseFixture"
|
||||||
|
|
||||||
// describe("logout", true, [], () => {
|
// describe("logout", true, [], {}, () => {
|
||||||
// test("should be able logout", async ({ codeServerPage }) => {
|
// test("should be able logout", async ({ codeServerPage }) => {
|
||||||
// // Recommended by Playwright for async navigation
|
// // Recommended by Playwright for async navigation
|
||||||
// // https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151
|
// // https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151
|
||||||
|
@ -3,6 +3,7 @@ import * as cp from "child_process"
|
|||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { Page } from "playwright"
|
import { Page } from "playwright"
|
||||||
|
import util from "util"
|
||||||
import { logError, plural } from "../../../src/common/util"
|
import { logError, plural } from "../../../src/common/util"
|
||||||
import { onLine } from "../../../src/node/util"
|
import { onLine } from "../../../src/node/util"
|
||||||
import { PASSWORD, workspaceDir } from "../../utils/constants"
|
import { PASSWORD, workspaceDir } from "../../utils/constants"
|
||||||
@ -39,7 +40,11 @@ export class CodeServer {
|
|||||||
private closed = false
|
private closed = false
|
||||||
private _workspaceDir: Promise<string> | undefined
|
private _workspaceDir: Promise<string> | undefined
|
||||||
|
|
||||||
constructor(name: string, private readonly codeServerArgs: string[]) {
|
constructor(
|
||||||
|
name: string,
|
||||||
|
private readonly codeServerArgs: string[],
|
||||||
|
private readonly codeServerEnv: NodeJS.ProcessEnv,
|
||||||
|
) {
|
||||||
this.logger = logger.named(name)
|
this.logger = logger.named(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,8 @@ export class CodeServer {
|
|||||||
"node",
|
"node",
|
||||||
[
|
[
|
||||||
process.env.CODE_SERVER_TEST_ENTRY || ".",
|
process.env.CODE_SERVER_TEST_ENTRY || ".",
|
||||||
|
"--extensions-dir",
|
||||||
|
path.join(dir, "extensions"),
|
||||||
...this.codeServerArgs,
|
...this.codeServerArgs,
|
||||||
// Using port zero will spawn on a random port.
|
// Using port zero will spawn on a random port.
|
||||||
"--bind-addr",
|
"--bind-addr",
|
||||||
@ -107,8 +114,6 @@ export class CodeServer {
|
|||||||
path.join(dir, "config.yaml"),
|
path.join(dir, "config.yaml"),
|
||||||
"--user-data-dir",
|
"--user-data-dir",
|
||||||
dir,
|
dir,
|
||||||
"--extensions-dir",
|
|
||||||
path.join(__dirname, "../extensions"),
|
|
||||||
// The last argument is the workspace to open.
|
// The last argument is the workspace to open.
|
||||||
dir,
|
dir,
|
||||||
],
|
],
|
||||||
@ -116,6 +121,7 @@ export class CodeServer {
|
|||||||
cwd: path.join(__dirname, "../../.."),
|
cwd: path.join(__dirname, "../../.."),
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
|
...this.codeServerEnv,
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -462,4 +468,24 @@ export class CodeServerPage {
|
|||||||
await this.reloadUntilEditorIsReady()
|
await this.reloadUntilEditorIsReady()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a command in t root of the instance's workspace directory.
|
||||||
|
*/
|
||||||
|
async exec(command: string): Promise<void> {
|
||||||
|
await util.promisify(cp.exec)(command, {
|
||||||
|
cwd: await this.workspaceDir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install an extension by ID to the instance's temporary extension
|
||||||
|
* directory.
|
||||||
|
*/
|
||||||
|
async installExtension(id: string): Promise<void> {
|
||||||
|
const dir = path.join(await this.workspaceDir, "extensions")
|
||||||
|
await util.promisify(cp.exec)(`node . --install-extension ${id} --extensions-dir ${dir}`, {
|
||||||
|
cwd: path.join(__dirname, "../../.."),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { describe, test, expect } from "./baseFixture"
|
import { describe, test, expect } from "./baseFixture"
|
||||||
|
|
||||||
describe("Open Help > About", true, [], () => {
|
describe("Open Help > About", true, [], {}, () => {
|
||||||
test("should see code-server version in about dialog", async ({ codeServerPage }) => {
|
test("should see code-server version in about dialog", async ({ codeServerPage }) => {
|
||||||
// Open using the menu.
|
// Open using the menu.
|
||||||
await codeServerPage.navigateMenus(["Help", "About"])
|
await codeServerPage.navigateMenus(["Help", "About"])
|
||||||
|
@ -4,7 +4,7 @@ import util from "util"
|
|||||||
import { clean, tmpdir } from "../utils/helpers"
|
import { clean, tmpdir } from "../utils/helpers"
|
||||||
import { describe, expect, test } from "./baseFixture"
|
import { describe, expect, test } from "./baseFixture"
|
||||||
|
|
||||||
describe("Integrated Terminal", true, [], () => {
|
describe("Integrated Terminal", true, [], {}, () => {
|
||||||
const testName = "integrated-terminal"
|
const testName = "integrated-terminal"
|
||||||
test.beforeAll(async () => {
|
test.beforeAll(async () => {
|
||||||
await clean(testName)
|
await clean(testName)
|
||||||
|
@ -313,6 +313,19 @@ describe("parser", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should use env var github token", async () => {
|
||||||
|
process.env.GITHUB_TOKEN = "ga-foo"
|
||||||
|
const args = parse([])
|
||||||
|
expect(args).toEqual({})
|
||||||
|
|
||||||
|
const defaultArgs = await setDefaults(args)
|
||||||
|
expect(defaultArgs).toEqual({
|
||||||
|
...defaults,
|
||||||
|
"github-auth": "ga-foo",
|
||||||
|
})
|
||||||
|
expect(process.env.GITHUB_TOKEN).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
it("should error if password passed in", () => {
|
it("should error if password passed in", () => {
|
||||||
expect(() => parse(["--password", "supersecret123"])).toThrowError(
|
expect(() => parse(["--password", "supersecret123"])).toThrowError(
|
||||||
"--password can only be set in the config file or passed in via $PASSWORD",
|
"--password can only be set in the config file or passed in via $PASSWORD",
|
||||||
@ -325,6 +338,12 @@ describe("parser", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should error if github-auth passed in", () => {
|
||||||
|
expect(() => parse(["--github-auth", "fdas423fs8a"])).toThrowError(
|
||||||
|
"--github-auth can only be set in the config file or passed in via $GITHUB_TOKEN",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("should filter proxy domains", async () => {
|
it("should filter proxy domains", async () => {
|
||||||
const args = parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "coder.com", "--proxy-domain", "coder.org"])
|
const args = parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "coder.com", "--proxy-domain", "coder.org"])
|
||||||
expect(args).toEqual({
|
expect(args).toEqual({
|
||||||
@ -374,6 +393,11 @@ describe("parser", () => {
|
|||||||
it("should ignore optional strings set to false", async () => {
|
it("should ignore optional strings set to false", async () => {
|
||||||
expect(parse(["--cert=false"])).toEqual({})
|
expect(parse(["--cert=false"])).toEqual({})
|
||||||
})
|
})
|
||||||
|
it("should use last flag", async () => {
|
||||||
|
expect(parse(["--port", "8081", "--port", "8082"])).toEqual({
|
||||||
|
port: 8082,
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("cli", () => {
|
describe("cli", () => {
|
||||||
|
2
vendor/package.json
vendored
2
vendor/package.json
vendored
@ -7,6 +7,6 @@
|
|||||||
"postinstall": "./postinstall.sh"
|
"postinstall": "./postinstall.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"code-oss-dev": "coder/vscode#6337ee490d16b7dfd8854d22c998f58d6cd21ef5"
|
"code-oss-dev": "coder/vscode#0fd21e4078ac1dddb26be024f5d4224a4b86da93"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
vendor/yarn.lock
vendored
4
vendor/yarn.lock
vendored
@ -274,9 +274,9 @@ clone-response@^1.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-response "^1.0.0"
|
mimic-response "^1.0.0"
|
||||||
|
|
||||||
code-oss-dev@coder/vscode#6337ee490d16b7dfd8854d22c998f58d6cd21ef5:
|
code-oss-dev@coder/vscode#0fd21e4078ac1dddb26be024f5d4224a4b86da93:
|
||||||
version "1.63.0"
|
version "1.63.0"
|
||||||
resolved "https://codeload.github.com/coder/vscode/tar.gz/6337ee490d16b7dfd8854d22c998f58d6cd21ef5"
|
resolved "https://codeload.github.com/coder/vscode/tar.gz/0fd21e4078ac1dddb26be024f5d4224a4b86da93"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@microsoft/applicationinsights-web" "^2.6.4"
|
"@microsoft/applicationinsights-web" "^2.6.4"
|
||||||
"@parcel/watcher" "2.0.3"
|
"@parcel/watcher" "2.0.3"
|
||||||
|
Reference in New Issue
Block a user