Skip to content

Commit

Permalink
Merge branch 'main' into ajiang/fix-hidden-noindex
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored Oct 3, 2024
2 parents 7cd9118 + 13a1382 commit 57c8021
Show file tree
Hide file tree
Showing 10 changed files with 43 additions and 30 deletions.
19 changes: 9 additions & 10 deletions packages/ui/app/src/auth/OAuth2Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@ export class OAuth2Client {
private readonly environment: string;
private readonly scope: string | undefined;
private readonly jwks: string | undefined;
private readonly redirectUri?: string;

constructor(
config: AuthEdgeConfigOAuth2Ory,
private readonly redirect_uri?: string,
) {
constructor(config: AuthEdgeConfigOAuth2Ory) {
this.clientId = config.clientId;
this.clientSecret = config.clientSecret;
this.environment = config.environment;
this.scope = config.scope;
this.jwks = config.jwks;
this.redirectUri = config.redirectUri;
}

public async getToken(code: string): Promise<OAuthTokenResponse> {
Expand All @@ -34,8 +33,8 @@ export class OAuth2Client {
form.append("client_secret", this.clientSecret);
form.append("grant_type", "authorization_code");
form.append("client_id", this.clientId);
if (this.redirect_uri != null) {
form.append("redirect_uri", this.redirect_uri);
if (this.redirectUri != null) {
form.append("redirect_uri", this.redirectUri);
}

const response = await fetch(urlJoin(this.environment, "/token"), {
Expand All @@ -55,8 +54,8 @@ export class OAuth2Client {
form.append("client_secret", this.clientSecret);
form.append("grant_type", "refresh_token");
form.append("client_id", this.clientId);
if (this.redirect_uri != null) {
form.append("redirect_uri", this.redirect_uri);
if (this.redirectUri != null) {
form.append("redirect_uri", this.redirectUri);
}

const response = await fetch(urlJoin(this.environment, "/token"), {
Expand All @@ -74,8 +73,8 @@ export class OAuth2Client {
const url = new URL(urlJoin(this.environment, "/auth"));
url.searchParams.set("response_type", "code");
url.searchParams.set("client_id", this.clientId);
if (this.redirect_uri != null) {
url.searchParams.set("redirect_uri", this.redirect_uri);
if (this.redirectUri != null) {
url.searchParams.set("redirect_uri", this.redirectUri);
}
if (state != null) {
url.searchParams.set("state", state);
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/app/src/auth/getApiKeyInjectionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function getAPIKeyInjectionConfig(
): Promise<APIKeyInjectionConfig> {
const config = await getAuthEdgeConfig(domain);
if (config?.type === "oauth2" && config.partner === "ory" && config["api-key-injection-enabled"]) {
const client = new OAuth2Client(config, `https://${domain}/api/auth/callback`);
const client = new OAuth2Client(config);
const tokens = cookies != null ? await client.getOrRefreshAccessTokenEdge(cookies) : undefined;

if (tokens != null) {
Expand Down Expand Up @@ -71,7 +71,7 @@ export async function getAPIKeyInjectionConfigNode(
): Promise<APIKeyInjectionConfig> {
const config = await getAuthEdgeConfig(domain);
if (config?.type === "oauth2" && config.partner === "ory" && config["api-key-injection-enabled"]) {
const client = new OAuth2Client(config, `https://${domain}/api/auth/callback`);
const client = new OAuth2Client(config);
const tokens = cookies != null ? await client.getOrRefreshAccessTokenNode(cookies) : undefined;

if (tokens != null) {
Expand Down
14 changes: 12 additions & 2 deletions packages/ui/app/src/auth/getAuthEdgeConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { captureMessage } from "@sentry/nextjs";
import { get } from "@vercel/edge-config";
import { AuthEdgeConfig, AuthEdgeConfigSchema } from "./types";

Expand All @@ -8,8 +9,17 @@ export async function getAuthEdgeConfig(currentDomain: string): Promise<AuthEdge
const toRet = domainToTokenConfigMap?.[currentDomain];

if (toRet != null) {
// if the config is present, it should be valid
return AuthEdgeConfigSchema.parse(toRet);
const config = AuthEdgeConfigSchema.safeParse(toRet);

// if the config is present, it should be valid.
// if it's malformed, custom auth for this domain will not work and may leak docs to the public.
if (!config.success) {
// eslint-disable-next-line no-console
console.error(config.error);
captureMessage(`Could not parse AuthEdgeConfigSchema for ${currentDomain}`, "fatal");
}

return config.data;
}

return;
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/app/src/auth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const AuthEdgeConfigOAuth2OrySchema = z.object({
scope: z.optional(z.string()),
clientId: z.string(),
clientSecret: z.string(),
redirectUri: z.string().optional(),
"api-key-injection-enabled": z.optional(z.boolean()),
});
export const AuthEdgeConfigOAuth2WebflowSchema = z.object({
Expand All @@ -25,6 +26,7 @@ export const AuthEdgeConfigOAuth2WebflowSchema = z.object({
scope: z.optional(z.union([z.string(), z.array(z.string())])),
clientId: z.string(),
clientSecret: z.string(),
redirectUri: z.string().optional(),
});

export const AuthEdgeConfigBasicTokenVerificationSchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ export function PlaygroundAuthorizationFormCard({
}
url.searchParams.set("state", state.toString());

if (apiKeyInjection.partner === "ory") {
if (apiKeyInjection.partner === "ory" || apiKeyInjection.partner === "webflow") {
const redirect_uri = urlJoin(window.location.origin, callbackApiRoute);
url.searchParams.set("redirect_uri", redirect_uri);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
withSecureCookie,
} from "@fern-ui/ui/auth";
import { NextRequest, NextResponse } from "next/server";
import urlJoin from "url-join";
import { WebflowClient } from "webflow-api";
import type { OauthScope } from "webflow-api/api/types/OAuthScope";

Expand All @@ -27,12 +26,10 @@ export default async function handler(req: NextRequest): Promise<NextResponse<AP
authenticated: false,
url: WebflowClient.authorizeURL({
clientId: edgeConfig.clientId,

// TODO: subpaths will not work
// redirectUri: `https://${domain}/api/fern-docs/oauth/webflow/callback`,
redirectUri: edgeConfig.redirectUri,

// note: this is not validated
scope: (edgeConfig.scope as OauthScope | OauthScope[]) ?? "authorized_user:read",
scope: (edgeConfig.scope as OauthScope | OauthScope[]) ?? [],
}),
});
}
Expand All @@ -59,7 +56,7 @@ export default async function handler(req: NextRequest): Promise<NextResponse<AP
let exp = expires;

if (edgeConfig != null && edgeConfig.type === "oauth2" && edgeConfig.partner === "ory") {
const oauthClient = new OAuth2Client(edgeConfig, urlJoin(`https://${domain}/api/auth/callback`));
const oauthClient = new OAuth2Client(edgeConfig);

const token = OryAccessTokenSchema.parse(await oauthClient.decode(access_token));
exp = token.exp == null ? undefined : new Date(token.exp * 1000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
if (req.method !== "GET") {
return new NextResponse(null, { status: 405 });
}
const domain = getXFernHostEdge(req);

// The authorization code returned by AuthKit
const code = req.nextUrl.searchParams.get("code");
const state = req.nextUrl.searchParams.get("state");
const error = req.nextUrl.searchParams.get("error");
const error_description = req.nextUrl.searchParams.get("error_description");
const redirectLocation = state ?? req.nextUrl.origin;
const redirectLocation = state ?? `https://${domain}/`;

if (error != null) {
return redirectWithLoginError(redirectLocation, error_description ?? error);
Expand All @@ -31,7 +32,6 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
return redirectWithLoginError(redirectLocation, "Couldn't login, please try again");
}

const domain = getXFernHostEdge(req);
const config = await getAuthEdgeConfig(domain);

if (config != null && config.type === "oauth2" && config.partner === "ory") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { getXFernHostEdge } from "@/server/xfernhost/edge";
import { NextRequest, NextResponse } from "next/server";

export const runtime = "edge";

export default async function GET(req: NextRequest): Promise<NextResponse> {
const domain = getXFernHostEdge(req);

const state = req.nextUrl.searchParams.get("state");
const redirectLocation = state ?? req.nextUrl.origin;
const redirectLocation = state ?? `https://${domain}/`;

const res = NextResponse.redirect(redirectLocation);
res.cookies.delete("fern_token");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
withSecureCookie,
} from "@fern-ui/ui/auth";
import { NextRequest, NextResponse } from "next/server";
import urlJoin from "url-join";

export const runtime = "edge";

Expand All @@ -23,11 +22,13 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
return new NextResponse(null, { status: 405 });
}

const domain = getXFernHostEdge(req);

const code = req.nextUrl.searchParams.get("code");
const state = req.nextUrl.searchParams.get("state");
const error = req.nextUrl.searchParams.get("error");
const error_description = req.nextUrl.searchParams.get("error_description");
const redirectLocation = state ?? req.nextUrl.origin;
const redirectLocation = state ?? `https://${domain}/`;

if (error != null) {
return redirectWithLoginError(redirectLocation, error_description ?? error);
Expand All @@ -37,14 +38,13 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
return redirectWithLoginError(redirectLocation, "Couldn't login, please try again");
}

const domain = getXFernHostEdge(req);
const config = await getAuthEdgeConfig(domain);

if (config == null || config.type !== "oauth2" || config.partner !== "ory") {
return redirectWithLoginError(redirectLocation, "Couldn't login, please try again");
}

const oauthClient = new OAuth2Client(config, urlJoin(`https://${domain}`, req.nextUrl.pathname));
const oauthClient = new OAuth2Client(config);
try {
const { access_token, refresh_token } = await oauthClient.getToken(code);
const token = OryAccessTokenSchema.parse(await oauthClient.decode(access_token));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
return new NextResponse(null, { status: 405 });
}

const domain = getXFernHostEdge(req);

const code = req.nextUrl.searchParams.get("code");
const state = req.nextUrl.searchParams.get("state");
const error = req.nextUrl.searchParams.get("error");
const error_description = req.nextUrl.searchParams.get("error_description");
const redirectLocation = state ?? req.nextUrl.origin;
const redirectLocation = state ?? `https://${domain}/`;

if (error != null) {
return redirectWithLoginError(redirectLocation, error_description ?? error);
Expand All @@ -30,7 +32,6 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
return redirectWithLoginError(redirectLocation, "Couldn't login, please try again");
}

const domain = getXFernHostEdge(req);
const config = await getAuthEdgeConfig(domain);

if (config == null || config.type !== "oauth2" || config.partner === "ory") {
Expand All @@ -41,6 +42,7 @@ export default async function GET(req: NextRequest): Promise<NextResponse> {
const accessToken = await WebflowClient.getAccessToken({
clientId: config.clientId,
clientSecret: config.clientSecret,
redirectUri: config.redirectUri,
code,
});

Expand Down

0 comments on commit 57c8021

Please sign in to comment.