Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions packages/features/auth/lib/verifyPassword.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { compare } from "bcryptjs";

export async function verifyPassword(password: string, hashedPassword: string) {
const isValid = await compare(password, hashedPassword);
return isValid;
/**
* Verifies a password against a hashed password with timing attack protection
* @param password - Plain text password to verify
* @param hashedPassword - Hashed password to compare against
* @returns Promise<boolean> indicating if password is valid
*/
export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
if (!password || !hashedPassword) {
return false;
}

try {
const isValid = await compare(password, hashedPassword);
return isValid;
} catch (error) {
console.error("Password verification failed", error);
return false;
}
}
Comment on lines 1 to 21

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] Remove the console.error statement to avoid exposing sensitive information in production. Use a logging library if necessary.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
import { compare } from "bcryptjs";
export async function verifyPassword(password: string, hashedPassword: string) {
const isValid = await compare(password, hashedPassword);
return isValid;
/**
* Verifies a password against a hashed password with timing attack protection
* @param password - Plain text password to verify
* @param hashedPassword - Hashed password to compare against
* @returns Promise<boolean> indicating if password is valid
*/
export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
if (!password || !hashedPassword) {
return false;
}
try {
const isValid = await compare(password, hashedPassword);
return isValid;
} catch (error) {
console.error("Password verification failed", error);
return false;
}
}
import { compare } from "bcryptjs";
/**
* Verifies a password against a hashed password with timing attack protection
* @param password - Plain text password to verify
* @param hashedPassword - Hashed password to compare against
* @returns Promise<boolean> indicating if password is valid
*/
export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
if (!password || !hashedPassword) {
return false;
}
try {
const isValid = await compare(password, hashedPassword);
return isValid;
} catch (error) {
// Log the error using a logging library instead of console.error
// Example: logger.error("Password verification failed", error);
return false;
}
}

17 changes: 15 additions & 2 deletions packages/lib/auth/hashPassword.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { hash } from "bcryptjs";

