Merge branch 'master' into semaphore

This commit is contained in:
Moriz Wahl 2022-12-22 14:10:57 +01:00 committed by GitHub
commit 8ebea39870
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 231 additions and 2240 deletions

View File

@ -139,7 +139,6 @@ jobs:
build_test_nginx:
name: Docker Build Test - Nginx
runs-on: ubuntu-latest
needs: [build_test_backend, build_test_admin, build_test_frontend]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################

View File

@ -4,8 +4,30 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.16.0](https://github.com/gradido/gradido/compare/1.15.0...1.16.0)
- refactor(backend): cleaning user related old password junk [`#2426`](https://github.com/gradido/gradido/pull/2426)
- fix(database): consistent transaction table [`#2453`](https://github.com/gradido/gradido/pull/2453)
- refactor(backend): dissolve admin resolver [`#2416`](https://github.com/gradido/gradido/pull/2416)
- fix(backend): email verification code never expired [`#2418`](https://github.com/gradido/gradido/pull/2418)
- fix(database): consistent deleted at bewteen users and user contacts [`#2451`](https://github.com/gradido/gradido/pull/2451)
- feat(backend): log client timezone offset [`#2454`](https://github.com/gradido/gradido/pull/2454)
- refactor(backend): refactor more emails to translatables [`#2398`](https://github.com/gradido/gradido/pull/2398)
- fix(backend): delete / undelete email contact as well [`#2444`](https://github.com/gradido/gradido/pull/2444)
- feat(backend): 🍰 Mark creation via link [`#2363`](https://github.com/gradido/gradido/pull/2363)
- fix(backend): run all timers for high values [`#2452`](https://github.com/gradido/gradido/pull/2452)
- fix(backend): critical bug [`#2443`](https://github.com/gradido/gradido/pull/2443)
- fix(other): missing files for docker production build [`#2442`](https://github.com/gradido/gradido/pull/2442)
- fix(frontend): in contribution messages formular a message can be send twice, when clicking the submit button fast [`#2424`](https://github.com/gradido/gradido/pull/2424)
- fix(backend): wrong month for contribution near turn of month [`#2201`](https://github.com/gradido/gradido/pull/2201)
- feat(backend): add federation config properties [`#2374`](https://github.com/gradido/gradido/pull/2374)
- fix(backend): moved all jest & type-definition related packages into the `devDependencies` section [`#2385`](https://github.com/gradido/gradido/pull/2385)
#### [1.15.0](https://github.com/gradido/gradido/compare/1.14.1...1.15.0)
> 26 November 2022
- chore(release): v1.15.0 [`#2425`](https://github.com/gradido/gradido/pull/2425)
- fix(database): wrong balance and decay values [`#2423`](https://github.com/gradido/gradido/pull/2423)
- fix(backend): wrong balance after transaction receive [`#2422`](https://github.com/gradido/gradido/pull/2422)
- feat(other): feature gradido roadmap [`#2301`](https://github.com/gradido/gradido/pull/2301)

View File

@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido",
"main": "index.js",
"author": "Moriz Wahl",
"version": "1.15.0",
"version": "1.16.0",
"license": "Apache-2.0",
"private": false,
"scripts": {

View File

@ -65,7 +65,6 @@ export default {
},
})
.then((result) => {
this.toastSuccess(this.$t('user_recovered'))
this.$emit('updateDeletedAt', {
userId: this.item.userId,
deletedAt: result.data.unDeleteUser,

View File

@ -107,9 +107,7 @@ COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
# Copy log4js-config.json to provide log configuration
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 --from=build ${DOCKER_WORKDIR}/run ./run

View File

@ -1,6 +1,6 @@
{
"name": "gradido-backend",
"version": "1.15.0",
"version": "1.16.0",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend",

View File

@ -1,5 +1,5 @@
import { JwtPayload } from 'jsonwebtoken'
export interface CustomJwtPayload extends JwtPayload {
pubKey: Buffer
gradidoID: string
}

View File

@ -11,8 +11,8 @@ export const decode = (token: string): CustomJwtPayload | null => {
}
}
export const encode = (pubKey: Buffer): string => {
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
export const encode = (gradidoID: string): string => {
const token = jwt.sign({ gradidoID }, CONFIG.JWT_SECRET, {
expiresIn: CONFIG.JWT_EXPIRES_IN,
})
return token

View File

@ -10,7 +10,7 @@ Decimal.set({
})
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
LOG4JS_CONFIG: 'log4js-config.json',
// 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

View File

@ -5,9 +5,8 @@ import { AuthChecker } from 'type-graphql'
import { decode, encode } from '@/auth/JWT'
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
import { RIGHTS } from '@/auth/RIGHTS'
import { getCustomRepository } from '@dbTools/typeorm'
import { UserRepository } from '@repository/User'
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
import { User } from '@entity/User'
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
context.role = ROLE_UNAUTHORIZED // unauthorized user
@ -26,14 +25,16 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
if (!decoded) {
throw new Error('403.13 - Client certificate revoked')
}
// Set context pubKey
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
// Set context gradidoID
context.gradidoID = decoded.gradidoID
// 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
const userRepository = getCustomRepository(UserRepository)
try {
const user = await userRepository.findByPubkeyHex(context.pubKey)
const user = await User.findOneOrFail({
where: { gradidoID: decoded.gradidoID },
relations: ['emailContact'],
})
context.user = user
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
} catch {
@ -48,7 +49,7 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
}
// set new header token
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
context.setHeaders.push({ key: 'token', value: encode(decoded.gradidoID) })
return true
}

View File

@ -16,12 +16,12 @@ import { Transaction } from '@model/Transaction'
import { TransactionList } from '@model/TransactionList'
import { Order } from '@enum/Order'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import { calculateBalance } from '@/util/validate'
import TransactionSendArgs from '@arg/TransactionSendArgs'
import Paginated from '@arg/Paginated'
import { backendLogger as logger } from '@/server/logger'
import { Context, getUser } from '@/server/context'
import { calculateBalance, isHexPublicKey } from '@/util/validate'
import { RIGHTS } from '@/auth/RIGHTS'
import { communityUser } from '@/util/communityUser'
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
@ -326,10 +326,6 @@ export class TransactionResolver {
// TODO this is subject to replay attacks
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
const recipientUser = await findUserByEmail(email)
@ -342,10 +338,6 @@ export class TransactionResolver {
logger.error(`The recipient account is not activated: recipientUser=${recipientUser}`)
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)
logger.info(

View File

@ -138,12 +138,8 @@ describe('UserResolver', () => {
firstName: 'Peter',
lastName: 'Lustig',
password: '0',
pubKey: null,
privKey: null,
// emailHash: expect.any(Buffer),
createdAt: expect.any(Date),
// emailChecked: false,
passphrase: expect.any(String),
language: 'de',
isAdmin: null,
deletedAt: null,

View File

@ -1,4 +1,3 @@
import fs from 'fs'
import i18n from 'i18n'
import { v4 as uuidv4 } from 'uuid'
import {
@ -60,8 +59,8 @@ import {
EventActivateAccount,
} from '@/event/Event'
import { getUserCreation, getUserCreations } from './util/creations'
import { isValidPassword } from '@/password/EncryptorUtils'
import { FULL_CREATION_AVAILABLE } from './const/const'
import { isValidPassword, SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
@ -76,79 +75,6 @@ const isLanguage = (language: string): boolean => {
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 => {
logger.trace(`newEmailContact...`)
const emailContact = new DbUserContact()
@ -191,7 +117,6 @@ export class UserResolver {
const clientTimezoneOffset = getClientTimezoneOffset(context)
const userEntity = getUser(context)
const user = new User(userEntity, await getUserCreation(userEntity.id, clientTimezoneOffset))
// user.pubkey = userEntity.pubKey.toString('hex')
// Elopage Status & Stored PublisherId
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
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)) {
logger.error('The User has no valid credentials.')
@ -259,7 +179,7 @@ export class UserResolver {
context.setHeaders.push({
key: 'token',
value: encode(dbUser.pubKey),
value: encode(dbUser.gradidoID),
})
const ev = new EventLogin()
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 eventRegister = new EventRegister()
@ -370,7 +285,6 @@ export class UserResolver {
dbUser.language = language
dbUser.publisherId = publisherId
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
dbUser.passphrase = passphrase.join(' ')
logger.debug('new dbUser=' + dbUser)
if (redeemCode) {
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()
await queryRunner.connect()
@ -575,34 +483,12 @@ export class UserResolver {
const user = userContact.user
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
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 = encryptPassword(user, password)
user.pubKey = keyPair[0]
user.privKey = encryptedPrivkey
logger.debug('User credentials updated ...')
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)) {
logger.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
userEntity.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
userEntity.password = encryptPassword(userEntity, passwordNew)
userEntity.privKey = encryptedPrivkey
}
const queryRunner = getConnection().createQueryRunner()

View File

@ -4,21 +4,6 @@ import { User as DbUser } from '@entity/User'
@EntityRepository(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(
select: string[],
searchCriteria: string,

View File

@ -16,8 +16,6 @@ const communityDbUser: dbUser = {
emailId: -1,
firstName: 'Gradido',
lastName: 'Akademie',
pubKey: Buffer.from(''),
privKey: Buffer.from(''),
deletedAt: null,
password: BigInt(0),
// emailHash: Buffer.from(''),
@ -26,7 +24,6 @@ const communityDbUser: dbUser = {
language: '',
isAdmin: null,
publisherId: 0,
passphrase: '',
// default password encryption type
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,
hasId: function (): boolean {

View File

@ -14,10 +14,6 @@ function isStringBoolean(value: string): boolean {
return false
}
function isHexPublicKey(publicKey: string): boolean {
return /^[0-9A-Fa-f]{64}$/i.test(publicKey)
}
async function calculateBalance(
userId: number,
amount: Decimal,
@ -45,4 +41,4 @@ async function calculateBalance(
return { balance, lastTransactionId: lastTransaction.id, decay }
}
export { isHexPublicKey, calculateBalance, isStringBoolean }
export { calculateBalance, isStringBoolean }

View 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[]
}

View 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
}

View File

@ -1 +1 @@
export { User } from './0053-change_password_encryption/User'
export { User } from './0057-clear_old_password_junk/User'

View File

@ -1 +1 @@
export { UserContact } from './0053-change_password_encryption/UserContact'
export { UserContact } from './0057-clear_old_password_junk/UserContact'

View 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;')
}

View File

@ -1,6 +1,6 @@
{
"name": "gradido-database",
"version": "1.15.0",
"version": "1.16.0",
"description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database",

View File

@ -1,6 +1,6 @@
{
"name": "bootstrap-vue-gradido-wallet",
"version": "1.15.0",
"version": "1.16.0",
"private": true,
"scripts": {
"start": "node run/server.js",

View File

@ -1,6 +1,6 @@
{
"name": "gradido",
"version": "1.15.0",
"version": "1.16.0",
"description": "Gradido",
"main": "index.js",
"repository": "git@github.com:gradido/gradido.git",