Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: make jwt parsing less strict #1621

Merged
merged 4 commits into from
Oct 9, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
});

const fernUser: FernUser = {
type: "user",
partner: "workos",
name:
user.firstName != null && user.lastName != null
? `${user.firstName} ${user.lastName}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
const { access_token, refresh_token } = await oauthClient.getToken(code);
const token = OryAccessTokenSchema.parse(await oauthClient.decode(access_token));
const fernUser: FernUser = {
type: "user",
partner: "ory",
name: token.ext?.name,
email: token.ext?.email,
};
Expand Down
19 changes: 7 additions & 12 deletions packages/ui/docs-bundle/src/server/DocsLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as FernNavigation from "@fern-api/fdr-sdk/navigation";
import type { AuthEdgeConfig, FernUser } from "@fern-ui/fern-docs-auth";
import { getAuthEdgeConfig } from "@fern-ui/fern-docs-edge-config";
import { verifyFernJWTConfig } from "./auth/FernJWT";
import { AuthProps } from "./authProps";
import { AuthProps, withAuthProps } from "./authProps";
import { loadWithUrl } from "./loadWithUrl";
import { pruneWithBasicTokenPublic } from "./withBasicTokenPublic";

Expand Down Expand Up @@ -39,10 +39,7 @@ export class DocsLoader {
return this;
}

private async loadAuth(): Promise<{
authConfig: AuthEdgeConfig | undefined;
user: FernUser | undefined;
}> {
private async loadAuth(): Promise<AuthProps | undefined> {
if (!this.auth) {
this.auth = await getAuthEdgeConfig(this.xFernHost);

Expand All @@ -55,10 +52,10 @@ export class DocsLoader {
console.error(e);
}
}
return {
authConfig: this.auth,
user: this.user,
};
if (!this.auth) {
return undefined;
}
return withAuthProps(this.auth, this.fernToken);
}

#loadForDocsUrlResponse: DocsV2Read.LoadDocsForUrlResponse | undefined;
Expand All @@ -75,9 +72,7 @@ export class DocsLoader {

private async loadDocs(): Promise<DocsV2Read.LoadDocsForUrlResponse | undefined> {
if (!this.#loadForDocsUrlResponse) {
const { user } = await this.loadAuth();
const authProps: AuthProps | undefined =
user && this.fernToken ? { user, token: this.fernToken } : undefined;
const authProps = await this.loadAuth();

const response = await loadWithUrl(this.xFernHost, authProps);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DocsPage } from "@fern-ui/ui";
import { track } from "@vercel/analytics/server";
import { GetServerSidePropsResult } from "next/types";
import { ComponentProps } from "react";
import { AuthPartner } from "./authProps";
import type { LoadWithUrlResponse } from "./loadWithUrl";

export class LoadDocsPerformanceTracker {
Expand All @@ -13,15 +14,15 @@ export class LoadDocsPerformanceTracker {
}: {
host: string;
slug: string[];
auth: "workos" | "ory" | "custom" | undefined;
auth: AuthPartner | undefined;
}): LoadDocsPerformanceTracker {
return new LoadDocsPerformanceTracker(host, slug, auth);
}

private constructor(
private host: string,
private slug: string[],
private auth: "workos" | "ory" | "custom" | undefined,
private auth: AuthPartner | undefined,
) {}

private loadDocsDurationMs: number | undefined;
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/docs-bundle/src/server/auth/FernJWT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export async function verifyFernJWT(token: string, secret?: string, issuer?: str
const verified = await jwtVerify(token, getJwtTokenSecret(secret), {
issuer: issuer ?? "https://buildwithfern.com",
});
return FernUserSchema.parse(verified.payload.fern);
// if the token is undefined, FernUser will be an empty object
return FernUserSchema.optional().parse(verified.payload.fern) ?? {};
}

export async function verifyFernJWTConfig(token: string, authConfig: AuthEdgeConfig | undefined): Promise<FernUser> {
Expand Down
22 changes: 17 additions & 5 deletions packages/ui/docs-bundle/src/server/authProps.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
import type { FernUser } from "@fern-ui/fern-docs-auth";
import type { AuthEdgeConfig, FernUser } from "@fern-ui/fern-docs-auth";
import { getAuthEdgeConfig } from "@fern-ui/fern-docs-edge-config";
import { verifyFernJWTConfig } from "./auth/FernJWT";

export type AuthPartner = "workos" | "ory" | "webflow" | "custom";

export interface AuthProps {
token: string;
user: FernUser;
partner: AuthPartner;
}

/**
* In Venus, workos tokens are prefixed with "workos_" to differentiate them from "fern_" tokens.
*/
function withPrefix(token: string, partner: FernUser["partner"]): string {
function withPrefix(token: string, partner: AuthPartner): string {
return `${partner}_${token}`;
}

export async function withAuthProps(xFernHost: string, fernToken: string | null | undefined): Promise<AuthProps> {
export async function withAuthProps(
xFernHostOrAuthConfig: string | AuthEdgeConfig,
fernToken: string | null | undefined,
): Promise<AuthProps> {
if (fernToken == null) {
throw new Error("Missing fern_token cookie");
}
const config = await getAuthEdgeConfig(xFernHost);
const config =
typeof xFernHostOrAuthConfig === "string"
? await getAuthEdgeConfig(xFernHostOrAuthConfig)
: xFernHostOrAuthConfig;
const partner =
config?.type === "oauth2" ? config.partner : config?.type === "basic_token_verification" ? "custom" : "workos";
const user: FernUser = await verifyFernJWTConfig(fernToken, config);
const token = withPrefix(fernToken, user.partner);
const token = withPrefix(fernToken, partner);

const authProps: AuthProps = {
token,
user,
partner,
};

return authProps;
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/docs-bundle/src/server/getDocsPageProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function getDocsPageProps(
return { notFound: true };
}

const performance = LoadDocsPerformanceTracker.init({ host: xFernHost, slug, auth: auth?.user.partner });
const performance = LoadDocsPerformanceTracker.init({ host: xFernHost, slug, auth: auth?.partner });

/**
* Load the docs for the given URL.
Expand Down
2 changes: 0 additions & 2 deletions packages/ui/fern-docs-auth/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { z } from "zod";

export const FernUserSchema = z.object({
type: z.literal("user"),
partner: z.union([z.literal("workos"), z.literal("ory"), z.literal("custom")]),
name: z.string().optional(),
email: z.string().optional(),
});
Expand Down
Loading