Skip to content
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

Chore: validateParams to accept different validators per request method #26357

Merged
merged 9 commits into from
Aug 1, 2022
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type Options = (
twoFactorOptions?: ITwoFactorOptions;
}
) & {
validateParams?: ValidateFunction;
validateParams?: ValidateFunction | { [key in Method]?: ValidateFunction };
ggazzo marked this conversation as resolved.
Show resolved Hide resolved
authOrAnonRequired?: true;
};

Expand Down
11 changes: 9 additions & 2 deletions apps/meteor/app/api/server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,16 @@ export class APIClass extends Restivus {
try {
api.enforceRateLimit(objectForRateLimitMatch, this.request, this.response, this.userId);

if (_options.validateParams && !_options.validateParams(this.request.method === 'GET' ? this.queryParams : this.bodyParams)) {
throw new Meteor.Error('invalid-params', _options.validateParams.errors?.map((error) => error.message).join('\n '));
if (_options.validateParams) {
const requestMethod = this.request.method;
const validatorFunc =
typeof _options.validateParams === 'function' ? _options.validateParams : _options.validateParams[requestMethod];

if (validatorFunc && !validatorFunc(requestMethod === 'GET' ? this.queryParams : this.bodyParams)) {
throw new Meteor.Error('invalid-params', _options.validateParams.errors?.map((error) => error.message).join('\n '));
}
}

if (shouldVerifyPermissions && (!this.userId || !hasAllPermission(this.userId, _options.permissionsRequired))) {
throw new Meteor.Error('error-unauthorized', 'User does not have the permissions required for this action', {
permissions: _options.permissionsRequired,
Expand Down
62 changes: 28 additions & 34 deletions apps/meteor/app/livechat/imports/server/rest/departments.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isLivechatDepartmentProps } from '@rocket.chat/rest-typings';
import { isGETLivechatDepartmentProps, isPOSTLivechatDepartmentProps } from '@rocket.chat/rest-typings';
import { Match, check } from 'meteor/check';

import { API } from '../../../../api/server';
Expand All @@ -15,7 +15,7 @@ import {

API.v1.addRoute(
'livechat/department',
{ authRequired: true, validateParams: isLivechatDepartmentProps },
{ authRequired: true, validateParams: { GET: isGETLivechatDepartmentProps, POST: isPOSTLivechatDepartmentProps } },
{
async get() {
if (!hasAtLeastOnePermission(this.userId, ['view-livechat-departments', 'view-l-room'])) {
Expand All @@ -27,22 +27,20 @@ API.v1.addRoute(

const { text, enabled, onlyMyDepartments, excludeDepartmentId } = this.queryParams;

const { departments, total } = Promise.await(
findDepartments({
userId: this.userId,
text,
enabled: enabled === 'true',
onlyMyDepartments: onlyMyDepartments === 'true',
excludeDepartmentId,
pagination: {
offset,
count,
// IMO, sort type shouldn't be record, but a generic of the model we're trying to sort
// or the form { [k: keyof T]: number | string }
sort: sort as any,
},
}),
);
const { departments, total } = await findDepartments({
userId: this.userId,
text,
enabled: enabled === 'true',
onlyMyDepartments: onlyMyDepartments === 'true',
excludeDepartmentId,
pagination: {
offset,
count,
// IMO, sort type shouldn't be record, but a generic of the model we're trying to sort
// or the form { [k: keyof T]: number | string }
sort: sort as any,
},
});

return API.v1.success({ departments, count: departments.length, offset, total });
},
Expand Down Expand Up @@ -170,20 +168,18 @@ API.v1.addRoute(
'livechat/department.autocomplete',
{ authRequired: true },
{
get() {
async get() {
const { selector, onlyMyDepartments } = this.queryParams;
if (!selector) {
return API.v1.failure("The 'selector' param is required");
}

return API.v1.success(
Promise.await(
findDepartmentsToAutocomplete({
uid: this.userId,
selector: JSON.parse(selector),
onlyMyDepartments: onlyMyDepartments === 'true',
}),
),
await findDepartmentsToAutocomplete({
uid: this.userId,
selector: JSON.parse(selector),
onlyMyDepartments: onlyMyDepartments === 'true',
}),
);
},
},
Expand Down Expand Up @@ -239,7 +235,7 @@ API.v1.addRoute(
'livechat/department.listByIds',
{ authRequired: true },
{
get() {
async get() {
const { ids } = this.queryParams;
const { fields } = this.parseJsonQuery();
if (!ids) {
Expand All @@ -250,13 +246,11 @@ API.v1.addRoute(
}

return API.v1.success(
Promise.await(
findDepartmentsBetweenIds({
uid: this.userId,
ids,
fields,
}),
),
await findDepartmentsBetweenIds({
uid: this.userId,
ids,
fields,
}),
);
},
},
Expand Down
75 changes: 74 additions & 1 deletion packages/rest-typings/src/v1/omnichannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,80 @@ const LivechatDepartmentSchema = {
additionalProperties: false,
};

export const isLivechatDepartmentProps = ajv.compile<LivechatDepartmentProps>(LivechatDepartmentSchema);
export const isGETLivechatDepartmentProps = ajv.compile<LivechatDepartmentProps>(LivechatDepartmentSchema);

type POSTLivechatDepartmentProps = {
department: {
enabled: boolean;
name: string;
email: string;
description?: string;
showOnRegistration: boolean;
showOnOfflineForm: boolean;
requestTagsBeforeClosingChat?: boolean;
chatClosingTags?: string[];
fallbackForwardDepartment?: string;
};
agents: string[];
};

const POSTLivechatDepartmentSchema = {
type: 'object',
properties: {
department: {
type: 'object',
properties: {
enabled: {
type: 'boolean',
},
name: {
type: 'string',
},
description: {
type: 'string',
nullable: true,
},
showOnRegistration: {
type: 'boolean',
},
showOnOfflineForm: {
type: 'boolean',
},
requestTagsBeforeClosingChat: {
type: 'boolean',
nullable: true,
},
chatClosingTags: {
type: 'array',
items: {
type: 'string',
},
nullable: true,
},
fallbackForwardDepartment: {
type: 'string',
nullable: true,
},
email: {
type: 'string',
},
},
required: ['name', 'email', 'enabled', 'showOnRegistration', 'showOnOfflineForm'],
additionalProperties: true,
},
agents: {
type: 'array',
items: {
type: 'string',
},
nullable: true,
},
},
required: ['department'],
additionalProperties: false,
};

export const isPOSTLivechatDepartmentProps = ajv.compile<POSTLivechatDepartmentProps>(POSTLivechatDepartmentSchema);

type LivechatDepartmentsAvailableByUnitIdProps = PaginatedRequest<{ text: string; onlyMyDepartments?: 'true' | 'false' }>;

Expand Down