Skip to content

enhance email verification and password reset processes#1562

Merged
Artuomka merged 2 commits intomainfrom
backend_security_report
Feb 5, 2026
Merged

enhance email verification and password reset processes#1562
Artuomka merged 2 commits intomainfrom
backend_security_report

Conversation

@Artuomka
Copy link
Collaborator

@Artuomka Artuomka commented Feb 5, 2026

No description provided.

Copilot AI review requested due to automatic review settings February 5, 2026 08:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the security of email verification and password reset processes by implementing token hashing before database storage. Instead of storing raw verification tokens directly in the database, the system now hashes them using HMAC-SHA256, ensuring that database compromise does not expose usable verification tokens.

Changes:

  • Introduced hashVerificationToken() method in the Encryptor class for consistent token hashing using HMAC-SHA256
  • Updated all verification-related repository methods to return both the entity and the raw token, while storing only the hashed version
  • Modified all verification flows to hash incoming tokens before database lookup
  • Standardized code formatting from spaces to tabs across affected files

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
backend/src/helpers/encryption/encryptor.ts Added hashVerificationToken() method for HMAC-SHA256 token hashing
backend/src/entities/email/repository/email-verification-custom-repository-extension.ts Updated to hash email verification tokens before storage and return raw token
backend/src/entities/email/repository/email-verification.repository.interface.ts Updated interface to return { entity, rawToken } instead of just entity
backend/src/entities/user/user-password/repository/user-password-custom-repository-extension.ts Updated to hash password reset tokens before storage and return raw token
backend/src/entities/user/user-password/repository/password-reset-repository.interface.ts Updated interface to return { entity, rawToken } instead of just entity
backend/src/entities/user/user-email/repository/email-change-custom-repository-extension.ts Updated to hash email change tokens before storage and return raw token
backend/src/entities/user/user-email/repository/email-change.repository.interface.ts Updated interface to return { entity, rawToken } instead of just entity
backend/src/entities/user/user-invitation/repository/user-invitation-custom-repository-extension.ts Updated to hash user invitation tokens before storage and return raw token
backend/src/entities/user/user-invitation/repository/user-invitation-repository.interface.ts Updated interface to return { entity, rawToken } instead of just entity
backend/src/entities/company-info/invitation-in-company/repository/invitation-in-company-custom-repository-extension.ts Updated to hash company invitation tokens before storage and return raw token
backend/src/entities/company-info/invitation-in-company/repository/invitation-repository.interface.ts Updated interface to return { entity, rawToken } instead of just entity
backend/src/entities/user/use-cases/verify-user-email.use.case.ts Hash incoming verification token before database lookup
backend/src/entities/user/use-cases/verify-reset-user-password.use.case.ts Hash incoming reset token before database lookup
backend/src/entities/user/use-cases/verify-change-user-email.use.case.ts Hash incoming email change token before database lookup
backend/src/entities/user/use-cases/request-reset-user-password.use.case.ts Use raw token from repository for email sending
backend/src/entities/user/use-cases/request-email-verification.use.case.ts Use raw token from repository for email sending
backend/src/entities/user/use-cases/request-change-user-email.use.case.ts Use raw token from repository for email sending
backend/src/entities/company-info/use-cases/verify-invite-user-in-company.use.case.ts Hash incoming invitation token before database lookup
backend/src/entities/company-info/use-cases/invite-user-in-company.use.case.ts Use raw token from repository for email sending
backend/src/entities/company-info/use-cases/check-verification-link.available.use.case.ts Hash incoming verification token before database lookup
backend/src/microservices/saas-microservice/use-cases/saas-usual-register-user.use.case.ts Use raw token from repository for email sending

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (!privateKey) {
throw new Error('PRIVATE_KEY environment variable is required for token hashing');
}
return Encryptor.hashDataHMAC(token);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hashVerificationToken method calls getPrivateKey() explicitly to check if it exists, but then calls hashDataHMAC() which also calls getPrivateKey() internally. This results in redundant calls to getPrivateKey(). Consider removing the explicit check since hashDataHMAC() already retrieves the private key and will work correctly. If the explicit null check is needed for a better error message, the private key should be passed to hashDataHMAC() to avoid the double retrieval.

Suggested change
return Encryptor.hashDataHMAC(token);
return Encryptor.hashDataHMACexternalKey(privateKey, token);

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +22
const foundVerificationEntity =
await this._dbContext.emailVerificationRepository.findVerificationWithVerificationString(hashedToken);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change for existing verification tokens stored in the database. All existing verification strings in the database are unhashed raw tokens, but the verification logic now expects them to be hashed. When users try to use existing verification links (for email verification, password reset, email change, company invitations, or user invitations), they will fail because the code will hash the incoming token and try to match it against an unhashed value in the database. A data migration is needed to either: 1) Invalidate all existing verification tokens by deleting them, or 2) Create a migration that temporarily supports both hashed and unhashed tokens during a transition period. Consider the user experience impact of invalidating existing verification links.

Suggested change
const foundVerificationEntity =
await this._dbContext.emailVerificationRepository.findVerificationWithVerificationString(hashedToken);
let foundVerificationEntity =
await this._dbContext.emailVerificationRepository.findVerificationWithVerificationString(hashedToken);
// Backward compatibility: if no verification is found with the hashed token,
// try to find one using the raw (unhashed) verification string.
if (!foundVerificationEntity) {
foundVerificationEntity =
await this._dbContext.emailVerificationRepository.findVerificationWithVerificationString(
verificationString,
);
}

Copilot uses AI. Check for mistakes.
Comment on lines 160 to 165
static hashDataHMAC(dataToHash: string): string {
const privateKey = Encryptor.getPrivateKey();
const hmac = createHmac('sha256', privateKey);
hmac.update(dataToHash);
return hmac.digest('hex');
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hashDataHMAC method at line 160 doesn't check if privateKey is null/undefined before using it. While hashVerificationToken adds this check before calling hashDataHMAC, other potential callers of hashDataHMAC might not have this protection. Consider adding the null check inside hashDataHMAC itself for consistency and to prevent potential runtime errors.

Copilot uses AI. Check for mistakes.
@Artuomka Artuomka enabled auto-merge February 5, 2026 08:46
@Artuomka Artuomka merged commit 6c783d0 into main Feb 5, 2026
25 checks passed
@Artuomka Artuomka deleted the backend_security_report branch February 5, 2026 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant