feat(web): Added admin user config to user settings (#15380)

* feat(web): Added admin user config to user settings

* feat (web) - cleaned up the files and added tests

* feat (web) - added missing files

* feat (web) - updated per review comments

* feat (web) - e2e admin command test failures
This commit is contained in:
nosajthenitram
2025-06-11 21:11:13 -05:00
committed by GitHub
parent 22eef5f3c5
commit e5219f1f31
15 changed files with 308 additions and 20 deletions

View File

@@ -37,6 +37,24 @@ export class CliService extends BaseService {
await this.updateConfig(config);
}
async grantAdminAccess(email: string): Promise<void> {
const user = await this.userRepository.getByEmail(email);
if (!user) {
throw new Error('User does not exist');
}
await this.userRepository.update(user.id, { isAdmin: true });
}
async revokeAdminAccess(email: string): Promise<void> {
const user = await this.userRepository.getByEmail(email);
if (!user) {
throw new Error('User does not exist');
}
await this.userRepository.update(user.id, { isAdmin: false });
}
async disableOAuthLogin(): Promise<void> {
const config = await this.getConfig({ withCache: false });
config.oauth.enabled = false;

View File

@@ -4,6 +4,7 @@ import { JobName, UserStatus } from 'src/enum';
import { UserAdminService } from 'src/services/user-admin.service';
import { authStub } from 'test/fixtures/auth.stub';
import { userStub } from 'test/fixtures/user.stub';
import { factory } from 'test/small.factory';
import { newTestService, ServiceMocks } from 'test/utils';
import { describe } from 'vitest';
@@ -116,7 +117,7 @@ describe(UserAdminService.name, () => {
it('should throw error if user could not be found', async () => {
mocks.user.get.mockResolvedValue(void 0);
await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toThrowError(BadRequestException);
await expect(sut.delete(authStub.admin, 'not-found', {})).rejects.toThrowError(BadRequestException);
expect(mocks.user.delete).not.toHaveBeenCalled();
});
@@ -124,8 +125,11 @@ describe(UserAdminService.name, () => {
await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toBeInstanceOf(ForbiddenException);
});
it('should require the auth user be an admin', async () => {
await expect(sut.delete(authStub.user1, authStub.admin.user.id, {})).rejects.toBeInstanceOf(ForbiddenException);
it('should not allow deleting own account', async () => {
const user = factory.userAdmin({ isAdmin: false });
const auth = factory.auth({ user });
mocks.user.get.mockResolvedValue(user);
await expect(sut.delete(auth, user.id, {})).rejects.toBeInstanceOf(ForbiddenException);
expect(mocks.user.delete).not.toHaveBeenCalled();
});

View File

@@ -52,6 +52,10 @@ export class UserAdminService extends BaseService {
async update(auth: AuthDto, id: string, dto: UserAdminUpdateDto): Promise<UserAdminResponseDto> {
const user = await this.findOrFail(id, {});
if (dto.isAdmin !== undefined && dto.isAdmin !== auth.user.isAdmin && auth.user.id === id) {
throw new BadRequestException('Admin status can only be changed by another admin');
}
if (dto.quotaSizeInBytes && user.quotaSizeInBytes !== dto.quotaSizeInBytes) {
await this.userRepository.syncUsage(id);
}
@@ -89,9 +93,9 @@ export class UserAdminService extends BaseService {
async delete(auth: AuthDto, id: string, dto: UserAdminDeleteDto): Promise<UserAdminResponseDto> {
const { force } = dto;
const { isAdmin } = await this.findOrFail(id, {});
if (isAdmin) {
throw new ForbiddenException('Cannot delete admin user');
await this.findOrFail(id, {});
if (auth.user.id === id) {
throw new ForbiddenException('Cannot delete your own account');
}
await this.albumRepository.softDeleteAll(id);