feat(server): Add publicUsers toggle for user search (#14330)

* feat(server): Add publicUsers toggle for user search

* tests

* docs: add check:typescript for web PR checklist

* return auth.user when publicUsers is false - app testing

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Sam Holton 2024-11-26 10:51:01 -05:00 committed by GitHub
parent b6ec79cbdd
commit 5417e34fb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 93 additions and 11 deletions

View file

@ -149,6 +149,7 @@ export interface SystemConfig {
server: {
externalDomain: string;
loginPageMessage: string;
publicUsers: boolean;
};
user: {
deleteDelay: number;
@ -296,6 +297,7 @@ export const defaults = Object.freeze<SystemConfig>({
server: {
externalDomain: '',
loginPageMessage: '',
publicUsers: true,
},
notifications: {
smtp: {

View file

@ -39,8 +39,8 @@ export class UserController {
@Get()
@Authenticated()
searchUsers(): Promise<UserResponseDto[]> {
return this.service.search();
searchUsers(@Auth() auth: AuthDto): Promise<UserResponseDto[]> {
return this.service.search(auth);
}
@Get('me')

View file

@ -144,6 +144,7 @@ export class ServerConfigDto {
isInitialized!: boolean;
isOnboarded!: boolean;
externalDomain!: string;
publicUsers!: boolean;
mapDarkStyleUrl!: string;
mapLightStyleUrl!: string;
}

View file

@ -404,6 +404,9 @@ class SystemConfigServerDto {
@IsString()
loginPageMessage!: string;
@IsBoolean()
publicUsers!: boolean;
}
class SystemConfigSmtpTransportDto {

View file

@ -169,6 +169,7 @@ describe(ServerService.name, () => {
isInitialized: undefined,
isOnboarded: false,
externalDomain: '',
publicUsers: true,
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
});

View file

@ -110,6 +110,7 @@ export class ServerService extends BaseService {
isInitialized,
isOnboarded: onboarding?.isOnboarded || false,
externalDomain: config.server.externalDomain,
publicUsers: config.server.publicUsers,
mapDarkStyleUrl: config.map.darkStyle,
mapLightStyleUrl: config.map.lightStyle,
};

View file

@ -133,6 +133,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
server: {
externalDomain: '',
loginPageMessage: '',
publicUsers: true,
},
storageTemplate: {
enabled: false,

View file

@ -38,9 +38,9 @@ describe(UserService.name, () => {
});
describe('getAll', () => {
it('should get all users', async () => {
it('admin should get all users', async () => {
userMock.getList.mockResolvedValue([userStub.admin]);
await expect(sut.search()).resolves.toEqual([
await expect(sut.search(authStub.admin)).resolves.toEqual([
expect.objectContaining({
id: authStub.admin.user.id,
email: authStub.admin.user.email,
@ -48,6 +48,29 @@ describe(UserService.name, () => {
]);
expect(userMock.getList).toHaveBeenCalledWith({ withDeleted: false });
});
it('non-admin should get all users when publicUsers enabled', async () => {
userMock.getList.mockResolvedValue([userStub.user1]);
await expect(sut.search(authStub.user1)).resolves.toEqual([
expect.objectContaining({
id: authStub.user1.user.id,
email: authStub.user1.user.email,
}),
]);
expect(userMock.getList).toHaveBeenCalledWith({ withDeleted: false });
});
it('non-admin user should only receive itself when publicUsers is disabled', async () => {
userMock.getList.mockResolvedValue([userStub.user1]);
systemMock.get.mockResolvedValue(systemConfigStub.publicUsersDisabled);
await expect(sut.search(authStub.user1)).resolves.toEqual([
expect.objectContaining({
id: authStub.user1.user.id,
email: authStub.user1.user.email,
}),
]);
expect(userMock.getList).not.toHaveBeenCalledWith({ withDeleted: false });
});
});
describe('get', () => {

View file

@ -19,8 +19,14 @@ import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/uti
@Injectable()
export class UserService extends BaseService {
async search(): Promise<UserResponseDto[]> {
const users = await this.userRepository.getList({ withDeleted: false });
async search(auth: AuthDto): Promise<UserResponseDto[]> {
const config = await this.getConfig({ withCache: false });
let users: UserEntity[] = [auth.user];
if (auth.user.isAdmin || config.server.publicUsers) {
users = await this.userRepository.getList({ withDeleted: false });
}
return users.map((user) => mapUser(user));
}