diff --git a/.changeset/tall-scissors-boil.md b/.changeset/tall-scissors-boil.md new file mode 100644 index 00000000000..f47d7866074 --- /dev/null +++ b/.changeset/tall-scissors-boil.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/core-typings": patch +"@rocket.chat/rest-typings": patch +--- + +Add OpenAPI support for the Rocket.Chat oauth-apps.get API endpoints by migrating to a modern chained route definition syntax and utilizing shared AJV schemas for validation to enhance API documentation and ensure type safety through response validation. diff --git a/apps/meteor/app/api/server/definition.ts b/apps/meteor/app/api/server/definition.ts index 3642108b2b8..c4eb519c9d4 100644 --- a/apps/meteor/app/api/server/definition.ts +++ b/apps/meteor/app/api/server/definition.ts @@ -303,6 +303,8 @@ export type TypedThis bodyParams: TOptions['body'] extends ValidateFunction ? Body : never; requestIp?: string; + route: string; + response: Response; }; type PromiseOrValue = T | Promise; diff --git a/apps/meteor/app/api/server/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts index c12522b753a..3af8b8002b5 100644 --- a/apps/meteor/app/api/server/v1/oauthapps.ts +++ b/apps/meteor/app/api/server/v1/oauthapps.ts @@ -2,7 +2,6 @@ import type { IOAuthApps } from '@rocket.chat/core-typings'; import { OAuthApps } from '@rocket.chat/models'; import { ajv, - isOauthAppsGetParams, validateUnauthorizedErrorResponse, validateBadRequestErrorResponse, validateForbiddenErrorResponse, @@ -89,6 +88,45 @@ const UpdateOAuthAppParamsSchema = { const isUpdateOAuthAppParams = ajv.compile(UpdateOAuthAppParamsSchema); +type OauthAppsGetParams = { clientId: string } | { appId: string } | { _id: string }; + +const oauthAppsGetParamsSchema = { + oneOf: [ + { + type: 'object', + properties: { + _id: { + type: 'string', + }, + }, + required: ['_id'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + clientId: { + type: 'string', + }, + }, + required: ['clientId'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + appId: { + type: 'string', + }, + }, + required: ['appId'], + additionalProperties: false, + }, + ], +}; + +const isOauthAppsGetParams = ajv.compile(oauthAppsGetParamsSchema); + const oauthAppsEndpoints = API.v1 .get( 'oauth-apps.list', @@ -218,13 +256,31 @@ const oauthAppsEndpoints = API.v1 return API.v1.success(result); }, - ); + ) + .get( + 'oauth-apps.get', + { + authRequired: true, + query: isOauthAppsGetParams, + response: { + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 200: ajv.compile<{ oauthApp: IOAuthApps }>({ + type: 'object', + properties: { + oauthApp: { anyOf: [{ $ref: '#/components/schemas/IOAuthApps' }, { type: 'null' }] }, + success: { + type: 'boolean', + enum: [true], + }, + }, + required: ['oauthApp', 'success'], + additionalProperties: false, + }), + }, + }, -API.v1.addRoute( - 'oauth-apps.get', - { authRequired: true, validateParams: isOauthAppsGetParams }, - { - async get() { + async function action() { const isOAuthAppsManager = await hasPermissionAsync(this.userId, 'manage-oauth-apps'); const oauthApp = await OAuthApps.findOneAuthAppByIdOrClientId( @@ -244,8 +300,7 @@ API.v1.addRoute( oauthApp, }); }, - }, -); + ); export type OauthAppsEndpoints = ExtractRoutesFromAPI; diff --git a/apps/meteor/tests/end-to-end/api/oauthapps.ts b/apps/meteor/tests/end-to-end/api/oauthapps.ts index 39fa9442234..0832fb155d9 100644 --- a/apps/meteor/tests/end-to-end/api/oauthapps.ts +++ b/apps/meteor/tests/end-to-end/api/oauthapps.ts @@ -286,8 +286,9 @@ describe('[OAuthApps]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-params'); expect(res.body).to.have.property('error'); - expect(res.body.error).to.include('must be string').and.include('must match exactly one schema in oneOf [invalid-params]'); + expect(res.body.error).to.include('must be string').and.include('must match exactly one schema in oneOf'); }); }); @@ -311,8 +312,9 @@ describe('[OAuthApps]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-params'); expect(res.body).to.have.property('error'); - expect(res.body.error).to.include('must be string').and.include('must match exactly one schema in oneOf [invalid-params]'); + expect(res.body.error).to.include('must be string').and.include('must match exactly one schema in oneOf'); }); }); @@ -336,8 +338,9 @@ describe('[OAuthApps]', () => { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-params'); expect(res.body).to.have.property('error'); - expect(res.body.error).to.include('must be string').and.include('must match exactly one schema in oneOf [invalid-params]'); + expect(res.body.error).to.include('must be string').and.include('must match exactly one schema in oneOf'); }); }); diff --git a/packages/core-typings/src/IOAuthApps.ts b/packages/core-typings/src/IOAuthApps.ts index 3f7205c378f..435888a4629 100644 --- a/packages/core-typings/src/IOAuthApps.ts +++ b/packages/core-typings/src/IOAuthApps.ts @@ -3,7 +3,7 @@ export interface IOAuthApps { name: string; active: boolean; clientId: string; - clientSecret: string; + clientSecret?: string; redirectUri: string; _createdAt: Date; _createdBy: { diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 8198854729d..6b669d9b9b1 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -33,7 +33,6 @@ import type { MailerEndpoints } from './v1/mailer'; import type { MeEndpoints } from './v1/me'; import type { MiscEndpoints } from './v1/misc'; import type { ModerationEndpoints } from './v1/moderation'; -import type { OAuthAppsEndpoint } from './v1/oauthapps'; import type { OmnichannelEndpoints } from './v1/omnichannel'; import type { PresenceEndpoints } from './v1/presence'; import type { PushEndpoints } from './v1/push'; @@ -89,7 +88,6 @@ export interface Endpoints AssetsEndpoints, EmailInboxEndpoints, MailerEndpoints, - OAuthAppsEndpoint, SubscriptionsEndpoints, AutoTranslateEndpoints, ImportEndpoints, @@ -231,8 +229,6 @@ export * from './v1/dm/DmHistoryProps'; export * from './v1/integrations'; export * from './v1/licenses'; export * from './v1/omnichannel'; -export * from './v1/oauthapps'; -export * from './v1/oauthapps/OAuthAppsGetParamsGET'; export * from './helpers/PaginatedRequest'; export * from './helpers/PaginatedResult'; export * from './helpers/ReplacePlaceholders'; diff --git a/packages/rest-typings/src/v1/oauthapps.ts b/packages/rest-typings/src/v1/oauthapps.ts deleted file mode 100644 index 0fadcf16f78..00000000000 --- a/packages/rest-typings/src/v1/oauthapps.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { IOAuthApps } from '@rocket.chat/core-typings'; - -import type { OauthAppsGetParams } from './oauthapps/OAuthAppsGetParamsGET'; - -export type OAuthAppsEndpoint = { - '/v1/oauth-apps.get': { - GET: (params: OauthAppsGetParams) => { - oauthApp: IOAuthApps; - }; - }; -}; diff --git a/packages/rest-typings/src/v1/oauthapps/OAuthAppsGetParamsGET.ts b/packages/rest-typings/src/v1/oauthapps/OAuthAppsGetParamsGET.ts deleted file mode 100644 index d1c9a854701..00000000000 --- a/packages/rest-typings/src/v1/oauthapps/OAuthAppsGetParamsGET.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ajv } from '../Ajv'; - -export type OauthAppsGetParams = { clientId: string } | { appId: string } | { _id: string }; - -const oauthAppsGetParamsSchema = { - oneOf: [ - { - type: 'object', - properties: { - _id: { - type: 'string', - }, - }, - required: ['_id'], - additionalProperties: false, - }, - { - type: 'object', - properties: { - clientId: { - type: 'string', - }, - }, - required: ['clientId'], - additionalProperties: false, - }, - { - type: 'object', - properties: { - appId: { - type: 'string', - }, - }, - required: ['appId'], - additionalProperties: false, - }, - ], -}; - -export const isOauthAppsGetParams = ajv.compile(oauthAppsGetParamsSchema);