fix(server): don't publicly reveal user count (#4409)

* fix: don't reveal user count publicly

* fix: mobile and user controller

* fix: update other frontend endpoints

* fix: revert openapi change

* chore: open api

* fix: initialize

* openapi

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jonathan Jogenfors
2023-10-11 04:37:13 +02:00
committed by GitHub
parent 09bf1c9175
commit 41befc0948
20 changed files with 101 additions and 15 deletions

View File

@@ -5004,6 +5004,17 @@
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"User"
]
@@ -7340,6 +7351,9 @@
},
"ServerConfigDto": {
"properties": {
"isInitialized": {
"type": "boolean"
},
"loginPageMessage": {
"type": "string"
},
@@ -7357,7 +7371,8 @@
"trashDays",
"oauthButtonText",
"loginPageMessage",
"mapTileUrl"
"mapTileUrl",
"isInitialized"
],
"type": "object"
},

View File

@@ -18,6 +18,7 @@ export const IUserRepository = 'IUserRepository';
export interface IUserRepository {
get(id: string, withDeleted?: boolean): Promise<UserEntity | null>;
getAdmin(): Promise<UserEntity | null>;
hasAdmin(): Promise<boolean>;
getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | null>;
getByStorageLabel(storageLabel: string): Promise<UserEntity | null>;
getByOAuthId(oauthId: string): Promise<UserEntity | null>;

View File

@@ -85,6 +85,7 @@ export class ServerConfigDto {
mapTileUrl!: string;
@ApiProperty({ type: 'integer' })
trashDays!: number;
isInitialized!: boolean;
}
export class ServerFeaturesDto implements FeatureFlags {

View File

@@ -74,11 +74,14 @@ export class ServerInfoService {
// TODO move to system config
const loginPageMessage = process.env.PUBLIC_LOGIN_PAGE_MESSAGE || '';
const isInitialized = await this.userRepository.hasAdmin();
return {
loginPageMessage,
mapTileUrl: config.map.tileUrl,
trashDays: config.trash.days,
oauthButtonText: config.oauth.buttonText,
isInitialized,
};
}

View File

@@ -26,7 +26,7 @@ import {
} from '@nestjs/common';
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
import { Response as Res } from 'express';
import { AdminRoute, AuthUser, Authenticated, PublicRoute } from '../app.guard';
import { AdminRoute, AuthUser, Authenticated } from '../app.guard';
import { FileUploadInterceptor, Route } from '../app.interceptor';
import { UseValidation } from '../app.utils';
import { UUIDParamDto } from './dto/uuid-param.dto';
@@ -59,7 +59,7 @@ export class UserController {
return this.service.create(createUserDto);
}
@PublicRoute()
@AdminRoute()
@Get('count')
getUserCount(@Query() dto: CountDto): Promise<UserCountResponseDto> {
return this.service.getCount(dto);

View File

@@ -16,6 +16,10 @@ export class UserRepository implements IUserRepository {
return this.userRepository.findOne({ where: { isAdmin: true } });
}
async hasAdmin(): Promise<boolean> {
return this.userRepository.exist({ where: { isAdmin: true } });
}
async getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | null> {
let builder = this.userRepository.createQueryBuilder('user').where({ email });

View File

@@ -102,6 +102,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
oauthButtonText: 'Login with OAuth',
mapTileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
trashDays: 30,
isInitialized: true,
});
});
});

View File

@@ -311,10 +311,10 @@ describe(`${UserController.name}`, () => {
});
describe('GET /user/count', () => {
it('should not require authentication', async () => {
it('should require authentication', async () => {
const { status, body } = await request(server).get(`/user/count`);
expect(status).toBe(200);
expect(body).toEqual({ userCount: 1 });
expect(status).toBe(401);
expect(body).toEqual(errorStub.unauthorized);
});
it('should start with just the admin', async () => {

View File

@@ -14,5 +14,6 @@ export const newUserRepositoryMock = (): jest.Mocked<IUserRepository> => {
delete: jest.fn(),
getDeletedUsers: jest.fn(),
restore: jest.fn(),
hasAdmin: jest.fn(),
};
};