import randomatic from 'randomatic'; import { AbstractRepository, EntityRepository, getManager, Transaction, TransactionManager, } from 'typeorm'; import { ApiClient, User } from '@/entities'; import { encodeApiKey } from '@/routes/utils'; import { BasicAuthStrategy } from '@/services/AuthProvider/strategies'; import { TooManyAPIClientsError } from './types'; @EntityRepository(ApiClient) export default class ApiClientRepository extends AbstractRepository { @Transaction() async generateApiKey( { user }: { user: User }, @TransactionManager() manager = getManager() ): Promise { const apiClientCount = await manager.count(ApiClient, { where: { user }, take: 1, }); const userAlreadyHasAClient = apiClientCount > 0; if (userAlreadyHasAClient) { throw new TooManyAPIClientsError('This user already has an api key.'); } return this.recursivelyGenerateApiKey({ user }, manager); } @Transaction() private async recursivelyGenerateApiKey( { user, maxRetries = 3 }: { user: User; maxRetries?: number }, @TransactionManager() manager = getManager() ): Promise { const strategy = new BasicAuthStrategy(); const clientSecret = strategy.generateSecret(); const passwordData = await strategy.generatePersistablePasswordData( clientSecret ); // generate a 24 character long alphanumeric string const clientId = randomatic('Aa0', 24); try { const client = ApiClient.create({ clientId, clientSecret: passwordData, user, }); await manager.save(client); } catch (error) { if (maxRetries > 0) { return this.recursivelyGenerateApiKey( { user, maxRetries: maxRetries - 1 }, manager ); } throw error; } return encodeApiKey({ clientId, clientSecret }); } @Transaction() async revokeApiKey( { user }: { user: User }, @TransactionManager() manager = getManager() ): Promise { const apiClients = await manager.find(ApiClient, { user }); if (apiClients.length > 1) { throw new TooManyAPIClientsError( 'You should not have more than one api key. Contact support to fix this.' ); } await manager.softRemove(apiClients); } }