textbook/fauxauth

View on GitHub
packages/e2e/fauxauth.spec.js

Summary

Maintainability
A
2 hrs
Test Coverage
import axios from "axios";
import { remote } from "webdriverio";

const baseUrl = process.env.FAUXAUTH_URL || "http://localhost:3000";

/** @type {import("webdriverio").RemoteOptions} */
const webdriverConfig = {
    baseUrl,
    capabilities: {
        browserName: process.env.BROWSER || "chrome",
        "goog:chromeOptions": {
            args: ["disable-gpu", "headless"],
        },
    },
    hostname: process.env.SELENIUM_HOST,
    logLevel: process.env.WD_LOG_LEVEL || "warn",
    path: process.env.SELENIUM_PATH,
    port: process.env.SELENIUM_PORT ? parseInt(process.env.SELENIUM_PORT, 10) : undefined,
};

describe("fauxauth", () => {
    let browser;

    beforeAll(async () => {
        browser = await remote(webdriverConfig);
    });

    beforeEach(async () => {
        const { statusCode } = await makeRequest("/_configuration", { method: "DELETE" });
        if (statusCode !== 204) {
            throw new Error("failed to reset configuration");
        }
    });

    afterAll(async () => {
        if (browser) {
            await browser.deleteSession();
        }
    });

    it("works with default configuration", async () => {
        const state = "testing";

        const { headers: { location } } = await authorize({ state });
        const url = new URL(location);
        expect(url.origin).toBe("http://example.org");
        expect(url.searchParams.get("state")).toBe(state);
        expect(url.searchParams.get("code")).toMatch(/[0-9a-f]{20}/);

        const { body } = await accessToken({ code: url.searchParams.get("code"), state });
        expect(body).toMatch(/access_token=[0-9a-f]{40}/);
    });

    it("works with custom configuration", async () => {
        const code = "deadbeef";
        const token = "somereallylongtoken";
        await configure([
            { op: "add", path: `/codes/${code}`, value: { token } },
            { op: "replace", path: "/clientSecret", value: "notsosecret" },
        ]);

        const { body } = await accessToken({ client_secret: "notsosecret", code });
        expect(body).toContain(`access_token=${token}`);
    });

    it("supports scope management", async () => {
        const { headers } = await authorize({ scope: "public_repo read:user" });
        const url = new URL(headers.location);

        const { body } = await accessToken({ code: url.searchParams.get("code") });
        expect(Object.fromEntries(new URLSearchParams(body).entries())).toEqual({
            access_token: expect.stringMatching(/^[a-f\d]{40}$/),
            scope: "public_repo,read:user",
            token_type: "bearer",
        });
    });

    it("appends scopes if requested", async () => {
        await configure([{ op: "replace", path: "/appendScopes", value: true }]);

        const { headers } = await authorize({ scope: "public_repo read:user" });
        const url = new URL(headers.location);

        const { body } = await accessToken({ code: url.searchParams.get("code") });
        expect(Object.fromEntries(new URLSearchParams(body).entries())).toEqual({
            access_token: expect.stringMatching(/^[a-f\d]{40}\/public_repo\/read:user$/),
            scope: "public_repo,read:user",
            token_type: "bearer",
        });
    });

    describe("with a token map", () => {
        it("provides a browser flow", async () => {
            await configure([{
                op: "add",
                path: "/tokenMap",
                value: { "Administrator": "secretadmintoken", "User": "secretusertoken" },
            }]);

            const options = new URLSearchParams({
                client_id: "1ae9b0ca17e754106b51",
                state: "bananas",
                redirect_uri: "http://example.org/test",
            });
            await browser.url(`/authorize?${options}`);
            const select = await browser.$("#role-select");
            await select.selectByVisibleText("User");
            const button = await browser.$("#submit-button");
            await button.click();

            const url = new URL(await browser.getUrl());
            expect(url.origin).toBe("http://example.org");
            expect(url.pathname).toBe("/test");
            expect(Object.fromEntries(url.searchParams.entries())).toEqual({
                code: expect.stringMatching(/^[a-f\d]{20}$/i),
                state: "bananas",
            });

            const { body } = await accessToken({ code: url.searchParams.get("code") });
            expect(body).toContain("access_token=secretusertoken");
        });

        it("supports scope management", async () => {
            await configure([{
                op: "add",
                path: "/tokenMap",
                value: { "Administrator": "secretadmintoken", "User": "secretusertoken" },
            }]);

            const options = new URLSearchParams({
                client_id: "1ae9b0ca17e754106b51",
                redirect_uri: "http://example.org/test",
                scope: ["read:user", "read:org"].join(" "),
                state: "bananas",
            });
            await browser.url(`/authorize?${options}`);
            const select = await browser.$("#role-select");
            await select.selectByVisibleText("Administrator");
            const readScope = await browser.$("#scope-read\\:user");
            await expect(readScope.isSelected()).resolves.toBe(true);
            const emailScope = await browser.$("#scope-read\\:org");
            await expect(emailScope.isSelected()).resolves.toBe(true);
            await emailScope.click();
            await expect(emailScope.isSelected()).resolves.toBe(false);
            const button = await browser.$("#submit-button");
            await button.click();

            const url = new URL(await browser.getUrl());

            const { body } = await accessToken({ code: url.searchParams.get("code") });
            expect(Object.fromEntries(new URLSearchParams(body).entries())).toEqual({
                access_token: "secretadmintoken",
                scope: "read:user",
                token_type: "bearer",
            });
        });
    });
});

const accessToken = async (params) => {
    const res = await makeRequest("/access_token", {
        method: "POST",
        body: new URLSearchParams({
            client_id: "1ae9b0ca17e754106b51",
            client_secret: "3efb56fdbac1cb21f3d4fea9b70036e04a34d068",
            ...params,
        }).toString(),
    });
    expect(res.statusCode).toBe(200);
    return res;
};

const authorize = async (params) => {
    const res = await makeRequest("/authorize", {
        followRedirect: false,
        qs: { client_id: "1ae9b0ca17e754106b51", ...params },
    });
    expect(res.statusCode).toBe(302);
    return res;
};

const configure = async (body) => {
    const res = await makeRequest("/_configuration", { body, json: true, method: "PATCH" });
    expect(res.statusCode).toBe(200);
    return res;
};

const makeRequest = (url, options) => axios
    .request({
        baseURL: baseUrl,
        data: options.body,
        method: options.method,
        params: options.qs,
        maxRedirects: options.followRedirect !== false ? 5 : 0,
        url,
    })
    .catch((error) => {
        if (error.response) {
            return error.response;
        }
        throw error;
    })
    .then(({ data, headers, status }) => ({
        body: data,
        headers,
        statusCode: status,
    }));