mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2026-11-10 12:28:28 +00:00
Check a dummy hash when a login names no account
Without a password to check, a login for an unknown account skipped bcrypt and answered far sooner than one with a wrong password, which still told which emails have an account.
This commit is contained in:
1 parent
c08f2e65a8
commit
e93b771ff5
2 files changed
+32
-12
No files matched your search
+13
-11
@@ -34,18 +34,20 @@ export default {
|
||||
.andWhere("is_disabled", 0)
|
||||
.first();
|
||||
|
||||
if (!user) {
|
||||
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N);
|
||||
}
|
||||
// An unknown, disabled or passwordless account goes through the same queries and the same password check
|
||||
// as a real one, and fails with the same error: answering any sooner or any differently would tell anyone
|
||||
// trying addresses which ones have an account. There is no user 0, so its lookup finds nothing. An empty
|
||||
// secret, which creating a user through the API can store, is no hash: bcrypt would reject it at once.
|
||||
const auth = await authModel
|
||||
.query()
|
||||
.where("user_id", "=", user?.id ?? 0)
|
||||
.where("type", "=", "password")
|
||||
.first();
|
||||
|
||||
const auth = await authModel.query().where("user_id", "=", user.id).where("type", "=", "password").first();
|
||||
|
||||
if (!auth) {
|
||||
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N);
|
||||
}
|
||||
|
||||
const valid = await auth.verifyPassword(data.secret);
|
||||
if (!valid) {
|
||||
const valid = auth?.secret
|
||||
? await auth.verifyPassword(data.secret)
|
||||
: await authModel.verifyDummyPassword(data.secret);
|
||||
if (!user || !valid) {
|
||||
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N);
|
||||
}
|
||||
|
||||
|
||||
+19
-1
@@ -1,6 +1,7 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import { Model } from "objection";
|
||||
import db from "../db.js";
|
||||
@@ -11,10 +12,15 @@ import User from "./user.js";
|
||||
Model.knex(db());
|
||||
|
||||
const boolFields = ["is_deleted"];
|
||||
const saltRounds = 13;
|
||||
|
||||
// A hash of a random value, for a login that names an account with no password to check it against. Hashed
|
||||
// with the same cost as real passwords, and made at startup so the first such login takes no longer either.
|
||||
const dummyHash = bcrypt.hash(crypto.randomBytes(32).toString("hex"), saltRounds);
|
||||
|
||||
function encryptPassword() {
|
||||
if (this.type === "password" && this.secret) {
|
||||
return bcrypt.hash(this.secret, 13).then((hash) => {
|
||||
return bcrypt.hash(this.secret, saltRounds).then((hash) => {
|
||||
this.secret = hash;
|
||||
});
|
||||
}
|
||||
@@ -60,6 +66,18 @@ class Auth extends Model {
|
||||
return bcrypt.compare(password, this.secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take as long as verifyPassword does, then fail. bcrypt is nearly all of the time a login takes, so a login
|
||||
* that skipped it for an unknown account would answer far sooner, and say which accounts exist.
|
||||
*
|
||||
* @param {String} password
|
||||
* @returns {Promise<false>}
|
||||
*/
|
||||
static async verifyDummyPassword(password) {
|
||||
await bcrypt.compare(password, await dummyHash);
|
||||
return false;
|
||||
}
|
||||
|
||||
static get name() {
|
||||
return "Auth";
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user