Skip to content

Commit 6c783d0

Browse files
authored
Merge pull request #1562 from rocket-admin/backend_security_report
enhance email verification and password reset processes
2 parents 2afe37c + 89fe0a3 commit 6c783d0

21 files changed

+725
-698
lines changed

backend/src/entities/company-info/invitation-in-company/repository/invitation-in-company-custom-repository-extension.ts

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,62 @@ import { InvitationInCompanyEntity } from '../invitation-in-company.entity.js';
55
import { IInvitationInCompanyRepository } from './invitation-repository.interface.js';
66

77
export const invitationInCompanyCustomRepositoryExtension: IInvitationInCompanyRepository = {
8-
async createOrUpdateInvitationInCompany(
9-
companyInfo: CompanyInfoEntity,
10-
groupId: string | null,
11-
inviterId: string,
12-
newUserEmail: string,
13-
invitedUserRole: UserRoleEnum,
14-
): Promise<InvitationInCompanyEntity> {
15-
const qb = this.createQueryBuilder('invitation_in_company')
16-
.leftJoinAndSelect('invitation_in_company.company', 'company')
17-
.where('company.id = :companyId', { companyId: companyInfo.id })
18-
.andWhere('invitation_in_company.invitedUserEmail = :newUserEmail', { newUserEmail: newUserEmail?.toLowerCase() });
19-
const foundInvitation = await qb.getOne();
20-
if (foundInvitation) {
21-
await this.remove(foundInvitation);
22-
}
23-
const newInvitation = new InvitationInCompanyEntity();
24-
newInvitation.verification_string = Encryptor.generateRandomString();
25-
newInvitation.company = companyInfo;
26-
newInvitation.groupId = groupId ? groupId : null;
27-
newInvitation.inviterId = inviterId;
28-
newInvitation.invitedUserEmail = newUserEmail?.toLowerCase();
29-
newInvitation.role = invitedUserRole;
30-
return await this.save(newInvitation);
31-
},
8+
async createOrUpdateInvitationInCompany(
9+
companyInfo: CompanyInfoEntity,
10+
groupId: string | null,
11+
inviterId: string,
12+
newUserEmail: string,
13+
invitedUserRole: UserRoleEnum,
14+
): Promise<{ entity: InvitationInCompanyEntity; rawToken: string }> {
15+
const qb = this.createQueryBuilder('invitation_in_company')
16+
.leftJoinAndSelect('invitation_in_company.company', 'company')
17+
.where('company.id = :companyId', { companyId: companyInfo.id })
18+
.andWhere('invitation_in_company.invitedUserEmail = :newUserEmail', {
19+
newUserEmail: newUserEmail?.toLowerCase(),
20+
});
21+
const foundInvitation = await qb.getOne();
22+
if (foundInvitation) {
23+
await this.remove(foundInvitation);
24+
}
25+
const rawToken = Encryptor.generateRandomString();
26+
const newInvitation = new InvitationInCompanyEntity();
27+
newInvitation.verification_string = Encryptor.hashVerificationToken(rawToken);
28+
newInvitation.company = companyInfo;
29+
newInvitation.groupId = groupId ? groupId : null;
30+
newInvitation.inviterId = inviterId;
31+
newInvitation.invitedUserEmail = newUserEmail?.toLowerCase();
32+
newInvitation.role = invitedUserRole;
33+
const entity = await this.save(newInvitation);
34+
return { entity, rawToken };
35+
},
3236

33-
async deleteOldInvitationsInCompany(companyId: string): Promise<void> {
34-
const qb = this.createQueryBuilder('invitation_in_company')
35-
.leftJoinAndSelect('invitation_in_company.company', 'company')
36-
.where('company.id = :companyId', { companyId })
37-
.andWhere("invitation_in_company.createdAt < NOW() - INTERVAL '1 day'");
38-
const foundInvitations = await qb.getMany();
39-
if (foundInvitations.length) {
40-
await this.remove(foundInvitations);
41-
}
42-
},
37+
async deleteOldInvitationsInCompany(companyId: string): Promise<void> {
38+
const qb = this.createQueryBuilder('invitation_in_company')
39+
.leftJoinAndSelect('invitation_in_company.company', 'company')
40+
.where('company.id = :companyId', { companyId })
41+
.andWhere("invitation_in_company.createdAt < NOW() - INTERVAL '1 day'");
42+
const foundInvitations = await qb.getMany();
43+
if (foundInvitations.length) {
44+
await this.remove(foundInvitations);
45+
}
46+
},
4347

44-
async findNonExpiredInvitationInCompanyWithUsersByVerificationString(
45-
verificationString: string,
46-
): Promise<InvitationInCompanyEntity> {
47-
const qb = this.createQueryBuilder('invitation_in_company')
48-
.leftJoinAndSelect('invitation_in_company.company', 'company')
49-
.leftJoinAndSelect('company.users', 'users')
50-
.where("invitation_in_company.createdAt > NOW() - INTERVAL '1 day'")
51-
.where('invitation_in_company.verification_string = :verificationString', { verificationString });
52-
return await qb.getOne();
53-
},
48+
async findNonExpiredInvitationInCompanyWithUsersByVerificationString(
49+
verificationString: string,
50+
): Promise<InvitationInCompanyEntity> {
51+
const qb = this.createQueryBuilder('invitation_in_company')
52+
.leftJoinAndSelect('invitation_in_company.company', 'company')
53+
.leftJoinAndSelect('company.users', 'users')
54+
.where("invitation_in_company.createdAt > NOW() - INTERVAL '1 day'")
55+
.where('invitation_in_company.verification_string = :verificationString', { verificationString });
56+
return await qb.getOne();
57+
},
5458

55-
async countNonExpiredInvitationsInCompany(companyId: string): Promise<number> {
56-
const qb = this.createQueryBuilder('invitation_in_company')
57-
.leftJoin('invitation_in_company.company', 'company')
58-
.where('company.id = :companyId', { companyId })
59-
.andWhere("invitation_in_company.createdAt > NOW() - INTERVAL '1 day'");
60-
return await qb.getCount();
61-
},
59+
async countNonExpiredInvitationsInCompany(companyId: string): Promise<number> {
60+
const qb = this.createQueryBuilder('invitation_in_company')
61+
.leftJoin('invitation_in_company.company', 'company')
62+
.where('company.id = :companyId', { companyId })
63+
.andWhere("invitation_in_company.createdAt > NOW() - INTERVAL '1 day'");
64+
return await qb.getCount();
65+
},
6266
};

