Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/blue-points-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates)
8 changes: 4 additions & 4 deletions apps/meteor/app/api/server/middlewares/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export function authenticationMiddleware(
if (userId && authToken) {
req.user = (await Users.findOneByIdAndLoginToken(userId as string, hashLoginToken(authToken as string))) || undefined;
} else {
req.user = await oAuth2ServerAuth({
headers: req.headers as Record<string, string | undefined>,
query: req.query as Record<string, string | undefined>,
});
const { authorization } = req.headers;
const accessToken = typeof req.query.access_token === 'string' ? req.query.access_token : undefined;
delete req.query.access_token;
req.user = await oAuth2ServerAuth({ authorization, accessToken });
}

if (config.rejectUnauthorized && !req.user) {
Expand Down
20 changes: 10 additions & 10 deletions apps/meteor/app/oauth2-server-config/server/oauth/oauth2-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ async function getAccessToken(accessToken: string) {
return OAuthAccessTokens.findOneByAccessToken(accessToken);
}

export async function oAuth2ServerAuth(partialRequest: {
headers: Record<string, string | undefined>;
query: Record<string, string | undefined>;
}): Promise<IUser | undefined> {
const headerToken = partialRequest.headers.authorization?.replace('Bearer ', '');
const queryToken = partialRequest.query.access_token;
const incomingToken = headerToken || queryToken;
export async function oAuth2ServerAuth(partialRequest: { authorization?: string; accessToken?: string }): Promise<IUser | undefined> {
const headerToken = partialRequest.authorization?.replace('Bearer ', '');
const incomingToken = headerToken || partialRequest.accessToken;

if (!incomingToken) {
return;
Expand Down Expand Up @@ -76,10 +72,14 @@ oauth2server.app.get('/oauth/userinfo', async (req: Request, res: Response) => {
});

API.v1.addAuthMethod((routeContext) => {
const headers = Object.fromEntries(routeContext.request.headers.entries());
const query = (isPlainObject(routeContext.queryParams) ? routeContext.queryParams : {}) as Record<string, string | undefined>;
const authorization = routeContext.request.headers.get('authorization') ?? undefined;
const query = isPlainObject(routeContext.queryParams) ? routeContext.queryParams : {};
const accessToken = typeof query.access_token === 'string' ? query.access_token : undefined;
if (routeContext.queryParams?.access_token) {
delete routeContext.queryParams.access_token;
}

return oAuth2ServerAuth({ headers, query });
return oAuth2ServerAuth({ authorization, accessToken });
});

(WebApp.connectHandlers as unknown as ReturnType<typeof express>).use(oauth2server.app);
22 changes: 22 additions & 0 deletions apps/meteor/tests/end-to-end/api/oauth-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,27 @@ describe('[OAuth Server]', () => {
expect(res.body).to.have.nested.property('user._id', 'rocketchat.internal.admin.test');
});
});

const malformedTokenPayloads = [
{ query: { 'access_token[$ne]': 'null' }, description: '$ne operator' },
{ query: { 'access_token[$exists]': 'true' }, description: '$exists operator' },
{ query: { 'access_token[$gt]': '' }, description: '$gt operator' },
{ query: { 'access_token[$regex]': '.*' }, description: '$regex operator' },
{ query: { access_token: 'invalid-token' }, description: 'invalid string token' },
];

malformedTokenPayloads.forEach(({ query, description }) => {
it(`should reject access_token with ${description}`, async () => {
await request
.get(api('me'))
.query(query)
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
expect(res.body).to.have.property('message', 'You must be logged in to do this.');
});
});
});
});
});
Loading