Skip to content

Commit e78ad0f

Browse files
committed
fix: prevent invisible-status bypass via statusDefault fallback
1 parent 7e051b3 commit e78ad0f

3 files changed

Lines changed: 30 additions & 12 deletions

File tree

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,12 +2008,6 @@ API.v1
20082008
});
20092009
}
20102010

2011-
if (this.bodyParams.status === 'offline' && !settings.get('Accounts_AllowInvisibleStatusOption')) {
2012-
throw new Meteor.Error('error-status-not-allowed', 'Invisible status is disabled', {
2013-
method: 'users.setStatus',
2014-
});
2015-
}
2016-
20172011
const { status, message, expiresAt } = this.bodyParams;
20182012

20192013
const statusExpiresAt = expiresAt ? new Date(expiresAt) : undefined;
@@ -2025,8 +2019,15 @@ API.v1
20252019

20262020
// If status is missing (message-only update), keep the user's chosen status (statusDefault),
20272021
// not the computed status — otherwise a transient auto-away/offline gets pinned as a manual claim.
2028-
const statusToUpdate = status || user.statusDefault || ('online' as UserStatus);
2029-
await Presence.setStatus(user._id, statusToUpdate, message, statusExpiresAt);
2022+
const effectiveStatus = status || user.statusDefault || ('online' as UserStatus);
2023+
2024+
if (effectiveStatus === 'offline' && !settings.get('Accounts_AllowInvisibleStatusOption')) {
2025+
throw new Meteor.Error('error-status-not-allowed', 'Invisible status is disabled', {
2026+
method: 'users.setStatus',
2027+
});
2028+
}
2029+
2030+
await Presence.setStatus(user._id, effectiveStatus, message, statusExpiresAt);
20302031

20312032
if (status) {
20322033
void wrapExceptions(() => Calendar.cancelUpcomingStatusChanges(user._id)).suppress();

apps/meteor/app/user-status/server/methods/setUserStatus.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ export const setUserStatusMethod = async (
2424
});
2525
}
2626

27-
if (statusType === UserStatus.OFFLINE && !settings.get('Accounts_AllowInvisibleStatusOption')) {
27+
const effectiveStatus = statusType || user.statusDefault || UserStatus.ONLINE;
28+
29+
if (effectiveStatus === UserStatus.OFFLINE && !settings.get('Accounts_AllowInvisibleStatusOption')) {
2830
throw new Meteor.Error('error-status-not-allowed', 'Invisible status is disabled', {
2931
method: 'setUserStatus',
3032
});
3133
}
3234

33-
// If only the status message changes (no statusType), use the user's statusDefault (their chosen status).
34-
// This avoids overwriting transient status (auto-away, offline) with a manual claim.
35-
await Presence.setStatus(user._id, statusType || user.statusDefault || UserStatus.ONLINE, statusText);
35+
await Presence.setStatus(user._id, effectiveStatus, statusText);
3636
};
3737

3838
Meteor.methods<ServerMethods>({

apps/meteor/tests/end-to-end/api/users.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5713,6 +5713,23 @@ describe('[Users]', () => {
57135713

57145714
await updateSetting('Accounts_AllowInvisibleStatusOption', true);
57155715
});
5716+
it('should reject a message-only update when status resolves to offline via statusDefault and "Accounts_AllowInvisibleStatusOption" is disabled', async () => {
5717+
await updateSetting('Accounts_AllowInvisibleStatusOption', true);
5718+
await request.post(api('users.setStatus')).set(credentials).send({ status: 'offline' }).expect(200);
5719+
await updateSetting('Accounts_AllowInvisibleStatusOption', false);
5720+
5721+
await request
5722+
.post(api('users.setStatus'))
5723+
.set(credentials)
5724+
.send({ message: 'still trying to stay invisible' })
5725+
.expect(400)
5726+
.expect((res) => {
5727+
expect(res.body).to.have.property('success', false);
5728+
expect(res.body.errorType).to.be.equal('error-status-not-allowed');
5729+
});
5730+
5731+
await updateSetting('Accounts_AllowInvisibleStatusOption', true);
5732+
});
57165733
it('should return an error when the payload is missing all supported fields', (done) => {
57175734
void request
57185735
.post(api('users.setStatus'))

0 commit comments

Comments
 (0)