Overview

Secure password hashing with Argon2id. Includes strength checking and rehash detection.

Features

  • Argon2id hashing
  • OWASP configuration
  • Strength checking
  • Rehash detection

Installation

npx hanma add password --framework nest

Dependencies

argon2

Code

import * as argon2 from "argon2";

// Argon2id configuration (OWASP recommended)
const HASH_OPTIONS: argon2.Options = {
  type: argon2.argon2id,
  memoryCost: 65536, // 64 MB
  timeCost: 3, // Number of iterations
  parallelism: 4, // Degree of parallelism
  hashLength: 32, // Output hash length
};

/**
 * Hash a password using Argon2id.
 */
export async function hashPassword(password: string): Promise<string> {
  if (!password || password.length === 0) {
    throw new Error("Password cannot be empty");
  }

  return argon2.hash(password, HASH_OPTIONS);
}

/**
 * Verify a password against a hash.
 */
export async function verifyPassword(
  hash: string,
  password: string
): Promise<boolean> {
  if (!hash || !password) {
    return false;
  }

  try {
    return await argon2.verify(hash, password);
  } catch {
    return false;
  }
}

/**
 * Check if a hash needs to be rehashed (e.g., after security upgrade).
 */
export function needsRehash(hash: string): boolean {
  return argon2.needsRehash(hash, HASH_OPTIONS);
}

/**
 * Rehash password if needed (call after successful verification).
 */
export async function rehashIfNeeded(
  hash: string,
  password: string
): Promise<string | null> {
  if (needsRehash(hash)) {
    return hashPassword(password);
  }
  return null;
}

// Password strength validation
export interface PasswordStrength {
  score: number; // 0-4
  feedback: string[];
}

export function checkPasswordStrength(password: string): PasswordStrength {
  const feedback: string[] = [];
  let score = 0;

  if (password.length >= 8) score++;
  else feedback.push("Password should be at least 8 characters");

  if (password.length >= 12) score++;

  if (/[a-z]/.test(password) && /[A-Z]/.test(password)) {
    score++;
  } else {
    feedback.push("Password should include uppercase and lowercase letters");
  }

  if (/\d/.test(password)) {
    score++;
  } else {
    feedback.push("Password should include at least one number");
  }

  if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
    score++;
  } else {
    feedback.push("Password should include at least one special character");
  }

  // Common patterns to avoid
  const commonPatterns = [
    /^123/,
    /password/i,
    /qwerty/i,
    /abc123/i,
    /(.)\1{2,}/, // Repeated characters
  ];

  for (const pattern of commonPatterns) {
    if (pattern.test(password)) {
      score = Math.max(0, score - 1);
      feedback.push("Avoid common password patterns");
      break;
    }
  }

  return { score: Math.min(4, score), feedback };
}

// Usage:
/*
import { hashPassword, verifyPassword, checkPasswordStrength } from "./utils/password";

@Injectable()
export class AuthService {
  async register(email: string, password: string) {
    const strength = checkPasswordStrength(password);
    if (strength.score < 3) {
      throw new BadRequestException({
        message: "Password too weak",
        feedback: strength.feedback,
      });
    }

    const hashedPassword = await hashPassword(password);
    // Save user with hashedPassword
  }

  async login(email: string, password: string) {
    const user = await this.findByEmail(email);
    
    const isValid = await verifyPassword(user.password, password);
    if (!isValid) {
      throw new UnauthorizedException("Invalid credentials");
    }

    // Check if password needs rehashing
    const newHash = await rehashIfNeeded(user.password, password);
    if (newHash) {
      await this.updatePassword(user.id, newHash);
    }

    return user;
  }
}
*/

Usage

const hash = await hashPassword(password);
const valid = await verifyPassword(hash, password);

Dependencies

Frequently Asked Questions

How do I install Password Utilities?
Run the following command: npx hanma add password --framework nest
What dependencies are required?
This snippet requires: argon2. Make sure to install them before using the snippet.
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to src/utils/password.ts.
Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.