new password implementation set up and test

This commit is contained in:
joseji 2022-11-17 13:38:16 +01:00
parent 13df737c09
commit 47d8469eb3
4 changed files with 103 additions and 81 deletions

View File

@ -36,6 +36,10 @@ import { UserContact } from '@entity/UserContact'
import { OptInType } from '../enum/OptInType' import { OptInType } from '../enum/OptInType'
import { UserContactType } from '../enum/UserContactType' import { UserContactType } from '../enum/UserContactType'
import { bobBaumeister } from '@/seeds/users/bob-baumeister' import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { encryptPassword } from '@/password/PasswordEncryptr'
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
import { find } from 'lodash'
import { SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
// import { klicktippSignIn } from '@/apis/KlicktippController' // import { klicktippSignIn } from '@/apis/KlicktippController'
@ -491,7 +495,8 @@ describe('UserResolver', () => {
}) })
it('updates the password', () => { it('updates the password', () => {
expect(newUser.password).toEqual('3917921995996627700') const encryptedPass = encryptPassword(newUser, 'Aa12345_')
expect(newUser.password.toString()).toEqual(encryptedPass.toString())
}) })
/* /*
@ -1159,6 +1164,64 @@ describe('UserResolver', () => {
}) })
}) })
}) })
describe('password encryption type', () => {
describe('user just registered', () => {
let bibi: User
it('password type should be gradido id', async () => {
const users = await User.find()
bibi = users[1]
expect(bibi).toEqual(
expect.objectContaining({
password: SecretKeyCryptographyCreateKey(bibi.gradidoID.toString(), 'Aa12345_')[0]
.readBigUInt64LE()
.toString(),
passwordEncryptionType: PasswordEncryptionType.GRADIDO_ID,
}),
)
})
})
describe('user has encryption type email', () => {
const variables = {
email: 'bibi@bloxberg.de',
password: 'Aa12345_',
publisherId: 1234,
}
let bibi: User
beforeAll(async () => {
const users = await User.find()
bibi = users[1]
bibi.passwordEncryptionType = PasswordEncryptionType.EMAIL
bibi.password = SecretKeyCryptographyCreateKey(
'bibi@bloxberg.de',
'Aa12345_',
)[0].readBigUInt64LE()
await bibi.save()
})
it('changes to gradidoID on login', async () => {
await mutate({ mutation: login, variables: variables })
const users = await User.find()
bibi = users[0]
expect(bibi).toEqual(
expect.objectContaining({
password: SecretKeyCryptographyCreateKey(bibi.gradidoID.toString(), 'Aa12345_')[0]
.readBigUInt64LE()
.toString(),
passwordEncryptionType: PasswordEncryptionType.GRADIDO_ID,
}),
)
})
})
})
}) })
describe('printTimeDuration', () => { describe('printTimeDuration', () => {

View File

@ -39,17 +39,15 @@ import { SearchAdminUsersResult } from '@model/AdminUser'
import Paginated from '@arg/Paginated' import Paginated from '@arg/Paginated'
import { Order } from '@enum/Order' import { Order } from '@enum/Order'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { isValidPassword, SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptr'
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const sodium = require('sodium-native') const sodium = require('sodium-native')
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const random = require('random-bigint') const random = require('random-bigint')
// We will reuse this for changePassword
const isPassword = (password: string): boolean => {
return !!password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9 \\t\\n\\r]).{8,}$/)
}
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
const DEFAULT_LANGUAGE = 'de' const DEFAULT_LANGUAGE = 'de'
const isLanguage = (language: string): boolean => { const isLanguage = (language: string): boolean => {
@ -106,48 +104,6 @@ const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
return [pubKey, privKey] return [pubKey, privKey]
} }
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 !== sodium.crypto_shorthash_KEYBYTES) {
logger.error(
`ServerKey has an invalid size. The size must be ${sodium.crypto_shorthash_KEYBYTES} bytes.`,
)
throw new Error(
`ServerKey has an invalid size. The size must be ${sodium.crypto_shorthash_KEYBYTES} bytes.`,
)
}
const state = Buffer.alloc(sodium.crypto_hash_sha512_STATEBYTES)
sodium.crypto_hash_sha512_init(state)
sodium.crypto_hash_sha512_update(state, Buffer.from(salt))
sodium.crypto_hash_sha512_update(state, configLoginAppSecret)
const hash = Buffer.alloc(sodium.crypto_hash_sha512_BYTES)
sodium.crypto_hash_sha512_final(state, hash)
const encryptionKey = Buffer.alloc(sodium.crypto_box_SEEDBYTES)
const opsLimit = 10
const memLimit = 33554432
const algo = 2
sodium.crypto_pwhash(
encryptionKey,
Buffer.from(password),
hash.slice(0, sodium.crypto_pwhash_SALTBYTES),
opsLimit,
memLimit,
algo,
)
const encryptionKeyHash = Buffer.alloc(sodium.crypto_shorthash_BYTES)
sodium.crypto_shorthash(encryptionKeyHash, encryptionKey, configLoginServerKey)
logger.debug(
`SecretKeyCryptographyCreateKey...successful: encryptionKeyHash= ${encryptionKeyHash}, encryptionKey= ${encryptionKey}`,
)
return [encryptionKeyHash, encryptionKey]
}
/* /*
const getEmailHash = (email: string): Buffer => { const getEmailHash = (email: string): Buffer => {
logger.trace('getEmailHash...') logger.trace('getEmailHash...')
@ -343,12 +299,16 @@ export class UserResolver {
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code // TODO we want to catch this on the frontend and ask the user to check his emails or resend code
throw new Error('User has no private or publicKey') throw new Error('User has no private or publicKey')
} }
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
const loginUserPassword = BigInt(dbUser.password.toString()) if (!verifyPassword(dbUser, password)) {
if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
logger.error('The User has no valid credentials.') logger.error('The User has no valid credentials.')
throw new Error('No user with this credentials') throw new Error('No user with this credentials')
} }
if (dbUser.passwordEncryptionType !== PasswordEncryptionType.GRADIDO_ID) {
dbUser.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
dbUser.password = encryptPassword(dbUser, password)
}
// add pubKey in logger-context for layout-pattern X{user} to print it in each logging message // add pubKey in logger-context for layout-pattern X{user} to print it in each logging message
logger.addContext('user', dbUser.id) logger.addContext('user', dbUser.id)
logger.debug('validation of login credentials successful...') logger.debug('validation of login credentials successful...')
@ -623,7 +583,7 @@ export class UserResolver {
): Promise<boolean> { ): Promise<boolean> {
logger.info(`setPassword(${code}, ***)...`) logger.info(`setPassword(${code}, ***)...`)
// Validate Password // Validate Password
if (!isPassword(password)) { if (!isValidPassword(password)) {
logger.error('Password entered is lexically invalid') logger.error('Password entered is lexically invalid')
throw new Error( throw new Error(
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!', 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
@ -681,10 +641,11 @@ export class UserResolver {
userContact.emailChecked = true userContact.emailChecked = true
// Update Password // Update Password
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
const passwordHash = SecretKeyCryptographyCreateKey(userContact.email, password) // return short and long hash const passwordHash = SecretKeyCryptographyCreateKey(userContact.email, password) // return short and long hash
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1]) const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
user.password = passwordHash[0].readBigUInt64LE() // using the shorthash user.password = encryptPassword(user, password)
user.pubKey = keyPair[0] user.pubKey = keyPair[0]
user.privKey = encryptedPrivkey user.privKey = encryptedPrivkey
logger.debug('User credentials updated ...') logger.debug('User credentials updated ...')
@ -789,7 +750,7 @@ export class UserResolver {
if (password && passwordNew) { if (password && passwordNew) {
// Validate Password // Validate Password
if (!isPassword(passwordNew)) { if (!isValidPassword(passwordNew)) {
logger.error('newPassword does not fullfil the rules') logger.error('newPassword does not fullfil the rules')
throw new Error( throw new Error(
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!', 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
@ -801,7 +762,7 @@ export class UserResolver {
userEntity.emailContact.email, userEntity.emailContact.email,
password, password,
) )
if (BigInt(userEntity.password.toString()) !== oldPasswordHash[0].readBigUInt64LE()) { if (!verifyPassword(userEntity, password)) {
logger.error(`Old password is invalid`) logger.error(`Old password is invalid`)
throw new Error(`Old password is invalid`) throw new Error(`Old password is invalid`)
} }
@ -817,7 +778,7 @@ export class UserResolver {
logger.debug('PrivateKey encrypted...') logger.debug('PrivateKey encrypted...')
// Save new password hash and newly encrypted private key // Save new password hash and newly encrypted private key
userEntity.password = newPasswordHash[0].readBigUInt64LE() userEntity.password = encryptPassword(userEntity, passwordNew)
userEntity.privKey = encryptedPrivkey userEntity.privKey = encryptedPrivkey
} }

View File

@ -1,30 +1,28 @@
import { User } from '@entity/User' import { User } from '@entity/User'
import { logger } from '@test/testSetup' // import { logger } from '@test/testSetup' getting error "jest is not defined"
import { getBasicCryptographicKey, SecretKeyCryptographyCreateKey } from './EncryptorUtils' import { getBasicCryptographicKey, SecretKeyCryptographyCreateKey } from './EncryptorUtils'
export class PasswordEncryptr { export const encryptPassword = (dbUser: User, password: string): bigint => {
async encryptPassword(dbUser: User, password: string): Promise<bigint> {
const basicKey = getBasicCryptographicKey(dbUser) const basicKey = getBasicCryptographicKey(dbUser)
if (!basicKey) logger.error('Password not set for user ' + dbUser.id) if (!basicKey) {
else { // logger.error('Password not set for user ' + dbUser.id)
throw new Error('Password not set for user ' + dbUser.id) // user has no password
} else {
const keyBuffer = SecretKeyCryptographyCreateKey(basicKey, password) // return short and long hash const keyBuffer = SecretKeyCryptographyCreateKey(basicKey, password) // return short and long hash
const passwordHash = keyBuffer[0].readBigUInt64LE() const passwordHash = keyBuffer[0].readBigUInt64LE()
return passwordHash return passwordHash
} }
throw new Error('Password not set for user ' + dbUser.id) // user has no password
} }
async verifyPassword(dbUser: User, password: string): Promise<boolean> { export const verifyPassword = (dbUser: User, password: string): boolean => {
const basicKey = getBasicCryptographicKey(dbUser) const basicKey = getBasicCryptographicKey(dbUser)
if (!basicKey) logger.error('Password not set for user ' + dbUser.id) if (!basicKey) {
else { // logger.error('Password not set for user ' + dbUser.id)
if (BigInt(password) !== (await this.encryptPassword(dbUser, dbUser.password.toString()))) { throw new Error('Password not set for user ' + dbUser.id) // user has no password
} else {
if (dbUser.password.toString() !== encryptPassword(dbUser, password).toString()) {
return false return false
} }
return true return true
} }
throw new Error('Password not set for user ' + dbUser.id) // user has no password
}
} }

View File

@ -27,7 +27,7 @@ const communityDbUser: dbUser = {
publisherId: 0, publisherId: 0,
passphrase: '', passphrase: '',
// default password encryption type // default password encryption type
passwordEncryptionType: 2, passwordEncryptionType: 0,
hasId: function (): boolean { hasId: function (): boolean {
throw new Error('Function not implemented.') throw new Error('Function not implemented.')
}, },