diff --git a/apps/meteor/app/api/server/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts index a9d48884595d0..1ea9175c03d8b 100644 --- a/apps/meteor/app/api/server/v1/oauthapps.ts +++ b/apps/meteor/app/api/server/v1/oauthapps.ts @@ -1,8 +1,9 @@ -import { isOauthAppsGetParams } from '@rocket.chat/rest-typings'; +import { isOauthAppsGetParams, isOauthAppsAddParams } from '@rocket.chat/rest-typings'; import { OAuthApps } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { API } from '../api'; +import { addOAuthApp } from '../../../oauth2-server-config/server/admin/methods/addOAuthApp'; API.v1.addRoute( 'oauth-apps.list', @@ -41,3 +42,18 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'oauth-apps.create', + { + authRequired: true, + validateParams: isOauthAppsAddParams, + }, + { + async post() { + const application = await addOAuthApp(this.bodyParams, this.userId); + + return API.v1.success({ application }); + }, + }, +); diff --git a/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.js b/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.js index d330da92ae329..2faf285d5fb86 100644 --- a/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.js +++ b/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.js @@ -1,44 +1,50 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import _ from 'underscore'; -import { OAuthApps } from '@rocket.chat/models'; +import { OAuthApps, Users } from '@rocket.chat/models'; -import { hasPermission } from '../../../../authorization'; -import { Users } from '../../../../models/server'; +import { hasPermission } from '../../../../authorization/server'; import { parseUriList } from '../functions/parseUriList'; +import { methodDeprecationLogger } from '../../../../lib/server/lib/deprecationWarningLogger'; -Meteor.methods({ - async addOAuthApp(application) { - if (!hasPermission(this.userId, 'manage-oauth-apps')) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'addOAuthApp' }); - } - if (!_.isString(application.name) || application.name.trim() === '') { - throw new Meteor.Error('error-invalid-name', 'Invalid name', { method: 'addOAuthApp' }); - } - if (!_.isString(application.redirectUri) || application.redirectUri.trim() === '') { - throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', { - method: 'addOAuthApp', - }); - } - if (!_.isBoolean(application.active)) { - throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', { - method: 'addOAuthApp', - }); - } +export async function addOAuthApp(application, uid) { + if (!hasPermission(uid, 'manage-oauth-apps')) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'addOAuthApp' }); + } + if (!_.isString(application.name) || application.name.trim() === '') { + throw new Meteor.Error('error-invalid-name', 'Invalid name', { method: 'addOAuthApp' }); + } + if (!_.isString(application.redirectUri) || application.redirectUri.trim() === '') { + throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', { + method: 'addOAuthApp', + }); + } + if (!_.isBoolean(application.active)) { + throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', { + method: 'addOAuthApp', + }); + } + + application.redirectUri = parseUriList(application.redirectUri); - application.redirectUri = parseUriList(application.redirectUri); + if (application.redirectUri.length === 0) { + throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', { + method: 'addOAuthApp', + }); + } - if (application.redirectUri.length === 0) { - throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', { - method: 'addOAuthApp', - }); - } + application.clientId = Random.id(); + application.clientSecret = Random.secret(); + application._createdAt = new Date(); + application._createdBy = await Users.findOne(uid, { projection: { username: 1 } }); + application._id = (await OAuthApps.insertOne(application)).insertedId; + return application; +} + +Meteor.methods({ + async addOAuthApp(application) { + methodDeprecationLogger.warn('addOAuthApp is deprecated and will be removed in future versions of Rocket.Chat'); - application.clientId = Random.id(); - application.clientSecret = Random.secret(); - application._createdAt = new Date(); - application._createdBy = Users.findOne(this.userId, { fields: { username: 1 } }); - application._id = (await OAuthApps.insertOne(application)).insertedId; - return application; + return addOAuthApp(application, this.userId); }, }); diff --git a/apps/meteor/client/views/admin/oauthApps/OAuthAddApp.tsx b/apps/meteor/client/views/admin/oauthApps/OAuthAddApp.tsx index baf08634ce897..cec7ac2d4f0e6 100644 --- a/apps/meteor/client/views/admin/oauthApps/OAuthAddApp.tsx +++ b/apps/meteor/client/views/admin/oauthApps/OAuthAddApp.tsx @@ -1,5 +1,5 @@ import { Button, ButtonGroup, TextInput, Field, TextAreaInput, ToggleSwitch, FieldGroup } from '@rocket.chat/fuselage'; -import { useToastMessageDispatch, useRoute, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useRoute, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, useCallback } from 'react'; import { useForm, SubmitHandler, Controller } from 'react-hook-form'; @@ -22,7 +22,7 @@ const OAuthAddApp = (): ReactElement => { control, } = useForm(); - const saveApp = useMethod('addOAuthApp'); + const saveApp = useEndpoint('POST', '/v1/oauth-apps.create'); const router = useRoute('admin-oauth-apps'); diff --git a/apps/meteor/tests/end-to-end/api/18-oauthapps.js b/apps/meteor/tests/end-to-end/api/18-oauthapps.js index ae9fe645ae022..25240f80bd75c 100644 --- a/apps/meteor/tests/end-to-end/api/18-oauthapps.js +++ b/apps/meteor/tests/end-to-end/api/18-oauthapps.js @@ -63,4 +63,102 @@ describe('[OAuthApps]', function () { .end(done); }); }); + + describe('[/oauth-apps.create]', function () { + it('should return an error when the user does not have the necessary permission', async function () { + await updatePermission('manage-oauth-apps', []); + + await request + .post(api('oauth-apps.create')) + .set(credentials) + .send({ + name: 'error', + redirectUri: 'error', + active: false, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + console.log('res.body ->', res.body); + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-not-allowed'); + }); + + await updatePermission('manage-oauth-apps', ['admin']); + }); + + it("should return an error when the 'name' property is invalid", async function () { + await request + .post(api('oauth-apps.create')) + .set(credentials) + .send({ + name: '', + redirectUri: 'error', + active: false, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-name'); + }); + }); + + it("should return an error when the 'redirectUri' property is invalid", async function () { + await request + .post(api('oauth-apps.create')) + .set(credentials) + .send({ + name: 'error', + redirectUri: '', + active: false, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-invalid-redirectUri'); + }); + }); + + it("should return an error when the 'active' property is not a boolean", async function () { + await request + .post(api('oauth-apps.create')) + .set(credentials) + .send({ + name: 'error', + redirectUri: 'error', + active: 'anything', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }); + }); + + it('should create an oauthApp', async function () { + const name = `new app ${Date.now()}`; + const redirectUri = 'http://localhost:3000'; + const active = true; + + await request + .post(api('oauth-apps.create')) + .set(credentials) + .send({ + name, + redirectUri, + active, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('application.name', name); + expect(res.body).to.have.nested.property('application.redirectUri', redirectUri); + expect(res.body).to.have.nested.property('application.active', active); + }); + }); + }); }); diff --git a/packages/rest-typings/src/v1/oauthapps.ts b/packages/rest-typings/src/v1/oauthapps.ts index 9dd2dfe3b984b..0dba788f5318a 100644 --- a/packages/rest-typings/src/v1/oauthapps.ts +++ b/packages/rest-typings/src/v1/oauthapps.ts @@ -7,20 +7,6 @@ const ajv = new Ajv({ export type OauthAppsGetParams = { clientId: string } | { appId: string }; -export type OAuthAppsEndpoint = { - '/v1/oauth-apps.list': { - GET: (params: { uid: IUser['_id'] }) => { - oauthApps: IOAuthApps[]; - }; - }; - - '/v1/oauth-apps.get': { - GET: (params: OauthAppsGetParams) => { - oauthApp: IOAuthApps; - }; - }; -}; - const oauthAppsGetParamsSchema = { oneOf: [ { @@ -47,3 +33,46 @@ const oauthAppsGetParamsSchema = { }; export const isOauthAppsGetParams = ajv.compile(oauthAppsGetParamsSchema); + +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); + +export type OAuthAppsEndpoint = { + '/v1/oauth-apps.list': { + GET: (params: { uid: IUser['_id'] }) => { + oauthApps: IOAuthApps[]; + }; + }; + + '/v1/oauth-apps.get': { + GET: (params: OauthAppsGetParams) => { + oauthApp: IOAuthApps; + }; + }; + + '/v1/oauth-apps.create': { + POST: (params: OauthAppsAddParams) => { application: IOAuthApps }; + }; +};