Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/real-mails-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/http-router": patch
---

fix(api): return 403 for authorization failures, reserve 401 for missing session
2 changes: 1 addition & 1 deletion apps/meteor/ee/server/api/ldap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ API.v1.post(
},
async function action() {
if (!this.userId) {
throw new Error('error-invalid-user');
throw new Error('unauthorized');
}

if (!(await hasPermissionAsync(this.user, 'sync-auth-services-users'))) {
Expand Down
22 changes: 13 additions & 9 deletions apps/meteor/server/api/ApiClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -895,23 +895,27 @@ export class APIClass<TBasePath extends string = '', TOperations extends Record<
}
} catch (e: any) {
result = ((e: any) => {
switch (e.error) {
const errorKey = typeof e === 'string' ? e : e?.error ?? e?.message;
const errorMessage = typeof e === 'string'
? e
: e?.message || e?.reason || (typeof e?.error === 'string' ? e.error : undefined) || String(e);
switch (errorKey) {
case 'error-too-many-requests':
return api.tooManyRequests(typeof e === 'string' ? e : e.message);
return api.tooManyRequests(errorMessage);
case 'unauthorized':
case 'error-invalid-user':
return api.unauthorized(errorMessage);
case 'error-unauthorized':
if (applyBreakingChanges) {
return api.unauthorized(typeof e === 'string' ? e : e.message);
}
return api.forbidden(typeof e === 'string' ? e : e.message);
case 'error-not-authorized':
return api.forbidden(errorMessage);
case 'forbidden':
case 'error-forbidden':
if (applyBreakingChanges) {
return api.forbidden(typeof e === 'string' ? e : e.message);
return api.forbidden(errorMessage);
}
return api.failure(typeof e === 'string' ? e : e.message, e.error, process.env.TEST_MODE ? e.stack : undefined, e);
return api.failure(errorMessage, e?.error, process.env.TEST_MODE ? e?.stack : undefined, e);
default:
return api.failure(typeof e === 'string' ? e : e.message, e.error, process.env.TEST_MODE ? e.stack : undefined, e);
return api.failure(errorMessage, e?.error, process.env.TEST_MODE ? e?.stack : undefined, e);
Comment thread
surjeetkumar8006 marked this conversation as resolved.
}
})(e);
} finally {
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/server/api/v1/ldap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ API.v1.post(
},
async function action() {
if (!this.userId) {
throw new Error('error-invalid-user');
throw new Error('unauthorized');
}

if (settings.get<boolean>('LDAP_Enable') !== true) {
Expand Down Expand Up @@ -65,7 +65,7 @@ API.v1.post(
},
async function action() {
if (!this.userId) {
throw new Error('error-invalid-user');
throw new Error('unauthorized');
}

if (settings.get<boolean>('LDAP_Enable') !== true) {
Expand Down
9 changes: 2 additions & 7 deletions apps/meteor/server/api/v1/middlewares/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,8 @@ export const permissionsMiddleware =
}

if (!hasPermission) {
if (applyBreakingChanges) {
const forbidden = API.v1.forbidden('User does not have the permissions required for this action [error-unauthorized]');
return c.json(forbidden.body, forbidden.statusCode);
}

const failure = API.v1.forbidden('User does not have the permissions required for this action [error-unauthorized]');
return c.json(failure.body, failure.statusCode);
const forbidden = API.v1.forbidden('User does not have the permissions required for this action [error-unauthorized]');
return c.json(forbidden.body, forbidden.statusCode);
}

return next();
Expand Down
12 changes: 9 additions & 3 deletions apps/meteor/server/api/v1/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ API.v1.get(
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
async function action() {
Expand Down Expand Up @@ -775,6 +776,7 @@ API.v1.get(
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
async function action() {
Expand Down Expand Up @@ -810,6 +812,7 @@ API.v1.get(
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
async function action() {
Expand Down Expand Up @@ -1136,6 +1139,7 @@ API.v1.get(
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
404: validateNotFoundErrorResponse,
},
},
Expand All @@ -1154,7 +1158,7 @@ API.v1.get(
}

if (findResult.broadcast && !(await hasPermissionAsync(this.user, 'view-broadcast-member-list', findResult._id))) {
return API.v1.unauthorized();
return API.v1.forbidden();
}

// Ensures that role priorities for the specified room are synchronized correctly.
Expand Down Expand Up @@ -1294,13 +1298,14 @@ API.v1.post(
200: successResponseSchema,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
async function action() {
const { roomId } = this.bodyParams;

if (!(await canAccessRoomIdAsync(roomId, this.userId))) {
return API.v1.unauthorized();
return API.v1.forbidden();
}

const user = await Users.findOneById(this.userId, { projection: { _id: 1 } });
Expand Down Expand Up @@ -1697,13 +1702,14 @@ export const roomEndpoints = API.v1
response: {
200: roomsBannedUsersResponseSchema,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
async function action() {
const { roomId } = this.queryParams;

if (!(await canAccessRoomIdAsync(roomId, this.userId))) {
return API.v1.unauthorized();
return API.v1.forbidden();
}

const { offset, count } = await getPaginationItems(this.queryParams);
Expand Down
4 changes: 2 additions & 2 deletions docs/api-endpoint-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ expect(res.body).to.have.property('errorType', 'invalid-params');
expect(res.body).to.have.property('errorType', 'error-invalid-params');
```

This only affects **query** parameter validation (GET/DELETE). Body parameter validation (POST/PUT) keeps `'invalid-params'`.
This affects both **query** parameter validation (GET/DELETE) and **body** parameter validation (POST/PUT).

### Error message format changes

Expand All @@ -615,7 +615,7 @@ expect(res.body).to.have.property('error', "must have required property 'platfor

When migrating an endpoint, search for its tests and update:

1. `errorType` from `'invalid-params'` to `'error-invalid-params'` (for query params only)
1. `errorType` from `'invalid-params'` to `'error-invalid-params'` for query and body params
2. Remove `' [invalid-params]'` suffix from `error` message assertions
3. Verify that status codes remain the same (400 for validation errors)

Expand Down
4 changes: 2 additions & 2 deletions packages/http-router/src/Router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,12 @@ describe('Router', () => {
const invalidResponse = await request(app).post('/api/validate-body').send({ name: 'John' });

expect(invalidResponse.status).toBe(400);
expect(invalidResponse.body).toHaveProperty('errorType', 'invalid-params');
expect(invalidResponse.body).toHaveProperty('errorType', 'error-invalid-params');

const invalidTypeResponse = await request(app).post('/api/validate-body').send({ name: 'John', age: 'thirty' });

expect(invalidTypeResponse.status).toBe(400);
expect(invalidTypeResponse.body).toHaveProperty('errorType', 'invalid-params');
expect(invalidTypeResponse.body).toHaveProperty('errorType', 'error-invalid-params');
});

it('should validate response body in test mode', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/http-router/src/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export class Router<
return c.json(
{
success: false,
errorType: 'invalid-params',
errorType: 'error-invalid-params',
error: validatorFn.errors?.map((error: any) => error.message).join('\n '),
},
400,
Expand Down