diff --git a/apps/api/src/security/dto/transfer-organization.dto.ts b/apps/api/src/security/dto/transfer-organization.dto.ts new file mode 100644 index 0000000..57ab064 --- /dev/null +++ b/apps/api/src/security/dto/transfer-organization.dto.ts @@ -0,0 +1,7 @@ +import { IsMongoId } from 'class-validator'; +import { ITransferOrganizationDto } from 'shared-types'; + +export class TransferOrganizationDto implements ITransferOrganizationDto { + @IsMongoId() + user: string; +} diff --git a/apps/api/src/security/security.controller.ts b/apps/api/src/security/security.controller.ts index 4ee2cba..5851707 100644 --- a/apps/api/src/security/security.controller.ts +++ b/apps/api/src/security/security.controller.ts @@ -30,6 +30,8 @@ import { SecurityUtils } from './helpers/security.utils'; import { HasOrganizationAccessPipe } from './pipes/has-organization-access.pipe'; import { SecurityValidationPipe } from './pipes/security-validation.pipe'; import { SecurityService } from './security.service'; +import { HasOwnerAccessPipe } from './pipes/has-owner-access.pipe'; +import { TransferOrganizationDto } from './dto/transfer-organization.dto'; @Controller('security') @ApiTags('security') @@ -109,6 +111,19 @@ export class SecurityController { return { statusCode: 200 }; } + @Post(':org/transfer') + @ApiOperation({ summary: 'Transfer organization ownership to another user' }) + @HttpCode(200) + async transfer( + @Param('org', ParseObjectIdPipe, HasOwnerAccessPipe) org: Types.ObjectId, + @Body() body: TransferOrganizationDto, + ): Promise { + const to = new Types.ObjectId(body.user); + + await this.securityService.transferOwnership(org, to); + return { statusCode: 200 }; + } + @Get(':id') @ApiOperation({ summary: 'List organization security rules' }) async listRules( diff --git a/apps/api/src/security/security.service.ts b/apps/api/src/security/security.service.ts index 2021031..793772c 100644 --- a/apps/api/src/security/security.service.ts +++ b/apps/api/src/security/security.service.ts @@ -24,7 +24,11 @@ export class SecurityService { return rule ? rule.role : null; } - async addRule(org: Types.ObjectId, user: Types.ObjectId) { + async addRule( + org: Types.ObjectId, + user: Types.ObjectId, + role: OrganizationSecurityRole = OrganizationSecurityRole.MEMBER, + ) { const ruleExist = await this.ruleExist(org, user._id); if (ruleExist) { throw new BadRequestException('This user is already a member of this organization'); @@ -32,10 +36,39 @@ export class SecurityService { return this.organizationAclService.addRule(org, { user: user._id, - role: OrganizationSecurityRole.MEMBER, + role, }); } + async transferOwnership(organization: Types.ObjectId, to: Types.ObjectId) { + const owner = await this.organizationAclService.getOwner(organization); + + if (owner.equals(to)) { + throw new BadRequestException('Provided user is already owner of this organization'); + } + + const isTargetAnMember = await this.ruleExist(organization, to); + if (!isTargetAnMember) { + throw new BadRequestException('Provided user is not a part of this organization'); + } + + const role = await this.organizationAclService.updateRule( + organization, + to, + OrganizationSecurityRole.OWNER, + ); + + if (owner) { + await this.organizationAclService.updateRule( + organization, + owner, + OrganizationSecurityRole.ADMIN, + ); + } + + return role; + } + async updateRule(org: Types.ObjectId, user: Types.ObjectId, newRole: OrganizationSecurityRole) { return this.organizationAclService.updateRule(org, user, newRole); } diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index b306597..2c4a9da 100644 --- a/packages/shared-types/src/index.ts +++ b/packages/shared-types/src/index.ts @@ -36,6 +36,7 @@ import { ICreateWarehouseDto } from './warehouse/ICreateWarehouseDto'; import { ICreateWarehouseInOrgDto } from './warehouse/ICreateWarehouseInOrgDto'; import { IUpdateWarehouseDto } from './warehouse/IUpdateWarehouseDto'; import { WarehouseDto } from './warehouse/WarehouseDto'; +import { ITransferOrganizationDto } from './organizations/ITransferOrganizationDto'; export { BasicInventoryItemDto, @@ -76,5 +77,6 @@ export { IUpdateEmailDto, IDeleteAccountDto, ApiKeyDto, + ITransferOrganizationDto, }; diff --git a/packages/shared-types/src/organizations/ITransferOrganizationDto.ts b/packages/shared-types/src/organizations/ITransferOrganizationDto.ts new file mode 100644 index 0000000..f9f08c2 --- /dev/null +++ b/packages/shared-types/src/organizations/ITransferOrganizationDto.ts @@ -0,0 +1,3 @@ +export interface ITransferOrganizationDto { + user: string; +}