mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 01:46:07 +00:00
use worker
This commit is contained in:
parent
02a2b46cf5
commit
c7e24b4b82
@ -51,7 +51,8 @@
|
||||
"type-graphql": "^1.1.1",
|
||||
"typed-rest-client": "^1.8.11",
|
||||
"uuid": "^8.3.2",
|
||||
"xregexp": "^5.1.1"
|
||||
"xregexp": "^5.1.1",
|
||||
"workerpool": "^9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^3.2.1",
|
||||
|
||||
@ -587,8 +587,8 @@ describe('UserResolver', () => {
|
||||
expect(newUser.emailContact.emailChecked).toBeTruthy()
|
||||
})
|
||||
|
||||
it('updates the password', () => {
|
||||
const encryptedPass = encryptPassword(newUser, 'Aa12345_')
|
||||
it('updates the password', async () => {
|
||||
const encryptedPass = await encryptPassword(newUser, 'Aa12345_')
|
||||
expect(newUser.password.toString()).toEqual(encryptedPass.toString())
|
||||
})
|
||||
|
||||
@ -1546,7 +1546,9 @@ describe('UserResolver', () => {
|
||||
|
||||
expect(bibi).toEqual(
|
||||
expect.objectContaining({
|
||||
password: SecretKeyCryptographyCreateKey(bibi.gradidoID.toString(), 'Aa12345_')[0]
|
||||
password: Buffer.from(
|
||||
(await SecretKeyCryptographyCreateKey(bibi.gradidoID.toString(), 'Aa12345_'))[0],
|
||||
)
|
||||
.readBigUInt64LE()
|
||||
.toString(),
|
||||
passwordEncryptionType: PasswordEncryptionType.GRADIDO_ID,
|
||||
@ -1570,10 +1572,9 @@ describe('UserResolver', () => {
|
||||
})
|
||||
bibi = usercontact.user
|
||||
bibi.passwordEncryptionType = PasswordEncryptionType.EMAIL
|
||||
bibi.password = SecretKeyCryptographyCreateKey(
|
||||
'bibi@bloxberg.de',
|
||||
'Aa12345_',
|
||||
)[0].readBigUInt64LE()
|
||||
bibi.password = Buffer.from(
|
||||
(await SecretKeyCryptographyCreateKey('bibi@bloxberg.de', 'Aa12345_'))[0],
|
||||
).readBigUInt64LE()
|
||||
|
||||
await bibi.save()
|
||||
})
|
||||
@ -1590,7 +1591,9 @@ describe('UserResolver', () => {
|
||||
expect(bibi).toEqual(
|
||||
expect.objectContaining({
|
||||
firstName: 'Bibi',
|
||||
password: SecretKeyCryptographyCreateKey(bibi.gradidoID.toString(), 'Aa12345_')[0]
|
||||
password: Buffer.from(
|
||||
(await SecretKeyCryptographyCreateKey(bibi.gradidoID.toString(), 'Aa12345_'))[0],
|
||||
)
|
||||
.readBigUInt64LE()
|
||||
.toString(),
|
||||
passwordEncryptionType: PasswordEncryptionType.GRADIDO_ID,
|
||||
|
||||
@ -162,7 +162,7 @@ export class UserResolver {
|
||||
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
|
||||
throw new LogError('The User has not set a password yet', dbUser)
|
||||
}
|
||||
if (!verifyPassword(dbUser, password)) {
|
||||
if (!(await verifyPassword(dbUser, password))) {
|
||||
throw new LogError('No user with this credentials', dbUser)
|
||||
}
|
||||
|
||||
@ -178,7 +178,7 @@ export class UserResolver {
|
||||
|
||||
if (dbUser.passwordEncryptionType !== PasswordEncryptionType.GRADIDO_ID) {
|
||||
dbUser.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||
dbUser.password = encryptPassword(dbUser, password)
|
||||
dbUser.password = await encryptPassword(dbUser, password)
|
||||
await dbUser.save()
|
||||
}
|
||||
// add pubKey in logger-context for layout-pattern X{user} to print it in each logging message
|
||||
@ -502,7 +502,7 @@ export class UserResolver {
|
||||
|
||||
// Update Password
|
||||
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||
user.password = encryptPassword(user, password)
|
||||
user.password = await encryptPassword(user, password)
|
||||
logger.debug('User credentials updated ...')
|
||||
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
@ -632,13 +632,13 @@ export class UserResolver {
|
||||
)
|
||||
}
|
||||
|
||||
if (!verifyPassword(user, password)) {
|
||||
if (!(await verifyPassword(user, password))) {
|
||||
throw new LogError(`Old password is invalid`)
|
||||
}
|
||||
|
||||
// Save new password hash and newly encrypted private key
|
||||
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||
user.password = encryptPassword(user, passwordNew)
|
||||
user.password = await encryptPassword(user, passwordNew)
|
||||
}
|
||||
|
||||
// Save hideAmountGDD value
|
||||
|
||||
50
backend/src/password/EncryptionWorker.ts
Normal file
50
backend/src/password/EncryptionWorker.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { worker } from 'workerpool'
|
||||
|
||||
import {
|
||||
crypto_box_SEEDBYTES,
|
||||
crypto_hash_sha512_init,
|
||||
crypto_hash_sha512_update,
|
||||
crypto_hash_sha512_final,
|
||||
crypto_hash_sha512_BYTES,
|
||||
crypto_hash_sha512_STATEBYTES,
|
||||
crypto_shorthash_BYTES,
|
||||
crypto_pwhash_SALTBYTES,
|
||||
crypto_pwhash,
|
||||
crypto_shorthash,
|
||||
} from 'sodium-native'
|
||||
|
||||
export const SecretKeyCryptographyCreateKey = (
|
||||
salt: string,
|
||||
password: string,
|
||||
configLoginAppSecret: Buffer,
|
||||
configLoginServerKey: Buffer,
|
||||
): Buffer[] => {
|
||||
const state = Buffer.alloc(crypto_hash_sha512_STATEBYTES)
|
||||
crypto_hash_sha512_init(state)
|
||||
crypto_hash_sha512_update(state, Buffer.from(salt))
|
||||
crypto_hash_sha512_update(state, configLoginAppSecret)
|
||||
const hash = Buffer.alloc(crypto_hash_sha512_BYTES)
|
||||
crypto_hash_sha512_final(state, hash)
|
||||
|
||||
const encryptionKey = Buffer.alloc(crypto_box_SEEDBYTES)
|
||||
const opsLimit = 10
|
||||
const memLimit = 33554432
|
||||
const algo = 2
|
||||
crypto_pwhash(
|
||||
encryptionKey,
|
||||
Buffer.from(password),
|
||||
hash.slice(0, crypto_pwhash_SALTBYTES),
|
||||
opsLimit,
|
||||
memLimit,
|
||||
algo,
|
||||
)
|
||||
|
||||
const encryptionKeyHash = Buffer.alloc(crypto_shorthash_BYTES)
|
||||
crypto_shorthash(encryptionKeyHash, encryptionKey, configLoginServerKey)
|
||||
|
||||
return [encryptionKeyHash, encryptionKey]
|
||||
}
|
||||
|
||||
worker({
|
||||
SecretKeyCryptographyCreateKey,
|
||||
})
|
||||
@ -2,7 +2,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { cpus } from 'os'
|
||||
import path from 'path'
|
||||
|
||||
import { User } from '@entity/User'
|
||||
import { pool } from 'workerpool'
|
||||
|
||||
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
|
||||
|
||||
@ -10,61 +14,54 @@ import { CONFIG } from '@/config'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import {
|
||||
crypto_shorthash_KEYBYTES,
|
||||
crypto_box_SEEDBYTES,
|
||||
crypto_hash_sha512_init,
|
||||
crypto_hash_sha512_update,
|
||||
crypto_hash_sha512_final,
|
||||
crypto_hash_sha512_BYTES,
|
||||
crypto_hash_sha512_STATEBYTES,
|
||||
crypto_shorthash_BYTES,
|
||||
crypto_pwhash_SALTBYTES,
|
||||
crypto_pwhash,
|
||||
crypto_shorthash,
|
||||
} from 'sodium-native'
|
||||
import { crypto_shorthash_KEYBYTES } from 'sodium-native'
|
||||
|
||||
const configLoginAppSecret = Buffer.from(CONFIG.LOGIN_APP_SECRET, 'hex')
|
||||
const configLoginServerKey = Buffer.from(CONFIG.LOGIN_SERVER_KEY, 'hex')
|
||||
|
||||
// TODO: put maxQueueSize into config
|
||||
const encryptionWorkerPool = pool(
|
||||
path.join(__dirname, '..', '..', 'build', 'src', 'password', '/EncryptionWorker.js'),
|
||||
{
|
||||
maxQueueSize: 30 * cpus().length,
|
||||
},
|
||||
)
|
||||
|
||||
// We will reuse this for changePassword
|
||||
export const isValidPassword = (password: string): boolean => {
|
||||
return !!password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9 \\t\\n\\r]).{8,}$/)
|
||||
}
|
||||
|
||||
export const SecretKeyCryptographyCreateKey = (salt: string, password: string): Buffer[] => {
|
||||
logger.trace('SecretKeyCryptographyCreateKey...')
|
||||
const configLoginAppSecret = Buffer.from(CONFIG.LOGIN_APP_SECRET, 'hex')
|
||||
const configLoginServerKey = Buffer.from(CONFIG.LOGIN_SERVER_KEY, 'hex')
|
||||
if (configLoginServerKey.length !== crypto_shorthash_KEYBYTES) {
|
||||
throw new LogError(
|
||||
'ServerKey has an invalid size',
|
||||
configLoginServerKey.length,
|
||||
crypto_shorthash_KEYBYTES,
|
||||
)
|
||||
/**
|
||||
* @param salt
|
||||
* @param password
|
||||
* @returns can throw an exception if worker pool is full, if more than 30 * cpu core count logins happen in a time range of 30 seconds
|
||||
*/
|
||||
export const SecretKeyCryptographyCreateKey = async (
|
||||
salt: string,
|
||||
password: string,
|
||||
): Promise<Uint8Array[]> => {
|
||||
try {
|
||||
logger.trace('call worker for: SecretKeyCryptographyCreateKey')
|
||||
if (configLoginServerKey.length !== crypto_shorthash_KEYBYTES) {
|
||||
throw new LogError(
|
||||
'ServerKey has an invalid size',
|
||||
configLoginServerKey.length,
|
||||
crypto_shorthash_KEYBYTES,
|
||||
)
|
||||
}
|
||||
return (await encryptionWorkerPool.exec('SecretKeyCryptographyCreateKey', [
|
||||
salt,
|
||||
password,
|
||||
configLoginAppSecret,
|
||||
configLoginServerKey,
|
||||
])) as Promise<Uint8Array[]>
|
||||
} catch (e) {
|
||||
// pool is throwing this error
|
||||
// throw new Error('Max queue size of ' + this.maxQueueSize + ' reached');
|
||||
// will be shown in frontend to user
|
||||
throw new LogError('Server is full, please try again in 10 minutes.', e)
|
||||
}
|
||||
|
||||
const state = Buffer.alloc(crypto_hash_sha512_STATEBYTES)
|
||||
crypto_hash_sha512_init(state)
|
||||
crypto_hash_sha512_update(state, Buffer.from(salt))
|
||||
crypto_hash_sha512_update(state, configLoginAppSecret)
|
||||
const hash = Buffer.alloc(crypto_hash_sha512_BYTES)
|
||||
crypto_hash_sha512_final(state, hash)
|
||||
|
||||
const encryptionKey = Buffer.alloc(crypto_box_SEEDBYTES)
|
||||
const opsLimit = 10
|
||||
const memLimit = 33554432
|
||||
const algo = 2
|
||||
crypto_pwhash(
|
||||
encryptionKey,
|
||||
Buffer.from(password),
|
||||
hash.slice(0, crypto_pwhash_SALTBYTES),
|
||||
opsLimit,
|
||||
memLimit,
|
||||
algo,
|
||||
)
|
||||
|
||||
const encryptionKeyHash = Buffer.alloc(crypto_shorthash_BYTES)
|
||||
crypto_shorthash(encryptionKeyHash, encryptionKey, configLoginServerKey)
|
||||
|
||||
return [encryptionKeyHash, encryptionKey]
|
||||
}
|
||||
|
||||
export const getUserCryptographicSalt = (dbUser: User): string => {
|
||||
|
||||
@ -3,13 +3,14 @@ import { User } from '@entity/User'
|
||||
// import { logger } from '@test/testSetup' getting error "jest is not defined"
|
||||
import { getUserCryptographicSalt, SecretKeyCryptographyCreateKey } from './EncryptorUtils'
|
||||
|
||||
export const encryptPassword = (dbUser: User, password: string): bigint => {
|
||||
export const encryptPassword = async (dbUser: User, password: string): Promise<bigint> => {
|
||||
const salt = getUserCryptographicSalt(dbUser)
|
||||
const keyBuffer = SecretKeyCryptographyCreateKey(salt, password) // return short and long hash
|
||||
const passwordHash = keyBuffer[0].readBigUInt64LE()
|
||||
const keyBuffer = await SecretKeyCryptographyCreateKey(salt, password) // return short and long hash
|
||||
const passwordHash = Buffer.from(keyBuffer[0]).readBigUInt64LE()
|
||||
return passwordHash
|
||||
}
|
||||
|
||||
export const verifyPassword = (dbUser: User, password: string): boolean => {
|
||||
return dbUser.password.toString() === encryptPassword(dbUser, password).toString()
|
||||
export const verifyPassword = async (dbUser: User, password: string): Promise<boolean> => {
|
||||
const encryptedPassword = await encryptPassword(dbUser, password)
|
||||
return dbUser.password.toString() === encryptedPassword.toString()
|
||||
}
|
||||
|
||||
@ -23,7 +23,6 @@ export const creationFactory = async (
|
||||
mutation: login,
|
||||
variables: { email: creation.email, password: 'Aa12345_' },
|
||||
})
|
||||
|
||||
const {
|
||||
data: { createContribution: contribution },
|
||||
} = await mutate({ mutation: createContribution, variables: { ...creation } })
|
||||
|
||||
@ -3709,7 +3709,7 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
"gradido-database@file:../database":
|
||||
version "2.3.1"
|
||||
version "2.4.1"
|
||||
dependencies:
|
||||
"@types/uuid" "^8.3.4"
|
||||
cross-env "^7.0.3"
|
||||
@ -7369,6 +7369,11 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
|
||||
workerpool@^9.2.0:
|
||||
version "9.2.0"
|
||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.2.0.tgz#f74427cbb61234708332ed8ab9cbf56dcb1c4371"
|
||||
integrity sha512-PKZqBOCo6CYkVOwAxWxQaSF2Fvb5Iv2fCeTP7buyWI2GiynWr46NcXSgK/idoV6e60dgCBfgYc+Un3HMvmqP8w==
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user