Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reset password email #339

Merged
merged 1 commit into from
Nov 14, 2024
Merged

feat: reset password email #339

merged 1 commit into from
Nov 14, 2024

Conversation

Nictheboy
Copy link
Contributor

No description provided.

Copy link

coderabbitai bot commented Nov 14, 2024

Walkthrough

This pull request introduces new environment variables to the sample.env file, specifically FRONTEND_BASE_URL and PASSWORD_RESET_PREFIX, enhancing the application's configuration. Corresponding changes are made in the configuration.ts file to utilize these new variables. The EmailModule is updated to import ConfigModule, enabling the injection of configuration values into the EmailService. The sendPasswordResetEmail method is modified to construct a password reset URL using the new configuration properties. Additionally, the email template for password resets is updated to simplify user instructions.

Changes

File Change Summary
sample.env Added FRONTEND_BASE_URL=http://localhost:3000 and PASSWORD_RESET_PREFIX=/account/recover/password/verify?token=.
src/common/config/configuration.ts Added properties frontendBaseUrl and passwordResetPath to the exported configuration object.
src/email/email.module.ts Updated EmailModule to import ConfigModule from @nestjs/config.
src/email/email.service.ts Updated EmailService constructor to inject ConfigService and modified sendPasswordResetEmail to use the new URL construction.
src/resources/email-templates/password-reset.english.hbs Updated email template to prompt users to use a link for password reset instead of manual instructions.

Possibly related PRs

  • feat: add configuration COOKIE_BASE_URL #307: The addition of the COOKIE_BASE_URL environment variable in sample.env is related to the main PR's introduction of new environment variables, indicating a similar context of enhancing configuration options.

Suggested reviewers

  • HuanCheng65

Poem

🐰 In the meadow where bunnies play,
New links for passwords come our way!
With URLs bright and templates anew,
Resetting passwords is easy to do!
Hops of joy for changes made,
In our code, a bright upgrade! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented Nov 14, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 91.49%. Comparing base (725c086) to head (2c8c084).
Report is 6 commits behind head on dev.

Additional details and impacted files
@@           Coverage Diff           @@
##              dev     #339   +/-   ##
=======================================
  Coverage   91.48%   91.49%           
=======================================
  Files          71       71           
  Lines        2736     2739    +3     
  Branches      379      384    +5     
=======================================
+ Hits         2503     2506    +3     
  Misses        233      233           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (5)
src/resources/email-templates/password-reset.english.hbs (2)

1-3: Add security notices and token expiration information.

While the template is clear and user-friendly, consider adding important security-related information:

 <p>Hello, {{ username }}. You are trying to reset your password.</p>
 <p>Please use the following link to reset your password:</p>
 <p><a href="{{ resetUrl }}">{{ resetUrl }}</a></p>
+<p>This link will expire in {{ expirationTime }} minutes.</p>
+<p>For your security:</p>
+<ul>
+  <li>Never share this link with anyone</li>
+  <li>Make sure you're on our official website before entering your new password</li>
+  <li>We will never ask for your password via email</li>
+</ul>

1-3: Add support contact information.

Consider adding contact information in case users encounter issues:

 <p>Hello, {{ username }}. You are trying to reset your password.</p>
 <p>Please use the following link to reset your password:</p>
 <p><a href="{{ resetUrl }}">{{ resetUrl }}</a></p>
+<p>If you didn't request this password reset or need assistance, please contact our support team at {{ supportEmail }}.</p>
src/common/config/configuration.ts (1)

29-32: Document the configuration requirements

Consider adding JSDoc comments to document:

  1. The expected format of the frontend URL
  2. The purpose of these configuration values
  3. The complete reset URL structure
+  /**
+   * Base URL of the frontend application where users will be redirected
+   * @example 'https://example.com'
+   */
     frontendBaseUrl: process.env.FRONTEND_BASE_URL || '',
+  /**
+   * Path and query parameter structure for password reset URLs
+   * The final URL will be: ${frontendBaseUrl}${passwordResetPath}${token}
+   * @example '/account/recover/password/verify?token='
+   */
     passwordResetPath:
       process.env.PASSWORD_RESET_PREFIX ||
       '/account/recover/password/verify?token=',
src/email/email.service.ts (1)

Line range hint 23-41: Consider additional security measures for password reset

While the basic functionality is implemented, consider these security enhancements:

  1. Add rate limiting for password reset emails
  2. Log password reset attempts (without tokens) for security monitoring
  3. Consider adding email template versioning for audit purposes

Would you like me to provide implementation examples for these security enhancements?

sample.env (1)

48-50: Review password reset token security considerations

Exposing reset tokens in URLs can pose security risks:

  1. Tokens may be logged in browser history
  2. Tokens can appear in referrer headers when clicking links
  3. URLs might be inadvertently shared or cached

