Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/tall-scissors-boil.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions apps/meteor/app/api/server/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ export type TypedThis<TOptions extends TypedOptions, TPath extends string = ''>
bodyParams: TOptions['body'] extends ValidateFunction<infer Body> ? Body : never;

requestIp?: string;
route: string;
response: Response;
Comment on lines +306 to +307
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was missing in the typedThis, so I added it.

};

type PromiseOrValue<T> = T | Promise<T>;
Expand Down
73 changes: 64 additions & 9 deletions apps/meteor/app/api/server/v1/oauthapps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { IOAuthApps } from '@rocket.chat/core-typings';
import { OAuthApps } from '@rocket.chat/models';
import {
ajv,
isOauthAppsGetParams,
validateUnauthorizedErrorResponse,
validateBadRequestErrorResponse,
validateForbiddenErrorResponse,
Expand Down Expand Up @@ -89,6 +88,45 @@ const UpdateOAuthAppParamsSchema = {

const isUpdateOAuthAppParams = ajv.compile<UpdateOAuthAppParams>(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<OauthAppsGetParams>(oauthAppsGetParamsSchema);

const oauthAppsEndpoints = API.v1
.get(
'oauth-apps.list',
Expand Down Expand Up @@ -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(
Expand All @@ -244,8 +300,7 @@ API.v1.addRoute(
oauthApp,
});
},
},
);
);

export type OauthAppsEndpoints = ExtractRoutesFromAPI<typeof oauthAppsEndpoints>;

Expand Down
9 changes: 6 additions & 3 deletions apps/meteor/tests/end-to-end/api/oauthapps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Comment on lines +289 to +291
Copy link
Contributor Author

@ahmed-n-abdeltwab ahmed-n-abdeltwab Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error was must match exactly one schema in oneOf but didn’t include [invalid-params], so I added a separate assertion for errorType instead.

});
});

Expand All @@ -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');
});
});

Expand All @@ -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');
});
});

Expand Down
2 changes: 1 addition & 1 deletion packages/core-typings/src/IOAuthApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export interface IOAuthApps {
name: string;
active: boolean;
clientId: string;
clientSecret: string;
clientSecret?: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IOAuthApps used in findOneAuthAppByIdOrClientId doesn’t include clientSecret, so I made it optional in the interface

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

packages/models/src/models/OAuthApps.ts
findOneAuthAppByIdOrClientId(
		props: { clientId: string } | { appId: string } | { _id: string },
		options?: FindOptions<IOAuthApps>,
	): Promise<IOAuthApps | null> {
		return this.findOne(
			{
				...('_id' in props && { _id: props._id }),
				...('appId' in props && { _id: props.appId }),
				...('clientId' in props && { clientId: props.clientId }),
			},
			options,
		);
	}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this change, AJV would throw an error saying clientSecret is required, even though it's not returned

redirectUri: string;
_createdAt: Date;
_createdBy: {
Expand Down
4 changes: 0 additions & 4 deletions packages/rest-typings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -89,7 +88,6 @@ export interface Endpoints
AssetsEndpoints,
EmailInboxEndpoints,
MailerEndpoints,
OAuthAppsEndpoint,
SubscriptionsEndpoints,
AutoTranslateEndpoints,
ImportEndpoints,
Expand Down Expand Up @@ -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';
Expand Down
11 changes: 0 additions & 11 deletions packages/rest-typings/src/v1/oauthapps.ts

This file was deleted.

40 changes: 0 additions & 40 deletions packages/rest-typings/src/v1/oauthapps/OAuthAppsGetParamsGET.ts

This file was deleted.

Loading