mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
new password implementation set up and test
This commit is contained in:
parent
13df737c09
commit
47d8469eb3
@ -36,6 +36,10 @@ import { UserContact } from '@entity/UserContact'
|
||||
import { OptInType } from '../enum/OptInType'
|
||||
import { UserContactType } from '../enum/UserContactType'
|
||||
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'
|
||||
|
||||
@ -491,7 +495,8 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
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', () => {
|
||||
|
||||
@ -39,17 +39,15 @@ import { SearchAdminUsersResult } from '@model/AdminUser'
|
||||
import Paginated from '@arg/Paginated'
|
||||
import { Order } from '@enum/Order'
|
||||
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
|
||||
const sodium = require('sodium-native')
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
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 DEFAULT_LANGUAGE = 'de'
|
||||
const isLanguage = (language: string): boolean => {
|
||||
@ -106,48 +104,6 @@ const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
|
||||
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 => {
|
||||
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
|
||||
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 (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
|
||||
|
||||
if (!verifyPassword(dbUser, password)) {
|
||||
logger.error('The User has no valid 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
|
||||
logger.addContext('user', dbUser.id)
|
||||
logger.debug('validation of login credentials successful...')
|
||||
@ -623,7 +583,7 @@ export class UserResolver {
|
||||
): Promise<boolean> {
|
||||
logger.info(`setPassword(${code}, ***)...`)
|
||||
// Validate Password
|
||||
if (!isPassword(password)) {
|
||||
if (!isValidPassword(password)) {
|
||||
logger.error('Password entered is lexically invalid')
|
||||
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!',
|
||||
@ -681,10 +641,11 @@ export class UserResolver {
|
||||
userContact.emailChecked = true
|
||||
|
||||
// Update Password
|
||||
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||
const passwordHash = SecretKeyCryptographyCreateKey(userContact.email, password) // return short and long hash
|
||||
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||
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.privKey = encryptedPrivkey
|
||||
logger.debug('User credentials updated ...')
|
||||
@ -789,7 +750,7 @@ export class UserResolver {
|
||||
|
||||
if (password && passwordNew) {
|
||||
// Validate Password
|
||||
if (!isPassword(passwordNew)) {
|
||||
if (!isValidPassword(passwordNew)) {
|
||||
logger.error('newPassword does not fullfil the rules')
|
||||
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!',
|
||||
@ -801,7 +762,7 @@ export class UserResolver {
|
||||
userEntity.emailContact.email,
|
||||
password,
|
||||
)
|
||||
if (BigInt(userEntity.password.toString()) !== oldPasswordHash[0].readBigUInt64LE()) {
|
||||
if (!verifyPassword(userEntity, password)) {
|
||||
logger.error(`Old password is invalid`)
|
||||
throw new Error(`Old password is invalid`)
|
||||
}
|
||||
@ -817,7 +778,7 @@ export class UserResolver {
|
||||
logger.debug('PrivateKey encrypted...')
|
||||
|
||||
// Save new password hash and newly encrypted private key
|
||||
userEntity.password = newPasswordHash[0].readBigUInt64LE()
|
||||
userEntity.password = encryptPassword(userEntity, passwordNew)
|
||||
userEntity.privKey = encryptedPrivkey
|
||||
}
|
||||
|
||||
|
||||
@ -1,30 +1,28 @@
|
||||
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'
|
||||
|
||||
export class PasswordEncryptr {
|
||||
async encryptPassword(dbUser: User, password: string): Promise<bigint> {
|
||||
export const encryptPassword = (dbUser: User, password: string): bigint => {
|
||||
const basicKey = getBasicCryptographicKey(dbUser)
|
||||
if (!basicKey) logger.error('Password not set for user ' + dbUser.id)
|
||||
else {
|
||||
if (!basicKey) {
|
||||
// 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 passwordHash = keyBuffer[0].readBigUInt64LE()
|
||||
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)
|
||||
if (!basicKey) logger.error('Password not set for user ' + dbUser.id)
|
||||
else {
|
||||
if (BigInt(password) !== (await this.encryptPassword(dbUser, dbUser.password.toString()))) {
|
||||
if (!basicKey) {
|
||||
// logger.error('Password not set for user ' + dbUser.id)
|
||||
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 true
|
||||
}
|
||||
|
||||
throw new Error('Password not set for user ' + dbUser.id) // user has no password
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ const communityDbUser: dbUser = {
|
||||
publisherId: 0,
|
||||
passphrase: '',
|
||||
// default password encryption type
|
||||
passwordEncryptionType: 2,
|
||||
passwordEncryptionType: 0,
|
||||
hasId: function (): boolean {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user