Skip to content

Commit

Permalink
feat(docs): oauth playground enablement (#4571)
Browse files Browse the repository at this point in the history
* first pass, need to update FDR

* default

* eslint

* working, changing to api reference

* converge config into api reference

* added changelog entry and documentation page

* updated fdr version
  • Loading branch information
RohinBhargava authored Sep 7, 2024
1 parent 49b90b1 commit 486b68f
Show file tree
Hide file tree
Showing 22 changed files with 175 additions and 54 deletions.
3 changes: 3 additions & 0 deletions fern/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,9 @@ navigation:
- page: Write Markdown in API Reference
icon: fa-regular fa-pencil
path: ./pages/fern-docs/content/api-ref-content.mdx
- page: Customize API Playground
icon: fa-regular fa-square-terminal
path: ./pages/fern-docs/content/customize-api-playground.mdx

- section: Integrations
slug: integrations
Expand Down
35 changes: 35 additions & 0 deletions fern/pages/fern-docs/content/customize-api-playground.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: Customize your API Playground
description: Customize your API Playground Settings
---

If you subscribe to Fern's Pro or Enterprise Plans, you can customize your API Playground settings to suit your customers needs.

All configuration settings are defined in the `docs.yml` file, under API Reference navigation configuration, in a `Playground` object.

### Filtering Server Urls

If you have multiple environments for your API, you can filter the server URLs that are displayed in the API Playground.

To filter server URLs, add the `environments` property to the `PlaygroundSettings` object in your `docs.yml`, like so:

```yaml
navigation:
playground:
environments:
- Staging-A
- Staging-B
```
### Enabling OAuth 2.0 Authorization Injection
If you have defined an endpoint that executes OAuth 2.0 Client Credentials Authorization in your API definition, you can enable OAuth 2.0 Authorization Injection in your API Playground.
More information on enabling OAuth 2.0 Authorization Injection can be found [here](/learn/api-definition/fern/authentication#oauth-client-credentials).
To enable OAuth 2.0 Authorization Injection, simply add the `oauth` feature flag to the `PlaygroundSettings` object in your `docs.yml`, like so:

```yaml
navigation:
playground:
oauth: true
```
31 changes: 31 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
- changelogEntry:
- summary: |
The Fern Docs CLI now supports OAuth 2.0 Client Credentials injection in API playgrounds.
To enable this feature, you can define the OAuth Authorization Scheme in your API configuration,
and enable the feature in your docs configuration.
API configuration:
```yml
api:
auth-schemes:
OAuth:
scheme: oauth
type: client-credentials
get-token:
endpoint: endpoint.authorization
```
(More Information)[https://buildwithfern.com/learn/api-definition/fern/authentication#oauth-client-credentials]
Docs configuration:
```yml
navigation:
section: API Reference
playground:
oauth: true
```
(More Information)[https://buildwithfern.com/learn/docs/api-references/customize-api-playground]
type: feat
createdAt: '2024-09-07'
irVersion: 53
version: 0.41.6

- changelogEntry:
- summary: |
Fix an issue with non-deterministic file ordering when OpenAPI is used as input.
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/configuration/fern/definition/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,7 @@ types:
docs: A list of environment IDs that are allowed to be used in the playground.
If not provided, all environments are allowed. And if the provided list is empty, the playground should be disabled.
button: optional<PlaygroundButtonSettings>
oauth: optional<boolean>

PlaygroundButtonSettings:
properties:
Expand All @@ -983,3 +984,4 @@ types:
message:
type: string
docs: The message to display in the announcement bar. Markdown is supported.

2 changes: 1 addition & 1 deletion packages/cli/configuration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@fern-api/fs-utils": "workspace:*",
"@fern-api/task-context": "workspace:*",
"@fern-api/fern-definition-schema": "workspace:*",
"@fern-fern/fdr-cjs-sdk": "0.1.0",
"@fern-fern/fdr-cjs-sdk": "0.108.0-e5e9d69a4",
"@fern-fern/fiddle-sdk": "0.0.584",
"@fern-fern/generators-sdk": "0.107.0-00fe26566",
"find-up": "^6.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export interface PlaygroundSettings {
/** A list of environment IDs that are allowed to be used in the playground. If not provided, all environments are allowed. And if the provided list is empty, the playground should be disabled. */
environments?: string[];
button?: FernDocsConfig.PlaygroundButtonSettings;
oauth?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ export const PlaygroundSettings: core.serialization.ObjectSchema<
> = core.serialization.object({
environments: core.serialization.list(core.serialization.string()).optional(),
button: core.serialization.lazyObject(async () => (await import("../../..")).PlaygroundButtonSettings).optional(),
oauth: core.serialization.boolean().optional(),
});

export declare namespace PlaygroundSettings {
interface Raw {
environments?: string[] | null;
button?: serializers.PlaygroundButtonSettings.Raw | null;
oauth?: boolean | null;
}
}
2 changes: 1 addition & 1 deletion packages/cli/docs-markdown-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"dependencies": {
"@fern-api/fs-utils": "workspace:*",
"@fern-api/task-context": "workspace:*",
"@fern-fern/fdr-cjs-sdk": "0.1.0",
"@fern-fern/fdr-cjs-sdk": "0.108.0-e5e9d69a4",
"gray-matter": "^4.0.3",
"mdast-util-from-markdown": "^2.0.1",
"mdast-util-mdx": "^3.0.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/docs-preview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-api/docs-resolver": "workspace:*",
"@fern-api/fdr-sdk": "0.107.0-58bab3016",
"@fern-api/docs-resolver": "workspace:*",
"@fern-api/fdr-sdk": "0.108.0-e5e9d69a4",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/ir-sdk": "workspace:*",
"@fern-api/logger": "workspace:*",
"@fern-api/logger": "workspace:*",
"@fern-api/project-loader": "workspace:*",
"@fern-api/register": "workspace:*",
"@fern-api/task-context": "workspace:*",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/docs-preview/src/previewDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,17 @@ class ReferencedAPICollector {

public addReferencedAPI({
ir,
snippetsConfig
snippetsConfig,
playgroundConfig
}: {
ir: IntermediateRepresentation;
snippetsConfig: APIV1Write.SnippetsConfig;
playgroundConfig?: DocsV1Read.PlaygroundConfig;
}): APIDefinitionID {
try {
const id = uuidv4();

const apiDefinition = convertIrToFdrApi({ ir, snippetsConfig });
const apiDefinition = convertIrToFdrApi({ ir, snippetsConfig, playgroundConfig });

const dbApiDefinition = convertAPIDefinitionToDb(
apiDefinition,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/docs-resolver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@fern-api/configuration": "workspace:*",
"@fern-api/core-utils": "workspace:*",
"@fern-api/docs-markdown-utils": "workspace:*",
"@fern-api/fdr-sdk": "0.107.0-58bab3016",
"@fern-api/fdr-sdk": "0.108.0-e5e9d69a4",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/ir-generator": "workspace:*",
"@fern-api/ir-sdk": "workspace:*",
Expand Down
11 changes: 8 additions & 3 deletions packages/cli/docs-resolver/src/DocsDefinitionResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class DocsDefinitionResolver {
private registerApi: (opts: {
ir: IntermediateRepresentation;
snippetsConfig: APIV1Write.SnippetsConfig;
playgroundConfig?: DocsV1Write.PlaygroundConfig;
}) => Promise<string>
) {}

Expand Down Expand Up @@ -261,6 +262,7 @@ export class DocsDefinitionResolver {
this.parsedDocsConfig.announcement != null
? { text: this.parsedDocsConfig.announcement.message }
: undefined,
playground: undefined,
// deprecated
logo: undefined,
logoV2: undefined,
Expand Down Expand Up @@ -288,7 +290,6 @@ export class DocsDefinitionResolver {

private async convertNavigationConfig(): Promise<DocsV1Write.NavigationConfig> {
const slug = FernNavigation.SlugGenerator.init(FernNavigation.utils.slugjoin(this.getDocsBasePath()));
const landingPage = this.parsedDocsConfig.landingPage;
switch (this.parsedDocsConfig.navigation.type) {
case "versioned": {
const versions = await Promise.all(
Expand Down Expand Up @@ -379,8 +380,12 @@ export class DocsDefinitionResolver {
packageName: undefined,
context: this.taskContext
});
const apiDefinitionId = await this.registerApi({ ir, snippetsConfig });
const api = convertIrToApiDefinition(ir, apiDefinitionId);
const apiDefinitionId = await this.registerApi({
ir,
snippetsConfig,
playgroundConfig: { oauth: item.playground?.oauth }
});
const api = convertIrToApiDefinition(ir, apiDefinitionId, { oauth: item.playground?.oauth });
const node = new ApiReferenceNodeConverter(
item,
api,
Expand Down
17 changes: 14 additions & 3 deletions packages/cli/docs-resolver/src/utils/convertIrToApiDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { APIV1Read, convertAPIDefinitionToDb, convertDbAPIDefinitionToRead, SDKSnippetHolder } from "@fern-api/fdr-sdk";
import {
APIV1Read,
DocsV1Read,
convertAPIDefinitionToDb,
convertDbAPIDefinitionToRead,
SDKSnippetHolder
} from "@fern-api/fdr-sdk";
import { IntermediateRepresentation } from "@fern-api/ir-sdk";
import { convertIrToFdrApi } from "@fern-api/register";

Expand All @@ -12,10 +18,15 @@ const EMPTY_SNIPPET_HOLDER = new SDKSnippetHolder({

export function convertIrToApiDefinition(
ir: IntermediateRepresentation,
apiDefinitionId: string
apiDefinitionId: string,
playgroundConfig?: DocsV1Read.PlaygroundConfig
): APIV1Read.ApiDefinition {
// the navigation constructor doesn't need to know about snippets, so we can pass an empty object
return convertDbAPIDefinitionToRead(
convertAPIDefinitionToDb(convertIrToFdrApi({ ir, snippetsConfig: {} }), apiDefinitionId, EMPTY_SNIPPET_HOLDER)
convertAPIDefinitionToDb(
convertIrToFdrApi({ ir, snippetsConfig: {}, playgroundConfig }),
apiDefinitionId,
EMPTY_SNIPPET_HOLDER
)
);
}
2 changes: 1 addition & 1 deletion packages/cli/ete-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
},
"dependencies": {
"@fern-api/configuration": "workspace:*",
"@fern-fern/fdr-cjs-sdk": "0.1.0",
"@fern-fern/fdr-cjs-sdk": "0.108.0-e5e9d69a4",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/logging-execa": "workspace:*",
"@fern-typescript/fetcher": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@fern-api/core-utils": "workspace:*",
"@fern-api/docs-resolver": "workspace:*",
"@fern-api/logging-execa": "workspace:*",
"@fern-fern/fdr-cjs-sdk": "0.1.0",
"@fern-fern/fdr-cjs-sdk": "0.108.0-e5e9d69a4",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/ir-generator": "workspace:*",
"@fern-api/ir-migrations": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ export async function publishDocs({
}
}
},
async ({ ir, snippetsConfig }) => {
const apiDefinition = convertIrToFdrApi({ ir, snippetsConfig });
async ({ ir, snippetsConfig, playgroundConfig }) => {
const apiDefinition = convertIrToFdrApi({ ir, snippetsConfig, playgroundConfig });
context.logger.debug("Calling registerAPI... ", JSON.stringify(apiDefinition, undefined, 4));
const response = await fdr.api.v1.register.registerApiDefinition({
orgId: organization,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/register/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@fern-api/configuration": "workspace:*",
"@fern-api/core": "workspace:*",
"@fern-api/core-utils": "workspace:*",
"@fern-fern/fdr-cjs-sdk": "0.1.0",
"@fern-fern/fdr-cjs-sdk": "0.108.0-e5e9d69a4",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/ir-generator": "workspace:*",
"@fern-api/ir-sdk": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { entries } from "@fern-api/core-utils";
import { IntermediateRepresentation } from "@fern-api/ir-sdk";
import { FernRegistry as FdrCjsSdk } from "@fern-fern/fdr-cjs-sdk";
import { PlaygroundConfig } from "@fern-fern/fdr-cjs-sdk/api/resources/docs/resources/v1/resources/commons";
import { convertIrAvailability, convertPackage } from "./convertPackage";
import { convertTypeReference, convertTypeShape } from "./convertTypeShape";
import { convertAuth } from "./covertAuth";

export function convertIrToFdrApi({
ir,
snippetsConfig
snippetsConfig,
playgroundConfig
}: {
ir: IntermediateRepresentation;
snippetsConfig: FdrCjsSdk.api.v1.register.SnippetsConfig;
playgroundConfig?: PlaygroundConfig;
}): FdrCjsSdk.api.v1.register.ApiDefinition {
const fdrApi: FdrCjsSdk.api.v1.register.ApiDefinition = {
types: {},
subpackages: {},
rootPackage: convertPackage(ir.rootPackage, ir),
auth: convertAuth(ir.auth),
auth: convertAuth(ir.auth, ir, playgroundConfig),
snippetsConfiguration: snippetsConfig,
globalHeaders: ir.headers.map(
(header): FdrCjsSdk.api.v1.register.Header => ({
Expand Down
37 changes: 29 additions & 8 deletions packages/cli/register/src/ir-to-fdr-converter/covertAuth.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { assertNever } from "@fern-api/core-utils";
import { FernIr as Ir } from "@fern-api/ir-sdk";
import { FernRegistry as FdrCjsSdk } from "@fern-fern/fdr-cjs-sdk";
import { PlaygroundConfig } from "@fern-fern/fdr-cjs-sdk/api/resources/docs/resources/v1/resources/commons";

export function convertAuth(auth: Ir.auth.ApiAuth): FdrCjsSdk.api.v1.register.ApiAuth | undefined {
export function convertAuth(
auth: Ir.auth.ApiAuth,
ir: Ir.ir.IntermediateRepresentation,
playgroundConfig?: PlaygroundConfig
): FdrCjsSdk.api.v1.register.ApiAuth | undefined {
const scheme = auth.schemes[0];
if (auth.schemes.length === 1 && scheme != null) {
switch (scheme.type) {
Expand All @@ -24,13 +29,29 @@ export function convertAuth(auth: Ir.auth.ApiAuth): FdrCjsSdk.api.v1.register.Ap
nameOverride: scheme.name.name.originalName,
prefix: scheme.prefix
};
case "oauth":
// TODO: Add support oauth for FDR. For now, we just map
// it to the default bearer auth.
return {
type: "bearerAuth",
tokenName: "token"
};
case "oauth": {
const tokenPath =
scheme.configuration.tokenEndpoint.responseProperties.accessToken.propertyPath
?.map((p) => p.originalName)
.join(".") || "$.body.access_token";

return playgroundConfig?.oauth
? {
type: "oAuth",
value: {
type: "clientCredentials",
value: {
type: "referencedEndpoint",
endpointId: scheme.configuration.tokenEndpoint.endpointReference.endpointId,
accessTokenLocator: tokenPath
}
}
}
: {
type: "bearerAuth",
tokenName: "token"
};
}
default:
assertNever(scheme);
}
Expand Down
7 changes: 5 additions & 2 deletions packages/cli/register/src/registerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IntermediateRepresentation } from "@fern-api/ir-sdk";
import { TaskContext } from "@fern-api/task-context";
import { FernWorkspace } from "@fern-api/workspace-loader";
import { FernRegistry as FdrCjsSdk } from "@fern-fern/fdr-cjs-sdk";
import { PlaygroundConfig } from "@fern-fern/fdr-cjs-sdk/api/resources/docs/resources/v1/resources/commons";
import { convertIrToFdrApi } from "./ir-to-fdr-converter/convertIrToFdrApi";

export async function registerApi({
Expand All @@ -14,14 +15,16 @@ export async function registerApi({
context,
token,
audiences,
snippetsConfig
snippetsConfig,
playgroundConfig
}: {
organization: string;
workspace: FernWorkspace;
context: TaskContext;
token: FernToken;
audiences: Audiences;
snippetsConfig: FdrCjsSdk.api.v1.register.SnippetsConfig;
playgroundConfig?: PlaygroundConfig;
}): Promise<{ id: FdrCjsSdk.ApiDefinitionId; ir: IntermediateRepresentation }> {
const ir = await generateIntermediateRepresentation({
workspace,
Expand All @@ -40,7 +43,7 @@ export async function registerApi({
token: token.value
});

const apiDefinition = convertIrToFdrApi({ ir, snippetsConfig });
const apiDefinition = convertIrToFdrApi({ ir, snippetsConfig, playgroundConfig });
context.logger.debug("Calling registerAPI... ", JSON.stringify(apiDefinition, undefined, 4));
const response = await fdrService.api.v1.register.registerApiDefinition({
orgId: organization,
Expand Down
Loading

0 comments on commit 486b68f

Please sign in to comment.