diff --git a/backend/src/modules/auth/auth.providers.ts b/backend/src/modules/auth/auth.providers.ts index e0ad2b074..92da76ac1 100644 --- a/backend/src/modules/auth/auth.providers.ts +++ b/backend/src/modules/auth/auth.providers.ts @@ -9,6 +9,7 @@ import ValidateUserEmailUseCase from './applications/validate-user-email.use-cas import { TYPES } from './interfaces/types'; import { ResetPasswordRepository } from './repository/reset-password.repository'; import GetTokenAuthService from './services/get-token.auth.service'; +import ValidateUserAuthService from './services/validate-user.auth.service'; export const getTokenAuthService = { provide: TYPES.services.GetTokenAuthService, @@ -17,7 +18,7 @@ export const getTokenAuthService = { export const validateUserAuthService = { provide: TYPES.services.ValidateAuthService, - useClass: ValidateUserEmailUseCase + useClass: ValidateUserAuthService }; export const registerUserUseCase = { diff --git a/backend/src/modules/auth/controller/auth.controller.spec.ts b/backend/src/modules/auth/controller/auth.controller.spec.ts index 88b22bdbf..ed9f84fe2 100644 --- a/backend/src/modules/auth/controller/auth.controller.spec.ts +++ b/backend/src/modules/auth/controller/auth.controller.spec.ts @@ -5,17 +5,18 @@ import * as request from 'supertest'; import mockedUser from 'src/libs/test-utils/mocks/user.mock'; import AuthController from 'src/modules/auth/controller/auth.controller'; import EmailModule from 'src/modules/mailer/mailer.module'; -import * as Auth from 'src/modules/auth/interfaces/types'; -import * as Teams from 'src/modules/teams/interfaces/types'; -import * as Boards from 'src/modules/boards/interfaces/types'; -import * as User from 'src/modules/users/interfaces/types'; +import { RegisterUserUseCaseInterface } from '../interfaces/applications/register-user.use-case.interface'; +import { RegisterGuestUserUseCaseInterface } from '../interfaces/applications/register-guest-user.use-case.interface'; +import { ValidateUserEmailUseCaseInterface } from '../interfaces/applications/validate-email.use-case.interface'; +import { RefreshTokenUseCaseInterface } from '../interfaces/applications/refresh-token.use-case.interface'; +import { StatisticsAuthUserUseCaseInterface } from '../interfaces/applications/statistics.auth.use-case.interface'; +import { ResetPasswordUseCaseInterface } from '../interfaces/applications/reset-password.use-case.interface'; +import { CreateResetTokenUseCaseInterface } from '../interfaces/applications/create-reset-token.use-case.interface'; +import { SignInUseCaseInterface } from '../interfaces/applications/signIn.use-case.interface'; +import { TYPES } from '../interfaces/types'; import { createMock } from '@golevelup/ts-jest'; -import { RegisterAuthApplication } from '../applications/register.auth.application'; -import { GetTeamApplication } from 'src/modules/teams/applications/get.team.application'; -import { CreateResetTokenAuthApplication } from '../applications/create-reset-token.use-case'; -import { UpdateUserApplication } from 'src/modules/users/use-cases/update.user.application'; -import { GetBoardApplication } from 'src/modules/boards/applications/get.board.application'; -import { GetUserApplication } from 'src/modules/users/use-cases/get.user.application'; +import { ResetPasswordRepositoryInterface } from '../repository/reset-password.repository.interface'; +import { getModelToken } from '@nestjs/mongoose'; describe('AuthController', () => { let app: INestApplication; @@ -26,32 +27,44 @@ describe('AuthController', () => { controllers: [AuthController], providers: [ { - provide: Auth.TYPES.applications.RegisterAuthApplication, - useValue: createMock() + provide: TYPES.applications.RegisterUserUseCase, + useValue: createMock() }, { - provide: Auth.TYPES.applications.GetTokenAuthApplication, - useValue: createMock() + provide: TYPES.applications.RegisterGuestUserUseCase, + useValue: createMock() }, { - provide: Auth.TYPES.applications.CreateResetTokenAuthApplication, - useValue: createMock() + provide: TYPES.applications.ValidateUserEmailUseCase, + useValue: createMock() }, { - provide: Auth.TYPES.applications.UpdateUserApplication, - useValue: createMock() + provide: TYPES.applications.RefreshTokenUseCase, + useValue: createMock() }, { - provide: Teams.TYPES.applications.GetTeamApplication, - useValue: createMock() + provide: TYPES.applications.StatisticsAuthUserUseCase, + useValue: createMock() }, { - provide: Boards.TYPES.applications.GetBoardApplication, - useValue: createMock() + provide: TYPES.applications.ResetPasswordUseCase, + useValue: createMock() }, { - provide: User.TYPES.applications.GetUserApplication, - useValue: createMock() + provide: TYPES.applications.CreateResetTokenUseCase, + useValue: createMock() + }, + { + provide: TYPES.applications.SignInUseCase, + useValue: createMock() + }, + { + provide: getModelToken('ResetPassword'), + useValue: {} + }, + { + provide: TYPES.repository.ResetPasswordRepository, + useValue: createMock() } ] }).compile(); diff --git a/backend/src/modules/auth/interfaces/types.ts b/backend/src/modules/auth/interfaces/types.ts index a5dff66f8..83faa182e 100644 --- a/backend/src/modules/auth/interfaces/types.ts +++ b/backend/src/modules/auth/interfaces/types.ts @@ -15,10 +15,7 @@ export const TYPES = { SignInUseCase: 'SignInUseCase', RefreshTokenUseCase: 'RefreshTokenUseCase', ResetPasswordUseCase: 'ResetPasswordUseCase', - CreateResetTokenUseCase: 'CreateResetTokenUseCase', - CreateResetTokenAuthApplication: 'CreateResetTokenAuthApplication', - UpdatePasswordAuthApplication: 'UpdatePasswordAuthApplication', - UpdateUserApplication: 'UpdateUserApplication' + CreateResetTokenUseCase: 'CreateResetTokenUseCase' }, repository: { ResetPasswordRepository: 'ResetPasswordRepository' diff --git a/backend/src/modules/auth/services/get-token.auth.service.spec.ts b/backend/src/modules/auth/services/get-token.auth.service.spec.ts index 47f68bd07..e2fb2a2f4 100644 --- a/backend/src/modules/auth/services/get-token.auth.service.spec.ts +++ b/backend/src/modules/auth/services/get-token.auth.service.spec.ts @@ -4,18 +4,21 @@ import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; import configService from 'src/libs/test-utils/mocks/configService.mock'; import jwtService from 'src/libs/test-utils/mocks/jwtService.mock'; -import GetTokenAuthService from 'src/modules/auth/services/get-token.auth.service'; import { updateUserService, userRepository } from 'src/modules/users/users.providers'; +import { getTokenAuthService, resetPasswordRepository } from '../auth.providers'; +import { GetTokenAuthServiceInterface } from '../interfaces/services/get-token.auth.service.interface'; +import { TYPES } from '../interfaces/types'; describe('AuthService', () => { - let service: GetTokenAuthService; + let service: GetTokenAuthServiceInterface; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ - GetTokenAuthService, + getTokenAuthService, updateUserService, userRepository, + resetPasswordRepository, { provide: ConfigService, useValue: configService @@ -35,7 +38,7 @@ describe('AuthService', () => { ] }).compile(); - service = module.get(GetTokenAuthService); + service = module.get(TYPES.services.GetTokenAuthService); }); describe('when creating a jwt', () => { diff --git a/backend/src/modules/auth/services/validate-user.auth.service.spec.ts b/backend/src/modules/auth/services/validate-user.auth.service.spec.ts index d1a4f46e1..73e98bc0b 100644 --- a/backend/src/modules/auth/services/validate-user.auth.service.spec.ts +++ b/backend/src/modules/auth/services/validate-user.auth.service.spec.ts @@ -28,7 +28,7 @@ import { updateUserService, userRepository } from 'src/modules/users/users.providers'; -import { getTokenAuthService } from '../auth.providers'; +import { getTokenAuthService, resetPasswordRepository } from '../auth.providers'; jest.mock('bcrypt'); jest.mock('src/modules/schedules/services/create.schedules.service.ts'); @@ -68,6 +68,7 @@ describe('The AuthenticationService', () => { boardRepository, updateUserService, getBoardUserService, + resetPasswordRepository, { provide: ConfigService, useValue: configService diff --git a/backend/src/modules/azure/applications/auth.azure.application.ts b/backend/src/modules/azure/applications/auth.azure.application.ts deleted file mode 100644 index 420eac3ed..000000000 --- a/backend/src/modules/azure/applications/auth.azure.application.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { AuthAzureApplicationInterface } from '../interfaces/applications/auth.azure.application.interface'; -import { AuthAzureServiceInterface } from '../interfaces/services/auth.azure.service.interface'; -import { TYPES } from '../interfaces/types'; - -@Injectable() -export class AuthAzureApplication implements AuthAzureApplicationInterface { - constructor( - @Inject(TYPES.services.AuthAzureService) - private authAzureService: AuthAzureServiceInterface - ) {} - - registerOrLogin(azureToken: string) { - return this.authAzureService.loginOrRegisterAzureToken(azureToken); - } - - checkUserExistsInActiveDirectory(email: string) { - return this.authAzureService.checkUserExistsInActiveDirectory(email); - } -} diff --git a/backend/src/modules/azure/applications/check-user.azure.use-case.ts b/backend/src/modules/azure/applications/check-user.azure.use-case.ts new file mode 100644 index 000000000..b08e1b4e1 --- /dev/null +++ b/backend/src/modules/azure/applications/check-user.azure.use-case.ts @@ -0,0 +1,33 @@ +import { Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { AuthAzureServiceInterface } from '../interfaces/services/auth.azure.service.interface'; +import { TYPES } from '../interfaces/types'; +import * as UserType from 'src/modules/users/interfaces/types'; +import { GetUserServiceInterface } from 'src/modules/users/interfaces/services/get.user.service.interface'; +import { CheckUserAzureUseCaseInterface } from '../interfaces/applications/check-user.azure.use-case.interface'; +import { USER_NOT_FOUND } from 'src/libs/exceptions/messages'; + +@Injectable() +export class CheckUserAzureUseCase implements CheckUserAzureUseCaseInterface { + constructor( + @Inject(TYPES.services.AuthAzureService) + private authAzureService: AuthAzureServiceInterface, + @Inject(UserType.TYPES.services.GetUserService) + private readonly getUserService: GetUserServiceInterface + ) {} + + async execute(email: string) { + const existUserInAzure = await this.authAzureService.getUserFromAzure(email); + + if (existUserInAzure) { + return 'az'; + } + + const existUserInDB = await this.getUserService.getByEmail(email); + + if (existUserInDB) { + return 'local'; + } + + throw new NotFoundException(USER_NOT_FOUND); + } +} diff --git a/backend/src/modules/azure/applications/register-or-login.azure.use-case.ts b/backend/src/modules/azure/applications/register-or-login.azure.use-case.ts new file mode 100644 index 000000000..0834fd21d --- /dev/null +++ b/backend/src/modules/azure/applications/register-or-login.azure.use-case.ts @@ -0,0 +1,113 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { RegisterOrLoginAzureUseCaseInterface } from '../interfaces/applications/register-or-login.azure.use-case.interface'; +import { AuthAzureServiceInterface } from '../interfaces/services/auth.azure.service.interface'; +import { TYPES } from '../interfaces/types'; +import { AzureDecodedUser } from '../services/auth.azure.service'; +import jwt_decode from 'jwt-decode'; +import * as UserType from 'src/modules/users/interfaces/types'; +import { GetUserServiceInterface } from 'src/modules/users/interfaces/services/get.user.service.interface'; +import { CreateUserServiceInterface } from 'src/modules/users/interfaces/services/create.user.service.interface'; +import User from 'src/modules/users/entities/user.schema'; +import * as AuthType from 'src/modules/auth/interfaces/types'; +import * as StorageType from 'src/modules/storage/interfaces/types'; +import { UpdateUserServiceInterface } from 'src/modules/users/interfaces/services/update.user.service.interface'; +import { GetTokenAuthServiceInterface } from 'src/modules/auth/interfaces/services/get-token.auth.service.interface'; +import { signIn } from 'src/modules/auth/shared/login.auth'; +import { createHash } from 'node:crypto'; +import { StorageServiceInterface } from 'src/modules/storage/interfaces/services/storage.service'; + +@Injectable() +export class RegisterOrLoginAzureUseCase implements RegisterOrLoginAzureUseCaseInterface { + constructor( + @Inject(TYPES.services.AuthAzureService) + private authAzureService: AuthAzureServiceInterface, + @Inject(UserType.TYPES.services.GetUserService) + private readonly getUserService: GetUserServiceInterface, + @Inject(UserType.TYPES.services.CreateUserService) + private readonly createUserService: CreateUserServiceInterface, + @Inject(AuthType.TYPES.services.UpdateUserService) + private readonly updateUserService: UpdateUserServiceInterface, + @Inject(AuthType.TYPES.services.GetTokenAuthService) + private readonly getTokenService: GetTokenAuthServiceInterface, + @Inject(StorageType.TYPES.services.StorageService) + private readonly storageService: StorageServiceInterface + ) {} + + async execute(azureToken: string) { + const { unique_name, email, name, given_name, family_name } = ( + jwt_decode(azureToken) + ); + + const splitedName = name ? name.split(' ') : []; + const firstName = given_name ?? splitedName[0] ?? 'first'; + const lastName = family_name ?? splitedName[splitedName.length - 1] ?? 'last'; + + const emailOrUniqueName = email ?? unique_name; + + const userFromAzure = await this.authAzureService.getUserFromAzure(emailOrUniqueName); + + if (!userFromAzure) return null; + + const user = await this.getUserService.getByEmail(emailOrUniqueName); + + let userToAuthenticate: User; + + if (user) { + userToAuthenticate = user; + } else { + const createdUser = await this.createUserService.create({ + email: emailOrUniqueName, + firstName, + lastName, + providerAccountCreatedAt: userFromAzure.createdDateTime + }); + + if (!createdUser) return null; + + userToAuthenticate = createdUser; + } + + const avatarUrl = await this.getUserPhoto(userToAuthenticate); + + if (avatarUrl) { + await this.updateUserService.updateUserAvatar(userToAuthenticate._id, avatarUrl); + + userToAuthenticate.avatar = avatarUrl; + } + + return signIn(userToAuthenticate, this.getTokenService, 'azure'); + } + + private async getUserPhoto(user: User) { + const { email, avatar } = user; + const azureUser = await this.authAzureService.getUserFromAzure(email); + + if (!azureUser) return ''; + + try { + const blob = await this.authAzureService + .getGraphClient() + .api(`/users/${azureUser.id}/photo/$value`) + .get(); + + const buffer = Buffer.from(await blob.arrayBuffer()); + const hash = createHash('md5').update(buffer).digest('hex'); + + if (avatar) { + const avatarHash = avatar.split('/').pop().split('.').shift(); + + if (avatarHash === hash) return; + + await this.storageService.deleteFile(avatar); + } + + return this.storageService.uploadFile(hash, { + buffer, + mimetype: blob.type, + originalname: `${hash}.${blob.type.split('/').pop()}` + }); + } catch (ex) { + return ''; + } + } +} diff --git a/backend/src/modules/azure/azure.module.ts b/backend/src/modules/azure/azure.module.ts index eea30fa60..49aa54593 100644 --- a/backend/src/modules/azure/azure.module.ts +++ b/backend/src/modules/azure/azure.module.ts @@ -3,12 +3,12 @@ import AuthModule from '../auth/auth.module'; import { CommunicationModule } from '../communication/communication.module'; import { StorageModule } from '../storage/storage.module'; import UsersModule from '../users/users.module'; -import { authAzureApplication, authAzureService } from './azure.providers'; +import { authAzureService, checkUserUseCase, registerOrLoginUseCase } from './azure.providers'; import AzureController from './controller/azure.controller'; @Module({ imports: [UsersModule, AuthModule, CommunicationModule, StorageModule], controllers: [AzureController], - providers: [authAzureService, authAzureApplication] + providers: [authAzureService, checkUserUseCase, registerOrLoginUseCase] }) export default class AzureModule {} diff --git a/backend/src/modules/azure/azure.providers.ts b/backend/src/modules/azure/azure.providers.ts index 4121c75b6..a1f4190fc 100644 --- a/backend/src/modules/azure/azure.providers.ts +++ b/backend/src/modules/azure/azure.providers.ts @@ -1,4 +1,5 @@ -import { AuthAzureApplication } from './applications/auth.azure.application'; +import { CheckUserAzureUseCase } from './applications/check-user.azure.use-case'; +import { RegisterOrLoginAzureUseCase } from './applications/register-or-login.azure.use-case'; import { TYPES } from './interfaces/types'; import AuthAzureService from './services/auth.azure.service'; @@ -7,7 +8,12 @@ export const authAzureService = { useClass: AuthAzureService }; -export const authAzureApplication = { - provide: TYPES.applications.AuthAzureApplication, - useClass: AuthAzureApplication +export const checkUserUseCase = { + provide: TYPES.applications.CheckUserUseCase, + useClass: CheckUserAzureUseCase +}; + +export const registerOrLoginUseCase = { + provide: TYPES.applications.RegisterOrLoginUseCase, + useClass: RegisterOrLoginAzureUseCase }; diff --git a/backend/src/modules/azure/controller/azure.controller.spec.ts b/backend/src/modules/azure/controller/azure.controller.spec.ts index fbd5cc7a9..d0e12285f 100644 --- a/backend/src/modules/azure/controller/azure.controller.spec.ts +++ b/backend/src/modules/azure/controller/azure.controller.spec.ts @@ -3,8 +3,9 @@ import * as User from 'src/modules/users/interfaces/types'; import { Test } from '@nestjs/testing'; import { createMock } from '@golevelup/ts-jest'; import AzureController from './azure.controller'; -import { AuthAzureApplication } from '../applications/auth.azure.application'; import { GetUserServiceInterface } from 'src/modules/users/interfaces/services/get.user.service.interface'; +import { RegisterAuthServiceInterface } from 'src/modules/auth/interfaces/services/register.auth.service.interface'; +import { CheckUserAzureUseCaseInterface } from '../interfaces/applications/check-user.azure.use-case.interface'; describe('AzureController', () => { let controller: AzureController; @@ -14,8 +15,12 @@ describe('AzureController', () => { controllers: [AzureController], providers: [ { - provide: Azure.TYPES.applications.AuthAzureApplication, - useValue: createMock() + provide: Azure.TYPES.applications.RegisterOrLoginUseCase, + useValue: createMock() + }, + { + provide: Azure.TYPES.applications.CheckUserUseCase, + useValue: createMock() }, { provide: User.TYPES.services.GetUserService, diff --git a/backend/src/modules/azure/controller/azure.controller.ts b/backend/src/modules/azure/controller/azure.controller.ts index 612db36b3..65a79bde2 100644 --- a/backend/src/modules/azure/controller/azure.controller.ts +++ b/backend/src/modules/azure/controller/azure.controller.ts @@ -1,13 +1,4 @@ -import { - Body, - Controller, - Get, - HttpCode, - Inject, - NotFoundException, - Param, - Post -} from '@nestjs/common'; +import { Body, Controller, Get, HttpCode, Inject, Param, Post } from '@nestjs/common'; import { ApiBadRequestResponse, ApiBody, @@ -20,26 +11,24 @@ import { ApiUnauthorizedResponse } from '@nestjs/swagger'; import { EmailParam } from 'src/libs/dto/param/email.param'; -import { USER_NOT_FOUND } from 'src/libs/exceptions/messages'; import { BadRequestResponse } from 'src/libs/swagger/errors/bad-request.swagger'; import { InternalServerErrorResponse } from 'src/libs/swagger/errors/internal-server-error.swagger'; import { NotFoundResponse } from 'src/libs/swagger/errors/not-found.swagger'; import { UnauthorizedResponse } from 'src/libs/swagger/errors/unauthorized.swagger'; import { LoginResponse } from 'src/modules/auth/swagger/login.swagger'; -import * as User from 'src/modules/users/interfaces/types'; -import { AuthAzureApplicationInterface } from '../interfaces/applications/auth.azure.application.interface'; +import { RegisterOrLoginAzureUseCaseInterface } from '../interfaces/applications/register-or-login.azure.use-case.interface'; import { AzureToken } from '../interfaces/token.azure.dto'; import { TYPES } from '../interfaces/types'; -import { GetUserServiceInterface } from 'src/modules/users/interfaces/services/get.user.service.interface'; +import { CheckUserAzureUseCaseInterface } from '../interfaces/applications/check-user.azure.use-case.interface'; @ApiTags('Azure') @Controller('auth/azure') export default class AzureController { constructor( - @Inject(TYPES.applications.AuthAzureApplication) - private authAzureApp: AuthAzureApplicationInterface, - @Inject(User.TYPES.services.GetUserService) - private getUserService: GetUserServiceInterface + @Inject(TYPES.applications.RegisterOrLoginUseCase) + private registerOrLoginUseCase: RegisterOrLoginAzureUseCaseInterface, + @Inject(TYPES.applications.CheckUserUseCase) + private checkUserUseCase: CheckUserAzureUseCaseInterface ) {} @ApiOperation({ @@ -75,7 +64,7 @@ export default class AzureController { @HttpCode(200) @Post('/') loginOrRegistetruerAzureToken(@Body() azureToken: AzureToken) { - return this.authAzureApp.registerOrLogin(azureToken.token); + return this.registerOrLoginUseCase.execute(azureToken.token); } @ApiParam({ @@ -105,18 +94,6 @@ export default class AzureController { }) @Get('users/:email') async checkEmail(@Param() { email }: EmailParam) { - const existUserInAzure = await this.authAzureApp.checkUserExistsInActiveDirectory(email); - - if (existUserInAzure) { - return 'az'; - } - - const existUserInDB = await this.getUserService.getByEmail(email); - - if (existUserInDB) { - return 'local'; - } - - throw new NotFoundException(USER_NOT_FOUND); + return this.checkUserUseCase.execute(email); } } diff --git a/backend/src/modules/azure/interfaces/applications/auth.azure.application.interface.ts b/backend/src/modules/azure/interfaces/applications/auth.azure.application.interface.ts deleted file mode 100644 index 25298d0c8..000000000 --- a/backend/src/modules/azure/interfaces/applications/auth.azure.application.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import LoggedUserDto from 'src/modules/users/dto/logged.user.dto'; - -export interface AuthAzureApplicationInterface { - registerOrLogin(azureToken: string): Promise; - - checkUserExistsInActiveDirectory(email: string): Promise; -} diff --git a/backend/src/modules/azure/interfaces/applications/check-user.azure.use-case.interface.ts b/backend/src/modules/azure/interfaces/applications/check-user.azure.use-case.interface.ts new file mode 100644 index 000000000..a05dd1796 --- /dev/null +++ b/backend/src/modules/azure/interfaces/applications/check-user.azure.use-case.interface.ts @@ -0,0 +1,3 @@ +export interface CheckUserAzureUseCaseInterface { + execute(email: string): Promise<'local' | 'az'>; +} diff --git a/backend/src/modules/azure/interfaces/applications/register-or-login.azure.use-case.interface.ts b/backend/src/modules/azure/interfaces/applications/register-or-login.azure.use-case.interface.ts new file mode 100644 index 000000000..d4c2730ae --- /dev/null +++ b/backend/src/modules/azure/interfaces/applications/register-or-login.azure.use-case.interface.ts @@ -0,0 +1,5 @@ +import LoggedUserDto from 'src/modules/users/dto/logged.user.dto'; + +export interface RegisterOrLoginAzureUseCaseInterface { + execute(azureToken: string): Promise; +} diff --git a/backend/src/modules/azure/interfaces/services/auth.azure.service.interface.ts b/backend/src/modules/azure/interfaces/services/auth.azure.service.interface.ts index 33394064a..6958ca357 100644 --- a/backend/src/modules/azure/interfaces/services/auth.azure.service.interface.ts +++ b/backend/src/modules/azure/interfaces/services/auth.azure.service.interface.ts @@ -1,7 +1,8 @@ -import LoggedUserDto from 'src/modules/users/dto/logged.user.dto'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { AzureUserFound } from '../../services/auth.azure.service'; export interface AuthAzureServiceInterface { - loginOrRegisterAzureToken(azureToken: string): Promise; + getUserFromAzure(email: string): Promise; - checkUserExistsInActiveDirectory(email: string): Promise; + getGraphClient(): Client; } diff --git a/backend/src/modules/azure/interfaces/types.ts b/backend/src/modules/azure/interfaces/types.ts index b14a3834d..d893438ca 100644 --- a/backend/src/modules/azure/interfaces/types.ts +++ b/backend/src/modules/azure/interfaces/types.ts @@ -1,9 +1,9 @@ export const TYPES = { services: { - AuthAzureService: 'AuthAzureService', - CronAzureService: 'CronAzureService' + AuthAzureService: 'AuthAzureService' }, applications: { - AuthAzureApplication: 'AuthAzureApplication' + RegisterOrLoginUseCase: 'RegisterOrLoginUseCase', + CheckUserUseCase: 'CheckUserUseCase' } }; diff --git a/backend/src/modules/azure/services/auth.azure.service.ts b/backend/src/modules/azure/services/auth.azure.service.ts index c07a3afb5..ceb786864 100644 --- a/backend/src/modules/azure/services/auth.azure.service.ts +++ b/backend/src/modules/azure/services/auth.azure.service.ts @@ -1,24 +1,12 @@ -import { Inject, Injectable } from '@nestjs/common'; -import jwt_decode from 'jwt-decode'; +import { Injectable } from '@nestjs/common'; import isEmpty from 'src/libs/utils/isEmpty'; -import { GetTokenAuthServiceInterface } from 'src/modules/auth/interfaces/services/get-token.auth.service.interface'; -import * as AuthType from 'src/modules/auth/interfaces/types'; -import * as StorageType from 'src/modules/storage/interfaces/types'; -import { signIn } from 'src/modules/auth/shared/login.auth'; -import { CreateUserServiceInterface } from 'src/modules/users/interfaces/services/create.user.service.interface'; -import { GetUserServiceInterface } from 'src/modules/users/interfaces/services/get.user.service.interface'; -import * as UserType from 'src/modules/users/interfaces/types'; import { AuthAzureServiceInterface } from '../interfaces/services/auth.azure.service.interface'; import { ConfidentialClientApplication } from '@azure/msal-node'; import { Client } from '@microsoft/microsoft-graph-client'; import { ConfigService } from '@nestjs/config'; import { AZURE_AUTHORITY, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET } from 'src/libs/constants/azure'; -import { createHash } from 'node:crypto'; -import User from 'src/modules/users/entities/user.schema'; -import { StorageServiceInterface } from 'src/modules/storage/interfaces/services/storage.service'; -import { UpdateUserServiceInterface } from 'src/modules/users/interfaces/services/update.user.service.interface'; -type AzureUserFound = { +export type AzureUserFound = { id: string; mail: string; displayName: string; @@ -26,7 +14,7 @@ type AzureUserFound = { createdDateTime: Date; }; -type AzureDecodedUser = { +export type AzureDecodedUser = { unique_name: string; email: string; given_name: string; @@ -39,19 +27,7 @@ export default class AuthAzureService implements AuthAzureServiceInterface { private graphClient: Client; private authCredentials: { accessToken: string; expiresOn: Date }; - constructor( - @Inject(UserType.TYPES.services.CreateUserService) - private readonly createUserService: CreateUserServiceInterface, - @Inject(UserType.TYPES.services.GetUserService) - private readonly getUserService: GetUserServiceInterface, - @Inject(AuthType.TYPES.services.UpdateUserService) - private readonly updateUserService: UpdateUserServiceInterface, - @Inject(AuthType.TYPES.services.GetTokenAuthService) - private readonly getTokenService: GetTokenAuthServiceInterface, - private readonly configService: ConfigService, - @Inject(StorageType.TYPES.services.StorageService) - private readonly storageService: StorageServiceInterface - ) { + constructor(private readonly configService: ConfigService) { const confidentialClient = new ConfidentialClientApplication({ auth: { clientId: configService.get(AZURE_CLIENT_ID), @@ -79,49 +55,8 @@ export default class AuthAzureService implements AuthAzureServiceInterface { }); } - async loginOrRegisterAzureToken(azureToken: string) { - const { unique_name, email, name, given_name, family_name } = ( - jwt_decode(azureToken) - ); - - const splitedName = name ? name.split(' ') : []; - const firstName = given_name ?? splitedName[0] ?? 'first'; - const lastName = family_name ?? splitedName[splitedName.length - 1] ?? 'last'; - - const emailOrUniqueName = email ?? unique_name; - - const userFromAzure = await this.getUserFromAzure(emailOrUniqueName); - - if (!userFromAzure) return null; - - const user = await this.getUserService.getByEmail(emailOrUniqueName); - - let userToAuthenticate: User; - - if (user) { - userToAuthenticate = user; - } else { - const createdUser = await this.createUserService.create({ - email: emailOrUniqueName, - firstName, - lastName, - providerAccountCreatedAt: userFromAzure.createdDateTime - }); - - if (!createdUser) return null; - - userToAuthenticate = createdUser; - } - - const avatarUrl = await this.getUserPhoto(userToAuthenticate); - - if (avatarUrl) { - await this.updateUserService.updateUserAvatar(userToAuthenticate._id, avatarUrl); - - userToAuthenticate.avatar = avatarUrl; - } - - return signIn(userToAuthenticate, this.getTokenService, 'azure'); + getGraphClient() { + return this.graphClient; } async getUserFromAzure(email: string): Promise { @@ -140,34 +75,4 @@ export default class AuthAzureService implements AuthAzureServiceInterface { return !isEmpty(data); } - - private async getUserPhoto(user: User) { - const { email, avatar } = user; - const azureUser = await this.getUserFromAzure(email); - - if (!azureUser) return ''; - - try { - const blob = await this.graphClient.api(`/users/${azureUser.id}/photo/$value`).get(); - - const buffer = Buffer.from(await blob.arrayBuffer()); - const hash = createHash('md5').update(buffer).digest('hex'); - - if (avatar) { - const avatarHash = avatar.split('/').pop().split('.').shift(); - - if (avatarHash === hash) return; - - await this.storageService.deleteFile(avatar); - } - - return this.storageService.uploadFile(hash, { - buffer, - mimetype: blob.type, - originalname: `${hash}.${blob.type.split('/').pop()}` - }); - } catch (ex) { - return ''; - } - } } diff --git a/backend/src/modules/boards/services/update.board.service.spec.ts b/backend/src/modules/boards/services/update.board.service.spec.ts index 9639c6204..e506c8d08 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -6,6 +6,7 @@ import * as CommunicationsType from 'src/modules/communication/interfaces/types' import * as Cards from 'src/modules/cards/interfaces/types'; import * as Boards from 'src/modules/boards/interfaces/types'; import * as Teams from 'src/modules/teams/interfaces/types'; +import * as BoardUsers from 'src/modules/boardusers/interfaces/types'; import { DeepMocked, createMock } from '@golevelup/ts-jest'; import { BoardPhases } from 'src/libs/enum/board.phases'; import { BadRequestException } from '@nestjs/common'; @@ -19,6 +20,10 @@ import { BoardRepositoryInterface } from '../repositories/board.repository.inter import { updateBoardService } from '../boards.providers'; import { UpdateBoardServiceInterface } from '../interfaces/services/update.board.service.interface'; import { TeamFactory } from 'src/libs/test-utils/mocks/factories/team-factory.mock'; +import { CreateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/create.board.user.service.interface'; +import { DeleteBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/delete.board.user.service.interface'; +import { GetBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/get.board.user.service.interface'; +import { UpdateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/update.board.user.service.interface'; describe('UpdateBoardService', () => { let service: UpdateBoardServiceInterface; @@ -58,8 +63,20 @@ describe('UpdateBoardService', () => { useValue: createMock() }, { - provide: Boards.TYPES.repositories.BoardUserRepository, - useValue: {} + provide: BoardUsers.TYPES.services.CreateBoardUserService, + useValue: createMock() + }, + { + provide: BoardUsers.TYPES.services.GetBoardUserService, + useValue: createMock() + }, + { + provide: BoardUsers.TYPES.services.UpdateBoardUserService, + useValue: createMock() + }, + { + provide: BoardUsers.TYPES.services.DeleteBoardUserService, + useValue: createMock() }, { provide: EventEmitter2, diff --git a/backend/src/modules/communication/handlers/conversations-slack.handler.spec.ts b/backend/src/modules/communication/handlers/conversations-slack.handler.spec.ts index fc961a48e..5ea2438da 100644 --- a/backend/src/modules/communication/handlers/conversations-slack.handler.spec.ts +++ b/backend/src/modules/communication/handlers/conversations-slack.handler.spec.ts @@ -1,33 +1,31 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/lines-between-class-members */ import { UserDto } from 'src/modules/communication/dto/user.dto'; import { ConversationsSlackHandler } from 'src/modules/communication/handlers/conversations-slack.handler'; import { CommunicationGateAdapterInterface } from 'src/modules/communication/interfaces/communication-gate.adapter.interface'; const MakeSlackCommunicationGateAdapterStub = () => { class SlackCommunicationGateAdapterStub implements CommunicationGateAdapterInterface { - archive(channelId: string): Promise<{ ok: boolean; error?: string }> { + archive(_channelId: string): Promise<{ ok: boolean; error?: string }> { return Promise.resolve({ ok: true }); } - getEmailByPlatformUserId(email: string): Promise { + getEmailByPlatformUserId(_email: string): Promise { throw new Error('Method not implemented.'); } addChannel(name: string): Promise<{ id: string; name: string }> { return Promise.resolve({ id: 'any_id', name }); } addUsersToChannel( - channelId: string, - usersIds: string[] + _channelId: string, + _usersIds: string[] ): Promise<{ ok: boolean; fails?: string[] | undefined }> { return Promise.resolve({ ok: true }); } - getAllUsersByChannel(channelId: string): Promise { + getAllUsersByChannel(_channelId: string): Promise { return Promise.resolve(['any_user_id']); } - getEmailByUserId(userId: string): Promise { + getEmailByUserId(_userId: string): Promise { throw new Error('Method not implemented.'); } - addMessageToChannel(channelId: string, message: string): Promise<{ ok: boolean }> { + addMessageToChannel(_channelId: string, _message: string): Promise<{ ok: boolean }> { throw new Error('Method not implemented.'); } } diff --git a/backend/src/modules/users/applications/delete-user.use-case.ts b/backend/src/modules/users/applications/delete-user.use-case.ts index be4817ccb..d664e3b57 100644 --- a/backend/src/modules/users/applications/delete-user.use-case.ts +++ b/backend/src/modules/users/applications/delete-user.use-case.ts @@ -26,7 +26,7 @@ export class DeleteUserUseCase implements DeleteUserUseCaseInterface { await this.userRepository.startTransaction(); try { - this.deleteUser(userId, true); + await this.deleteUser(userId, true); const teamsOfUser = await this.getTeamUserService.getTeamsOfUser(userId); if (teamsOfUser.length > 0) {