mirror of https://github.com/coder/coder.git
chore: add e2e test against an external auth provider during workspace creation (#12985)
This commit is contained in:
parent
75223dfd8b
commit
319fd5bf1d
|
@ -31,6 +31,7 @@ import {
|
||||||
type Resource,
|
type Resource,
|
||||||
Response,
|
Response,
|
||||||
type RichParameter,
|
type RichParameter,
|
||||||
|
type ExternalAuthProviderResource,
|
||||||
} from "./provisionerGenerated";
|
} from "./provisionerGenerated";
|
||||||
|
|
||||||
// requiresEnterpriseLicense will skip the test if we're not running with an enterprise license
|
// requiresEnterpriseLicense will skip the test if we're not running with an enterprise license
|
||||||
|
@ -49,6 +50,7 @@ export const createWorkspace = async (
|
||||||
templateName: string,
|
templateName: string,
|
||||||
richParameters: RichParameter[] = [],
|
richParameters: RichParameter[] = [],
|
||||||
buildParameters: WorkspaceBuildParameter[] = [],
|
buildParameters: WorkspaceBuildParameter[] = [],
|
||||||
|
useExternalAuthProvider: string | undefined = undefined,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
await page.goto(`/templates/${templateName}/workspace`, {
|
await page.goto(`/templates/${templateName}/workspace`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
|
@ -59,6 +61,25 @@ export const createWorkspace = async (
|
||||||
await page.getByLabel("name").fill(name);
|
await page.getByLabel("name").fill(name);
|
||||||
|
|
||||||
await fillParameters(page, richParameters, buildParameters);
|
await fillParameters(page, richParameters, buildParameters);
|
||||||
|
|
||||||
|
if (useExternalAuthProvider !== undefined) {
|
||||||
|
// Create a new context for the popup which will be created when clicking the button
|
||||||
|
const popupPromise = page.waitForEvent("popup");
|
||||||
|
|
||||||
|
// Find the "Login with <Provider>" button
|
||||||
|
const externalAuthLoginButton = page
|
||||||
|
.getByRole("button")
|
||||||
|
.getByText("Login with GitHub");
|
||||||
|
await expect(externalAuthLoginButton).toBeVisible();
|
||||||
|
|
||||||
|
// Click it
|
||||||
|
await externalAuthLoginButton.click();
|
||||||
|
|
||||||
|
// Wait for authentication to occur
|
||||||
|
const popup = await popupPromise;
|
||||||
|
await popup.waitForSelector("text=You are now authenticated.");
|
||||||
|
}
|
||||||
|
|
||||||
await page.getByTestId("form-submit").click();
|
await page.getByTestId("form-submit").click();
|
||||||
|
|
||||||
await expectUrl(page).toHavePathName("/@admin/" + name);
|
await expectUrl(page).toHavePathName("/@admin/" + name);
|
||||||
|
@ -648,6 +669,37 @@ export const echoResponsesWithParameters = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const echoResponsesWithExternalAuth = (
|
||||||
|
providers: ExternalAuthProviderResource[],
|
||||||
|
): EchoProvisionerResponses => {
|
||||||
|
return {
|
||||||
|
parse: [
|
||||||
|
{
|
||||||
|
parse: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
plan: [
|
||||||
|
{
|
||||||
|
plan: {
|
||||||
|
externalAuthProviders: providers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
apply: [
|
||||||
|
{
|
||||||
|
apply: {
|
||||||
|
externalAuthProviders: providers,
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const fillParameters = async (
|
export const fillParameters = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
richParameters: RichParameter[] = [],
|
richParameters: RichParameter[] = [],
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import type { Page } from "@playwright/test";
|
import type { BrowserContext, Page } from "@playwright/test";
|
||||||
|
import http from "http";
|
||||||
|
import { coderPort, gitAuth } from "./constants";
|
||||||
|
|
||||||
export const beforeCoderTest = async (page: Page) => {
|
export const beforeCoderTest = async (page: Page) => {
|
||||||
// eslint-disable-next-line no-console -- Show everything that was printed with console.log()
|
// eslint-disable-next-line no-console -- Show everything that was printed with console.log()
|
||||||
|
@ -45,6 +47,41 @@ export const beforeCoderTest = async (page: Page) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const resetExternalAuthKey = async (context: BrowserContext) => {
|
||||||
|
// Find the session token so we can destroy the external auth link between tests, to ensure valid authentication happens each time.
|
||||||
|
const cookies = await context.cookies();
|
||||||
|
const sessionCookie = cookies.find((c) => c.name === "coder_session_token");
|
||||||
|
const options = {
|
||||||
|
method: "DELETE",
|
||||||
|
hostname: "127.0.0.1",
|
||||||
|
port: coderPort,
|
||||||
|
path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = http.request(options, (res) => {
|
||||||
|
let data = "";
|
||||||
|
res.on("data", (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on("end", () => {
|
||||||
|
// Both 200 (key deleted successfully) and 500 (key was not found) are valid responses.
|
||||||
|
if (res.statusCode !== 200 && res.statusCode !== 500) {
|
||||||
|
console.error("failed to delete external auth link", data);
|
||||||
|
throw new Error(
|
||||||
|
`failed to delete external auth link: HTTP response ${res.statusCode}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on("error", (err) => {
|
||||||
|
throw err.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
};
|
||||||
|
|
||||||
const isApiCall = (urlString: string): boolean => {
|
const isApiCall = (urlString: string): boolean => {
|
||||||
const url = new URL(urlString);
|
const url = new URL(urlString);
|
||||||
const apiPath = "/api/v2";
|
const apiPath = "/api/v2";
|
||||||
|
|
|
@ -115,7 +115,7 @@ export default defineConfig({
|
||||||
// Tests for Deployment / User Authentication / OIDC
|
// Tests for Deployment / User Authentication / OIDC
|
||||||
CODER_OIDC_ISSUER_URL: "https://accounts.google.com",
|
CODER_OIDC_ISSUER_URL: "https://accounts.google.com",
|
||||||
CODER_OIDC_EMAIL_DOMAIN: "coder.com",
|
CODER_OIDC_EMAIL_DOMAIN: "coder.com",
|
||||||
CODER_OIDC_CLIENT_ID: "1234567890", // FIXME: https://github.com/coder/coder/issues/12585
|
CODER_OIDC_CLIENT_ID: "1234567890",
|
||||||
CODER_OIDC_CLIENT_SECRET: "1234567890Secret",
|
CODER_OIDC_CLIENT_SECRET: "1234567890Secret",
|
||||||
CODER_OIDC_ALLOW_SIGNUPS: "false",
|
CODER_OIDC_ALLOW_SIGNUPS: "false",
|
||||||
CODER_OIDC_SIGN_IN_TEXT: "Hello",
|
CODER_OIDC_SIGN_IN_TEXT: "Hello",
|
||||||
|
|
|
@ -2,8 +2,37 @@ import type { Endpoints } from "@octokit/types";
|
||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import type { ExternalAuthDevice } from "api/typesGenerated";
|
import type { ExternalAuthDevice } from "api/typesGenerated";
|
||||||
import { gitAuth } from "../constants";
|
import { gitAuth } from "../constants";
|
||||||
import { Awaiter, createServer } from "../helpers";
|
import {
|
||||||
import { beforeCoderTest } from "../hooks";
|
Awaiter,
|
||||||
|
createServer,
|
||||||
|
createTemplate,
|
||||||
|
createWorkspace,
|
||||||
|
echoResponsesWithExternalAuth,
|
||||||
|
} from "../helpers";
|
||||||
|
import { beforeCoderTest, resetExternalAuthKey } from "../hooks";
|
||||||
|
|
||||||
|
test.beforeAll(async ({ baseURL }) => {
|
||||||
|
const srv = await createServer(gitAuth.webPort);
|
||||||
|
|
||||||
|
// The GitHub validate endpoint returns the currently authenticated user!
|
||||||
|
srv.use(gitAuth.validatePath, (req, res) => {
|
||||||
|
res.write(JSON.stringify(ghUser));
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||||
|
const r = (Math.random() + 1).toString(36).substring(7);
|
||||||
|
res.write(JSON.stringify({ access_token: r }));
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
srv.use(gitAuth.authPath, (req, res) => {
|
||||||
|
res.redirect(
|
||||||
|
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=` +
|
||||||
|
req.query.state,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context }) => resetExternalAuthKey(context));
|
||||||
|
|
||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
|
@ -57,23 +86,7 @@ test("external auth device", async ({ page }) => {
|
||||||
await page.waitForSelector("text=1 organization authorized");
|
await page.waitForSelector("text=1 organization authorized");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("external auth web", async ({ baseURL, page }) => {
|
test("external auth web", async ({ page }) => {
|
||||||
const srv = await createServer(gitAuth.webPort);
|
|
||||||
// The GitHub validate endpoint returns the currently authenticated user!
|
|
||||||
srv.use(gitAuth.validatePath, (req, res) => {
|
|
||||||
res.write(JSON.stringify(ghUser));
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
|
||||||
res.write(JSON.stringify({ access_token: "hello-world" }));
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
srv.use(gitAuth.authPath, (req, res) => {
|
|
||||||
res.redirect(
|
|
||||||
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=` +
|
|
||||||
req.query.state,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await page.goto(`/external-auth/${gitAuth.webProvider}`, {
|
await page.goto(`/external-auth/${gitAuth.webProvider}`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
|
@ -81,6 +94,17 @@ test("external auth web", async ({ baseURL, page }) => {
|
||||||
await page.waitForSelector("text=You've authenticated with GitHub!");
|
await page.waitForSelector("text=You've authenticated with GitHub!");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("successful external auth from workspace", async ({ page }) => {
|
||||||
|
const templateName = await createTemplate(
|
||||||
|
page,
|
||||||
|
echoResponsesWithExternalAuth([
|
||||||
|
{ id: gitAuth.webProvider, optional: false },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
await createWorkspace(page, templateName, [], [], gitAuth.webProvider);
|
||||||
|
});
|
||||||
|
|
||||||
const ghUser: Endpoints["GET /user"]["response"]["data"] = {
|
const ghUser: Endpoints["GET /user"]["response"]["data"] = {
|
||||||
login: "kylecarbs",
|
login: "kylecarbs",
|
||||||
id: 7122116,
|
id: 7122116,
|
||||||
|
|
Loading…
Reference in New Issue