@riocrypto/common-server 1.0.2871 → 1.0.2873
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -47,6 +47,8 @@ interface AdminAuthDoc extends Document {
|
|
|
47
47
|
authenticatorSecret?: string;
|
|
48
48
|
apiKeys?: APIKey[];
|
|
49
49
|
tokenVersion?: number;
|
|
50
|
+
signinAttempts?: number;
|
|
51
|
+
signinLockedUntil?: Date;
|
|
50
52
|
}
|
|
51
53
|
interface AdminAuthModel extends Model<AdminAuthDoc> {
|
|
52
54
|
build(attrs: AdminAuthAttrs): HydratedDocument<AdminAuthDoc>;
|
|
@@ -76,6 +76,13 @@ const buildAdminAuth = (mongoose) => {
|
|
|
76
76
|
type: Number,
|
|
77
77
|
default: 0,
|
|
78
78
|
},
|
|
79
|
+
signinAttempts: {
|
|
80
|
+
type: Number,
|
|
81
|
+
default: 0,
|
|
82
|
+
},
|
|
83
|
+
signinLockedUntil: {
|
|
84
|
+
type: Date,
|
|
85
|
+
},
|
|
79
86
|
telegram: {
|
|
80
87
|
type: Object,
|
|
81
88
|
},
|
|
@@ -103,6 +110,11 @@ const buildAdminAuth = (mongoose) => {
|
|
|
103
110
|
delete ret.password;
|
|
104
111
|
delete ret.previousPasswords;
|
|
105
112
|
delete ret.authenticatorSecret;
|
|
113
|
+
// Signin route answers with the admin document, and the admin
|
|
114
|
+
// listing endpoints hand these records to other admins. Neither
|
|
115
|
+
// needs to publish how close an account is to being locked.
|
|
116
|
+
delete ret.signinAttempts;
|
|
117
|
+
delete ret.signinLockedUntil;
|
|
106
118
|
},
|
|
107
119
|
},
|
|
108
120
|
});
|
|
@@ -13,19 +13,40 @@ exports.Password = void 0;
|
|
|
13
13
|
const crypto_1 = require("crypto");
|
|
14
14
|
const util_1 = require("util");
|
|
15
15
|
const scryptAsync = (0, util_1.promisify)(crypto_1.scrypt);
|
|
16
|
+
const KEY_LENGTH = 64;
|
|
17
|
+
// 8 bytes was under the 16 the scrypt authors call for. The salt travels with
|
|
18
|
+
// the hash, so widening it only affects credentials written from now on;
|
|
19
|
+
// anything already stored keeps its own salt and still verifies.
|
|
20
|
+
const SALT_BYTES = 16;
|
|
16
21
|
class Password {
|
|
17
22
|
static toHash(password) {
|
|
18
23
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
-
const salt = (0, crypto_1.randomBytes)(
|
|
20
|
-
const buf = (yield scryptAsync(password, salt,
|
|
24
|
+
const salt = (0, crypto_1.randomBytes)(SALT_BYTES).toString("hex");
|
|
25
|
+
const buf = (yield scryptAsync(password, salt, KEY_LENGTH));
|
|
21
26
|
return `${buf.toString("hex")}.${salt}`;
|
|
22
27
|
});
|
|
23
28
|
}
|
|
29
|
+
// Everything the platform checks against a stored secret comes through
|
|
30
|
+
// here: account passwords, security answers, and every one-time code. The
|
|
31
|
+
// comparison is constant-time because `===` on the hex strings returns as
|
|
32
|
+
// soon as two characters differ, which times how much of the stored hash a
|
|
33
|
+
// guess reproduced. That is a slow leak rather than an open door -- an
|
|
34
|
+
// attacker cannot steer scrypt's output toward a prefix cheaply -- but the
|
|
35
|
+
// fix costs nothing and the alternative is arguing about how slow.
|
|
24
36
|
static compare(storedPassword, suppliedPassword) {
|
|
25
37
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
38
|
const [hashedPassword, salt] = storedPassword.split(".");
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
if (!hashedPassword || !salt) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const buf = (yield scryptAsync(suppliedPassword, salt, KEY_LENGTH));
|
|
43
|
+
const stored = Buffer.from(hashedPassword, "hex");
|
|
44
|
+
// timingSafeEqual throws rather than returning false on a length
|
|
45
|
+
// mismatch, which only happens if the stored value is malformed.
|
|
46
|
+
if (stored.length !== buf.length) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return (0, crypto_1.timingSafeEqual)(stored, buf);
|
|
29
50
|
});
|
|
30
51
|
}
|
|
31
52
|
}
|