Consider these security improvements:

  1. Use POST requests with tokens in the request body instead of URL parameters
  2. Add comments about implementing token expiration
  3. Document one-time-use token requirements

Would you like assistance in implementing these security enhancements?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ace8fdc and 2c8c084.

📒 Files selected for processing (5)
  • sample.env (1 hunks)
  • src/common/config/configuration.ts (1 hunks)
  • src/email/email.module.ts (2 hunks)
  • src/email/email.service.ts (2 hunks)
  • src/resources/email-templates/password-reset.english.hbs (1 hunks)
🔇 Additional comments (3)
src/common/config/configuration.ts (1)

29-32: Verify the configuration usage across the codebase

Let's verify how these new configuration values are being used to construct the password reset URLs.

✅ Verification successful

Let me check the implementation details of the email service to verify how these configurations are being used.


Configuration values are properly used in password reset URL construction

The configuration values are correctly utilized in the EmailService.sendPasswordResetEmail method to construct the password reset URL. The implementation:

  • Properly injects the ConfigService
  • Uses configService.get() to retrieve both frontendBaseUrl and passwordResetPath
  • Concatenates them with the token to create the complete reset URL
  • No direct usage of environment variables was found elsewhere in the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find usage of these configuration values in email services or URL construction

# Look for URL construction using these config values
rg -l "frontendBaseUrl|passwordResetPath" --type ts

# Look for potential direct env var usage that should use config instead
rg -l "FRONTEND_BASE_URL|PASSWORD_RESET_PREFIX" --type ts

# Look for email service implementations
ast-grep --pattern 'class $_ implements EmailService {
  $$$
  sendPasswordResetEmail($$$) {
    $$$
  }
  $$$
}'

Length of output: 326


Script:

#!/bin/bash
# Check the implementation of email.service.ts to see how these configs are used
cat src/email/email.service.ts

Length of output: 1496

src/email/email.service.ts (1)

13-13: LGTM: Clean ConfigService integration

The ConfigService is properly imported and injected following NestJS best practices.

Also applies to: 20-20

src/email/email.module.ts (1)

15-15: LGTM!

The import of ConfigModule is correctly added to support configuration injection in the EmailService.

Comment on lines +29 to +32
frontendBaseUrl: process.env.FRONTEND_BASE_URL || '',
passwordResetPath:
process.env.PASSWORD_RESET_PREFIX ||
'/account/recover/password/verify?token=',
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve URL construction safety

The current implementation might lead to URL construction issues. Consider:

  1. Adding URL path joining logic to handle trailing slashes
  2. Making the token parameter name configurable
  3. Adding TypeScript interfaces for type safety
// Add at the top of the file
interface AppConfig {
  port: number;
  elasticsearch: ElasticsearchModuleOptions;
  jwt: {
    secret: string;
  };
  cookieBasePath: string;
  frontendBaseUrl: string;
  passwordReset: {
    path: string;
    tokenParam: string;
  };
}

// Modified configuration
export default (): AppConfig => ({
  // ... other config
  frontendBaseUrl: process.env.FRONTEND_BASE_URL?.replace(/\/$/, '') || '',
  passwordReset: {
    path: process.env.PASSWORD_RESET_PATH || '/account/recover/password/verify',
    tokenParam: process.env.PASSWORD_RESET_TOKEN_PARAM || 'token',
  },
});

@@ -26,6 +26,10 @@ export default () => {
// expiresIn: process.env.JWT_EXPIRES_IN,
},
cookieBasePath: process.env.COOKIE_BASE_PATH || '/',
frontendBaseUrl: process.env.FRONTEND_BASE_URL || '',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add validation for frontendBaseUrl

An empty string default for frontendBaseUrl could lead to invalid URLs being constructed. Consider adding validation and throwing a configuration error if the URL is invalid or empty in production.

