Skip to content

Commit

Permalink
(improvement, yaml): Add and update default OAuth YAML configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
amckinney committed May 9, 2024
1 parent 8ed84ba commit 9eb6eae
Show file tree
Hide file tree
Showing 44 changed files with 928 additions and 189 deletions.
1 change: 1 addition & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions packages/cli/configuration/src/DocsLinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface DocsLinks {
oauth: string;
}

export const DocsLinks: DocsLinks = {
oauth: "https://buildwithfern.com/learn/api-definition/fern/api-yml-reference#authentication"
};
1 change: 1 addition & 0 deletions packages/cli/configuration/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./commons/WithoutQuestionMarks";
export * from "./constants";
export * as dependenciesYml from "./dependencies-yml";
export * as docsYml from "./docs-yml";
export { DocsLinks } from "./DocsLinks";
export * as fernConfigJson from "./fern-config-json";
export * as generatorsYml from "./generators-yml";
export { GeneratorName } from "./generators-yml/GeneratorName";
Expand Down
173 changes: 131 additions & 42 deletions packages/cli/generation/ir-generator/src/converters/convertOAuthUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
import { RawSchemas } from "@fern-api/yaml-schema";
import { getRequestPropertyComponents, getResponsePropertyComponents } from "./services/convertProperty";

const DEFAULT_TOKEN_ENDPOINT: Omit<TokenEndpoint, "endpoint"> = {
requestProperties: {
type: "access_token",
client_id: ["client_id"],
client_secret: ["client_secret"],
scopes: undefined
},
responseProperties: {
type: "access_token",
access_token: ["access_token"],
expires_in: undefined,
refresh_token: undefined
}
};

const DEFAULT_REFRESH_TOKEN_ENDPOINT: Omit<RefreshTokenEndpoint, "endpoint"> = {
requestProperties: {
type: "refresh_token",
refresh_token: ["refresh_token"]
},
responseProperties: {
type: "access_token",
access_token: ["access_token"],
refresh_token: undefined,
expires_in: undefined
}
};

