mirror of https://github.com/immich-app/immich.git
feat: reset oauth ids (#20798)
parent
9ecaa3fa9d
commit
538d5c81ea
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
|
||||||
|
class AuthAdminApi {
|
||||||
|
AuthAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||||
|
|
||||||
|
final ApiClient apiClient;
|
||||||
|
|
||||||
|
/// This endpoint is an admin-only route, and requires the `adminAuth.unlinkAll` permission.
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
Future<Response> unlinkAllOAuthAccountsAdminWithHttpInfo() async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final apiPath = r'/admin/auth/unlink-all';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
apiPath,
|
||||||
|
'POST',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This endpoint is an admin-only route, and requires the `adminAuth.unlinkAll` permission.
|
||||||
|
Future<void> unlinkAllOAuthAccountsAdmin() async {
|
||||||
|
final response = await unlinkAllOAuthAccountsAdminWithHttpInfo();
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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: '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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: '' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue