diff --git a/.changeset/clean-flies-glow.md b/.changeset/clean-flies-glow.md new file mode 100644 index 0000000000000..b8f3413d1530e --- /dev/null +++ b/.changeset/clean-flies-glow.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Add OpenAPI support for the Rocket.Chat oauth-apps.create 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/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts index 78ab03e19621c..2a611337d3500 100644 --- a/apps/meteor/app/api/server/v1/oauthapps.ts +++ b/apps/meteor/app/api/server/v1/oauthapps.ts @@ -1,11 +1,13 @@ +import type { IOAuthApps } from '@rocket.chat/core-typings'; import { OAuthApps } from '@rocket.chat/models'; -import { isUpdateOAuthAppParams, isOauthAppsGetParams, isOauthAppsAddParams, isDeleteOAuthAppParams } from '@rocket.chat/rest-typings'; +import { ajv, isUpdateOAuthAppParams, isOauthAppsGetParams, isDeleteOAuthAppParams } from '@rocket.chat/rest-typings'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { addOAuthApp } from '../../../oauth2-server-config/server/admin/functions/addOAuthApp'; import { deleteOAuthApp } from '../../../oauth2-server-config/server/admin/methods/deleteOAuthApp'; import { updateOAuthApp } from '../../../oauth2-server-config/server/admin/methods/updateOAuthApp'; +import type { ExtractRoutesFromAPI } from '../ApiClass'; import { API } from '../api'; API.v1.addRoute( @@ -83,18 +85,106 @@ API.v1.addRoute( }, ); -API.v1.addRoute( +export type OauthAppsAddParams = { + name: string; + active: boolean; + redirectUri: string; +}; + +const OauthAppsAddParamsSchema = { + type: 'object', + properties: { + name: { + type: 'string', + }, + active: { + type: 'boolean', + }, + redirectUri: { + type: 'string', + }, + }, + required: ['name', 'active', 'redirectUri'], + additionalProperties: false, +}; + +const isOauthAppsAddParams = ajv.compile(OauthAppsAddParamsSchema); + +const oauthAppsCreateEndpoints = API.v1.post( 'oauth-apps.create', { authRequired: true, - validateParams: isOauthAppsAddParams, + body: isOauthAppsAddParams, permissionsRequired: ['manage-oauth-apps'], + response: { + 400: ajv.compile<{ + error?: string; + errorType?: string; + stack?: string; + details?: object; + }>({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [false] }, + stack: { type: 'string' }, + error: { type: 'string' }, + errorType: { type: 'string' }, + details: { type: 'object' }, + }, + required: ['success'], + additionalProperties: false, + }), + 401: ajv.compile({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [false] }, + status: { type: 'string' }, + message: { type: 'string' }, + error: { type: 'string' }, + errorType: { type: 'string' }, + }, + required: ['success'], + additionalProperties: false, + }), + 403: ajv.compile({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [false] }, + status: { type: 'string' }, + message: { type: 'string' }, + error: { type: 'string' }, + errorType: { type: 'string' }, + }, + required: ['success'], + additionalProperties: false, + }), + 200: ajv.compile<{ application: IOAuthApps }>({ + type: 'object', + properties: { + application: { $ref: '#/components/schemas/IOAuthApps' }, + success: { + type: 'boolean', + enum: [true], + }, + }, + required: ['application', 'success'], + additionalProperties: false, + }), + }, }, - { - async post() { - const application = await addOAuthApp(this.bodyParams, this.userId); - return API.v1.success({ application }); - }, + async function action() { + const application = await addOAuthApp(this.bodyParams, this.userId); + + return API.v1.success({ application }); }, ); + +type OauthAppsCreateEndpoints = ExtractRoutesFromAPI; + +export type OAuthAppsEndpoints = OauthAppsCreateEndpoints; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends OauthAppsCreateEndpoints {} +} diff --git a/apps/meteor/app/oauth2-server-config/server/admin/functions/addOAuthApp.ts b/apps/meteor/app/oauth2-server-config/server/admin/functions/addOAuthApp.ts index 43bcd0ce14bd5..cba0ef59619cc 100644 --- a/apps/meteor/app/oauth2-server-config/server/admin/functions/addOAuthApp.ts +++ b/apps/meteor/app/oauth2-server-config/server/admin/functions/addOAuthApp.ts @@ -1,10 +1,10 @@ import type { IOAuthApps, IUser } from '@rocket.chat/core-typings'; import { OAuthApps, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; -import type { OauthAppsAddParams } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; import { parseUriList } from './parseUriList'; +import type { OauthAppsAddParams } from '../../../../api/server/v1/oauthapps'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; export async function addOAuthApp(applicationParams: OauthAppsAddParams, uid: IUser['_id'] | undefined): Promise { diff --git a/packages/core-typings/src/Ajv.ts b/packages/core-typings/src/Ajv.ts index 1e227ec4c3e22..fea0f7b8084ea 100644 --- a/packages/core-typings/src/Ajv.ts +++ b/packages/core-typings/src/Ajv.ts @@ -3,6 +3,7 @@ import typia from 'typia'; import type { ICustomSound } from './ICustomSound'; import type { IInvite } from './IInvite'; import type { IMessage } from './IMessage'; +import type { IOAuthApps } from './IOAuthApps'; import type { ISubscription } from './ISubscription'; -export const schemas = typia.json.schemas<[ISubscription | IInvite | ICustomSound | IMessage], '3.0'>(); +export const schemas = typia.json.schemas<[ISubscription | IInvite | ICustomSound | IMessage | IOAuthApps], '3.0'>(); diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 7ff9fdfb5bd08..6bad8905cb4b9 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -237,7 +237,6 @@ export * from './v1/omnichannel'; export * from './v1/oauthapps'; export * from './v1/oauthapps/UpdateOAuthAppParamsPOST'; export * from './v1/oauthapps/OAuthAppsGetParamsGET'; -export * from './v1/oauthapps/OAuthAppsAddParamsPOST'; export * from './v1/oauthapps/DeleteOAuthAppParamsDELETE'; export * from './helpers/PaginatedRequest'; export * from './helpers/PaginatedResult'; diff --git a/packages/rest-typings/src/v1/oauthapps.ts b/packages/rest-typings/src/v1/oauthapps.ts index b0b1e3fc4b1f0..2e42cf12c877b 100644 --- a/packages/rest-typings/src/v1/oauthapps.ts +++ b/packages/rest-typings/src/v1/oauthapps.ts @@ -1,7 +1,6 @@ import type { IOAuthApps, IUser } from '@rocket.chat/core-typings'; import type { DeleteOAuthAppParams } from './oauthapps/DeleteOAuthAppParamsDELETE'; -import type { OauthAppsAddParams } from './oauthapps/OAuthAppsAddParamsPOST'; import type { OauthAppsGetParams } from './oauthapps/OAuthAppsGetParamsGET'; import type { UpdateOAuthAppParams } from './oauthapps/UpdateOAuthAppParamsPOST'; @@ -18,10 +17,6 @@ export type OAuthAppsEndpoint = { }; }; - '/v1/oauth-apps.create': { - POST: (params: OauthAppsAddParams) => { application: IOAuthApps }; - }; - '/v1/oauth-apps.update': { POST: (params: UpdateOAuthAppParams) => IOAuthApps | null; }; diff --git a/packages/rest-typings/src/v1/oauthapps/OAuthAppsAddParamsPOST.ts b/packages/rest-typings/src/v1/oauthapps/OAuthAppsAddParamsPOST.ts deleted file mode 100644 index dab42d97d0a33..0000000000000 --- a/packages/rest-typings/src/v1/oauthapps/OAuthAppsAddParamsPOST.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ajv } from '../Ajv'; - -export type OauthAppsAddParams = { - name: string; - active: boolean; - redirectUri: string; -}; - -const OauthAppsAddParamsSchema = { - type: 'object', - properties: { - name: { - type: 'string', - }, - active: { - type: 'boolean', - }, - redirectUri: { - type: 'string', - }, - }, - required: ['name', 'active', 'redirectUri'], - additionalProperties: false, -}; - -export const isOauthAppsAddParams = ajv.compile(OauthAppsAddParamsSchema);