From e9a1ece808aecf40b73d63beab8bdf8c91e6ac8e Mon Sep 17 00:00:00 2001 From: Yang Gao <6821729+criyle@users.noreply.github.com> Date: Tue, 9 Apr 2024 08:13:50 +0800 Subject: [PATCH] core: async hash calculation (#779) --- packages/hydrooj/src/handler/home.ts | 8 ++++---- packages/hydrooj/src/handler/user.ts | 6 +++--- packages/hydrooj/src/interface.ts | 2 +- packages/hydrooj/src/lib/hash.hydro.ts | 9 +++++++-- packages/hydrooj/src/model/user.ts | 9 +++++---- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index 679274245..004a876d5 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -216,8 +216,8 @@ class HomeSecurityHandler extends Handler { if (this.session.sudoUid) { const udoc = await user.getById(domainId, this.session.sudoUid); if (!udoc) throw new UserNotFoundError(this.session.sudoUid); - udoc.checkPassword(current); - } else this.user.checkPassword(current); + await udoc.checkPassword(current); + } else await this.user.checkPassword(current); await user.setPassword(this.user._id, password); await token.delByUid(this.user._id); this.response.redirect = this.url('user_login'); @@ -232,8 +232,8 @@ class HomeSecurityHandler extends Handler { if (this.session.sudoUid) { const udoc = await user.getById(domainId, this.session.sudoUid); if (!udoc) throw new UserNotFoundError(this.session.sudoUid); - udoc.checkPassword(current); - } else this.user.checkPassword(current); + await udoc.checkPassword(current); + } else await this.user.checkPassword(current); const udoc = await user.getByEmail(domainId, email); if (udoc) throw new UserAlreadyExistError(email); await this.limitRate('send_mail', 3600, 30); diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index 49dd6e2cd..7b7dd8576 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -117,7 +117,7 @@ class UserLoginHandler extends Handler { await token.del(authnChallenge, token.TYPE_WEBAUTHN); } else throw new ValidationError('2FA', 'Authn'); } - udoc.checkPassword(password); + await udoc.checkPassword(password); await user.setById(udoc._id, { loginat: new Date(), loginip: this.request.ip }); if (!udoc.hasPriv(PRIV.PRIV_USER_PROFILE)) throw new BlacklistedError(uname, udoc.banReason); this.session.viewLang = ''; @@ -153,7 +153,7 @@ class UserSudoHandler extends Handler { await token.del(authnChallenge, token.TYPE_WEBAUTHN); } else if (this.user.tfa && tfa) { if (!verifyTFA(this.user._tfa, tfa)) throw new InvalidTokenError('2FA'); - } else this.user.checkPassword(password); + } else await this.user.checkPassword(password); this.session.sudo = Date.now(); if (this.session.sudoArgs.method.toLowerCase() !== 'get') { this.response.template = 'user_sudo_redirect.html'; @@ -445,7 +445,7 @@ class UserDetailHandler extends Handler { class UserDeleteHandler extends Handler { async post({ password }) { - this.user.checkPassword(password); + await this.user.checkPassword(password); const tid = await ScheduleModel.add({ executeAfter: moment().add(7, 'days').toDate(), type: 'script', diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 83a76d55e..9d05486be 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -787,7 +787,7 @@ export interface ModuleInterfaces { get: (this: Handler) => Promise; callback: (this: Handler, args: Record) => Promise; }; - hash: (password: string, salt: string, user: User) => boolean | string; + hash: (password: string, salt: string, user: User) => boolean | string | Promise; render: (name: string, state: any) => string | Promise; } diff --git a/packages/hydrooj/src/lib/hash.hydro.ts b/packages/hydrooj/src/lib/hash.hydro.ts index c37127584..8d246413a 100644 --- a/packages/hydrooj/src/lib/hash.hydro.ts +++ b/packages/hydrooj/src/lib/hash.hydro.ts @@ -1,7 +1,12 @@ import crypto from 'crypto'; -function hash(password: string, salt: string) { - return crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256').toString('hex').substring(0, 64); +function hash(password: string, salt: string): Promise { + return new Promise((resolve, reject) => { + crypto.pbkdf2(password, salt, 100000, 64, 'sha256', (err, key) => { + if (err) reject(err); + else resolve(key.toString('hex').substring(0, 64)); + }); + }); } export = hash; diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index f15586cc5..501e8b264 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -141,10 +141,10 @@ export class User { return false; } - checkPassword(password: string) { + async checkPassword(password: string) { const h = global.Hydro.module.hash[this.hashType]; if (!h) throw new Error('Unknown hash method'); - const result = h(password, this._salt, this); + const result = await h(password, this._salt, this); if (result !== true && result !== this._hash) { throw new LoginError(this.uname); } @@ -290,7 +290,7 @@ class UserModel { const salt = String.random(); const res = await coll.findOneAndUpdate( { _id: uid }, - { $set: { salt, hash: pwhash(password, salt), hashType: 'hydro' } }, + { $set: { salt, hash: await pwhash(password, salt), hashType: 'hydro' } }, { returnDocument: 'after' }, ); deleteUserCache(res.value); @@ -328,7 +328,8 @@ class UserModel { mailLower: handleMailLower(mail), uname, unameLower: uname.trim().toLowerCase(), - hash: pwhash(password.toString(), salt), + // eslint-disable-next-line no-await-in-loop + hash: await pwhash(password.toString(), salt), salt, hashType: 'hydro', regat: new Date(),