Skip to content

Commit ddba5d6

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

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';
@@ -756,17 +758,70 @@ API.v1.addRoute(
756758
},
757759
);
758760

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

766-
const data = await generateAccessToken(this.userId, user._id);
822+
const data = await generateAccessToken(user._id, this.bodyParams.secret);
767823

768-
return data ? API.v1.success({ data }) : API.v1.forbidden();
769-
},
824+
return API.v1.success({ data });
770825
},
771826
);
772827

@@ -1429,3 +1484,10 @@ settings.watch<number>('Rate_Limiter_Limit_RegisterUser', (value) => {
14291484

14301485
API.v1.updateRateLimiterDictionaryForRoute(userRegisterRoute, value);
14311486
});
1487+
1488+
type UsersEndpoints = ExtractRoutesFromAPI<typeof usersEndpoints>;
1489+
1490+
declare module '@rocket.chat/rest-typings' {
1491+
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
1492+
interface Endpoints extends UsersEndpoints {}
1493+
}

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)