Skip to content

Commit

Permalink
reimplement isUniqueEmail validation
Browse files Browse the repository at this point in the history
  • Loading branch information
MajedAlaitwniCap committed Jun 6, 2024
1 parent c6aa238 commit 36bbc96
Show file tree
Hide file tree
Showing 15 changed files with 736 additions and 1,167 deletions.
3 changes: 0 additions & 3 deletions apps/server/src/apps/server.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Mail, MailService } from '@infra/mail';
/* eslint-disable no-console */
import { MikroORM } from '@mikro-orm/core';
import { AccountService } from '@modules/account';
import { AccountValidationService } from '@src/modules/account/domain/services/account.validation.service';
import { AccountUc } from '@src/modules/account/api/account.uc';
import { SystemRule } from '@modules/authorization/domain/rules';

Check warning on line 8 in apps/server/src/apps/server.app.ts

View workflow job for this annotation

GitHub Actions / nest_lint

'@modules/authorization/domain/rules' import is restricted from being used by a pattern. Do not deep import from a module
import { CollaborativeStorageUc } from '@modules/collaborative-storage/uc/collaborative-storage.uc';

Check warning on line 9 in apps/server/src/apps/server.app.ts

View workflow job for this annotation

GitHub Actions / nest_lint

'@modules/collaborative-storage/uc/collaborative-storage.uc' import is restricted from being used by a pattern. Do not deep import from a module
Expand Down Expand Up @@ -83,8 +82,6 @@ async function bootstrap() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
feathersExpress.services['nest-account-service'] = nestApp.get(AccountService);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
feathersExpress.services['nest-account-validation-service'] = nestApp.get(AccountValidationService);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
feathersExpress.services['nest-account-uc'] = nestApp.get(AccountUc);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
feathersExpress.services['nest-collaborative-storage-uc'] = nestApp.get(CollaborativeStorageUc);
Expand Down
6 changes: 0 additions & 6 deletions apps/server/src/modules/account/account.module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { MongoMemoryDatabaseModule } from '@infra/database';
import { AccountModule } from './account.module';
import { AccountIdmToDoMapper, AccountIdmToDoMapperDb, AccountIdmToDoMapperIdm } from './repo/micro-orm/mapper';
import { AccountService } from './domain/services/account.service';
import { AccountValidationService } from './domain/services/account.validation.service';

