Skip to content

Commit c4338fb

Browse files
sampaiodiegoggazzo
authored andcommitted
feat!: Undeprecate /v1/users.createToken endpoint (#36570)
1 parent f646616 commit c4338fb

File tree

5 files changed

+88
-25
lines changed

5 files changed

+88
-25
lines changed

.changeset/afraid-parents-bake.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@rocket.chat/rest-typings': minor
3+
'@rocket.chat/meteor': minor
4+
---
5+
6+
REST endpoint `/v1/users.createToken` is not deprecated anymore. It now requires a `secret` parameter to generate a token for a user. This change is part of the effort to enhance security by ensuring that tokens are generated with an additional layer of validation. The `secret` parameter is validated against a new environment variable `CREATE_TOKENS_FOR_USERS_SECRET`.

apps/meteor/app/api/server/v1/users.ts

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
isUsersSetPreferencesParamsPOST,
1919
isUsersCheckUsernameAvailabilityParamsGET,
2020
isUsersSendConfirmationEmailParamsPOST,
21+
ajv,
2122
} from '@rocket.chat/rest-typings';
2223
import { getLoginExpirationInMs, wrapExceptions } from '@rocket.chat/tools';
2324
import { Accounts } from 'meteor/accounts-base';
@@ -69,6 +70,7 @@ import { deleteUserOwnAccount } from '../../../lib/server/methods/deleteUserOwnA
6970
import { settings } from '../../../settings/server';
7071
import { isSMTPConfigured } from '../../../utils/server/functions/isSMTPConfigured';
7172
import { getURL } from '../../../utils/server/getURL';
73+
import type { ExtractRoutesFromAPI } from '../ApiClass';
7274
import { API } from '../api';
7375
import { getPaginationItems } from '../helpers/getPaginationItems';
7476
import { getUserFromParams } from '../helpers/getUserFromParams';
@@ -761,17 +763,70 @@ API.v1.addRoute(
761763
},
762764
);
763765

764-
API.v1.addRoute(
766+
const usersEndpoints = API.v1.post(
765767
'users.createToken',
766-
{ authRequired: true, deprecationVersion: '8.0.0' },
767768
{
768-
async post() {
769-
const user = await getUserFromParams(this.bodyParams);
769+
authRequired: true,
770+
body: ajv.compile<{ userId: string; secret: string }>({
771+
type: 'object',
772+
properties: {
773+
userId: {
774+
type: 'string',
775+
minLength: 1,
776+
},
777+
secret: {
778+
type: 'string',
779+
minLength: 1,
780+
},
781+
},
782+
required: ['userId', 'secret'],
783+
additionalProperties: false,
784+
}),
785+
response: {
786+
200: ajv.compile<{ data: { userId: string; authToken: string } }>({
787+
type: 'object',
788+
properties: {
789+
data: {
790+
type: 'object',
791+
properties: {
792+
userId: {
793+
type: 'string',
794+
minLength: 1,
795+
},
796+
authToken: {
797+
type: 'string',
798+
minLength: 1,
799+
},
800+
},
801+
required: ['userId'],
802+
additionalProperties: false,
803+
},
804+
success: {
805+
type: 'boolean',
806+
enum: [true],
807+
},
808+
},
809+
required: ['data', 'success'],
810+
additionalProperties: false,
811+
}),
812+
400: ajv.compile({
813+
type: 'object',
814+
properties: {
815+
success: { type: 'boolean', enum: [false] },
816+
error: { type: 'string' },
817+
errorType: { type: 'string' },
818+
},
819+
required: ['success'],
820+
additionalProperties: false,
821+
}),
822+
},
823+
},
824+
async function action() {
825+
const user = await getUserFromParams(this.bodyParams);
770826

771-
const data = await generateAccessToken(this.userId, user._id);
827+
const data = await generateAccessToken(user._id, this.bodyParams.secret);
772828

773-
return data ? API.v1.success({ data }) : API.v1.forbidden();
774-
},
829+
return API.v1.success({ data });
775830
},
776831
);
777832

@@ -1441,3 +1496,10 @@ settings.watch<number>('Rate_Limiter_Limit_RegisterUser', (value) => {
14411496

14421497
API.v1.updateRateLimiterDictionaryForRoute(userRegisterRoute, value);
14431498
});
1499+
1500+
type UsersEndpoints = ExtractRoutesFromAPI<typeof usersEndpoints>;
1501+
1502+
declare module '@rocket.chat/rest-typings' {
1503+
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
1504+
interface Endpoints extends UsersEndpoints {}
1505+
}

apps/meteor/app/lib/server/methods/createToken.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
import { User } from '@rocket.chat/core-services';
1+
import { MeteorError, User } from '@rocket.chat/core-services';
22
import { Accounts } from 'meteor/accounts-base';
33

4-
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
4+
declare module '@rocket.chat/ddp-client' {
5+
// eslint-disable-next-line @typescript-eslint/naming-convention
6+
interface ServerMethods {
7+
createToken(userId: string): { userId: string; authToken: string };
8+
}
9+
}
10+
11+
const { CREATE_TOKENS_FOR_USERS_SECRET } = process.env;
512

6-
export async function generateAccessToken(callee: string, userId: string) {
7-
if (
8-
!['yes', 'true'].includes(String(process.env.CREATE_TOKENS_FOR_USERS)) ||
9-
(callee !== userId && !(await hasPermissionAsync(callee, 'user-generate-access-token')))
10-
) {
11-
throw new Meteor.Error('error-not-authorized', 'Not authorized');
13+
export async function generateAccessToken(userId: string, secret: string) {
14+
if (secret !== CREATE_TOKENS_FOR_USERS_SECRET) {
15+
throw new MeteorError('error-not-authorized', 'Not authorized');
1216
}
1317

1418
const token = Accounts._generateStampedLoginToken();

apps/meteor/server/services/user/service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class UserService extends ServiceClassInternal implements IUserService {
99
protected name = 'user';
1010

1111
async ensureLoginTokensLimit(uid: string): Promise<void> {
12-
const [{ tokens }] = await Users.findAllResumeTokensByUserId(uid);
12+
const [{ tokens } = { tokens: [] }] = await Users.findAllResumeTokensByUserId(uid);
1313
if (tokens.length < getMaxLoginTokens()) {
1414
return;
1515
}

packages/rest-typings/src/v1/users.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,6 @@ export type UsersEndpoints = {
289289
};
290290
};
291291

292-
'/v1/users.createToken': {
293-
POST: (params: { userId?: string; username?: string; user?: string }) => {
294-
data: {
295-
userId: string;
296-
authToken: string;
297-
};
298-
};
299-
};
300-
301292
'/v1/users.create': {
302293
POST: (params: UserCreateParamsPOST) => {
303294
user: IUser;

0 commit comments

Comments
 (0)