zxcvbn3
Version:
realistic password strength estimation
69 lines • 3.6 kB
JavaScript
/**
* returns the six adjacent coordinates on a standard keyboard, where each row is slanted to the
* right from the last. adjacencies are clockwise, starting with key to the left, then two keys
* above, then right key, then two keys below. (that is, only near-diagonal keys are adjacent,
* so g's coordinate is adjacent to those of t,y,b,v, but not those of r,u,n,c.)
*/
export function get_slanted_adjacent_coords(x, y) {
return [[x - 1, y], [x, y - 1], [x + 1, y - 1], [x + 1, y], [x, y + 1], [x - 1, y + 1]];
}
/**
* returns the nine clockwise adjacent coordinates on a keypad, where each row is vert aligned.
*/
export function get_aligned_adjacent_coords(x, y) {
return [[x - 1, y], [x - 1, y - 1], [x, y - 1], [x + 1, y - 1], [x + 1, y], [x + 1, y + 1], [x, y + 1], [x - 1, y + 1]];
}
/**
* builds an adjacency graph as a dictionary: {character: [adjacent_characters]}.
* adjacent characters occur in a clockwise order.
* for example:
* on qwerty layout, 'g' maps to ['fF', 'tT', 'yY', 'hH', 'bB', 'vV']
* on keypad layout, '7' maps to [None, None, None, '=', '8', '5', '4', None]
*/
export function build_graph(layout_str, slanted) {
layout_str = layout_str.replace(/^\n|\n$/g, ""); // replace line breaks at start and end
const position_table = []; // maps from tuple (x,y) -> characters at that position.
const tokens = layout_str.split(/\n| /g).filter(t => t !== "").map(t => t.replace(/\n/g, ""));
const token_size = tokens[0].length;
const x_unit = token_size + 1; // x position unit len is token len plus 1 for the following whitespace.
const adjacency_func = slanted ? get_slanted_adjacent_coords : get_aligned_adjacent_coords;
for (const token of tokens) {
if (token.length != token_size) {
throw new Error('token len mismatch:\n ' + layout_str);
}
}
const lines = layout_str.split('\n');
for (const y of lines.keys()) {
const line = lines[y];
// the way I illustrated keys above, each qwerty row is indented one space in from the last
const slant = slanted ? y - 1 : 0;
for (const token of line.split(" ").filter(t => t !== "")) {
const x = Math.floor((line.indexOf(token) - slant - (slanted ? 1 : 0)) / x_unit);
const remainder = (line.indexOf(token) - slant - (slanted ? 1 : 0)) % x_unit;
if (remainder) {
throw new Error(`unexpected x offset ${remainder} for ${token} in:\n${layout_str}`);
}
if (!position_table[x]) {
position_table[x] = [];
}
position_table[x][y] = token;
}
}
const adjacency_graph = {};
for (const line of position_table) {
for (const chars of line) {
for (const char of chars || []) {
adjacency_graph[char] = [];
for (const [x2, y2] of adjacency_func(position_table.indexOf(line), line.indexOf(chars))) {
/* position in the list indicates direction
for qwerty, 0 is left, 1 is top, 2 is top right, ...)
for edge chars like 1 or m, insert None as a placeholder when needed
so that each character in the graph has a same - length adjacency list. */
adjacency_graph[char].push(position_table[x2] && position_table[x2][y2] ? position_table[x2][y2] : null);
}
}
}
}
return adjacency_graph;
}
//# sourceMappingURL=generate_adjacency_graph.js.map