export async function hashPassword(password: string) {
const hashedPassword = await hash(password, 12);
const DEFAULT_SALT_ROUNDS = 12;
const MIN_PASSWORD_LENGTH = 8;

/**
* Hashes a password using bcrypt with configurable salt rounds
* @param password - Plain text password to hash
* @param saltRounds - Number of salt rounds (default: 12)
* @returns Promise<string> containing the hashed password
*/
export async function hashPassword(password: string, saltRounds: number = DEFAULT_SALT_ROUNDS): Promise<string> {
if (!password || password.length < MIN_PASSWORD_LENGTH) {
throw new Error(`Password must be at least ${MIN_PASSWORD_LENGTH} characters long`);
}

const hashedPassword = await hash(password, saltRounds);
return hashedPassword;
}
Comment on lines 1 to 19

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The hashPassword function lacks error handling for the async hash operation. This can lead to unhandled promise rejections if the hashing fails. Wrap the hash call in a try/catch block to handle potential errors gracefully.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
import { hash } from "bcryptjs";
export async function hashPassword(password: string) {
const hashedPassword = await hash(password, 12);
const DEFAULT_SALT_ROUNDS = 12;
const MIN_PASSWORD_LENGTH = 8;
/**
* Hashes a password using bcrypt with configurable salt rounds
* @param password - Plain text password to hash
* @param saltRounds - Number of salt rounds (default: 12)
* @returns Promise<string> containing the hashed password
*/
export async function hashPassword(password: string, saltRounds: number = DEFAULT_SALT_ROUNDS): Promise<string> {
if (!password || password.length < MIN_PASSWORD_LENGTH) {
throw new Error(`Password must be at least ${MIN_PASSWORD_LENGTH} characters long`);
}
const hashedPassword = await hash(password, saltRounds);
return hashedPassword;
}
import { hash } from "bcryptjs";
const DEFAULT_SALT_ROUNDS = 12;
const MIN_PASSWORD_LENGTH = 8;
/**
* Hashes a password using bcrypt with configurable salt rounds
* @param password - Plain text password to hash
* @param saltRounds - Number of salt rounds (default: 12)
* @returns Promise<string> containing the hashed password
*/
export async function hashPassword(password: string, saltRounds: number = DEFAULT_SALT_ROUNDS): Promise<string> {
if (!password || password.length < MIN_PASSWORD_LENGTH) {
throw new Error(`Password must be at least ${MIN_PASSWORD_LENGTH} characters long`);
}
try {
const hashedPassword = await hash(password, saltRounds);
return hashedPassword;
} catch (error) {
throw new Error('Failed to hash password');
}
}

120 changes: 120 additions & 0 deletions packages/lib/auth/passwordStrength.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* Password strength validation utilities
*/

export interface PasswordStrengthResult {
isStrong: boolean;
score: number;
feedback: string[];
}

const MIN_LENGTH = 8;
const RECOMMENDED_LENGTH = 12;

/**
* Evaluates password strength based on various criteria
* @param password - Password to evaluate
* @returns PasswordStrengthResult with score and feedback
*/
export function evaluatePasswordStrength(password: string): PasswordStrengthResult {
const feedback: string[] = [];
let score = 0;

if (!password) {
return {
isStrong: false,
score: 0,
feedback: ["Password is required"],
};
}

if (password.length >= MIN_LENGTH) {
score += 20;
} else {
feedback.push(`Password must be at least ${MIN_LENGTH} characters`);
}

if (password.length >= RECOMMENDED_LENGTH) {
score += 10;
}

if (/[a-z]/.test(password)) {
score += 15;
} else {
feedback.push("Add lowercase letters");
}

if (/[A-Z]/.test(password)) {
score += 15;
} else {
feedback.push("Add uppercase letters");
}

if (/[0-9]/.test(password)) {
score += 20;
} else {
feedback.push("Add numbers");
}

if (/[^a-zA-Z0-9]/.test(password)) {
score += 20;
} else {
feedback.push("Add special characters");
}

const hasRepeatingChars = /(.)\1{2,}/.test(password);
if (hasRepeatingChars) {
score -= 10;
feedback.push("Avoid repeating characters");
}

const hasSequentialChars = /(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz|012|123|234|345|456|567|678|789)/i.test(password);
if (hasSequentialChars) {
score -= 10;
feedback.push("Avoid sequential characters");
}

return {
isStrong: score > 70,
score: Math.max(0, Math.min(100, score)),
feedback,
};
}

/**
* Checks if password meets minimum security requirements
* @param password - Password to validate
* @returns boolean indicating if password is valid
*/
export function isPasswordValid(password: string): boolean {
if (!password || password.length < MIN_LENGTH) {
return false;
}

const hasLetter = /[a-zA-Z]/.test(password);
const hasNumber = /[0-9]/.test(password);

return hasLetter || hasNumber;
}

/**
* Generates password validation error message
* @param password - Password to validate
* @returns Error message or null if valid
*/
export function getPasswordValidationError(password: string): string | null {
if (!password) {
return "Password is required";
}

if (password.length < MIN_LENGTH) {
return `Password must be at least ${MIN_LENGTH} characters long`;
}

const result = evaluatePasswordStrength(password);
if (!result.isStrong && result.feedback.length > 0) {
return result.feedback[0];
}

return null;
}
Comment on lines +1 to +120

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] Update the sequential character regex to include uppercase sequences for improved detection.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
/**
* Password strength validation utilities
*/
export interface PasswordStrengthResult {
isStrong: boolean;
score: number;
feedback: string[];
}
const MIN_LENGTH = 8;
const RECOMMENDED_LENGTH = 12;
/**
* Evaluates password strength based on various criteria
* @param password - Password to evaluate
* @returns PasswordStrengthResult with score and feedback
*/
export function evaluatePasswordStrength(password: string): PasswordStrengthResult {
const feedback: string[] = [];
let score = 0;
if (!password) {
return {
isStrong: false,
score: 0,
feedback: ["Password is required"],
};
}
if (password.length >= MIN_LENGTH) {
score += 20;
} else {
feedback.push(`Password must be at least ${MIN_LENGTH} characters`);
}
if (password.length >= RECOMMENDED_LENGTH) {
score += 10;
}
if (/[a-z]/.test(password)) {
score += 15;
} else {
feedback.push("Add lowercase letters");
}
if (/[A-Z]/.test(password)) {
score += 15;
} else {
feedback.push("Add uppercase letters");
}
if (/[0-9]/.test(password)) {
score += 20;
} else {
feedback.push("Add numbers");
}
if (/[^a-zA-Z0-9]/.test(password)) {
score += 20;
} else {
feedback.push("Add special characters");
}
const hasRepeatingChars = /(.)\1{2,}/.test(password);
if (hasRepeatingChars) {
score -= 10;
feedback.push("Avoid repeating characters");
}
const hasSequentialChars = /(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz|012|123|234|345|456|567|678|789)/i.test(password);
if (hasSequentialChars) {
score -= 10;
feedback.push("Avoid sequential characters");
}
return {
isStrong: score > 70,
score: Math.max(0, Math.min(100, score)),
feedback,
};
}
/**
* Checks if password meets minimum security requirements
* @param password - Password to validate
* @returns boolean indicating if password is valid
*/
export function isPasswordValid(password: string): boolean {
if (!password || password.length < MIN_LENGTH) {
return false;
}
const hasLetter = /[a-zA-Z]/.test(password);
const hasNumber = /[0-9]/.test(password);
return hasLetter || hasNumber;
}
/**
* Generates password validation error message
* @param password - Password to validate
* @returns Error message or null if valid
*/
export function getPasswordValidationError(password: string): string | null {
if (!password) {
return "Password is required";
}
if (password.length < MIN_LENGTH) {
return `Password must be at least ${MIN_LENGTH} characters long`;
}
const result = evaluatePasswordStrength(password);
if (!result.isStrong && result.feedback.length > 0) {
return result.feedback[0];
}
return null;
}
/**
* Password strength validation utilities
*/
export interface PasswordStrengthResult {
isStrong: boolean;
score: number;
feedback: string[];
}
const MIN_LENGTH = 8;
const RECOMMENDED_LENGTH = 12;
/**
* Evaluates password strength based on various criteria
* @param password - Password to evaluate
* @returns PasswordStrengthResult with score and feedback
*/
export function evaluatePasswordStrength(password: string): PasswordStrengthResult {
const feedback: string[] = [];
let score = 0;
if (!password) {
return {
isStrong: false,
score: 0,
feedback: ["Password is required"],
};
}
if (password.length >= MIN_LENGTH) {
score += 20;
} else {
feedback.push(`Password must be at least ${MIN_LENGTH} characters`);
}
if (password.length >= RECOMMENDED_LENGTH) {
score += 10;
}
if (/[a-z]/.test(password)) {
score += 15;
} else {
feedback.push("Add lowercase letters");
}
if (/[A-Z]/.test(password)) {
score += 15;
} else {
feedback.push("Add uppercase letters");
}
if (/[0-9]/.test(password)) {
score += 20;
} else {
feedback.push("Add numbers");
}
if (/[^a-zA-Z0-9]/.test(password)) {
score += 20;
} else {
feedback.push("Add special characters");
}
const hasRepeatingChars = /(.)\1{2,}/.test(password);
if (hasRepeatingChars) {
score -= 10;
feedback.push("Avoid repeating characters");
}
const hasSequentialChars = /(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz|ABC|BCD|CDE|DEF|EFG|FGH|GHI|HIJ|IJK|JKL|KLM|LMN|MNO|NOP|OPQ|PQR|QRS|RST|STU|TUV|UVW|VWX|WXY|XYZ|012|123|234|345|456|567|678|789)/.test(password);
if (hasSequentialChars) {
score -= 10;
feedback.push("Avoid sequential characters");
}
return {
isStrong: score > 70,
score: Math.max(0, Math.min(100, score)),
feedback,
};
}
/**
* Checks if password meets minimum security requirements
* @param password - Password to validate
* @returns boolean indicating if password is valid
*/
export function isPasswordValid(password: string): boolean {
if (!password || password.length < MIN_LENGTH) {
return false;
}
const hasLetter = /[a-zA-Z]/.test(password);
const hasNumber = /[0-9]/.test(password);
return hasLetter || hasNumber;
}
/**
* Generates password validation error message
* @param password - Password to validate
* @returns Error message or null if valid
*/
export function getPasswordValidationError(password: string): string | null {
if (!password) {
return "Password is required";
}
if (password.length < MIN_LENGTH) {
return `Password must be at least ${MIN_LENGTH} characters long`;
}
const result = evaluatePasswordStrength(password);
if (!result.isStrong && result.feedback.length > 0) {
return result.feedback[0];
}
return null;
}