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

[NEW] Add new API endpoints #8947

Merged
merged 13 commits into from
Dec 6, 2017
Merged
1 change: 1 addition & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ accounts-twitter@1.4.0
blaze-html-templates
check@1.2.5
ddp-rate-limiter@1.0.7
ddp-common
dynamic-import@0.2.0
ecmascript@0.9.0
ejson@1.1.0
Expand Down
3 changes: 3 additions & 0 deletions packages/rocketchat-api/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ Package.onUse(function(api) {

//Add v1 routes
api.addFiles('server/v1/channels.js', 'server');
api.addFiles('server/v1/rooms.js', 'server');
api.addFiles('server/v1/subscriptions.js', 'server');
api.addFiles('server/v1/chat.js', 'server');
api.addFiles('server/v1/commands.js', 'server');
api.addFiles('server/v1/groups.js', 'server');
api.addFiles('server/v1/im.js', 'server');
api.addFiles('server/v1/integrations.js', 'server');
api.addFiles('server/v1/misc.js', 'server');
api.addFiles('server/v1/push.js', 'server');
api.addFiles('server/v1/settings.js', 'server');
api.addFiles('server/v1/stats.js', 'server');
api.addFiles('server/v1/users.js', 'server');
Expand Down
167 changes: 165 additions & 2 deletions packages/rocketchat-api/server/api.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global Restivus */
/* global Restivus, DDP, DDPCommon */
import _ from 'underscore';

class API extends Restivus {
Expand All @@ -13,6 +13,7 @@ class API extends Restivus {
$loki: 0,
meta: 0,
members: 0,
usernames: 0, // Please use the `channel/dm/group.members` endpoint. This is disabled for performance reasons
importIds: 0
};
this.limitedUserFieldsToExclude = {
Expand All @@ -31,7 +32,7 @@ class API extends Restivus {
customFields: 0
};

this._config.defaultOptionsEndpoint = function() {
this._config.defaultOptionsEndpoint = function _defaultOptionsEndpoint() {
if (this.request.method === 'OPTIONS' && this.request.headers['access-control-request-method']) {
if (RocketChat.settings.get('API_Enable_CORS') === true) {
this.response.writeHead(200, {
Expand All @@ -57,6 +58,8 @@ class API extends Restivus {
success(result={}) {
if (_.isObject(result)) {
result.success = true;
// TODO: Remove this after three versions have been released. That means at 0.64 this should be gone. ;)
result.developerWarning = '[WARNING]: The "usernames" field has been removed for performance reasons. Please use the "*.members" endpoint to get a list of members/users in a room.';
}

return {
Expand Down Expand Up @@ -96,6 +99,16 @@ class API extends Restivus {
};
}

notFound(msg) {
return {
statusCode: 404,
body: {
success: false,
error: msg ? msg : 'Nothing was found'
}
};
}

addRoute(routes, options, endpoints) {
//Note: required if the developer didn't provide options
if (typeof endpoints === 'undefined') {
Expand Down Expand Up @@ -143,8 +156,158 @@ class API extends Restivus {
super.addRoute(route, options, endpoints);
});
}

_initAuth() {
const loginCompatibility = (bodyParams) => {
// Grab the username or email that the user is logging in with
const {user, username, email, password, code} = bodyParams;
const auth = {
password
};

if (typeof user === 'string') {
auth.user = user.includes('@') ? {email: user} : {username: user};
} else if (username) {
auth.user = {username};
} else if (email) {
auth.user = {email};
}

if (auth.user == null) {
return bodyParams;
}

if (auth.password && auth.password.hashed) {
auth.password = {
digest: auth.password,
algorithm: 'sha-256'
};
}

if (code) {
return {
totp: {
code,
login: auth
}
};
}

return auth;
};

const self = this;

this.addRoute('login', {authRequired: false}, {
post() {
const args = loginCompatibility(this.bodyParams);

const invocation = new DDPCommon.MethodInvocation({
connection: {}
});

let auth;
try {
auth = DDP._CurrentInvocation.withValue(invocation, () => Meteor.call('login', args));
} catch (error) {
let e = error;
if (error.reason === 'User not found') {
e = {
error: 'Unauthorized',
reason: 'Unauthorized'
};
}

return {
statusCode: 401,
body: {
status: 'error',
error: e.error,
message: e.reason || e.message
}
};
}

this.user = Meteor.users.findOne({
_id: auth.id
});

this.userId = this.user._id;

const response = {
status: 'success',
data: {
userId: this.userId,
authToken: auth.token,
tokenExpires: auth.tokenExpires
}
};

const extraData = self._config.onLoggedIn && self._config.onLoggedIn.call(this);

if (extraData != null) {
_.extend(response.data, {
extra: extraData
});
}

return response;
}
});

const logout = function() {
// Remove the given auth token from the user's account
const authToken = this.request.headers['x-auth-token'];
const hashedToken = Accounts._hashLoginToken(authToken);
const tokenLocation = self._config.auth.token;
const index = tokenLocation.lastIndexOf('.');
const tokenPath = tokenLocation.substring(0, index);
const tokenFieldName = tokenLocation.substring(index + 1);
const tokenToRemove = {};
tokenToRemove[tokenFieldName] = hashedToken;
const tokenRemovalQuery = {};
tokenRemovalQuery[tokenPath] = tokenToRemove;

Meteor.users.update(this.user._id, {
$pull: tokenRemovalQuery
});

const response = {
status: 'success',
data: {
message: 'You\'ve been logged out!'
}
};

// Call the logout hook with the authenticated user attached
const extraData = self._config.onLoggedOut && self._config.onLoggedOut.call(this);
if (extraData != null) {
_.extend(response.data, {
extra: extraData
});
}
return response;
};

/*
Add a logout endpoint to the API
After the user is logged out, the onLoggedOut hook is called (see Restfully.configure() for
adding hook).
*/
return this.addRoute('logout', {
authRequired: true
}, {
get() {
console.warn('Warning: Default logout via GET will be removed in Restivus v1.0. Use POST instead.');
console.warn(' See https://github.com/kahmali/meteor-restivus/issues/100');
return logout.call(this);
},
post: logout
});
}
}


RocketChat.API = {};

const getUserAuth = function _getUserAuth() {
Expand Down
73 changes: 62 additions & 11 deletions packages/rocketchat-api/server/v1/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,48 @@ RocketChat.API.v1.addRoute('chat.delete', { authRequired: true }, {
return RocketChat.API.v1.failure('The room id provided does not match where the message is from.');
}

if (this.bodyParams.asUser && msg.u._id !== this.userId && !RocketChat.authz.hasPermission(Meteor.userId(), 'force-delete-message', msg.rid)) {
return RocketChat.API.v1.failure('Unauthorized. You must have the permission "force-delete-message" to delete other\'s message as them.');
}

Meteor.runAsUser(this.bodyParams.asUser ? msg.u._id : this.userId, () => {
Meteor.call('deleteMessage', { _id: msg._id });
});

return RocketChat.API.v1.success({
_id: msg._id,
ts: Date.now()
ts: Date.now(),
message: msg
});
}
});

RocketChat.API.v1.addRoute('chat.syncMessages', { authRequired: true }, {
get() {
const { rid } = this.queryParams;
let lastUpdate = this.queryParams;
lastUpdate = lastUpdate ? new Date(lastUpdate) : lastUpdate;
if (!rid) {
return RocketChat.API.v1.failure('The "rid" query parameter must be provided.');
const { roomId, lastUpdate } = this.queryParams;

if (!roomId) {
throw new Meteor.Error('error-roomId-param-not-provided', 'The required "roomId" query param is missing.');
}

if (!lastUpdate) {
return RocketChat.API.v1.failure('The "lastUpdate" query parameter must be provided.');
throw new Meteor.Error('error-lastUpdate-param-not-provided', 'The required "lastUpdate" query param is missing.');
} else if (isNaN(Date.parse(lastUpdate))) {
throw new Meteor.Error('error-roomId-param-invalid', 'The "lastUpdate" query parameter must be a valid date.');
}

let result;
Meteor.runAsUser(this.userId, () => {
result = Meteor.call('messages/get', rid, { lastUpdate });
result = Meteor.call('messages/get', roomId, { lastUpdate: new Date(lastUpdate) });
});

if (!result) {
return RocketChat.API.v1.failure();
}

return RocketChat.API.v1.success({result});
return RocketChat.API.v1.success({
result
});
}
});

Expand All @@ -59,7 +68,6 @@ RocketChat.API.v1.addRoute('chat.getMessage', { authRequired: true }, {
return RocketChat.API.v1.failure('The "msgId" query parameter must be provided.');
}


let msg;
Meteor.runAsUser(this.userId, () => {
msg = Meteor.call('getSingleMessage', this.queryParams.msgId);
Expand All @@ -78,7 +86,7 @@ RocketChat.API.v1.addRoute('chat.getMessage', { authRequired: true }, {
RocketChat.API.v1.addRoute('chat.pinMessage', { authRequired: true }, {
post() {
if (!this.bodyParams.messageId || !this.bodyParams.messageId.trim()) {
throw new Meteor.Error('error-messageid-param-not-provided', 'The required "messageId" param is required.');
throw new Meteor.Error('error-messageid-param-not-provided', 'The required "messageId" param is missing.');
}

const msg = RocketChat.models.Messages.findOneById(this.bodyParams.messageId);
Expand Down Expand Up @@ -112,6 +120,49 @@ RocketChat.API.v1.addRoute('chat.postMessage', { authRequired: true }, {
}
});

RocketChat.API.v1.addRoute('chat.search', { authRequired: true }, {
get() {
const { roomId, searchText, limit } = this.queryParams;

if (!roomId) {
throw new Meteor.Error('error-roomId-param-not-provided', 'The required "roomId" query param is missing.');
}

if (!searchText) {
throw new Meteor.Error('error-searchText-param-not-provided', 'The required "searchText" query param is missing.');
}

if (limit && (typeof limit !== 'number' || isNaN(limit) || limit <= 0)) {
throw new Meteor.Error('error-limit-param-invalid', 'The "limit" query parameter must be a valid number and be greater than 0.');
}

let result;
Meteor.runAsUser(this.userId, () => result = Meteor.call('messageSearch', searchText, roomId, limit));

return RocketChat.API.v1.success({
messages: result.messages
});
}
});

// The difference between `chat.postMessage` and `chat.sendMessage` is that `chat.sendMessage` allows
// for passing a value for `_id` and the other one doesn't. Also, `chat.sendMessage` only sends it to
// one channel whereas the other one allows for sending to more than one channel at a time.
RocketChat.API.v1.addRoute('chat.sendMessage', { authRequired: true }, {
post() {
if (!this.bodyParams.message) {
throw new Meteor.Error('error-invalid-params', 'The "message" parameter must be provided.');
}

let message;
Meteor.runAsUser(this.userId, () => message = Meteor.call('sendMessage', this.bodyParams.message));

return RocketChat.API.v1.success({
message
});
}
});

RocketChat.API.v1.addRoute('chat.starMessage', { authRequired: true }, {
post() {
if (!this.bodyParams.messageId || !this.bodyParams.messageId.trim()) {
Expand Down
Loading