export interface TokenEndpoint {
endpoint: string;
requestProperties: OAuthAccessTokenRequestPropertyComponents;
Expand Down Expand Up @@ -33,58 +61,119 @@ interface OAuthRefreshTokenRequestPropertyComponents {
}

export function getTokenEndpoint(oauthSchema: RawSchemas.OAuthSchemeSchema): TokenEndpoint {
// const maybeScopes = oauthSchema["get-token"]["request-properties"].scopes;
const maybeExpiresIn = oauthSchema["get-token"]["response-properties"]["expires-in"];
const maybeRefreshToken = oauthSchema["get-token"]["response-properties"]["refresh-token"];
return {
endpoint: oauthSchema["get-token"].endpoint,
// TODO: Update the YAML schema and make this configurable with the following:
// requestProperties: {
// type: "access_token",
// client_id: getRequestPropertyComponents(oauthSchema["get-token"]["request-properties"]["client-id"]),
// client_secret: getRequestPropertyComponents(
// oauthSchema["get-token"]["request-properties"]["client-secret"]
// ),
// scopes: maybeScopes != null ? getRequestPropertyComponents(maybeScopes) : undefined
// },
requestProperties: {
type: "access_token",
client_id: ["client_id"],
client_secret: ["client_secret"],
scopes: undefined
},
responseProperties: {
type: "access_token",
access_token: getResponsePropertyComponents(
oauthSchema["get-token"]["response-properties"]["access-token"]
),
expires_in: maybeExpiresIn != null ? getResponsePropertyComponents(maybeExpiresIn) : undefined,
refresh_token: maybeRefreshToken != null ? getResponsePropertyComponents(maybeRefreshToken) : undefined
}
requestProperties: getTokenEndpointRequestProperties({
requestProperties: oauthSchema["get-token"]?.["request-properties"]
}),
responseProperties: getTokenEndpointResponseProperties({
responseProperties: oauthSchema["get-token"]?.["response-properties"]
})
};
}

export function getRefreshTokenEndpoint(oauthSchema: RawSchemas.OAuthSchemeSchema): RefreshTokenEndpoint | undefined {
if (oauthSchema["refresh-token"] == null) {
return undefined;
}
const maybeExpiresIn = oauthSchema["get-token"]["response-properties"]["expires-in"];
const maybeRefreshToken = oauthSchema["get-token"]["response-properties"]["refresh-token"];
return {
endpoint: oauthSchema["refresh-token"].endpoint,
requestProperties: {
type: "refresh_token",
refresh_token: getRequestPropertyComponents(
oauthSchema["refresh-token"]["request-properties"]["refresh-token"]
)
},
responseProperties: {
type: "access_token",
access_token: getResponsePropertyComponents(
oauthSchema["get-token"]["response-properties"]["access-token"]
),
expires_in: maybeExpiresIn != null ? getResponsePropertyComponents(maybeExpiresIn) : undefined,
refresh_token: maybeRefreshToken != null ? getResponsePropertyComponents(maybeRefreshToken) : undefined
}
requestProperties: getRefreshTokenEndpointRequestProperties({
requestProperties: oauthSchema["refresh-token"]?.["request-properties"]
}),
responseProperties: getRefreshTokenEndpointResponseProperties({
responseProperties: oauthSchema["refresh-token"]?.["response-properties"]
})
};
}

function getTokenEndpointRequestProperties({
requestProperties
}: {
requestProperties: RawSchemas.OAuthAccessTokenRequestPropertiesSchema | undefined;
}): OAuthAccessTokenRequestPropertyComponents {
if (requestProperties == null) {
return DEFAULT_TOKEN_ENDPOINT.requestProperties;
}
const maybeClientId = requestProperties["client-id"];
const maybeClientSecret = requestProperties["client-secret"];
const maybeScopes = requestProperties.scopes;
return {
type: "access_token",
client_id:
maybeClientId != null
? getRequestPropertyComponents(maybeClientId)
: DEFAULT_TOKEN_ENDPOINT.requestProperties.client_id,
client_secret:
maybeClientSecret != null
? getRequestPropertyComponents(maybeClientSecret)
: DEFAULT_TOKEN_ENDPOINT.requestProperties.client_secret,
scopes:
maybeScopes != null
? getRequestPropertyComponents(maybeScopes)
: DEFAULT_TOKEN_ENDPOINT.requestProperties.scopes
};
}

function getTokenEndpointResponseProperties({
responseProperties
}: {
responseProperties: RawSchemas.OAuthAccessTokenResponsePropertiesSchema | undefined;
}): OAuthAccessTokenResponsePropertyComponents {
return getTokenEndpointResponsePropertiesWithDefault({
responseProperties,
defaultValue: DEFAULT_TOKEN_ENDPOINT.responseProperties
});
}

function getRefreshTokenEndpointRequestProperties({
requestProperties
}: {
requestProperties: RawSchemas.OAuthRefreshTokenRequestPropertiesSchema | undefined;
}): OAuthRefreshTokenRequestPropertyComponents {
if (requestProperties == null) {
return DEFAULT_REFRESH_TOKEN_ENDPOINT.requestProperties;
}
const maybeRefreshToken = requestProperties["refresh-token"];
return {
type: "refresh_token",
refresh_token:
maybeRefreshToken != null
? getRequestPropertyComponents(maybeRefreshToken)
: DEFAULT_REFRESH_TOKEN_ENDPOINT.requestProperties.refresh_token
};
}

function getRefreshTokenEndpointResponseProperties({
responseProperties
}: {
responseProperties: RawSchemas.OAuthAccessTokenResponsePropertiesSchema | undefined;
}): OAuthAccessTokenResponsePropertyComponents {
return getTokenEndpointResponsePropertiesWithDefault({
responseProperties,
defaultValue: DEFAULT_REFRESH_TOKEN_ENDPOINT.responseProperties
});
}

function getTokenEndpointResponsePropertiesWithDefault({
responseProperties,
defaultValue
}: {
responseProperties: RawSchemas.OAuthAccessTokenResponsePropertiesSchema | undefined;
defaultValue: OAuthAccessTokenResponsePropertyComponents;
}): OAuthAccessTokenResponsePropertyComponents {
if (responseProperties == null) {
return defaultValue;
}
const maybeAccessToken = responseProperties["access-token"];
const maybeExpiresIn = responseProperties["expires-in"];
const maybeRefreshToken = responseProperties["refresh-token"];
return {
type: "access_token",
access_token:
maybeAccessToken != null ? getResponsePropertyComponents(maybeAccessToken) : defaultValue.access_token,
expires_in: maybeExpiresIn != null ? getResponsePropertyComponents(maybeExpiresIn) : defaultValue.expires_in,
refresh_token:
maybeRefreshToken != null ? getResponsePropertyComponents(maybeRefreshToken) : defaultValue.refresh_token
};
}
3 changes: 2 additions & 1 deletion packages/cli/yaml/validator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"@fern-api/yaml-schema": "workspace:*",
"chalk": "^5.0.1",
"lodash-es": "^4.17.21",
"strip-ansi": "^7.1.0"
"strip-ansi": "^7.1.0",
"terminal-link": "^3.0.0"
},
"devDependencies": {
"@types/jest": "^29.0.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
name: invalid
imports:
name: missing
imports:
auth: auth.yml

auth: OAuthScheme
auth-schemes:
OAuthScheme:
scheme: oauth
type: client-credentials
get-token:
get-token:
endpoint: auth.getTokenWithClientCredentials
response-properties:
access-token: $response.missing.access_token
expires-in: $response.missing.expires_in
refresh-token:
response-properties:
access-token: $response.accessToken
expires-in: $response.expiresIn
refresh-token:
endpoint: auth.refreshToken
request-properties:
refresh-token: $request.refreshTokenDoesNotExist
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ service:
getTokenWithClientCredentials:
path: /token
method: POST
request:
request:
name: GetTokenRequest
body:
properties:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: invalid
imports:
auth: auth.yml

auth: OAuthScheme
auth-schemes:
OAuthScheme:
scheme: oauth
type: client-credentials
get-token:
endpoint: auth.getToken
request-properties:
client-id: $request.credentials.client_id
client-secret: $request.credentials.client_secret
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
types:
TokenCredentials:
docs: |
The credentials required to retrieve an access token.
properties:
client_id: string
client_secret: string

TokenResponse:
docs: |
An OAuth token response.
properties:
access_token: string
expires_in: integer
refresh_token: optional<string>

service:
auth: false
base-path: /
endpoints:
getToken:
path: /token
method: POST
request:
name: GetTokenRequest
body:
properties:
credentials: TokenCredentials
response: TokenResponse
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: invalid
imports:
auth: auth.yml

auth: OAuthScheme
auth-schemes:
OAuthScheme:
scheme: oauth
type: client-credentials
get-token:
endpoint: auth.getToken
request-properties:
scopes: $request.scopes
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
types:
TokenResponse:
docs: |
An OAuth token response.
properties:
access_token: string
expires_in: integer
refresh_token: optional<string>

service:
auth: false
base-path: /
endpoints:
getToken:
path: /token
method: POST
request:
name: GetTokenRequest
body:
properties:
client_id: uuid
client_secret: uuid
scopes: integer
response: TokenResponse
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: alias
imports:
auth: auth.yml

auth: OAuthScheme
auth-schemes:
OAuthScheme:
scheme: oauth
type: client-credentials
get-token:
endpoint: auth.getToken
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
types:
ClientID: string
ClientSecret: string
AccessToken: string

TokenResponse:
docs: |
An OAuth token response.
properties:
access_token: AccessToken

service:
auth: false
base-path: /
endpoints:
getToken:
path: /token
method: POST
request:
name: GetTokenRequest
body:
properties:
client_id: ClientID
client_secret: ClientSecret
grant_type: literal<"client_credentials">
response: TokenResponse

0 comments on commit 9eb6eae

Please sign in to comment.