/
pwned.js
65 lines (57 loc) · 2.13 KB
/
pwned.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* @copyright 2021-2023 Chris Zuber <admin@kernvalley.us>
*/
import { sha1, HEX } from './hash.js';
import { getText } from './http.js';
import { listen } from './events.js';
const ENDPOINT = 'https://api.pwnedpasswords.com/range/';
const NL = '\r\n';
export async function pwnedCount(pwd, { signal } = {}) {
const hash = await sha1(pwd, { output: HEX });
const prefix = hash.substring(0, 5).toUpperCase();
const rest = `${hash.substring(5).toUpperCase()}:`;
try {
const match = await getText(new URL(prefix, ENDPOINT), { signal })
.then(lines => lines.split(NL).find(h => h.startsWith(rest)));
if (typeof match === 'string') {
return parseInt(match.split(':', 2).at(1));
} else {
return 0;
}
} catch(err) {
globalThis.reportError(err);
return NaN;
}
}
export async function pwned(pwd, { signal } = {}) {
const found = await pwnedCount(pwd, { signal });
if (Number.isNaN(found)) {
throw new DOMException('An error occured checking the password');
} else {
return found !== 0;
}
}
export async function pwnedEventHandler() {
if (! (this.setCustomValidity instanceof Function)) {
return;
} else if (this.validity.missingInput) {
this.setCustomValidity('Password is required');
} else if (this.validity.tooShort) {
this.setCustomValidity(`Passwords must be at least ${this.minLength} characters long`);
} else if (this.validity.tooLong) {
this.setCustomValidity(`Passwords may not be longer than ${this.maxLength} characters`);
} else if (this.validity.patternMismatch) {
this.setCustomValidity(this.dataset.patternMessage || 'Password does not meet requirements');
} else if (await pwned(this.value)) {
this.setCustomValidity('Password was found in a database breach.');
} else {
this.setCustomValidity('');
}
}
export function setListener(input, { event = 'change', capture = true, passive = true, signal } = {}) {
if (! (input instanceof HTMLInputElement && input.type === 'password')) {
throw new TypeError('`setListener()` is for `<input type="password">` only');
} else if (input.setCustomValidity instanceof Function) {
listen(input, event, pwnedEventHandler, { capture, passive, signal });
}
}