Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 1x | import {
Body,
Controller,
ForbiddenException,
Get,
Inject,
Param,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { omit } from 'lodash-es';
import { type Company as CompanyEntity, type User as UserEntity } from '@amalia/core/models';
import { assert } from '@amalia/ext/typescript';
import { BooleanPipe, EnumPipe, NumberPipe } from '@amalia/kernel/api';
import {
AmaliaAuthGuard,
CheckPolicies,
CurrentAuthenticatedContext,
CurrentUser,
CurrentUserCompany,
GetRawJwt,
PoliciesGuard,
ZodBodyValidationPipe,
} from '@amalia/kernel/auth/core';
import {
canImpersonateUsers,
canModifyUserProfiles,
canViewThisUserProfile,
defineAbilityFor,
} from '@amalia/kernel/auth/shared';
import { AuthenticatedContextDto, type AuthenticatedContext } from '@amalia/kernel/auth/types';
import { BulkCreateUserDto } from '@amalia/tenants/companies/core';
import { HierarchyContextService } from '@amalia/tenants/teams/hierarchy/core';
import {
UserRole,
type ImpersonateUserRequest,
type ListUsersRequest,
type UserContract,
type UserSettings,
} from '@amalia/tenants/users/types';
import { AppUsersRepository } from './appUsers.repository';
import { impersonateUserRequestSchema } from './dto/impersonate.dto';
import { listUsersRequestSchema } from './dto/list-users.dto';
import { userSettingsSchema } from './dto/user-settings.dto';
import { toUserResponseDto } from './dto/user.response-dto';
import { LogoutUserUseCase } from './usecases/logout-user.use-case';
import { RegisterUsersUseCase } from './usecases/register-users.use-case';
import { UpdateImpersonateTargetUseCase } from './usecases/update-impersonate-target.use-case';
import { UpdateUserAfterConnectionUseCase } from './usecases/update-user-after-connection.use-case';
import { UpdateUserSettingsUseCase } from './usecases/update-user-settings.use-case';
/**
* UNSAFE DTO
*
* DTOs missing for UserSettings, Avatar, Update...
*/
@UseGuards(AmaliaAuthGuard, PoliciesGuard)
@ApiBearerAuth()
@ApiTags('users')
@Controller('users')
export class UsersController {
public constructor(
@Inject(AppUsersRepository)
private readonly appUsersRepository: AppUsersRepository,
private readonly hierarchyContextService: HierarchyContextService,
private readonly logoutUserUseCase: LogoutUserUseCase,
private readonly registerUserUseCase: RegisterUsersUseCase,
private readonly updateImpersonateTargetUseCase: UpdateImpersonateTargetUseCase,
private readonly updateUserAfterConnectionUseCase: UpdateUserAfterConnectionUseCase,
private readonly updateUserSettingsUseCase: UpdateUserSettingsUseCase,
) {}
/**
* Returns connected user's identity.
* @param company
* @param authenticatedContext
* @param jwt
*/
@Get('me')
public async getAuthenticatedUser(
@CurrentUserCompany() company: CompanyEntity,
@CurrentAuthenticatedContext() authenticatedContext: AuthenticatedContext,
@GetRawJwt() jwt: string,
): Promise<AuthenticatedContextDto> {
// The frontend calls this endpoint when the app starts, so now would be a good time to update the tracker.
// Avoid updating if it's an admin user impersonating.
const { user: userToReturn, previousLastConnection } = authenticatedContext.meta?.amaliaImpersonatorEmail
? { user: authenticatedContext.user, previousLastConnection: null }
: await this.updateUserAfterConnectionUseCase.execute({ company, authenticatedContext, token: jwt });
return {
...omit(authenticatedContext, ['hierarchy', 'adminScopeContainer']),
user: userToReturn,
login: {
previousLastConnection,
},
hierarchyContextDehydrated: authenticatedContext.hierarchy.dehydrate(),
adminScopesContainerDehydrated: authenticatedContext.adminScopesContainer?.dehydrate() ?? null,
};
}
@Get(':userId/managers')
public async getUserManagers(
@CurrentUserCompany() company: CompanyEntity,
@CurrentAuthenticatedContext() authenticatedContext: AuthenticatedContext,
@Param('userId') userId: UserContract['id'],
@Query('startDate', new NumberPipe()) startDate: number,
@Query('endDate', new NumberPipe()) endDate: number,
@Query('filterByUserRoles', new EnumPipe({ enum: UserRole, multiple: true, optional: true }))
filterByUserRoles: UserRole[] = [],
@Query('withParents', new BooleanPipe({ optional: true })) withParents: boolean = true,
): Promise<UserContract[]> {
const ability = defineAbilityFor(authenticatedContext);
assert(canViewThisUserProfile(ability, { id: userId }), new ForbiddenException('You cannot view this user'));
const managers = await this.hierarchyContextService.getUserManagers(
company,
userId,
{
endDate,
startDate,
},
filterByUserRoles,
withParents,
);
// Filter out managers that the user cannot view
return managers.filter((manager) => canViewThisUserProfile(ability, { id: manager.id }));
}
@Patch('impersonate')
public async impersonate(
@CurrentUserCompany() company: CompanyEntity,
@CurrentAuthenticatedContext() authenticatedContext: AuthenticatedContext,
@ZodBodyValidationPipe({ schema: impersonateUserRequestSchema }) { userIdToImpersonate }: ImpersonateUserRequest,
): Promise<void> {
// If the user is not impersonating, we have to check the ability of the user to impersonate
if (!authenticatedContext.impersonation && !canImpersonateUsers(defineAbilityFor(authenticatedContext))) {
throw new ForbiddenException("You're not allowed to access to impersonation");
}
await this.updateImpersonateTargetUseCase.execute({ company, authenticatedContext, userIdToImpersonate });
}
/**
* Logout is actually done on the SPA, by querying auth0 to revoke our token.
*
* However, we're tracking the logout event on our backend.
*
* @param authenticatedContext
*/
@Patch('logout')
public async logout(@CurrentAuthenticatedContext() authenticatedContext: AuthenticatedContext): Promise<void> {
// Don't audit if user is super admin.
if (authenticatedContext.impersonation || authenticatedContext.meta?.amaliaImpersonatorEmail) {
return;
}
await this.logoutUserUseCase.execute({ authenticatedContext });
}
@Post('searches')
public async search(
@CurrentUserCompany() company: CompanyEntity,
@ZodBodyValidationPipe({ schema: listUsersRequestSchema })
{ externalIds: userExternalIds, ids: userIds, active, emails: userEmails }: ListUsersRequest,
): Promise<UserContract[]> {
const employees = await this.appUsersRepository.searchUsers(company, {
active,
userExternalIds,
userIds,
userEmails,
});
return employees.map(toUserResponseDto);
}
@Patch('settings')
public async updateSettings(
@CurrentUser() user: UserEntity,
@ZodBodyValidationPipe({ schema: userSettingsSchema }) settings: UserSettings,
): Promise<UserContract> {
return toUserResponseDto(await this.updateUserSettingsUseCase.execute({ currentUser: user, settings }));
}
/**
* Register/sync new users in batch.
* @param company the company to join
* @param bulkSyncUsersRequest
* @param currentUser
*/
@Post('registrations')
@CheckPolicies((ability) => canModifyUserProfiles(ability))
public async bulkSyncUsers(
@CurrentUserCompany() company: CompanyEntity,
@Body() bulkSyncUsersRequest: BulkCreateUserDto,
@CurrentUser() currentUser: UserEntity,
): Promise<void> {
return this.registerUserUseCase.execute({ company, users: bulkSyncUsersRequest.users, currentUser });
}
}
|