-    frontendBaseUrl: process.env.FRONTEND_BASE_URL || '',
+    frontendBaseUrl: (() => {
+      const url = process.env.FRONTEND_BASE_URL;
+      if (process.env.NODE_ENV === 'production' && (!url || !url.startsWith('http'))) {
+        throw new Error('FRONTEND_BASE_URL must be a valid URL in production');
+      }
+      return url || '';
+    })(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
frontendBaseUrl: process.env.FRONTEND_BASE_URL || '',
frontendBaseUrl: (() => {
const url = process.env.FRONTEND_BASE_URL;
if (process.env.NODE_ENV === 'production' && (!url || !url.startsWith('http'))) {
throw new Error('FRONTEND_BASE_URL must be a valid URL in production');
}
return url || '';
})(),

Comment on lines +35 to +38
resetUrl:
this.configService.get('frontendBaseUrl') +
this.configService.get('passwordResetPath') +
token,
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

URL construction needs improvement but with fallback values

The review's concerns are valid but can be refined based on the codebase evidence:

  1. Config values have fallback defaults:
    • frontendBaseUrl defaults to empty string
    • passwordResetPath defaults to '/account/recover/password/verify?token='

The suggested improvements are still valuable but can be simplified:

private buildResetUrl(token: string): string {
  const baseUrl = this.configService.get<string>('frontendBaseUrl');
  const resetPath = this.configService.get<string>('passwordResetPath');
  
  // Ensure clean URL parts
  const cleanBase = baseUrl.replace(/\/+$/, '') || '';
  const cleanPath = resetPath.replace(/^\/+|\/+$/g, '') || 'account/recover/password/verify';
  
  return `${cleanBase}/${cleanPath}?token=${encodeURIComponent(token)}`;
}

This implementation:

  • Maintains existing fallback behavior
  • Handles URL slashes
  • Properly encodes the token
  • Keeps the URL query parameter structure from the default config
🔗 Analysis chain

URL construction needs validation and error handling

The current URL construction has several potential issues:

  1. No validation of config values
  2. No handling of trailing/leading slashes
  3. No error handling for undefined config values
  4. Direct concatenation could create malformed URLs

Consider this safer implementation:

-        resetUrl:
-          this.configService.get('frontendBaseUrl') +
-          this.configService.get('passwordResetPath') +
-          token,
+        resetUrl: this.buildResetUrl(token),

+  private buildResetUrl(token: string): string {
+    const baseUrl = this.configService.getOrThrow<string>('frontendBaseUrl');
+    const resetPath = this.configService.getOrThrow<string>('passwordResetPath');
+    
+    // Ensure clean URL parts
+    const cleanBase = baseUrl.replace(/\/+$/, '');
+    const cleanPath = resetPath.replace(/^\/+|\/+$/g, '');
+    
+    try {
+      const url = new URL(`${cleanBase}/${cleanPath}/${token}`);
+      return url.toString();
+    } catch (error) {
+      throw new Error(`Failed to construct reset URL: ${error.message}`);
+    }
+  }

This implementation:

  • Uses getOrThrow to fail fast if config is missing
  • Properly handles URL slashes
  • Validates URL construction
  • Provides clear error messages

Let's verify the config setup:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the required config values are properly defined
rg -l 'frontendBaseUrl|passwordResetPath' --type ts

Length of output: 115


Script:

#!/bin/bash
# Check how these config values are defined and used
rg 'frontendBaseUrl|passwordResetPath' -A 3 -B 3 --type ts

Length of output: 1046

@@ -40,6 +41,7 @@ import { EmailService } from './email.service';
},
},
}),
ConfigModule,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use ConfigModule.forFeature() for better configuration management

The current implementation imports ConfigModule directly, which might lead to configuration initialization issues. In NestJS, feature modules should use ConfigModule.forFeature() to properly scope their configuration.

Apply this diff:

-    ConfigModule,
+    ConfigModule.forFeature(() => ({
+      // Validate and expose only email-related config
+      PASSWORD_RESET_PREFIX: process.env.PASSWORD_RESET_PREFIX,
+      FRONTEND_BASE_URL: process.env.FRONTEND_BASE_URL,
+      // Add other email-specific config keys here
+    })),

This change:

  • Explicitly declares which configuration keys are used by the EmailModule
  • Provides better encapsulation and maintainability
  • Enables configuration validation at the module level

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +44 to +46
# This url means the frontend url, usually it is the same as the CORS_ORIGINS
# It is used to send the password reset email
FRONTEND_BASE_URL=http://localhost:3000
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider security implications of using http in the default URL

While http://localhost:3000 is acceptable for local development, it's crucial to use HTTPS in production environments to prevent password reset tokens from being intercepted.

Consider adding a comment to warn about production security requirements:

 # This url means the frontend url, usually it is the same as the CORS_ORIGINS
 # It is used to send the password reset email
+# IMPORTANT: Use https:// URLs in production environments
 FRONTEND_BASE_URL=http://localhost:3000
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# This url means the frontend url, usually it is the same as the CORS_ORIGINS
# It is used to send the password reset email
FRONTEND_BASE_URL=http://localhost:3000
# This url means the frontend url, usually it is the same as the CORS_ORIGINS
# It is used to send the password reset email
# IMPORTANT: Use https:// URLs in production environments
FRONTEND_BASE_URL=http://localhost:3000

Copy link
Contributor

@andylizf andylizf left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks @Nictheboy

@Nictheboy Nictheboy merged commit 6178098 into dev Nov 14, 2024
41 checks passed
@Nictheboy Nictheboy deleted the feat-reset-password-email branch November 14, 2024 02:32
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.

2 participants