From 7386c3b06b58b448f645728856e3397fc874531e Mon Sep 17 00:00:00 2001 From: Makarov Sergey Date: Fri, 31 May 2024 18:23:02 +0300 Subject: [PATCH 1/5] added timingSafeEqual comparison. verifyPassword, hashWithSalt changed to async --- server/account/src/operations.ts | 48 ++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index 3e12e66d5b8..f75c31fba57 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -49,7 +49,7 @@ import platform, { getMetadata, PlatformError, Severity, Status, translate } fro import { cloneWorkspace } from '@hcengineering/server-backup' import { decodeToken, generateToken } from '@hcengineering/server-token' import toolPlugin, { connect, initModel, upgradeModel } from '@hcengineering/server-tool' -import { pbkdf2Sync, randomBytes } from 'crypto' +import { randomBytes, pbkdf2, timingSafeEqual } from 'crypto' import { Binary, Db, Filter, ObjectId, type MongoClient } from 'mongodb' import fetch from 'node-fetch' import { type StorageAdapter } from '../../core/types' @@ -146,12 +146,36 @@ export interface Invite { */ export type AccountInfo = Omit -function hashWithSalt (password: string, salt: Buffer): Buffer { - return pbkdf2Sync(password, salt, 1000, 32, 'sha256') +const SALT_LEN: number = 32 +const PBKDF2_KEY_LEN: number = 32 +const PBKDF2_ITERATIONS: number = 1000 +const PBKDF2_DIGEST: string = 'sha256' + +/* +TODO: remove 'salt' property +*/ +const hashWithSalt = (password: string, salt: Buffer): Promise => { + return new Promise((resolve, reject) => { + pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { + if (err) { + reject(err) + return + } + resolve(derivedKey) + }) + }) } -function verifyPassword (password: string, hash: Buffer, salt: Buffer): boolean { - return Buffer.compare(hash, hashWithSalt(password, salt)) === 0 +const verifyPassword = (password: string, hash: Buffer, salt: Buffer): Promise => { + return new Promise((resolve, reject) => { + pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { + if (err) { + reject(err) + return + } + resolve(timingSafeEqual(hash, derivedKey)) + }) + }) } function cleanEmail (email: string): string { @@ -245,7 +269,7 @@ async function getAccountInfo ( if (account.hash === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.InvalidPassword, { account: email })) } - if (!verifyPassword(password, Buffer.from(account.hash.buffer), Buffer.from(account.salt.buffer))) { + if (!await verifyPassword(password, Buffer.from(account.hash.buffer), Buffer.from(account.salt.buffer))) { throw new PlatformError(new Status(Severity.ERROR, platform.status.InvalidPassword, { account: email })) } return toAccountInfo(account) @@ -614,8 +638,8 @@ export async function createAcc ( extra?: Record ): Promise { const email = cleanEmail(_email) - const salt = randomBytes(32) - const hash = password !== null ? hashWithSalt(password, salt) : null + const salt = randomBytes(SALT_LEN) + const hash = password !== null ? await hashWithSalt(password, salt) : null const systemEmails = [systemAccountEmail] if (systemEmails.includes(email)) { @@ -1615,7 +1639,7 @@ export async function changePassword ( const account = await getAccountInfo(ctx, db, productId, branding, email, oldPassword) const salt = randomBytes(32) - const hash = hashWithSalt(password, salt) + const hash = await hashWithSalt(password, salt) await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) ctx.info('change-password success', { email }) @@ -1638,8 +1662,8 @@ export async function replacePassword (db: Db, productId: string, email: string, if (account === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email })) } - const salt = randomBytes(32) - const hash = hashWithSalt(password, salt) + const salt = randomBytes(SALT_LEN) + const hash = await hashWithSalt(password, salt) await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) } @@ -1730,7 +1754,7 @@ export async function restorePassword ( async function updatePassword (db: Db, account: Account, password: string | null): Promise { const salt = randomBytes(32) - const hash = password !== null ? hashWithSalt(password, salt) : null + const hash = password !== null ? await hashWithSalt(password, salt) : null await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) } From 10701c16bcfcd6475f299b5bd70e3400b6e1ff9f Mon Sep 17 00:00:00 2001 From: Makarov Sergey Date: Mon, 3 Jun 2024 13:52:11 +0300 Subject: [PATCH 2/5] try...catch blocks added, improvements --- server/account/src/operations.ts | 80 +++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index f75c31fba57..e1ece7c07df 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -59,6 +59,11 @@ const WORKSPACE_COLLECTION = 'workspace' const ACCOUNT_COLLECTION = 'account' const INVITE_COLLECTION = 'invite' +const SALT_LEN: number = 32 +const PBKDF2_KEY_LEN: number = 32 +const PBKDF2_ITERATIONS: number = 1000 +const PBKDF2_DIGEST: string = 'sha256' + /** * @public */ @@ -146,22 +151,21 @@ export interface Invite { */ export type AccountInfo = Omit -const SALT_LEN: number = 32 -const PBKDF2_KEY_LEN: number = 32 -const PBKDF2_ITERATIONS: number = 1000 -const PBKDF2_DIGEST: string = 'sha256' - -/* -TODO: remove 'salt' property -*/ -const hashWithSalt = (password: string, salt: Buffer): Promise => { +const hashPassword = (password: string): Promise> => { return new Promise((resolve, reject) => { - pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { - if (err) { + randomBytes(SALT_LEN, (err, randomSalt) => { + if (err !== null) { reject(err) return } - resolve(derivedKey) + const callBack = (err: Error | null, derivedKey: Buffer): void => { + if (err !== null) { + reject(err) + return + } + resolve({ derivedKey, randomSalt }) + } + pbkdf2(password, randomSalt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, callBack) }) }) } @@ -169,7 +173,7 @@ const hashWithSalt = (password: string, salt: Buffer): Promise => { const verifyPassword = (password: string, hash: Buffer, salt: Buffer): Promise => { return new Promise((resolve, reject) => { pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { - if (err) { + if (err !== null) { reject(err) return } @@ -638,8 +642,17 @@ export async function createAcc ( extra?: Record ): Promise { const email = cleanEmail(_email) - const salt = randomBytes(SALT_LEN) - const hash = password !== null ? await hashWithSalt(password, salt) : null + let hash: Buffer | null = null + let salt: Buffer | null = null + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + hash = derivedKey + salt = randomSalt + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } const systemEmails = [systemAccountEmail] if (systemEmails.includes(email)) { @@ -1638,11 +1651,15 @@ export async function changePassword ( const { email } = decodeToken(token) const account = await getAccountInfo(ctx, db, productId, branding, email, oldPassword) - const salt = randomBytes(32) - const hash = await hashWithSalt(password, salt) - - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) - ctx.info('change-password success', { email }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + ctx.info('change-password success', { email }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } } /** @@ -1662,10 +1679,15 @@ export async function replacePassword (db: Db, productId: string, email: string, if (account === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email })) } - const salt = randomBytes(SALT_LEN) - const hash = await hashWithSalt(password, salt) - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } } /** @@ -1753,10 +1775,14 @@ export async function restorePassword ( } async function updatePassword (db: Db, account: Account, password: string | null): Promise { - const salt = randomBytes(32) - const hash = password !== null ? await hashWithSalt(password, salt) : null - - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { err })) + } } /** From a839dc5ba34ca6bd0a16213de43bfe5f23f1e942 Mon Sep 17 00:00:00 2001 From: Makarov Sergey Date: Mon, 3 Jun 2024 13:52:11 +0300 Subject: [PATCH 3/5] try...catch blocks added, improvements Signed-off-by: Makarov Sergey --- server/account/src/operations.ts | 80 +++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index f75c31fba57..e1ece7c07df 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -59,6 +59,11 @@ const WORKSPACE_COLLECTION = 'workspace' const ACCOUNT_COLLECTION = 'account' const INVITE_COLLECTION = 'invite' +const SALT_LEN: number = 32 +const PBKDF2_KEY_LEN: number = 32 +const PBKDF2_ITERATIONS: number = 1000 +const PBKDF2_DIGEST: string = 'sha256' + /** * @public */ @@ -146,22 +151,21 @@ export interface Invite { */ export type AccountInfo = Omit -const SALT_LEN: number = 32 -const PBKDF2_KEY_LEN: number = 32 -const PBKDF2_ITERATIONS: number = 1000 -const PBKDF2_DIGEST: string = 'sha256' - -/* -TODO: remove 'salt' property -*/ -const hashWithSalt = (password: string, salt: Buffer): Promise => { +const hashPassword = (password: string): Promise> => { return new Promise((resolve, reject) => { - pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { - if (err) { + randomBytes(SALT_LEN, (err, randomSalt) => { + if (err !== null) { reject(err) return } - resolve(derivedKey) + const callBack = (err: Error | null, derivedKey: Buffer): void => { + if (err !== null) { + reject(err) + return + } + resolve({ derivedKey, randomSalt }) + } + pbkdf2(password, randomSalt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, callBack) }) }) } @@ -169,7 +173,7 @@ const hashWithSalt = (password: string, salt: Buffer): Promise => { const verifyPassword = (password: string, hash: Buffer, salt: Buffer): Promise => { return new Promise((resolve, reject) => { pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { - if (err) { + if (err !== null) { reject(err) return } @@ -638,8 +642,17 @@ export async function createAcc ( extra?: Record ): Promise { const email = cleanEmail(_email) - const salt = randomBytes(SALT_LEN) - const hash = password !== null ? await hashWithSalt(password, salt) : null + let hash: Buffer | null = null + let salt: Buffer | null = null + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + hash = derivedKey + salt = randomSalt + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } const systemEmails = [systemAccountEmail] if (systemEmails.includes(email)) { @@ -1638,11 +1651,15 @@ export async function changePassword ( const { email } = decodeToken(token) const account = await getAccountInfo(ctx, db, productId, branding, email, oldPassword) - const salt = randomBytes(32) - const hash = await hashWithSalt(password, salt) - - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) - ctx.info('change-password success', { email }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + ctx.info('change-password success', { email }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } } /** @@ -1662,10 +1679,15 @@ export async function replacePassword (db: Db, productId: string, email: string, if (account === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email })) } - const salt = randomBytes(SALT_LEN) - const hash = await hashWithSalt(password, salt) - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } } /** @@ -1753,10 +1775,14 @@ export async function restorePassword ( } async function updatePassword (db: Db, account: Account, password: string | null): Promise { - const salt = randomBytes(32) - const hash = password !== null ? await hashWithSalt(password, salt) : null - - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { err })) + } } /** From 8312f8c6a7be3b3d150eb62d4c90e0e68b9d1215 Mon Sep 17 00:00:00 2001 From: Makarov Sergey Date: Fri, 31 May 2024 18:23:02 +0300 Subject: [PATCH 4/5] added timingSafeEqual comparison. verifyPassword, hashWithSalt changed to async Signed-off-by: Makarov Sergey --- server/account/src/operations.ts | 48 ++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index 3e12e66d5b8..f75c31fba57 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -49,7 +49,7 @@ import platform, { getMetadata, PlatformError, Severity, Status, translate } fro import { cloneWorkspace } from '@hcengineering/server-backup' import { decodeToken, generateToken } from '@hcengineering/server-token' import toolPlugin, { connect, initModel, upgradeModel } from '@hcengineering/server-tool' -import { pbkdf2Sync, randomBytes } from 'crypto' +import { randomBytes, pbkdf2, timingSafeEqual } from 'crypto' import { Binary, Db, Filter, ObjectId, type MongoClient } from 'mongodb' import fetch from 'node-fetch' import { type StorageAdapter } from '../../core/types' @@ -146,12 +146,36 @@ export interface Invite { */ export type AccountInfo = Omit -function hashWithSalt (password: string, salt: Buffer): Buffer { - return pbkdf2Sync(password, salt, 1000, 32, 'sha256') +const SALT_LEN: number = 32 +const PBKDF2_KEY_LEN: number = 32 +const PBKDF2_ITERATIONS: number = 1000 +const PBKDF2_DIGEST: string = 'sha256' + +/* +TODO: remove 'salt' property +*/ +const hashWithSalt = (password: string, salt: Buffer): Promise => { + return new Promise((resolve, reject) => { + pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { + if (err) { + reject(err) + return + } + resolve(derivedKey) + }) + }) } -function verifyPassword (password: string, hash: Buffer, salt: Buffer): boolean { - return Buffer.compare(hash, hashWithSalt(password, salt)) === 0 +const verifyPassword = (password: string, hash: Buffer, salt: Buffer): Promise => { + return new Promise((resolve, reject) => { + pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { + if (err) { + reject(err) + return + } + resolve(timingSafeEqual(hash, derivedKey)) + }) + }) } function cleanEmail (email: string): string { @@ -245,7 +269,7 @@ async function getAccountInfo ( if (account.hash === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.InvalidPassword, { account: email })) } - if (!verifyPassword(password, Buffer.from(account.hash.buffer), Buffer.from(account.salt.buffer))) { + if (!await verifyPassword(password, Buffer.from(account.hash.buffer), Buffer.from(account.salt.buffer))) { throw new PlatformError(new Status(Severity.ERROR, platform.status.InvalidPassword, { account: email })) } return toAccountInfo(account) @@ -614,8 +638,8 @@ export async function createAcc ( extra?: Record ): Promise { const email = cleanEmail(_email) - const salt = randomBytes(32) - const hash = password !== null ? hashWithSalt(password, salt) : null + const salt = randomBytes(SALT_LEN) + const hash = password !== null ? await hashWithSalt(password, salt) : null const systemEmails = [systemAccountEmail] if (systemEmails.includes(email)) { @@ -1615,7 +1639,7 @@ export async function changePassword ( const account = await getAccountInfo(ctx, db, productId, branding, email, oldPassword) const salt = randomBytes(32) - const hash = hashWithSalt(password, salt) + const hash = await hashWithSalt(password, salt) await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) ctx.info('change-password success', { email }) @@ -1638,8 +1662,8 @@ export async function replacePassword (db: Db, productId: string, email: string, if (account === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email })) } - const salt = randomBytes(32) - const hash = hashWithSalt(password, salt) + const salt = randomBytes(SALT_LEN) + const hash = await hashWithSalt(password, salt) await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) } @@ -1730,7 +1754,7 @@ export async function restorePassword ( async function updatePassword (db: Db, account: Account, password: string | null): Promise { const salt = randomBytes(32) - const hash = password !== null ? hashWithSalt(password, salt) : null + const hash = password !== null ? await hashWithSalt(password, salt) : null await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) } From d6ad824644aea67492c6d0b935b460f0d8d21140 Mon Sep 17 00:00:00 2001 From: Makarov Sergey Date: Mon, 3 Jun 2024 13:52:11 +0300 Subject: [PATCH 5/5] try...catch blocks added, improvements Signed-off-by: Makarov Sergey --- server/account/src/operations.ts | 80 +++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index f75c31fba57..e1ece7c07df 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -59,6 +59,11 @@ const WORKSPACE_COLLECTION = 'workspace' const ACCOUNT_COLLECTION = 'account' const INVITE_COLLECTION = 'invite' +const SALT_LEN: number = 32 +const PBKDF2_KEY_LEN: number = 32 +const PBKDF2_ITERATIONS: number = 1000 +const PBKDF2_DIGEST: string = 'sha256' + /** * @public */ @@ -146,22 +151,21 @@ export interface Invite { */ export type AccountInfo = Omit -const SALT_LEN: number = 32 -const PBKDF2_KEY_LEN: number = 32 -const PBKDF2_ITERATIONS: number = 1000 -const PBKDF2_DIGEST: string = 'sha256' - -/* -TODO: remove 'salt' property -*/ -const hashWithSalt = (password: string, salt: Buffer): Promise => { +const hashPassword = (password: string): Promise> => { return new Promise((resolve, reject) => { - pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { - if (err) { + randomBytes(SALT_LEN, (err, randomSalt) => { + if (err !== null) { reject(err) return } - resolve(derivedKey) + const callBack = (err: Error | null, derivedKey: Buffer): void => { + if (err !== null) { + reject(err) + return + } + resolve({ derivedKey, randomSalt }) + } + pbkdf2(password, randomSalt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, callBack) }) }) } @@ -169,7 +173,7 @@ const hashWithSalt = (password: string, salt: Buffer): Promise => { const verifyPassword = (password: string, hash: Buffer, salt: Buffer): Promise => { return new Promise((resolve, reject) => { pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST, (err, derivedKey) => { - if (err) { + if (err !== null) { reject(err) return } @@ -638,8 +642,17 @@ export async function createAcc ( extra?: Record ): Promise { const email = cleanEmail(_email) - const salt = randomBytes(SALT_LEN) - const hash = password !== null ? await hashWithSalt(password, salt) : null + let hash: Buffer | null = null + let salt: Buffer | null = null + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + hash = derivedKey + salt = randomSalt + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } const systemEmails = [systemAccountEmail] if (systemEmails.includes(email)) { @@ -1638,11 +1651,15 @@ export async function changePassword ( const { email } = decodeToken(token) const account = await getAccountInfo(ctx, db, productId, branding, email, oldPassword) - const salt = randomBytes(32) - const hash = await hashWithSalt(password, salt) - - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) - ctx.info('change-password success', { email }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + ctx.info('change-password success', { email }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } } /** @@ -1662,10 +1679,15 @@ export async function replacePassword (db: Db, productId: string, email: string, if (account === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email })) } - const salt = randomBytes(SALT_LEN) - const hash = await hashWithSalt(password, salt) - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { account: email, err })) + } } /** @@ -1753,10 +1775,14 @@ export async function restorePassword ( } async function updatePassword (db: Db, account: Account, password: string | null): Promise { - const salt = randomBytes(32) - const hash = password !== null ? await hashWithSalt(password, salt) : null - - await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } }) + try { + if (password !== null) { + const { derivedKey, randomSalt } = await hashPassword(password) + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { randomSalt, derivedKey } }) + } + } catch (err) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, { err })) + } } /**