mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #2426 from gradido/clear-old-password-junk
refactor(backend): cleaning user related old password junk
This commit is contained in:
commit
a15b985c82
@ -107,9 +107,7 @@ COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
|
|||||||
COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
|
COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
|
||||||
# Copy log4js-config.json to provide log configuration
|
# Copy log4js-config.json to provide log configuration
|
||||||
COPY --from=build ${DOCKER_WORKDIR}/log4js-config.json ./log4js-config.json
|
COPY --from=build ${DOCKER_WORKDIR}/log4js-config.json ./log4js-config.json
|
||||||
# Copy memonic type since its referenced in the sources
|
|
||||||
# TODO: remove
|
|
||||||
COPY --from=build ${DOCKER_WORKDIR}/src/config/mnemonic.uncompressed_buffer13116.txt ./src/config/mnemonic.uncompressed_buffer13116.txt
|
|
||||||
# Copy run scripts run/
|
# Copy run scripts run/
|
||||||
# COPY --from=build ${DOCKER_WORKDIR}/run ./run
|
# COPY --from=build ${DOCKER_WORKDIR}/run ./run
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { JwtPayload } from 'jsonwebtoken'
|
import { JwtPayload } from 'jsonwebtoken'
|
||||||
|
|
||||||
export interface CustomJwtPayload extends JwtPayload {
|
export interface CustomJwtPayload extends JwtPayload {
|
||||||
pubKey: Buffer
|
gradidoID: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,8 +11,8 @@ export const decode = (token: string): CustomJwtPayload | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encode = (pubKey: Buffer): string => {
|
export const encode = (gradidoID: string): string => {
|
||||||
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
|
const token = jwt.sign({ gradidoID }, CONFIG.JWT_SECRET, {
|
||||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||||
})
|
})
|
||||||
return token
|
return token
|
||||||
|
|||||||
@ -10,7 +10,7 @@ Decimal.set({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const constants = {
|
const constants = {
|
||||||
DB_VERSION: '0056-consistent_transactions_table',
|
DB_VERSION: '0057-clear_old_password_junk',
|
||||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||||
LOG4JS_CONFIG: 'log4js-config.json',
|
LOG4JS_CONFIG: 'log4js-config.json',
|
||||||
// default log level on production should be info
|
// default log level on production should be info
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -5,9 +5,8 @@ import { AuthChecker } from 'type-graphql'
|
|||||||
import { decode, encode } from '@/auth/JWT'
|
import { decode, encode } from '@/auth/JWT'
|
||||||
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
|
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
|
||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { getCustomRepository } from '@dbTools/typeorm'
|
|
||||||
import { UserRepository } from '@repository/User'
|
|
||||||
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
||||||
|
import { User } from '@entity/User'
|
||||||
|
|
||||||
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||||
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||||
@ -26,14 +25,16 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
|||||||
if (!decoded) {
|
if (!decoded) {
|
||||||
throw new Error('403.13 - Client certificate revoked')
|
throw new Error('403.13 - Client certificate revoked')
|
||||||
}
|
}
|
||||||
// Set context pubKey
|
// Set context gradidoID
|
||||||
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
|
context.gradidoID = decoded.gradidoID
|
||||||
|
|
||||||
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
|
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
|
||||||
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
|
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
|
||||||
const userRepository = getCustomRepository(UserRepository)
|
|
||||||
try {
|
try {
|
||||||
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
const user = await User.findOneOrFail({
|
||||||
|
where: { gradidoID: decoded.gradidoID },
|
||||||
|
relations: ['emailContact'],
|
||||||
|
})
|
||||||
context.user = user
|
context.user = user
|
||||||
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||||
} catch {
|
} catch {
|
||||||
@ -48,7 +49,7 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set new header token
|
// set new header token
|
||||||
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
context.setHeaders.push({ key: 'token', value: encode(decoded.gradidoID) })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,12 +16,12 @@ import { Transaction } from '@model/Transaction'
|
|||||||
import { TransactionList } from '@model/TransactionList'
|
import { TransactionList } from '@model/TransactionList'
|
||||||
import { Order } from '@enum/Order'
|
import { Order } from '@enum/Order'
|
||||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||||
|
import { calculateBalance } from '@/util/validate'
|
||||||
import TransactionSendArgs from '@arg/TransactionSendArgs'
|
import TransactionSendArgs from '@arg/TransactionSendArgs'
|
||||||
import Paginated from '@arg/Paginated'
|
import Paginated from '@arg/Paginated'
|
||||||
|
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
import { Context, getUser } from '@/server/context'
|
import { Context, getUser } from '@/server/context'
|
||||||
import { calculateBalance, isHexPublicKey } from '@/util/validate'
|
|
||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { communityUser } from '@/util/communityUser'
|
import { communityUser } from '@/util/communityUser'
|
||||||
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
|
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
|
||||||
@ -315,10 +315,6 @@ export class TransactionResolver {
|
|||||||
|
|
||||||
// TODO this is subject to replay attacks
|
// TODO this is subject to replay attacks
|
||||||
const senderUser = getUser(context)
|
const senderUser = getUser(context)
|
||||||
if (senderUser.pubKey.length !== 32) {
|
|
||||||
logger.error(`invalid sender public key:${senderUser.pubKey}`)
|
|
||||||
throw new Error('invalid sender public key')
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate recipient user
|
// validate recipient user
|
||||||
const recipientUser = await findUserByEmail(email)
|
const recipientUser = await findUserByEmail(email)
|
||||||
@ -331,10 +327,6 @@ export class TransactionResolver {
|
|||||||
logger.error(`The recipient account is not activated: recipientUser=${recipientUser}`)
|
logger.error(`The recipient account is not activated: recipientUser=${recipientUser}`)
|
||||||
throw new Error('The recipient account is not activated')
|
throw new Error('The recipient account is not activated')
|
||||||
}
|
}
|
||||||
if (!isHexPublicKey(recipientUser.pubKey.toString('hex'))) {
|
|
||||||
logger.error(`invalid recipient public key: recipientUser=${recipientUser}`)
|
|
||||||
throw new Error('invalid recipient public key')
|
|
||||||
}
|
|
||||||
|
|
||||||
await executeTransaction(amount, memo, senderUser, recipientUser)
|
await executeTransaction(amount, memo, senderUser, recipientUser)
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@ -138,12 +138,8 @@ describe('UserResolver', () => {
|
|||||||
firstName: 'Peter',
|
firstName: 'Peter',
|
||||||
lastName: 'Lustig',
|
lastName: 'Lustig',
|
||||||
password: '0',
|
password: '0',
|
||||||
pubKey: null,
|
|
||||||
privKey: null,
|
|
||||||
// emailHash: expect.any(Buffer),
|
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
// emailChecked: false,
|
// emailChecked: false,
|
||||||
passphrase: expect.any(String),
|
|
||||||
language: 'de',
|
language: 'de',
|
||||||
isAdmin: null,
|
isAdmin: null,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import i18n from 'i18n'
|
import i18n from 'i18n'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import {
|
import {
|
||||||
@ -60,8 +59,8 @@ import {
|
|||||||
EventActivateAccount,
|
EventActivateAccount,
|
||||||
} from '@/event/Event'
|
} from '@/event/Event'
|
||||||
import { getUserCreation, getUserCreations } from './util/creations'
|
import { getUserCreation, getUserCreations } from './util/creations'
|
||||||
|
import { isValidPassword } from '@/password/EncryptorUtils'
|
||||||
import { FULL_CREATION_AVAILABLE } from './const/const'
|
import { FULL_CREATION_AVAILABLE } from './const/const'
|
||||||
import { isValidPassword, SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
|
|
||||||
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
||||||
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
|
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
|
||||||
|
|
||||||
@ -76,79 +75,6 @@ const isLanguage = (language: string): boolean => {
|
|||||||
return LANGUAGES.includes(language)
|
return LANGUAGES.includes(language)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PHRASE_WORD_COUNT = 24
|
|
||||||
const WORDS = fs
|
|
||||||
.readFileSync('src/config/mnemonic.uncompressed_buffer13116.txt')
|
|
||||||
.toString()
|
|
||||||
.split(',')
|
|
||||||
const PassphraseGenerate = (): string[] => {
|
|
||||||
logger.trace('PassphraseGenerate...')
|
|
||||||
const result = []
|
|
||||||
for (let i = 0; i < PHRASE_WORD_COUNT; i++) {
|
|
||||||
result.push(WORDS[sodium.randombytes_random() % 2048])
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
|
|
||||||
logger.trace('KeyPairEd25519Create...')
|
|
||||||
if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) {
|
|
||||||
logger.error('passphrase empty or to short')
|
|
||||||
throw new Error('passphrase empty or to short')
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = Buffer.alloc(sodium.crypto_hash_sha512_STATEBYTES)
|
|
||||||
sodium.crypto_hash_sha512_init(state)
|
|
||||||
|
|
||||||
// To prevent breaking existing passphrase-hash combinations word indices will be put into 64 Bit Variable to mimic first implementation of algorithms
|
|
||||||
for (let i = 0; i < PHRASE_WORD_COUNT; i++) {
|
|
||||||
const value = Buffer.alloc(8)
|
|
||||||
const wordIndex = WORDS.indexOf(passphrase[i])
|
|
||||||
value.writeBigInt64LE(BigInt(wordIndex))
|
|
||||||
sodium.crypto_hash_sha512_update(state, value)
|
|
||||||
}
|
|
||||||
// trailing space is part of the login_server implementation
|
|
||||||
const clearPassphrase = passphrase.join(' ') + ' '
|
|
||||||
sodium.crypto_hash_sha512_update(state, Buffer.from(clearPassphrase))
|
|
||||||
const outputHashBuffer = Buffer.alloc(sodium.crypto_hash_sha512_BYTES)
|
|
||||||
sodium.crypto_hash_sha512_final(state, outputHashBuffer)
|
|
||||||
|
|
||||||
const pubKey = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES)
|
|
||||||
const privKey = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES)
|
|
||||||
|
|
||||||
sodium.crypto_sign_seed_keypair(
|
|
||||||
pubKey,
|
|
||||||
privKey,
|
|
||||||
outputHashBuffer.slice(0, sodium.crypto_sign_SEEDBYTES),
|
|
||||||
)
|
|
||||||
logger.debug(`KeyPair creation ready. pubKey=${pubKey}`)
|
|
||||||
|
|
||||||
return [pubKey, privKey]
|
|
||||||
}
|
|
||||||
|
|
||||||
const SecretKeyCryptographyEncrypt = (message: Buffer, encryptionKey: Buffer): Buffer => {
|
|
||||||
logger.trace('SecretKeyCryptographyEncrypt...')
|
|
||||||
const encrypted = Buffer.alloc(message.length + sodium.crypto_secretbox_MACBYTES)
|
|
||||||
const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
|
|
||||||
nonce.fill(31) // static nonce
|
|
||||||
|
|
||||||
sodium.crypto_secretbox_easy(encrypted, message, nonce, encryptionKey)
|
|
||||||
logger.debug(`SecretKeyCryptographyEncrypt...successful: ${encrypted}`)
|
|
||||||
return encrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: Buffer): Buffer => {
|
|
||||||
logger.trace('SecretKeyCryptographyDecrypt...')
|
|
||||||
const message = Buffer.alloc(encryptedMessage.length - sodium.crypto_secretbox_MACBYTES)
|
|
||||||
const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
|
|
||||||
nonce.fill(31) // static nonce
|
|
||||||
|
|
||||||
sodium.crypto_secretbox_open_easy(message, encryptedMessage, nonce, encryptionKey)
|
|
||||||
|
|
||||||
logger.debug(`SecretKeyCryptographyDecrypt...successful: ${message}`)
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
const newEmailContact = (email: string, userId: number): DbUserContact => {
|
const newEmailContact = (email: string, userId: number): DbUserContact => {
|
||||||
logger.trace(`newEmailContact...`)
|
logger.trace(`newEmailContact...`)
|
||||||
const emailContact = new DbUserContact()
|
const emailContact = new DbUserContact()
|
||||||
@ -191,7 +117,6 @@ export class UserResolver {
|
|||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
const userEntity = getUser(context)
|
const userEntity = getUser(context)
|
||||||
const user = new User(userEntity, await getUserCreation(userEntity.id, clientTimezoneOffset))
|
const user = new User(userEntity, await getUserCreation(userEntity.id, clientTimezoneOffset))
|
||||||
// user.pubkey = userEntity.pubKey.toString('hex')
|
|
||||||
// Elopage Status & Stored PublisherId
|
// Elopage Status & Stored PublisherId
|
||||||
user.hasElopage = await this.hasElopage(context)
|
user.hasElopage = await this.hasElopage(context)
|
||||||
|
|
||||||
@ -223,11 +148,6 @@ 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 password set yet')
|
throw new Error('User has no password set yet')
|
||||||
}
|
}
|
||||||
if (!dbUser.pubKey || !dbUser.privKey) {
|
|
||||||
logger.error('The User has no private or publicKey.')
|
|
||||||
// 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')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!verifyPassword(dbUser, password)) {
|
if (!verifyPassword(dbUser, password)) {
|
||||||
logger.error('The User has no valid credentials.')
|
logger.error('The User has no valid credentials.')
|
||||||
@ -259,7 +179,7 @@ export class UserResolver {
|
|||||||
|
|
||||||
context.setHeaders.push({
|
context.setHeaders.push({
|
||||||
key: 'token',
|
key: 'token',
|
||||||
value: encode(dbUser.pubKey),
|
value: encode(dbUser.gradidoID),
|
||||||
})
|
})
|
||||||
const ev = new EventLogin()
|
const ev = new EventLogin()
|
||||||
ev.userId = user.id
|
ev.userId = user.id
|
||||||
@ -352,11 +272,6 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const passphrase = PassphraseGenerate()
|
|
||||||
// const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
|
||||||
// const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
|
||||||
// const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
|
||||||
// const emailHash = getEmailHash(email)
|
|
||||||
const gradidoID = await newGradidoID()
|
const gradidoID = await newGradidoID()
|
||||||
|
|
||||||
const eventRegister = new EventRegister()
|
const eventRegister = new EventRegister()
|
||||||
@ -370,7 +285,6 @@ export class UserResolver {
|
|||||||
dbUser.language = language
|
dbUser.language = language
|
||||||
dbUser.publisherId = publisherId
|
dbUser.publisherId = publisherId
|
||||||
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
|
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
|
||||||
dbUser.passphrase = passphrase.join(' ')
|
|
||||||
logger.debug('new dbUser=' + dbUser)
|
logger.debug('new dbUser=' + dbUser)
|
||||||
if (redeemCode) {
|
if (redeemCode) {
|
||||||
if (redeemCode.match(/^CL-/)) {
|
if (redeemCode.match(/^CL-/)) {
|
||||||
@ -391,12 +305,6 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO this field has no null allowed unlike the loginServer table
|
|
||||||
// dbUser.pubKey = Buffer.from(randomBytes(32)) // Buffer.alloc(32, 0) default to 0000...
|
|
||||||
// dbUser.pubkey = keyPair[0]
|
|
||||||
// loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
|
||||||
// loginUser.pubKey = keyPair[0]
|
|
||||||
// loginUser.privKey = encryptedPrivkey
|
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
@ -575,34 +483,12 @@ export class UserResolver {
|
|||||||
const user = userContact.user
|
const user = userContact.user
|
||||||
logger.debug('user with EmailVerificationCode found...')
|
logger.debug('user with EmailVerificationCode found...')
|
||||||
|
|
||||||
// Generate Passphrase if needed
|
|
||||||
if (!user.passphrase) {
|
|
||||||
const passphrase = PassphraseGenerate()
|
|
||||||
user.passphrase = passphrase.join(' ')
|
|
||||||
logger.debug('new Passphrase generated...')
|
|
||||||
}
|
|
||||||
|
|
||||||
const passphrase = user.passphrase.split(' ')
|
|
||||||
if (passphrase.length < PHRASE_WORD_COUNT) {
|
|
||||||
logger.error('Could not load a correct passphrase')
|
|
||||||
// TODO if this can happen we cannot recover from that
|
|
||||||
// this seem to be good on production data, if we dont
|
|
||||||
// make a coding mistake we do not have a problem here
|
|
||||||
throw new Error('Could not load a correct passphrase')
|
|
||||||
}
|
|
||||||
logger.debug('Passphrase is valid...')
|
|
||||||
|
|
||||||
// Activate EMail
|
// Activate EMail
|
||||||
userContact.emailChecked = true
|
userContact.emailChecked = true
|
||||||
|
|
||||||
// Update Password
|
// Update Password
|
||||||
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
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 = encryptPassword(user, password)
|
user.password = encryptPassword(user, password)
|
||||||
user.pubKey = keyPair[0]
|
|
||||||
user.privKey = encryptedPrivkey
|
|
||||||
logger.debug('User credentials updated ...')
|
logger.debug('User credentials updated ...')
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
@ -713,30 +599,14 @@ export class UserResolver {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This had some error cases defined - like missing private key. This is no longer checked.
|
|
||||||
const oldPasswordHash = SecretKeyCryptographyCreateKey(
|
|
||||||
userEntity.emailContact.email,
|
|
||||||
password,
|
|
||||||
)
|
|
||||||
if (!verifyPassword(userEntity, password)) {
|
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`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const privKey = SecretKeyCryptographyDecrypt(userEntity.privKey, oldPasswordHash[1])
|
|
||||||
logger.debug('oldPassword decrypted...')
|
|
||||||
const newPasswordHash = SecretKeyCryptographyCreateKey(
|
|
||||||
userEntity.emailContact.email,
|
|
||||||
passwordNew,
|
|
||||||
) // return short and long hash
|
|
||||||
logger.debug('newPasswordHash created...')
|
|
||||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1])
|
|
||||||
logger.debug('PrivateKey encrypted...')
|
|
||||||
|
|
||||||
// Save new password hash and newly encrypted private key
|
// Save new password hash and newly encrypted private key
|
||||||
userEntity.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
userEntity.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||||
userEntity.password = encryptPassword(userEntity, passwordNew)
|
userEntity.password = encryptPassword(userEntity, passwordNew)
|
||||||
userEntity.privKey = encryptedPrivkey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
|
|||||||
@ -4,21 +4,6 @@ import { User as DbUser } from '@entity/User'
|
|||||||
|
|
||||||
@EntityRepository(DbUser)
|
@EntityRepository(DbUser)
|
||||||
export class UserRepository extends Repository<DbUser> {
|
export class UserRepository extends Repository<DbUser> {
|
||||||
async findByPubkeyHex(pubkeyHex: string): Promise<DbUser> {
|
|
||||||
const dbUser = await this.createQueryBuilder('user')
|
|
||||||
.leftJoinAndSelect('user.emailContact', 'emailContact')
|
|
||||||
.where('hex(user.pubKey) = :pubkeyHex', { pubkeyHex })
|
|
||||||
.getOneOrFail()
|
|
||||||
/*
|
|
||||||
const dbUser = await this.findOneOrFail(`hex(user.pubKey) = { pubkeyHex }`)
|
|
||||||
const emailContact = await this.query(
|
|
||||||
`SELECT * from user_contacts where id = { dbUser.emailId }`,
|
|
||||||
)
|
|
||||||
dbUser.emailContact = emailContact
|
|
||||||
*/
|
|
||||||
return dbUser
|
|
||||||
}
|
|
||||||
|
|
||||||
async findBySearchCriteriaPagedFiltered(
|
async findBySearchCriteriaPagedFiltered(
|
||||||
select: string[],
|
select: string[],
|
||||||
searchCriteria: string,
|
searchCriteria: string,
|
||||||
|
|||||||
@ -16,8 +16,6 @@ const communityDbUser: dbUser = {
|
|||||||
emailId: -1,
|
emailId: -1,
|
||||||
firstName: 'Gradido',
|
firstName: 'Gradido',
|
||||||
lastName: 'Akademie',
|
lastName: 'Akademie',
|
||||||
pubKey: Buffer.from(''),
|
|
||||||
privKey: Buffer.from(''),
|
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
password: BigInt(0),
|
password: BigInt(0),
|
||||||
// emailHash: Buffer.from(''),
|
// emailHash: Buffer.from(''),
|
||||||
@ -26,7 +24,6 @@ const communityDbUser: dbUser = {
|
|||||||
language: '',
|
language: '',
|
||||||
isAdmin: null,
|
isAdmin: null,
|
||||||
publisherId: 0,
|
publisherId: 0,
|
||||||
passphrase: '',
|
|
||||||
// default password encryption type
|
// default password encryption type
|
||||||
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,
|
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,
|
||||||
hasId: function (): boolean {
|
hasId: function (): boolean {
|
||||||
|
|||||||
@ -14,10 +14,6 @@ function isStringBoolean(value: string): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function isHexPublicKey(publicKey: string): boolean {
|
|
||||||
return /^[0-9A-Fa-f]{64}$/i.test(publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calculateBalance(
|
async function calculateBalance(
|
||||||
userId: number,
|
userId: number,
|
||||||
amount: Decimal,
|
amount: Decimal,
|
||||||
@ -45,4 +41,4 @@ async function calculateBalance(
|
|||||||
return { balance, lastTransactionId: lastTransaction.id, decay }
|
return { balance, lastTransactionId: lastTransaction.id, decay }
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isHexPublicKey, calculateBalance, isStringBoolean }
|
export { calculateBalance, isStringBoolean }
|
||||||
|
|||||||
112
database/entity/0057-clear_old_password_junk/User.ts
Normal file
112
database/entity/0057-clear_old_password_junk/User.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
DeleteDateColumn,
|
||||||
|
OneToMany,
|
||||||
|
JoinColumn,
|
||||||
|
OneToOne,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { Contribution } from '../Contribution'
|
||||||
|
import { ContributionMessage } from '../ContributionMessage'
|
||||||
|
import { UserContact } from '../UserContact'
|
||||||
|
|
||||||
|
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||||
|
export class User extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'gradido_id',
|
||||||
|
length: 36,
|
||||||
|
nullable: false,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
gradidoID: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'alias',
|
||||||
|
length: 20,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
alias: string
|
||||||
|
|
||||||
|
@OneToOne(() => UserContact, (emailContact: UserContact) => emailContact.user)
|
||||||
|
@JoinColumn({ name: 'email_id' })
|
||||||
|
emailContact: UserContact
|
||||||
|
|
||||||
|
@Column({ name: 'email_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||||
|
emailId: number | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'first_name',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
firstName: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'last_name',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
lastName: string
|
||||||
|
|
||||||
|
@Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP', nullable: false })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', nullable: true })
|
||||||
|
deletedAt: Date | null
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', default: 0, unsigned: true })
|
||||||
|
password: BigInt
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'password_encryption_type',
|
||||||
|
type: 'int',
|
||||||
|
unsigned: true,
|
||||||
|
nullable: false,
|
||||||
|
default: 0,
|
||||||
|
})
|
||||||
|
passwordEncryptionType: number
|
||||||
|
|
||||||
|
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
|
||||||
|
language: string
|
||||||
|
|
||||||
|
@Column({ name: 'is_admin', type: 'datetime', nullable: true, default: null })
|
||||||
|
isAdmin: Date | null
|
||||||
|
|
||||||
|
@Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||||
|
referrerId?: number | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'contribution_link_id',
|
||||||
|
type: 'int',
|
||||||
|
unsigned: true,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
contributionLinkId?: number | null
|
||||||
|
|
||||||
|
@Column({ name: 'publisher_id', default: 0 })
|
||||||
|
publisherId: number
|
||||||
|
|
||||||
|
@OneToMany(() => Contribution, (contribution) => contribution.user)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
contributions?: Contribution[]
|
||||||
|
|
||||||
|
@OneToMany(() => ContributionMessage, (message) => message.user)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
messages?: ContributionMessage[]
|
||||||
|
|
||||||
|
@OneToMany(() => UserContact, (userContact: UserContact) => userContact.user)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
userContacts?: UserContact[]
|
||||||
|
}
|
||||||
57
database/entity/0057-clear_old_password_junk/UserContact.ts
Normal file
57
database/entity/0057-clear_old_password_junk/UserContact.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
DeleteDateColumn,
|
||||||
|
OneToOne,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { User } from './User'
|
||||||
|
|
||||||
|
@Entity('user_contacts', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||||
|
export class UserContact extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'type',
|
||||||
|
length: 100,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
type: string
|
||||||
|
|
||||||
|
@OneToOne(() => User, (user) => user.emailContact)
|
||||||
|
user: User
|
||||||
|
|
||||||
|
@Column({ name: 'user_id', type: 'int', unsigned: true, nullable: false })
|
||||||
|
userId: number
|
||||||
|
|
||||||
|
@Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Column({ name: 'email_verification_code', type: 'bigint', unsigned: true, unique: true })
|
||||||
|
emailVerificationCode: BigInt
|
||||||
|
|
||||||
|
@Column({ name: 'email_opt_in_type_id' })
|
||||||
|
emailOptInTypeId: number
|
||||||
|
|
||||||
|
@Column({ name: 'email_resend_count' })
|
||||||
|
emailResendCount: number
|
||||||
|
|
||||||
|
@Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
|
||||||
|
emailChecked: boolean
|
||||||
|
|
||||||
|
@Column({ length: 255, unique: false, nullable: true, collation: 'utf8mb4_unicode_ci' })
|
||||||
|
phone: string
|
||||||
|
|
||||||
|
@Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP', nullable: false })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' })
|
||||||
|
updatedAt: Date | null
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', nullable: true })
|
||||||
|
deletedAt: Date | null
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
export { User } from './0053-change_password_encryption/User'
|
export { User } from './0057-clear_old_password_junk/User'
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { UserContact } from './0053-change_password_encryption/UserContact'
|
export { UserContact } from './0057-clear_old_password_junk/UserContact'
|
||||||
|
|||||||
16
database/migrations/0057-clear_old_password_junk.ts
Normal file
16
database/migrations/0057-clear_old_password_junk.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn('ALTER TABLE users DROP COLUMN public_key;')
|
||||||
|
await queryFn('ALTER TABLE users DROP COLUMN privkey;')
|
||||||
|
await queryFn('ALTER TABLE users DROP COLUMN email_hash;')
|
||||||
|
await queryFn('ALTER TABLE users DROP COLUMN passphrase;')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn('ALTER TABLE users ADD COLUMN public_key binary(32) DEFAULT NULL;')
|
||||||
|
await queryFn('ALTER TABLE users ADD COLUMN privkey binary(80) DEFAULT NULL;')
|
||||||
|
await queryFn('ALTER TABLE users ADD COLUMN email_hash binary(32) DEFAULT NULL;')
|
||||||
|
await queryFn('ALTER TABLE users ADD COLUMN passphrase text DEFAULT NULL;')
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user