Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions app/octokit.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import type { RequestParameters } from "@octokit/auth-app/dist-types/types";
import * as Sentry from "@sentry/remix";
import type { RequestError } from "@octokit/request-error";
import type { GraphqlResponseError } from "@octokit/graphql";
import { GitHubAppAuthStrategy } from "./services/github-app-auth.server";
import { getInstallationForLogin } from "~/services/installation";
import { commitSession, getSession } from "./services/session.server";
import { catchError } from "./components";
Expand All @@ -26,7 +25,9 @@ export const redirectCookie = createCookie("redirect", {
});

export async function getUser(request: Requests): Promise<SessionShape> {
const res = await authenticator().isAuthenticated(toNodeRequest(request), {});
const res = await (
await authenticator()
).isAuthenticated(toNodeRequest(request), {});
if (!res) {
const set_cookie = await redirectCookie.serialize(request.url);
throw redirect("/login", {
Expand Down Expand Up @@ -118,26 +119,37 @@ export function getRootURL() {
}
}

export const gitHubStrategy = () => {
const callbackURL = `${getRootURL()}/auth/github/callback`;
export const gitHubStrategy = async () => {
const redirectURI = `${getRootURL()}/auth/github/callback`;

const { GitHubAppAuthStrategy } = await import(
"./services/github-app-auth.server.mjs"
);

return new GitHubAppAuthStrategy(
{
clientID: process.env.GITHUB_APP_CLIENT_ID!,
clientId: process.env.GITHUB_APP_CLIENT_ID!,
clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!,
callbackURL,
scope: ["user", "read:user"],
redirectURI,
scopes: ["user", "read:user"],
},
async ({ accessToken, profile, extraParams, refreshToken }) => {
async ({
tokens: {
access_token,
refresh_token,
accessTokenExpiresIn,
refreshTokenExpiresIn,
},
profile,
}) => {
const installationId = await getInstallationForLogin(profile._json);
console.log({ extraParams });
return {
login: profile._json.login,
installationId,
accessToken,
refreshToken,
accessTokenExpiry: extraParams.accessTokenExpiresAt,
refreshTokenExpiry: extraParams.refreshTokenExpiresAt,
accessToken: access_token,
refreshToken: refresh_token,
accessTokenExpiry: accessTokenExpiresIn,
refreshTokenExpiry: refreshTokenExpiresIn,
};
},
);
Expand Down Expand Up @@ -198,7 +210,7 @@ export async function call<Result, Variables extends RequestParameters>(

export async function logoutAndRedirect(request: Request) {
const res = await catchError<Response>(
authenticator().logout(request, {
(await authenticator()).logout(request, {
redirectTo: "/",
}),
);
Expand Down
4 changes: 3 additions & 1 deletion app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export const loader: LoaderFunction = async ({ request }) => {
ENV: {
SENTRY_DSN: process.env.SENTRY_DSN,
},
user: _.pick(await authenticator().isAuthenticated(request), ["login"]),
user: _.pick(await (await authenticator()).isAuthenticated(request), [
"login",
]),
colorMode: colorMode ?? "auto",
});
};
Expand Down
3 changes: 2 additions & 1 deletion app/routes/$owner/$repo/actions/$id/logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getInstallationOctokit,
getInstallationId,
} from "~/services/installation";
// @ts-expect-error
import * as asc from "ansi-sequence-parser";
import { GearIcon } from "@primer/octicons-react";
import {
Expand Down Expand Up @@ -137,7 +138,7 @@ export function ConstructLine({ line }: { line: string }) {
case "command":
case "endgroup":
default: {
const spans = asc.parseAnsiSequences(line!);
const spans = asc.parseAnsiSequences(line!) as { value: string }[];

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion app/routes/auth/github.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ export async function loader() {
}

export const action: ActionFunction = async ({ request }) => {
return authenticator().authenticate("github", request);
return (await authenticator()).authenticate("github", request);
};
4 changes: 3 additions & 1 deletion app/routes/auth/github/callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export const loader: LoaderFunction = async ({ request }) => {
}

try {
await authenticator().authenticate("github", request, {
await (
await authenticator()
).authenticate("github", request, {
successRedirect: await getRedirect(request),
failureRedirect: "/login",
});
Expand Down
4 changes: 3 additions & 1 deletion app/routes/auth/logout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { LoaderFunction } from "@remix-run/node";
import { authenticator } from "~/services/auth.server";

export const loader: LoaderFunction = async ({ request }) =>
await authenticator().logout(request, {
await (
await authenticator()
).logout(request, {
redirectTo: "/login",
});
4 changes: 2 additions & 2 deletions app/routes/debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getOctokit, getRootURL, getRedirect } from "~/octokit.server";
import { authenticator } from "~/services/auth.server";
import { splatObject } from "~/components/ErrorBoundary";
import * as Sentry from "@sentry/remix";
import getCache from "~/services/cache";
import { getCache } from "~/services/cache";

function pick<T>(obj: T, keys: (keyof T)[]) {
return _.pick(obj, keys);
Expand All @@ -21,7 +21,7 @@ async function timeout<T>(t: Promise<T>) {

export const loader = async ({ request }: LoaderFunctionArgs) => {
const userObject = await timeout(
authenticator()
(await authenticator())
.isAuthenticated(request)
.then((userObject) =>
userObject
Expand Down
4 changes: 3 additions & 1 deletion app/routes/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
const data = { error: session.get("error") };

// this will redirect if we're actually already logged in
await authenticator().isAuthenticated(request, {
await (
await authenticator()
).isAuthenticated(request, {
successRedirect: "/",
});

Expand Down
2 changes: 1 addition & 1 deletion app/routes/storybook/cache.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import getCache, { throwError } from "~/services/cache";
import { getCache, throwError } from "~/services/cache";
import type { LoaderFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

Expand Down
4 changes: 2 additions & 2 deletions app/services/auth.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export type SessionShape = Pick<

// Create an instance of the authenticator, pass a generic with what
// strategies will return and will store in the session
export let authenticator = _.memoize(() => {
export let authenticator = _.memoize(async () => {
const authenticator = new Authenticator<SessionShape>(sessionStorage);
authenticator.use(gitHubStrategy());
authenticator.use(await gitHubStrategy());
return authenticator;
});
4 changes: 2 additions & 2 deletions app/services/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type XCache = StrategyOptions["cache"] & {

const ONE_HOUR_IN_SECONDS = 60 * 60;

function getCache(): XCache {
function _getCache(): XCache {
const { env } = process;
const kv = createClient({
url:
Expand Down Expand Up @@ -47,4 +47,4 @@ function getCache(): XCache {
};
}

export default _.memoize(getCache);
export const getCache = _.memoize(_getCache);
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { GitHubExtraParams } from "remix-auth-github";
import { createAppAuth, createOAuthUserAuth } from "@octokit/auth-app";
import { octokitFromConfig } from "~/octokit.server";
import { octokitFromConfig } from "~/octokit.server.js";
import _ from "lodash";
import getCache from "~/services/cache";
import {getCache} from "~/services/cache.js";
import { GitHubStrategy } from "remix-auth-github";

function checkNonNull(name: string): NonNullable<string> {
Expand Down
5 changes: 4 additions & 1 deletion app/services/installation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { json } from "@remix-run/node";
import { getAppOctokit, getConfig } from "./github-app-auth.server";
import { getUser, octokitFromConfig } from "~/octokit.server";
import { createAppAuth } from "@octokit/auth-app";

export async function getInstallationOctokit(request: Request) {
const installationId = await getInstallationId(request);

const { getConfig } = await import("./github-app-auth.server.mjs");

const { auth, ...rest } = getConfig();
return octokitFromConfig({
authStrategy: createAppAuth,
Expand All @@ -30,6 +31,8 @@ export async function getInstallationId(
}

export async function getInstallationForLogin(user: { login: string }) {
const { getAppOctokit } = await import("./github-app-auth.server.mjs");

const appOctokit = await getAppOctokit();
const { data: installation } = await appOctokit.apps.getUserInstallation({
username: user.login,
Expand Down
4 changes: 3 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ export default defineConfig({

/* Run your local dev server before starting the tests */
webServer: {
command: "yarn dev",
command: "yarn dev --port 3000",
url: "http://127.0.0.1:3000",
reuseExistingServer: !process.env.CI,
stdout: "pipe",
stderr: "pipe",
},
});
5 changes: 4 additions & 1 deletion test/auth.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ describe("auth", () => {
it("logged in redirects", async () => {
const session = await getSession();

session.set(authenticator().sessionKey, { id: 1, login: "octocat" });
session.set((await authenticator()).sessionKey, {
id: 1,
login: "octocat",
});

const request = new Request("http://localhost");
request.headers.set("Cookie", await commitSession(session));
Expand Down
2 changes: 1 addition & 1 deletion test/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import getCache from "~/services/cache";
import { getCache } from "~/services/cache";
import dotenv from "dotenv";

dotenv.config();
Expand Down
23 changes: 14 additions & 9 deletions test/whatever.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { Octokit } from "@octokit/rest";
import { vitest } from "vitest";

test("hello", async () => {
let BasePage: React.FunctionComponent<{ asChildRoute: boolean }>;
try {
BasePage = (await import("~/routes/index")).default;
var BasePage = await loadComponent<{ asChildRoute: boolean }>(
"~/routes/index.js",
);
} catch (e) {
console.error(e);
throw e;
Expand All @@ -35,12 +36,12 @@ test("hello", async () => {
await screen.findByText("Action Statuses");
});

test('Displays error correctly', async () => {
let BasePage: React.FunctionComponent<{ asChildRoute: boolean }>;
let ErrorBoundary: React.FunctionComponent;
test("Displays error correctly", async () => {
try {
BasePage = (await import("~/routes/index")).default;
ErrorBoundary = (await import("~/root")).ErrorBoundary;
var BasePage = await loadComponent<{ asChildRoute: boolean }>(
"~/routes/index.js",
);
var ErrorBoundary = (await import("~/root.js")).ErrorBoundary;
} catch (e) {
console.error(e);
throw e;
Expand All @@ -54,13 +55,13 @@ test('Displays error correctly', async () => {
},
path: "/",
id: "root",
ErrorBoundary
ErrorBoundary,
},
]);

await renderPrimer(<Stub />);

expect((await screen.findByText("This is an error"))).toBeVisible();
expect(await screen.findByText("This is an error")).toBeVisible();
});

test("RequestError instanceof", async () => {
Expand Down Expand Up @@ -105,3 +106,7 @@ test("graphql", async () => {
expect(error.mock.calls).toMatchSnapshot();
expect(warn.mock.calls).toMatchSnapshot();
});

async function loadComponent<T>(path: string) {
return (await import(path)).default as unknown as React.FunctionComponent<T>;
}
8 changes: 4 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx", "app/services/github-app-auth.server.mts"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2021"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"moduleResolution": "nodenext",
"resolveJsonModule": true,
"target": "ES2019",
"module": "ES2020",
"module": "NodeNext",
"strict": true,
"allowJs": true,
"checkJs": true,
Expand All @@ -18,7 +18,7 @@
"~/*": ["./app/*"]
},
"skipLibCheck": true,
"types": ["jest", "node"],
"types": ["jest", "node", "@testing-library/jest-dom"],

// Remix takes care of building everything in `remix build`.
"noEmit": true
Expand Down
Loading
Loading