mirror of
https://github.com/immich-app/immich.git
synced 2025-11-22 10:00:41 +09:00
feat: reset oauth ids (#20798)
This commit is contained in:
18
server/src/controllers/auth-admin.controller.ts
Normal file
18
server/src/controllers/auth-admin.controller.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { AuthAdminService } from 'src/services/auth-admin.service';
|
||||
|
||||
@ApiTags('Auth (admin)')
|
||||
@Controller('admin/auth')
|
||||
export class AuthAdminController {
|
||||
constructor(private service: AuthAdminService) {}
|
||||
@Post('unlink-all')
|
||||
@Authenticated({ permission: Permission.AdminAuthUnlinkAll, admin: true })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
unlinkAllOAuthAccountsAdmin(@Auth() auth: AuthDto): Promise<void> {
|
||||
return this.service.unlinkAll(auth);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { APIKeyController } from 'src/controllers/api-key.controller';
|
||||
import { AppController } from 'src/controllers/app.controller';
|
||||
import { AssetMediaController } from 'src/controllers/asset-media.controller';
|
||||
import { AssetController } from 'src/controllers/asset.controller';
|
||||
import { AuthAdminController } from 'src/controllers/auth-admin.controller';
|
||||
import { AuthController } from 'src/controllers/auth.controller';
|
||||
import { DownloadController } from 'src/controllers/download.controller';
|
||||
import { DuplicateController } from 'src/controllers/duplicate.controller';
|
||||
@@ -40,6 +41,7 @@ export const controllers = [
|
||||
AssetController,
|
||||
AssetMediaController,
|
||||
AuthController,
|
||||
AuthAdminController,
|
||||
DownloadController,
|
||||
DuplicateController,
|
||||
FaceController,
|
||||
|
||||
@@ -235,6 +235,8 @@ export enum Permission {
|
||||
AdminUserRead = 'adminUser.read',
|
||||
AdminUserUpdate = 'adminUser.update',
|
||||
AdminUserDelete = 'adminUser.delete',
|
||||
|
||||
AdminAuthUnlinkAll = 'adminAuth.unlinkAll',
|
||||
}
|
||||
|
||||
export enum SharedLinkType {
|
||||
|
||||
@@ -194,6 +194,10 @@ export class UserRepository {
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
async updateAll(dto: Updateable<UserTable>) {
|
||||
await this.db.updateTable('user').set(dto).execute();
|
||||
}
|
||||
|
||||
restore(id: string) {
|
||||
return this.db
|
||||
.updateTable('user')
|
||||
|
||||
11
server/src/services/auth-admin.service.ts
Normal file
11
server/src/services/auth-admin.service.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthAdminService extends BaseService {
|
||||
async unlinkAll(_auth: AuthDto) {
|
||||
// TODO replace '' with null
|
||||
await this.userRepository.updateAll({ oauthId: '' });
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { ApiService } from 'src/services/api.service';
|
||||
import { AssetMediaService } from 'src/services/asset-media.service';
|
||||
import { AssetService } from 'src/services/asset.service';
|
||||
import { AuditService } from 'src/services/audit.service';
|
||||
import { AuthAdminService } from 'src/services/auth-admin.service';
|
||||
import { AuthService } from 'src/services/auth.service';
|
||||
import { BackupService } from 'src/services/backup.service';
|
||||
import { CliService } from 'src/services/cli.service';
|
||||
@@ -49,6 +50,7 @@ export const services = [
|
||||
AssetService,
|
||||
AuditService,
|
||||
AuthService,
|
||||
AuthAdminService,
|
||||
BackupService,
|
||||
CliService,
|
||||
DatabaseService,
|
||||
|
||||
66
server/test/medium/specs/services/auth-admin.service.spec.ts
Normal file
66
server/test/medium/specs/services/auth-admin.service.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { UserRepository } from 'src/repositories/user.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { AuthAdminService } from 'src/services/auth-admin.service';
|
||||
import { newMediumService } from 'test/medium.factory';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { getKyselyDB } from 'test/utils';
|
||||
|
||||
let defaultDatabase: Kysely<DB>;
|
||||
|
||||
const setup = (db?: Kysely<DB>) => {
|
||||
return newMediumService(AuthAdminService, {
|
||||
database: db || defaultDatabase,
|
||||
real: [UserRepository],
|
||||
mock: [LoggingRepository],
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
defaultDatabase = await getKyselyDB();
|
||||
});
|
||||
|
||||
describe(AuthAdminService.name, () => {
|
||||
describe('unlinkAll', () => {
|
||||
it('should reset user.oauthId', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const userRepo = ctx.get(UserRepository);
|
||||
const { user } = await ctx.newUser({ oauthId: 'test-oauth-id' });
|
||||
const auth = factory.auth();
|
||||
|
||||
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
||||
await expect(userRepo.get(user.id, { withDeleted: true })).resolves.toEqual(
|
||||
expect.objectContaining({ oauthId: '' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reset a deleted user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const userRepo = ctx.get(UserRepository);
|
||||
const { user } = await ctx.newUser({ oauthId: 'test-oauth-id', deletedAt: new Date() });
|
||||
const auth = factory.auth();
|
||||
|
||||
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
||||
await expect(userRepo.get(user.id, { withDeleted: true })).resolves.toEqual(
|
||||
expect.objectContaining({ oauthId: '' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reset multiple users', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const userRepo = ctx.get(UserRepository);
|
||||
const { user: user1 } = await ctx.newUser({ oauthId: '1' });
|
||||
const { user: user2 } = await ctx.newUser({ oauthId: '2', deletedAt: new Date() });
|
||||
const auth = factory.auth();
|
||||
|
||||
await expect(sut.unlinkAll(auth)).resolves.toBeUndefined();
|
||||
await expect(userRepo.get(user1.id, { withDeleted: true })).resolves.toEqual(
|
||||
expect.objectContaining({ oauthId: '' }),
|
||||
);
|
||||
await expect(userRepo.get(user2.id, { withDeleted: true })).resolves.toEqual(
|
||||
expect.objectContaining({ oauthId: '' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user