Skip to content

Commit

Permalink
Merge pull request #8947 from RocketChat/improvements/mobile-api
Browse files Browse the repository at this point in the history
[NEW] Add new API endpoints
  • Loading branch information
rodrigok authored Dec 6, 2017
2 parents 06b9c2e + 5bd2cb3 commit 5c73338
Show file tree
Hide file tree
Showing 17 changed files with 758 additions and 48 deletions.
1 change: 1 addition & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ [email protected]
blaze-html-templates
[email protected]
[email protected]
ddp-common
[email protected]
[email protected]
[email protected]
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
176 changes: 174 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,167 @@ 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;

// Remove tokenExpires to keep the old behavior
Meteor.users.update({
_id: this.user._id,
'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(auth.token)
}, {
$unset: {
'services.resume.loginTokens.$.when': 1
}
});

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

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

0 comments on commit 5c73338

Please sign in to comment.