diff --git a/.yarn/versions/f2947d6d.yml b/.yarn/versions/f2947d6d.yml new file mode 100644 index 0000000..7da9216 --- /dev/null +++ b/.yarn/versions/f2947d6d.yml @@ -0,0 +1,7 @@ +releases: + "@typoas/cli": minor + "@typoas/generator": minor + "@typoas/runtime": minor + +declined: + - "@typoas/react-query" diff --git a/CHANGELOG.md b/CHANGELOG.md index 520b94f..e9149e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add support for `openIdConnect` security scheme type (fixes [#69](https://github.com/Embraser01/typoas/pull/69)) [#70](https://github.com/Embraser01/typoas/pull/70) + --- ## 4.0.0 - 2024-08-21 diff --git a/README.md b/README.md index 5c6491f..8c02f68 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ Main features are: - **React Query** integration - Support for `allOf`, `oneOf` and `anyOf` schemas. - Automatically convert `format: 'date-time'` to JS `Date` -- Handle **API Key**, **HTTP Config** and **OAuth2**1 auth security schemes +- Handle **API Key**, **HTTP Config**, **OAuth2**1 and **OIDC**1 auth security schemes - JSDoc for schemas and operations - Uses `fetch` api (can be customized) - Non JSON content type support - Small bundle size - And more... -> 1: OAuth2 scheme does not handle flows to retrieve an `accessToken`. +> 1: OAuth2 and OpenIDConnect scheme do not handle flows to retrieve an `accessToken`. > You need to provide your own `accessToken` through the `provider.getConfig()` function. The project is split into 4 packages: @@ -253,11 +253,12 @@ const ctx = createContext({ }); ``` -It supports 4 types of security schemes: +It supports 5 types of security schemes: - `apiKey` mode - `http` bearer and basic mode - `oauth2` mode +- `openIdConnect` mode The `getConfig` function should return the configuration needed to authenticate the request. Returning `null` will skip the authentification. diff --git a/packages/typoas-generator/src/generator/components/security-scheme.ts b/packages/typoas-generator/src/generator/components/security-scheme.ts index b1d8de3..cfeb94f 100644 --- a/packages/typoas-generator/src/generator/components/security-scheme.ts +++ b/packages/typoas-generator/src/generator/components/security-scheme.ts @@ -52,6 +52,11 @@ export function createConfigTypeFromSecurityScheme( case 'oauth2': return createRuntimeRefType(ExportedRef.OAuth2SecurityAuthentication); case 'openIdConnect': + return createRuntimeRefType( + ExportedRef.OpenIdConnectSecurityAuthentication, + ); + default: + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new Error(`Unsupported security scheme '${securityScheme.type}'`); } } @@ -123,6 +128,15 @@ export function createRuntimeSecurityClass( [factory.createObjectLiteralExpression(args), authProviderExpression], ); case 'openIdConnect': + return factory.createNewExpression( + createRuntimeRefProperty( + ExportedRef.OpenIdConnectSecurityAuthentication, + ), + undefined, + [factory.createObjectLiteralExpression(args), authProviderExpression], + ); + default: + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new Error(`Unsupported security scheme '${securityScheme.type}'`); } } diff --git a/packages/typoas-generator/src/generator/utils/ref.ts b/packages/typoas-generator/src/generator/utils/ref.ts index 20d7d85..5623258 100644 --- a/packages/typoas-generator/src/generator/utils/ref.ts +++ b/packages/typoas-generator/src/generator/utils/ref.ts @@ -14,6 +14,7 @@ export enum ExportedRef { HttpBasicSecurityAuthentication = 'HttpBasicSecurityAuthentication', HttpBearerSecurityAuthentication = 'HttpBearerSecurityAuthentication', OAuth2SecurityAuthentication = 'OAuth2SecurityAuthentication', + OpenIdConnectSecurityAuthentication = 'OpenIdConnectSecurityAuthentication', StatusResponse = 'StatusResponse', BaseFetcherData = 'BaseFetcherData', } diff --git a/packages/typoas-runtime/src/auth/base.ts b/packages/typoas-runtime/src/auth/base.ts index 14a247a..d2f8c03 100644 --- a/packages/typoas-runtime/src/auth/base.ts +++ b/packages/typoas-runtime/src/auth/base.ts @@ -15,3 +15,12 @@ export interface SecurityAuthentication { export interface AuthProvider { getConfig(): Promise | T | null; } + +/** + * Configuration for the OAuth2 and OIDC authentication scheme. + */ +export type BaseFlowConfig = { + accessToken: string; + // Allow overriding the token type (default: Bearer) + tokenType?: string; +}; diff --git a/packages/typoas-runtime/src/auth/index.ts b/packages/typoas-runtime/src/auth/index.ts index 2c15d05..e1f8e09 100644 --- a/packages/typoas-runtime/src/auth/index.ts +++ b/packages/typoas-runtime/src/auth/index.ts @@ -3,3 +3,4 @@ export * from './api-key-auth'; export * from './http-auth-basic'; export * from './http-auth-bearer'; export * from './oauth2-auth'; +export * from './open-id-connect-auth'; diff --git a/packages/typoas-runtime/src/auth/oauth2-auth.ts b/packages/typoas-runtime/src/auth/oauth2-auth.ts index e3ee032..79ad499 100644 --- a/packages/typoas-runtime/src/auth/oauth2-auth.ts +++ b/packages/typoas-runtime/src/auth/oauth2-auth.ts @@ -1,16 +1,14 @@ -import type { AuthProvider, SecurityAuthentication } from './base'; +import type { + AuthProvider, + BaseFlowConfig, + SecurityAuthentication, +} from './base'; import type { RequestContext } from '../fetcher'; // We don't actually need to have any configuration for OAuth2 // because we only use the already generated accessToken. export type OAuth2Configuration = Record; -export type BaseFlowConfig = { - accessToken: string; - // Allow overriding the token type (default: Bearer) - tokenType?: string; -}; - export class OAuth2SecurityAuthentication implements SecurityAuthentication { constructor( private config: OAuth2Configuration, diff --git a/packages/typoas-runtime/src/auth/open-id-connect-auth.ts b/packages/typoas-runtime/src/auth/open-id-connect-auth.ts new file mode 100644 index 0000000..b2cc3c0 --- /dev/null +++ b/packages/typoas-runtime/src/auth/open-id-connect-auth.ts @@ -0,0 +1,34 @@ +import type { + AuthProvider, + BaseFlowConfig, + SecurityAuthentication, +} from './base'; +import type { RequestContext } from '../fetcher'; + +// We don't actually need to have any configuration for OpenIdConnect +// because we only use the already generated accessToken. +export type OpenIdConnectConfiguration = Record; + +export class OpenIdConnectSecurityAuthentication + implements SecurityAuthentication +{ + constructor( + private config: OpenIdConnectConfiguration, + private provider?: AuthProvider, + ) {} + + async applySecurityAuthentication(context: RequestContext): Promise { + if (!this.provider) return; + + const res = await this.provider.getConfig(); + if (res === null) { + return; + } + + const { accessToken, tokenType } = res; + return context.setHeaderParam( + 'Authorization', + `${tokenType || 'Bearer'} ${accessToken}`, + ); + } +} diff --git a/packages/typoas-runtime/src/context/types.ts b/packages/typoas-runtime/src/context/types.ts index d77a1e4..171f82a 100644 --- a/packages/typoas-runtime/src/context/types.ts +++ b/packages/typoas-runtime/src/context/types.ts @@ -5,7 +5,11 @@ import { SerializerOptions, } from '../fetcher'; import type { TransformEntity } from '../transformers'; -import type { AuthProvider, SecurityAuthentication } from '../auth'; +import { + AuthProvider, + OpenIdConnectSecurityAuthentication, + SecurityAuthentication, +} from '../auth'; import type { Transform } from '../transformers'; import type { BaseServerConfiguration } from '../configuration'; import type { @@ -89,7 +93,9 @@ export type AuthProviderConfig = ? BearerAuthConfig : T extends OAuth2SecurityAuthentication ? BaseFlowConfig - : never; + : T extends OpenIdConnectSecurityAuthentication + ? BaseFlowConfig + : never; export type ResponseHandler = { transforms?: TransformEntity;