-
Notifications
You must be signed in to change notification settings - Fork 13.3k
chore: migrate user.info endpoint to new OpenAPI pattern with AJV validations #39239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
0cfda21
e1347e7
f1b6cdd
9a9c475
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| chore: migrate users.info endpoint to new OpenAPI pattern with AJV validation | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,10 @@ | ||
| import { MeteorError, Team, api, Calendar } from '@rocket.chat/core-services'; | ||
| import type { IExportOperation, ILoginToken, IPersonalAccessToken, IUser, UserStatus } from '@rocket.chat/core-typings'; | ||
| import type { IExportOperation, ILoginToken, ISubscription, IPersonalAccessToken, IUser, UserStatus } from '@rocket.chat/core-typings'; | ||
| import { Users, Subscriptions, Sessions } from '@rocket.chat/models'; | ||
| import { | ||
| isUserCreateParamsPOST, | ||
| isUserSetActiveStatusParamsPOST, | ||
| isUserDeactivateIdleParamsPOST, | ||
| isUsersInfoParamsGetProps, | ||
| isUsersListStatusProps, | ||
| isUsersSendWelcomeEmailProps, | ||
| isUserRegisterParamsPOST, | ||
|
|
@@ -426,55 +425,6 @@ API.v1.addRoute( | |
| }, | ||
| ); | ||
|
|
||
| API.v1.addRoute( | ||
| 'users.info', | ||
| { authRequired: true, validateParams: isUsersInfoParamsGetProps }, | ||
| { | ||
| async get() { | ||
| const searchTerms: [string, 'id' | 'username' | 'importId'] | false = | ||
| ('userId' in this.queryParams && !!this.queryParams.userId && [this.queryParams.userId, 'id']) || | ||
| ('username' in this.queryParams && !!this.queryParams.username && [this.queryParams.username, 'username']) || | ||
| ('importId' in this.queryParams && !!this.queryParams.importId && [this.queryParams.importId, 'importId']); | ||
|
|
||
| if (!searchTerms) { | ||
| return API.v1.failure('Invalid search query.'); | ||
| } | ||
|
|
||
| const user = await getFullUserDataByIdOrUsernameOrImportId(this.userId, ...searchTerms); | ||
|
|
||
| if (!user) { | ||
| return API.v1.failure('User not found.'); | ||
| } | ||
| const myself = user._id === this.userId; | ||
| if (this.queryParams.includeUserRooms === 'true' && (myself || (await hasPermissionAsync(this.userId, 'view-other-user-channels')))) { | ||
| return API.v1.success({ | ||
| user: { | ||
| ...user, | ||
| rooms: await Subscriptions.findByUserId(user._id, { | ||
| projection: { | ||
| rid: 1, | ||
| name: 1, | ||
| t: 1, | ||
| roles: 1, | ||
| unread: 1, | ||
| federated: 1, | ||
| }, | ||
| sort: { | ||
| t: 1, | ||
| name: 1, | ||
| }, | ||
| }).toArray(), | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| return API.v1.success({ | ||
| user, | ||
| }); | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| API.v1.addRoute( | ||
| 'users.list', | ||
| { | ||
|
|
@@ -752,6 +702,69 @@ API.v1.addRoute( | |
| }, | ||
| ); | ||
|
|
||
| type UsersInfoParamsGet = ({ userId: string } | { username: string } | { importId: string }) & { | ||
| fields?: string; | ||
| includeUserRooms?: string; | ||
| }; | ||
|
|
||
| const UsersInfoParamsGetSchema = { | ||
| anyOf: [ | ||
| { | ||
| type: 'object', | ||
| properties: { | ||
| userId: { | ||
| type: 'string', | ||
| }, | ||
| includeUserRooms: { | ||
| type: 'string', | ||
| }, | ||
| fields: { | ||
| type: 'string', | ||
| nullable: true, | ||
| }, | ||
| }, | ||
| required: ['userId'], | ||
| additionalProperties: false, | ||
| }, | ||
| { | ||
| type: 'object', | ||
| properties: { | ||
| username: { | ||
| type: 'string', | ||
| }, | ||
| includeUserRooms: { | ||
| type: 'string', | ||
| }, | ||
| fields: { | ||
| type: 'string', | ||
| nullable: true, | ||
| }, | ||
| }, | ||
| required: ['username'], | ||
| additionalProperties: false, | ||
| }, | ||
| { | ||
| type: 'object', | ||
| properties: { | ||
| importId: { | ||
| type: 'string', | ||
| }, | ||
| includeUserRooms: { | ||
| type: 'string', | ||
| }, | ||
| fields: { | ||
| type: 'string', | ||
| nullable: true, | ||
| }, | ||
| }, | ||
| required: ['importId'], | ||
| additionalProperties: false, | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| const isUsersInfoParamsGetProps = ajv.compile<UsersInfoParamsGet>(UsersInfoParamsGetSchema); | ||
|
|
||
| const usersEndpoints = API.v1 | ||
| .post( | ||
| 'users.createToken', | ||
|
|
@@ -877,7 +890,99 @@ const usersEndpoints = API.v1 | |
|
|
||
| return API.v1.success({ suggestions }); | ||
| }, | ||
| ); | ||
| ) | ||
| .get( | ||
| 'users.info', | ||
| { | ||
| authRequired: true, | ||
| query: isUsersInfoParamsGetProps, | ||
| response: { | ||
| 400: validateBadRequestErrorResponse, | ||
| 401: validateUnauthorizedErrorResponse, | ||
|
|
||
| 200: ajv.compile<{ | ||
| user: IUser & { rooms?: Pick<ISubscription, 'rid' | 'name' | 't' | 'roles' | 'unread'> & { federated?: boolean }[] }; | ||
| success: true; | ||
| }>({ | ||
| type: 'object', | ||
| properties: { | ||
| user: { | ||
| allOf: [ | ||
| { $ref: '#/components/schemas/IUser' }, | ||
| { | ||
| type: 'object', | ||
| properties: { | ||
| rooms: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'object', | ||
| properties: { | ||
| rid: { type: 'string' }, | ||
| name: { type: 'string' }, | ||
| t: { type: 'string' }, | ||
| roles: { type: 'array', items: { type: 'string' } }, | ||
| unread: { type: 'number' }, | ||
| federated: { type: 'boolean' }, | ||
| }, | ||
| required: ['rid', 't', 'unread', 'name'], | ||
| additionalProperties: false, | ||
| }, | ||
| }, | ||
| }, | ||
| additionalProperties: true, | ||
| }, | ||
| ], | ||
| }, | ||
| success: { type: 'boolean', enum: [true] }, | ||
| }, | ||
| required: ['user', 'success'], | ||
| additionalProperties: false, | ||
| }), | ||
|
Comment on lines
+903
to
+940
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to review your JSON schema because it doesn't match the TypeScript definition correctly. The current TypeScript definition: user: IUser & {
rooms?: (Pick<ISubscription, 'rid' | 'name' | 't' | 'roles' | 'unread'> & { federated?: boolean[] })[]
}The schema should reflect this structure strictly. Additionally, we must avoid using additionalProperties: true to ensure the schema remains strict and prevents the injection of unexpected data |
||
| }, | ||
| }, | ||
| async function action() { | ||
| const searchTerms: [string, 'id' | 'username' | 'importId'] | false = | ||
| ('userId' in this.queryParams && !!this.queryParams.userId && [this.queryParams.userId, 'id']) || | ||
| ('username' in this.queryParams && !!this.queryParams.username && [this.queryParams.username, 'username']) || | ||
| ('importId' in this.queryParams && !!this.queryParams.importId && [this.queryParams.importId, 'importId']); | ||
|
|
||
| if (!searchTerms) { | ||
| return API.v1.failure('Invalid search query.'); | ||
| } | ||
|
|
||
| const user = await getFullUserDataByIdOrUsernameOrImportId(this.userId, ...searchTerms); | ||
|
|
||
| if (!user) { | ||
| return API.v1.failure('User not found.'); | ||
| } | ||
| const myself = user._id === this.userId; | ||
| if (this.queryParams.includeUserRooms === 'true' && (myself || (await hasPermissionAsync(this.userId, 'view-other-user-channels')))) { | ||
| return API.v1.success({ | ||
| user: { | ||
| ...user, | ||
| rooms: await Subscriptions.findByUserId(user._id, { | ||
| projection: { | ||
| rid: 1, | ||
| name: 1, | ||
| t: 1, | ||
| roles: 1, | ||
| unread: 1, | ||
| federated: 1, | ||
| }, | ||
| sort: { | ||
| t: 1, | ||
| name: 1, | ||
| }, | ||
| }).toArray(), | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| return API.v1.success({ | ||
| user, | ||
| }); | ||
| }, | ||
| ); | ||
|
|
||
| API.v1.addRoute( | ||
| 'users.getPreferences', | ||
|
|
@@ -1555,7 +1660,6 @@ settings.watch<number>('Rate_Limiter_Limit_RegisterUser', (value) => { | |
| }); | ||
|
|
||
| type UsersEndpoints = ExtractRoutesFromAPI<typeof usersEndpoints>; | ||
|
|
||
| declare module '@rocket.chat/rest-typings' { | ||
| // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface | ||
| interface Endpoints extends UsersEndpoints {} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,6 @@ import type { UserLogoutParamsPOST } from './users/UserLogoutParamsPOST'; | |
| import type { UserRegisterParamsPOST } from './users/UserRegisterParamsPOST'; | ||
| import type { UserSetActiveStatusParamsPOST } from './users/UserSetActiveStatusParamsPOST'; | ||
| import type { UsersAutocompleteParamsGET } from './users/UsersAutocompleteParamsGET'; | ||
| import type { UsersInfoParamsGet } from './users/UsersInfoParamsGet'; | ||
| import type { UsersListStatusParamsGET } from './users/UsersListStatusParamsGET'; | ||
| import type { UsersListTeamsParamsGET } from './users/UsersListTeamsParamsGET'; | ||
| import type { UsersSendConfirmationEmailParamsPOST } from './users/UsersSendConfirmationEmailParamsPOST'; | ||
|
|
@@ -18,26 +17,11 @@ import type { UsersSetPreferencesParamsPOST } from './users/UsersSetPreferencePa | |
| import type { UsersUpdateOwnBasicInfoParamsPOST } from './users/UsersUpdateOwnBasicInfoParamsPOST'; | ||
| import type { UsersUpdateParamsPOST } from './users/UsersUpdateParamsPOST'; | ||
|
|
||
| type UsersInfo = { userId?: IUser['_id']; username?: IUser['username'] }; | ||
|
|
||
| const UsersInfoSchema = { | ||
| type: 'object', | ||
| properties: { | ||
| userId: { | ||
| type: 'string', | ||
| nullable: true, | ||
| }, | ||
| username: { | ||
| type: 'string', | ||
| nullable: true, | ||
| }, | ||
| }, | ||
| required: [], | ||
| additionalProperties: false, | ||
| type UsersInfoParamsGet = ({ userId: string } | { username: string } | { importId: string }) & { | ||
| fields?: string; | ||
| includeUserRooms?: string; | ||
| }; | ||
|
|
||
| export const isUsersInfoProps = ajv.compile<UsersInfo>(UsersInfoSchema); | ||
|
|
||
| type Users2faSendEmailCode = { emailOrUsername: string }; | ||
|
|
||
| const Users2faSendEmailCodeSchema = { | ||
|
|
@@ -271,6 +255,12 @@ export type UsersEndpoints = { | |
| }; | ||
| }; | ||
|
|
||
| '/v1/users.info': { | ||
| GET: (params: UsersInfoParamsGet) => { | ||
| user: IUser & { rooms?: Pick<ISubscription, 'rid' | 'name' | 't' | 'roles' | 'unread'>[] }; | ||
| }; | ||
| }; | ||
|
|
||
| '/v1/users.create': { | ||
| POST: (params: UserCreateParamsPOST) => { | ||
| user: IUser; | ||
|
|
@@ -327,11 +317,6 @@ export type UsersEndpoints = { | |
| }; | ||
| }; | ||
|
|
||
| '/v1/users.info': { | ||
| GET: (params: UsersInfoParamsGet) => { | ||
| user: IUser & { rooms?: Pick<ISubscription, 'rid' | 'name' | 't' | 'roles' | 'unread'>[] }; | ||
| }; | ||
| }; | ||
|
Comment on lines
-330
to
-334
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should return this code should solve the problem you're having with the legacy SDK.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I return this block of code,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the PR #38913 that I previously mentioned to you
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
you should also return the UserInfoParamsGet to solve the error |
||
|
|
||
| '/v1/users.register': { | ||
| POST: (params: UserRegisterParamsPOST) => { | ||
|
|
@@ -373,7 +358,6 @@ export type UsersEndpoints = { | |
| export * from './users/UserCreateParamsPOST'; | ||
| export * from './users/UserSetActiveStatusParamsPOST'; | ||
| export * from './users/UserDeactivateIdleParamsPOST'; | ||
| export * from './users/UsersInfoParamsGet'; | ||
| export * from './users/UsersListStatusParamsGET'; | ||
| export * from './users/UsersSendWelcomeEmailParamsPOST'; | ||
| export * from './users/UserRegisterParamsPOST'; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use minor instead of patch for this project