alyxshang / shangshield.ts

Check the security of your password using the Shang algorithm. :dragon: :shield: :sauropod:
https://github.com/alyxshang/shangshield.ts
Other
0 stars 0 forks source link

Single character passwords #1

Open stefnotch opened 3 hours ago

stefnotch commented 3 hours ago

I was bored, so I figured I'd do a bit of code golf. While doing so, I noticed a bug. In this case, the implementation returns NaN.

Deno.test("Single character password scoring.", () =>
  assertEquals(
    shangshield.securityScore("a", letterWeight, specialCharWeight),
    1 * letterWeight
  )
);
stefnotch commented 3 hours ago

Here we go, a slightly horrifying golfed version

/*
ShangShield.ts by Alyx Shang.
Licensed under the FSL v1.
*/

/**
 * A JSON interface to save information
 * about the analysis of the strength of
 * a password.
 */
export interface SecurityInfo {
  password: string;
  score: number;
  cutOff: number;
  isSecure: boolean;
}

const charValues = new Map(
  "abcdefghijklmnopqrstuvwxyz"
    .split("")
    .map(
      (c, i) => [c, (letterWeight: number) => letterWeight * (i + 1)] as const
    )
    .concat("0123456789".split("").map((c, i) => [c, () => i] as const))
);

/**
 * This function reduces a character down to a number that mathematical
 * operations can be conducted on.
 * @param {string} char The character to reduce to a number entity.
 * @param {number} letterWeight The weight assigned to normal alphabets.
 * @param {number} specialCharWeight The weight assigned to special characters.
 * @returns {number} The number the character has been reduced to is returned.
 */
function reduceCharactersToNumber(
  char: string,
  letterWeight: number,
  specialCharWeight: number
): number {
  return (
    charValues.get(char.toLowerCase())?.(letterWeight) ?? specialCharWeight
  );
}

/**
 * This function calculates the security score of the supplied password
 * using the supplied weights.
 * @param {string} pwd The password to calculate the strength score of.
 * @param {number} letterWeight The weight assigned to normal alphabets.
 * @param {number} specialCharWeight The weight assigned to special characters.
 * @returns {number} A number that reflects how secure a password is.
 */
export function securityScore(
  pwd: string,
  letterWeight: number,
  specialCharWeight: number
): number {
  const scores = pwd
    .split("")
    .map((char) =>
      reduceCharactersToNumber(char, letterWeight, specialCharWeight)
    );
  if (scores.length === 0) {
    return 0;
  }
  if (scores.length === 1) {
    return scores[0];
  }

  const differences = scores
    .map((score, i) => Math.abs(score - scores[i - 1]))
    .slice(1);
  differences[differences.length - 1] *= 2; // Double count the last score difference
  return differences.reduce((a, b) => a + b, 0);
}

/**
 * This function checks whether the security score of the supplied
 * password is larger or smaller than the supplied "cutOff" value.
 * @param {string} pwd The password to calculate the strength score of.
 * @param {number} letterWeight The weight assigned to normal alphabets.
 * @param {number} specialCharWeight The weight assigned to special characters.
 * @param {number} cutOff The value that the calculated security score has to be
 * smaller or larger than.
 * @returns {boolean} Returns a boolean that reflects whether the calculated
 * security score of the supplied password is larger or smaller than the
 * supplied "cutOff" value.
 */
export function isSecure(
  pwd: string,
  letterWeight: number,
  specialCharWeight: number,
  cutOff: number
): boolean {
  return shieldSummary(pwd, letterWeight, specialCharWeight, cutOff).isSecure;
}

/**
 * This function runs the analysis functions for password strength
 * analysis and adds the information to an instance of the "SecurityInfo"
 * JSON interface. This instance is then returned.
 * @param {string} password The password whose strength to analyze.
 * @param {number} letterWeight The weight assigned to letters of the alphabet.
 * @param {number} specialCharWeight The weight assigned to special characters.
 * @param {number} cutOff The value that the calculated security score has to be
 * larger or greater than for the password to be deemed secure.
 * @returns {SecurityInfo} Returns an instance of the "SecurityInfo" JSON
 * interface providing a summary of the analysis results.
 */
export function shieldSummary(
  password: string,
  letterWeight: number,
  specialCharWeight: number,
  cutOff: number
): SecurityInfo {
  const score = securityScore(password, letterWeight, specialCharWeight);
  return {
    password,
    score,
    cutOff,
    isSecure: score >= cutOff,
  };
}