describe('AccountModule', () => {
let module: TestingModule;
Expand Down Expand Up @@ -32,11 +31,6 @@ describe('AccountModule', () => {
expect(accountService).toBeDefined();
});

it('should have the account validation service defined', () => {
const accountValidationService = module.get(AccountValidationService);
expect(accountValidationService).toBeDefined();
});

describe('when FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED is enabled', () => {
let moduleFeatureEnabled: TestingModule;

Expand Down
4 changes: 1 addition & 3 deletions apps/server/src/modules/account/account.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { AccountIdmToDoMapper, AccountIdmToDoMapperDb, AccountIdmToDoMapperIdm }
import { AccountServiceDb } from './domain/services/account-db.service';
import { AccountServiceIdm } from './domain/services/account-idm.service';
import { AccountService } from './domain/services/account.service';
import { AccountValidationService } from './domain/services/account.validation.service';

function accountIdmToDtoMapperFactory(configService: ConfigService<AccountConfig, true>): AccountIdmToDoMapper {
if (configService.get<boolean>('FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED') === true) {
Expand All @@ -29,13 +28,12 @@ function accountIdmToDtoMapperFactory(configService: ConfigService<AccountConfig
AccountServiceDb,
AccountServiceIdm,
AccountService,
AccountValidationService,
{
provide: AccountIdmToDoMapper,
useFactory: accountIdmToDtoMapperFactory,
inject: [ConfigService],
},
],
exports: [AccountService, AccountValidationService],
exports: [AccountService],
})
export class AccountModule {}
5 changes: 0 additions & 5 deletions apps/server/src/modules/account/api/account.uc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { schoolEntityFactory, setupEntities, userFactory } from '@shared/testing
import { Account, AccountSave } from '../domain';
import { AccountEntity } from '../domain/entity/account.entity';
import { AccountService } from '../domain/services';
import { AccountValidationService } from '../domain/services/account.validation.service';
import { AccountEntityToDoMapper } from '../repo/micro-orm/mapper';
import { AccountUc } from './account.uc';
import { AccountSearchDto, AccountSearchType, UpdateAccountDto } from './dto';
Expand Down Expand Up @@ -50,10 +49,6 @@ describe('AccountUc', () => {
provide: ConfigService,
useValue: createMock<ConfigService>(),
},
{
provide: AccountValidationService,
useValue: createMock<AccountValidationService>(),
},
{
provide: AuthorizationService,
useValue: createMock<AuthorizationService>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ import { Test, TestingModule } from '@nestjs/testing';
import { EntityNotFoundError } from '@shared/common';
import { IdmAccount } from '@shared/domain/interface';
import { EntityId } from '@shared/domain/types';
import { UserRepo } from '@shared/repo';
import { setupEntities, userFactory } from '@shared/testing';
import { Logger } from '@src/core/logger';
import bcrypt from 'bcryptjs';
import { v1 } from 'uuid';
import { Account } from '../account';
import { AccountConfig } from '../../account-config';
import { AccountRepo } from '../../repo/micro-orm/account.repo';
import { accountDoFactory } from '../../testing';
import { Account } from '../account';
import { AccountEntity } from '../entity/account.entity';
import { AccountServiceDb } from './account-db.service';
import { accountDoFactory } from '../../testing';

describe('AccountDbService', () => {
let module: TestingModule;
Expand Down Expand Up @@ -57,6 +58,10 @@ describe('AccountDbService', () => {
provide: IdentityManagementService,
useValue: createMock<IdentityManagementService>(),
},
{
provide: UserRepo,
useValue: createMock<UserRepo>(),
},
],
}).compile();
accountRepo = module.get(AccountRepo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config/dist/config.service';
import { EntityNotFoundError } from '@shared/common';
import { Counted, EntityId } from '@shared/domain/types';
import { UserRepo } from '@shared/repo';
import bcrypt from 'bcryptjs';
import { AccountConfig } from '../../account-config';
import { AccountRepo } from '../../repo/micro-orm/account.repo';
Expand All @@ -15,7 +16,8 @@ export class AccountServiceDb {
constructor(
private readonly accountRepo: AccountRepo,
private readonly idmService: IdentityManagementService,
private readonly configService: ConfigService<AccountConfig, true>
private readonly configService: ConfigService<AccountConfig, true>,
private readonly userRepo: UserRepo
) {}

async findById(id: EntityId): Promise<Account> {
Expand Down Expand Up @@ -142,4 +144,20 @@ export class AccountServiceDb {

return account;
}

async isUniqueEmail(email: string, userId?: EntityId, accountId?: EntityId, systemId?: EntityId): Promise<boolean> {
const foundUsers = await this.userRepo.findByEmail(email);
const [accounts] = await this.accountRepo.searchByUsernameExactMatch(email);
const filteredAccounts = accounts.filter((foundAccount) => foundAccount.systemId === systemId);

const multipleUsers = foundUsers.length > 1;
const multipleAccounts = filteredAccounts.length > 1;
// paranoid 'toString': legacy code may call userId or accountId as ObjectID
const oneUserWithoutGivenId = foundUsers.length === 1 && foundUsers[0].id.toString() !== userId?.toString();
const oneAccountWithoutGivenId =
filteredAccounts.length === 1 && filteredAccounts[0].id.toString() !== accountId?.toString();
const isUnique = !(multipleUsers || multipleAccounts || oneUserWithoutGivenId || oneAccountWithoutGivenId);

return isUnique;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,10 @@ export class AccountServiceIdm extends AbstractAccountService {
}
throw new EntityNotFoundError(`Account with id ${id.toString()} not found`);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async isUniqueEmail(email: string, _userId?: EntityId, accountId?: EntityId, systemId?: EntityId): Promise<boolean> {
const kc = await this.identityManager.findAccountsByUsername(email);
return kc.length > 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ export abstract class AbstractAccountService {
abstract deleteByUserId(userId: EntityId): Promise<EntityId[]>;

abstract searchByUsernameExactMatch(userName: string): Promise<Counted<Account[]>>;

abstract isUniqueEmail(email: string, userId?: EntityId, accountId?: EntityId, systemId?: EntityId): Promise<boolean>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ import { Test, TestingModule } from '@nestjs/testing';
import { IdmAccount } from '@shared/domain/interface';
import { UserRepo } from '@shared/repo';
import { cleanupCollections } from '@shared/testing';
import { v1 } from 'uuid';
import { Logger } from '@src/core/logger';
import { KeycloakIdentityManagementService } from '@src/infra/identity-management/keycloak/service/keycloak-identity-management.service';
import { v1 } from 'uuid';
import { Account, AccountSave } from '..';
import { AccountEntity } from '../entity/account.entity';
import { AccountRepo } from '../../repo/micro-orm/account.repo';
import { AccountIdmToDoMapper, AccountIdmToDoMapperDb } from '../../repo/micro-orm/mapper';
import { accountFactory } from '../../testing';
import { AccountEntity } from '../entity/account.entity';
import { AccountServiceDb } from './account-db.service';
import { AccountServiceIdm } from './account-idm.service';
import { AccountService } from './account.service';
import { AbstractAccountService } from './account.service.abstract';
import { AccountValidationService } from './account.validation.service';
import { accountFactory } from '../../testing';

describe('AccountService Integration', () => {
let module: TestingModule;
Expand Down Expand Up @@ -93,7 +93,10 @@ describe('AccountService Integration', () => {
AccountServiceDb,
AccountRepo,
UserRepo,
AccountValidationService,
{
provide: KeycloakIdentityManagementService,
useValue: createMock<KeycloakIdentityManagementService>(),
},
{
provide: AccountIdmToDoMapper,
useValue: new AccountIdmToDoMapperDb(),
Expand Down
Loading

0 comments on commit 36bbc96

Please sign in to comment.