backend/src/entities/company-info/invitation-in-company/repository/invitation-repository.interface.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@ import { CompanyInfoEntity } from '../../company-info.entity.js';
33
import { InvitationInCompanyEntity } from '../invitation-in-company.entity.js';
44

55
export interface IInvitationInCompanyRepository {
6-
createOrUpdateInvitationInCompany(
7-
companyInfo: CompanyInfoEntity,
8-
groupId: string | null,
9-
inviterId: string,
10-
newUserEmail: string,
11-
invitedUserRole: UserRoleEnum,
12-
): Promise<InvitationInCompanyEntity>;
6+
createOrUpdateInvitationInCompany(
7+
companyInfo: CompanyInfoEntity,
8+
groupId: string | null,
9+
inviterId: string,
10+
newUserEmail: string,
11+
invitedUserRole: UserRoleEnum,
12+
): Promise<{ entity: InvitationInCompanyEntity; rawToken: string }>;
1313

14-
deleteOldInvitationsInCompany(companyId: string): Promise<void>;
14+
deleteOldInvitationsInCompany(companyId: string): Promise<void>;
1515

16-
findNonExpiredInvitationInCompanyWithUsersByVerificationString(
17-
verificationString: string,
18-
): Promise<InvitationInCompanyEntity>;
16+
findNonExpiredInvitationInCompanyWithUsersByVerificationString(
17+
verificationString: string,
18+
): Promise<InvitationInCompanyEntity>;
1919

20-
countNonExpiredInvitationsInCompany(companyId: string): Promise<number>;
20+
countNonExpiredInvitationsInCompany(companyId: string): Promise<number>;
2121
}

backend/src/entities/company-info/use-cases/check-verification-link.available.use.case.ts

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,33 @@ import { IGlobalDatabaseContext } from '../../../common/application/global-datab
44
import { BaseType } from '../../../common/data-injection.tokens.js';
55
import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js';
66
import { ICheckVerificationLinkAvailable } from './company-info-use-cases.interface.js';
7+
import { Encryptor } from '../../../helpers/encryption/encryptor.js';
78

89
@Injectable()
910
export class CheckIsVerificationLinkAvailable
10-
extends AbstractUseCase<string, SuccessResponse>
11-
implements ICheckVerificationLinkAvailable
11+
extends AbstractUseCase<string, SuccessResponse>
12+
implements ICheckVerificationLinkAvailable
1213
{
13-
constructor(
14-
@Inject(BaseType.GLOBAL_DB_CONTEXT)
15-
protected _dbContext: IGlobalDatabaseContext,
16-
) {
17-
super();
18-
}
14+
constructor(
15+
@Inject(BaseType.GLOBAL_DB_CONTEXT)
16+
protected _dbContext: IGlobalDatabaseContext,
17+
) {
18+
super();
19+
}
1920

20-
protected async implementation(verificationString: string): Promise<SuccessResponse> {
21-
const foundInvitation =
22-
await this._dbContext.invitationInCompanyRepository.findNonExpiredInvitationInCompanyWithUsersByVerificationString(
23-
verificationString,
24-
);
25-
if (!foundInvitation) {
26-
return {
27-
success: false,
28-
};
29-
}
30-
return {
31-
success: true,
32-
};
33-
}
21+
protected async implementation(verificationString: string): Promise<SuccessResponse> {
22+
const hashedToken = Encryptor.hashVerificationToken(verificationString);
23+
const foundInvitation =
24+
await this._dbContext.invitationInCompanyRepository.findNonExpiredInvitationInCompanyWithUsersByVerificationString(
25+
hashedToken,
26+
);
27+
if (!foundInvitation) {
28+
return {
29+
success: false,
30+
};
31+
}
32+
return {
33+
success: true,
34+
};
35+
}
3436
}

0 commit comments

Comments
 (0)