mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge login_call_createUser to implementation of login.
This commit is contained in:
commit
195ea5ad89
@ -26,4 +26,6 @@ DB_DATABASE=gradido_community
|
||||
COMMUNITY_NAME=
|
||||
COMMUNITY_URL=
|
||||
COMMUNITY_REGISTER_URL=
|
||||
COMMUNITY_DESCRIPTION=
|
||||
COMMUNITY_DESCRIPTION=
|
||||
LOGIN_APP_SECRET=21ffbbc616fe
|
||||
LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
|
||||
7205
backend/package-lock.json
generated
Normal file
7205
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -27,11 +27,12 @@
|
||||
"graphql": "^15.5.1",
|
||||
"jest": "^27.2.4",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"libsodium-wrappers": "^0.7.9",
|
||||
"module-alias": "^2.2.2",
|
||||
"mysql2": "^2.3.0",
|
||||
"nodemailer": "^6.6.5",
|
||||
"random-bigint": "^0.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sodium-native": "^3.3.0",
|
||||
"ts-jest": "^27.0.5",
|
||||
"type-graphql": "^1.1.1",
|
||||
"typeorm": "^0.2.38"
|
||||
|
||||
@ -48,9 +48,14 @@ const email = {
|
||||
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
||||
}
|
||||
|
||||
const loginServer = {
|
||||
LOGIN_APP_SECRET: process.env.LOGIN_APP_SECRET || '21ffbbc616fe',
|
||||
LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a',
|
||||
}
|
||||
|
||||
// This is needed by graphql-directive-auth
|
||||
process.env.APP_SECRET = server.JWT_SECRET
|
||||
|
||||
const CONFIG = { ...server, ...database, ...klicktipp, ...community, ...email }
|
||||
const CONFIG = { ...server, ...database, ...klicktipp, ...community, ...email, ...loginServer }
|
||||
|
||||
export default CONFIG
|
||||
|
||||
2048
backend/src/config/mnemonic.english.txt
Normal file
2048
backend/src/config/mnemonic.english.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
backend/src/config/mnemonic.words_ulf.encoding.txt
Normal file
2048
backend/src/config/mnemonic.words_ulf.encoding.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
backend/src/config/mnemonic.words_ulf.txt
Normal file
2048
backend/src/config/mnemonic.words_ulf.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
backend/src/config/mnemonic.words_ulf_org.txt
Normal file
2048
backend/src/config/mnemonic.words_ulf_org.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,15 +13,15 @@ const isAuthorized: AuthChecker<any> = async (
|
||||
) => {
|
||||
if (context.token) {
|
||||
const decoded = decode(context.token)
|
||||
if (decoded.sessionId && decoded.sessionId !== 0) {
|
||||
const result = await apiGet(
|
||||
`${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`,
|
||||
)
|
||||
context.sessionId = decoded.sessionId
|
||||
// if (decoded.sessionId && decoded.sessionId !== 0) {
|
||||
// const result = await apiGet(
|
||||
// `${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`,
|
||||
// )
|
||||
// context.sessionId = decoded.sessionId
|
||||
context.pubKey = decoded.pubKey
|
||||
context.setHeaders.push({ key: 'token', value: encode(decoded.sessionId, decoded.pubKey) })
|
||||
return result.success
|
||||
}
|
||||
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
||||
return true
|
||||
// }
|
||||
}
|
||||
throw new Error('401 Unauthorized')
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import fs from 'fs'
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
|
||||
import { from_hex as fromHex } from 'libsodium-wrappers'
|
||||
import { getCustomRepository } from 'typeorm'
|
||||
import { BaseEntity, getConnection, getCustomRepository, QueryRunner } from 'typeorm'
|
||||
import CONFIG from '../../config'
|
||||
import { LoginViaVerificationCode } from '../model/LoginViaVerificationCode'
|
||||
import { SendPasswordResetEmailResponse } from '../model/SendPasswordResetEmailResponse'
|
||||
@ -25,12 +25,178 @@ import { CheckEmailResponse } from '../model/CheckEmailResponse'
|
||||
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
||||
import { Setting } from '../enum/Setting'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import { LoginUser } from '@entity/LoginUser'
|
||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||
import { LoginUserBackupRepository } from '../../typeorm/repository/LoginUserBackup'
|
||||
import { LoginUser } from '@entity/LoginUser'
|
||||
import { LoginUserBackup } from '@entity/LoginUserBackup'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
|
||||
// TODO apparently the types are cannot be loaded correctly? IDK whats wrong and we have to use require
|
||||
// import {
|
||||
// /* eslint-disable camelcase */
|
||||
// randombytes_random,
|
||||
// crypto_hash_sha512_instance,
|
||||
// crypto_hash_sha512_BYTES,
|
||||
// crypto_sign_seed_keypair,
|
||||
// crypto_sign_PUBLICKEYBYTES,
|
||||
// crypto_sign_SECRETKEYBYTES,
|
||||
// /* eslint-enable camelcase */
|
||||
// } from 'sodium-native'
|
||||
// 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']
|
||||
const DEFAULT_LANGUAGE = 'de'
|
||||
const isLanguage = (language: string): boolean => {
|
||||
return LANGUAGES.includes(language)
|
||||
}
|
||||
|
||||
const PHRASE_WORD_COUNT = 24
|
||||
const WORDS = fs.readFileSync('src/config/mnemonic.english.txt').toString().split('\n')
|
||||
const PassphraseGenerate = (): string[] => {
|
||||
const result = []
|
||||
for (let i = 0; i < PHRASE_WORD_COUNT; i++) {
|
||||
result.push(WORDS[sodium.randombytes_random() % 2048])
|
||||
}
|
||||
return result
|
||||
/*
|
||||
return [
|
||||
'behind',
|
||||
'salmon',
|
||||
'fluid',
|
||||
'orphan',
|
||||
'frost',
|
||||
'elder',
|
||||
'amateur',
|
||||
'always',
|
||||
'panel',
|
||||
'palm',
|
||||
'leopard',
|
||||
'essay',
|
||||
'punch',
|
||||
'title',
|
||||
'fun',
|
||||
'annual',
|
||||
'page',
|
||||
'hundred',
|
||||
'journey',
|
||||
'select',
|
||||
'figure',
|
||||
'tunnel',
|
||||
'casual',
|
||||
'bar',
|
||||
]
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
Test results:
|
||||
INSERT INTO `login_users` (`id`, `email`, `first_name`, `last_name`, `username`, `description`, `password`, `pubkey`, `privkey`, `email_hash`, `created`, `email_checked`, `passphrase_shown`, `language`, `disabled`, `group_id`, `publisher_id`) VALUES
|
||||
// old
|
||||
(1, 'peter@lustig.de', 'peter', 'lustig', '', '', 4747956395458240931, 0x8c75edd507f470e5378f927489374694d68f3d155523f1c4402c36affd35a7ed, 0xb0e310655726b088631ccfd31ad6470ee50115c161dde8559572fa90657270ff13dc1200b2d3ea90dfbe92f3a4475ee4d9cee4989e39736a0870c33284bc73a8ae690e6da89f241a121eb3b500c22885, 0x9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036, '2021-11-03 20:05:04', 0, 0, 'de', 0, 1, 0);
|
||||
// new
|
||||
(2, 'peter@lustig.de', 'peter', 'lustig', '', '', 4747956395458240931, 0x8c75edd507f470e5378f927489374694d68f3d155523f1c4402c36affd35a7ed, 0xb0e310655726b088631ccfd31ad6470ee50115c161dde8559572fa90657270ff13dc1200b2d3ea90dfbe92f3a4475ee4d9cee4989e39736a0870c33284bc73a8ae690e6da89f241a121eb3b500c22885, 0x9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036, '2021-11-03 20:22:15', 0, 0, 'de', 0, 1, 0);
|
||||
INSERT INTO `login_user_backups` (`id`, `user_id`, `passphrase`, `mnemonic_type`) VALUES
|
||||
// old
|
||||
(1, 1, 'behind salmon fluid orphan frost elder amateur always panel palm leopard essay punch title fun annual page hundred journey select figure tunnel casual bar ', 2);
|
||||
// new
|
||||
(2, 2, 'behind salmon fluid orphan frost elder amateur always panel palm leopard essay punch title fun annual page hundred journey select figure tunnel casual bar ', 2);
|
||||
*/
|
||||
|
||||
const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
|
||||
if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) {
|
||||
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),
|
||||
)
|
||||
|
||||
return [pubKey, privKey]
|
||||
}
|
||||
|
||||
const SecretKeyCryptographyCreateKey = (salt: string, password: string): Buffer[] => {
|
||||
// TODO: put that in the actual config
|
||||
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) {
|
||||
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)
|
||||
|
||||
return [encryptionKeyHash, encryptionKey]
|
||||
}
|
||||
|
||||
const getEmailHash = (email: string): Buffer => {
|
||||
const emailHash = Buffer.alloc(sodium.crypto_generichash_BYTES)
|
||||
sodium.crypto_generichash(emailHash, Buffer.from(email))
|
||||
return emailHash
|
||||
}
|
||||
|
||||
const SecretKeyCryptographyEncrypt = (message: Buffer, encryptionKey: Buffer): Buffer => {
|
||||
const encrypted = Buffer.alloc(sodium.crypto_secretbox_MACBYTES + message.length)
|
||||
const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
|
||||
nonce.fill(31) // static nonce
|
||||
|
||||
sodium.crypto_secretbox_easy(encrypted, message, nonce, encryptionKey)
|
||||
return encrypted
|
||||
}
|
||||
|
||||
@Resolver()
|
||||
export class UserResolver {
|
||||
private userRepository = getCustomRepository(UserRepository)
|
||||
|
||||
private userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
|
||||
@Query(() => User)
|
||||
@ -52,54 +218,59 @@ export class UserResolver {
|
||||
|
||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||
const loginUser = await loginUserRepository.findByEmail(email)
|
||||
if (loginUser.password)
|
||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||
// loginUser.password = passwordHash[0].readBigUInt64LE()
|
||||
if (loginUser.password !== passwordHash[0].readBigUInt64LE()) {
|
||||
throw new Error('No user with this credentials')
|
||||
}
|
||||
|
||||
context.setHeaders.push({
|
||||
key: 'token',
|
||||
value: encode(result.data.session_id, result.data.user.public_hex),
|
||||
value: encode(loginUser.pubKey),
|
||||
})
|
||||
throw new Error('WIP')
|
||||
// const user = new User(result.data.user)
|
||||
// Hack: Database Field is not validated properly and not nullable
|
||||
if (user.publisherId === 0) {
|
||||
user.publisherId = undefined
|
||||
}
|
||||
user.hasElopage = result.data.hasElopage
|
||||
// read additional settings from settings table
|
||||
// if (user.publisherId === 0) {
|
||||
// user.publisherId = undefined
|
||||
// }
|
||||
// user.hasElopage = result.data.hasElopage
|
||||
// // read additional settings from settings table
|
||||
// const userRepository = getCustomRepository(UserRepository)
|
||||
let userEntity: void | DbUser
|
||||
userEntity = await this.userRepository.findByPubkeyHex(user.pubkey).catch(() => {
|
||||
userEntity = new DbUser()
|
||||
userEntity.firstName = user.firstName
|
||||
userEntity.lastName = user.lastName
|
||||
userEntity.username = user.username
|
||||
userEntity.email = user.email
|
||||
userEntity.pubkey = Buffer.from(fromHex(user.pubkey))
|
||||
// let userEntity: void | DbUser
|
||||
// userEntity = await userRepository.findByPubkeyHex(user.pubkey).catch(() => {
|
||||
// userEntity = new DbUser()
|
||||
// userEntity.firstName = user.firstName
|
||||
// userEntity.lastName = user.lastName
|
||||
// userEntity.username = user.username
|
||||
// userEntity.email = user.email
|
||||
// userEntity.pubkey = Buffer.from(user.pubkey, 'hex')
|
||||
|
||||
this.userRepository.save(userEntity).catch(() => {
|
||||
throw new Error('error by save userEntity')
|
||||
})
|
||||
})
|
||||
if (!userEntity) {
|
||||
throw new Error('error with cannot happen')
|
||||
}
|
||||
// userRepository.save(userEntity).catch(() => {
|
||||
// throw new Error('error by save userEntity')
|
||||
// })
|
||||
// })
|
||||
// if (!userEntity) {
|
||||
// throw new Error('error with cannot happen')
|
||||
// }
|
||||
|
||||
// Save publisherId if Elopage is not yet registered
|
||||
if (!user.hasElopage && publisherId) {
|
||||
user.publisherId = publisherId
|
||||
await this.updateUserInfos(
|
||||
{ publisherId },
|
||||
{ sessionId: result.data.session_id, pubKey: result.data.user.public_hex },
|
||||
)
|
||||
}
|
||||
// // Save publisherId if Elopage is not yet registered
|
||||
// if (!user.hasElopage && publisherId) {
|
||||
// user.publisherId = publisherId
|
||||
// await this.updateUserInfos(
|
||||
// { publisherId },
|
||||
// { sessionId: result.data.session_id, pubKey: result.data.user.public_hex },
|
||||
// )
|
||||
// }
|
||||
|
||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
const coinanimation = await userSettingRepository
|
||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||
.catch((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
return user
|
||||
// const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
// const coinanimation = await userSettingRepository
|
||||
// .readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||
// .catch((error) => {
|
||||
// throw new Error(error)
|
||||
// })
|
||||
// user.coinanimation = coinanimation
|
||||
// return user
|
||||
}
|
||||
|
||||
@Query(() => LoginViaVerificationCode)
|
||||
@ -132,35 +303,125 @@ export class UserResolver {
|
||||
async createUser(
|
||||
@Args() { email, firstName, lastName, password, language, publisherId }: CreateUserArgs,
|
||||
): Promise<string> {
|
||||
const payload = {
|
||||
email,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
password,
|
||||
emailType: 2,
|
||||
login_after_register: true,
|
||||
language: language,
|
||||
publisher_id: publisherId,
|
||||
}
|
||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'createUser', payload)
|
||||
if (!result.success) {
|
||||
throw new Error(result.data)
|
||||
const username = ''
|
||||
|
||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||
// default int publisher_id = 0;
|
||||
|
||||
// Validate Language (no throw)
|
||||
if (!isLanguage(language)) {
|
||||
language = DEFAULT_LANGUAGE
|
||||
}
|
||||
|
||||
const user = new User(result.data.user)
|
||||
const dbuser = new DbUser()
|
||||
dbuser.pubkey = Buffer.from(fromHex(user.pubkey))
|
||||
dbuser.email = user.email
|
||||
dbuser.firstName = user.firstName
|
||||
dbuser.lastName = user.lastName
|
||||
dbuser.username = user.username
|
||||
// Validate Password
|
||||
if (!isPassword(password)) {
|
||||
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!',
|
||||
)
|
||||
}
|
||||
|
||||
// Validate username
|
||||
// TODO: never true
|
||||
if (username.length > 3 && !this.checkUsername({ username })) {
|
||||
throw new Error('Username already in use')
|
||||
}
|
||||
|
||||
// Validate email unique
|
||||
// TODO: i can register an email in upper/lower case twice
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
userRepository.save(dbuser).catch(() => {
|
||||
throw new Error('error saving user')
|
||||
})
|
||||
const usersFound = await userRepository.count({ email })
|
||||
if (usersFound !== 0) {
|
||||
// TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent.
|
||||
throw new Error(`User already exists.`)
|
||||
}
|
||||
|
||||
return 'success'
|
||||
const passphrase = PassphraseGenerate()
|
||||
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||
const emailHash = getEmailHash(email)
|
||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||
|
||||
// Table: login_users
|
||||
const loginUser = new LoginUser()
|
||||
loginUser.email = email
|
||||
loginUser.firstName = firstName
|
||||
loginUser.lastName = lastName
|
||||
loginUser.username = username
|
||||
loginUser.description = ''
|
||||
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||
loginUser.emailHash = emailHash
|
||||
loginUser.language = language
|
||||
loginUser.groupId = 1
|
||||
loginUser.publisherId = publisherId
|
||||
loginUser.pubKey = keyPair[0]
|
||||
loginUser.privKey = encryptedPrivkey
|
||||
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||
try {
|
||||
const { id: loginUserId } = await queryRunner.manager.save(loginUser).catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('insert LoginUser failed', error)
|
||||
throw new Error('insert user failed')
|
||||
})
|
||||
|
||||
// Table: login_user_backups
|
||||
const loginUserBackup = new LoginUserBackup()
|
||||
loginUserBackup.userId = loginUserId
|
||||
loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
|
||||
loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
|
||||
|
||||
await queryRunner.manager.save(loginUserBackup).catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('insert LoginUserBackup failed', error)
|
||||
throw new Error('insert user backup failed')
|
||||
})
|
||||
|
||||
// Table: state_users
|
||||
const dbUser = new DbUser()
|
||||
dbUser.pubkey = keyPair[0]
|
||||
dbUser.email = email
|
||||
dbUser.firstName = firstName
|
||||
dbUser.lastName = lastName
|
||||
dbUser.username = username
|
||||
|
||||
await queryRunner.manager.save(dbUser).catch((er) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Error while saving dbUser', er)
|
||||
throw new Error('error saving user')
|
||||
})
|
||||
|
||||
// Store EmailOptIn in DB
|
||||
const emailOptIn = new LoginEmailOptIn()
|
||||
emailOptIn.userId = loginUserId
|
||||
emailOptIn.verificationCode = random(64) // TODO generate verificationCode
|
||||
emailOptIn.emailOptInTypeId = 2
|
||||
|
||||
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
||||
// TODO: Send error email instead of throw error
|
||||
// if (!emailOptInModel->insertIntoDB(false)) {
|
||||
// emailOptInModel->sendErrorsAsEmail();
|
||||
// return stateError("insert emailOptIn failed");
|
||||
// }
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Error while saving emailOptIn', error)
|
||||
throw new Error('error saving email opt in')
|
||||
})
|
||||
// TODO: Send EmailOptIn to user.email
|
||||
// emailOptIn->setBaseUrl(user->getGroupBaseUrl() + ServerConfig::g_frontend_checkEmailPath);
|
||||
// em->addEmail(new model::Email(emailOptIn, user, model::Email::convertTypeFromInt(emailType)));
|
||||
await queryRunner.commitTransaction()
|
||||
return 'success'
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
await rollbackAutoIncrement(queryRunner, LoginUser, `login_users`)
|
||||
await rollbackAutoIncrement(queryRunner, LoginUserBackup, `login_user_backups`)
|
||||
await rollbackAutoIncrement(queryRunner, DbUser, `state_users`)
|
||||
throw e
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => SendPasswordResetEmailResponse)
|
||||
@ -227,7 +488,7 @@ export class UserResolver {
|
||||
},
|
||||
}
|
||||
let response: UpdateUserInfosResponse | undefined
|
||||
// const userRepository = getCustomRepository(UserRepository)
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
|
||||
if (
|
||||
firstName ||
|
||||
@ -243,7 +504,7 @@ export class UserResolver {
|
||||
if (!result.success) throw new Error(result.data)
|
||||
response = new UpdateUserInfosResponse(result.data)
|
||||
|
||||
const userEntity = await this.userRepository.findByPubkeyHex(context.pubKey)
|
||||
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
let userEntityChanged = false
|
||||
if (firstName) {
|
||||
userEntity.firstName = firstName
|
||||
@ -258,7 +519,7 @@ export class UserResolver {
|
||||
userEntityChanged = true
|
||||
}
|
||||
if (userEntityChanged) {
|
||||
this.userRepository.save(userEntity).catch((error) => {
|
||||
userRepository.save(userEntity).catch((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
}
|
||||
@ -266,10 +527,10 @@ export class UserResolver {
|
||||
if (coinanimation !== undefined) {
|
||||
// load user and balance
|
||||
|
||||
const userEntity = await this.userRepository.findByPubkeyHex(context.pubKey)
|
||||
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
|
||||
// const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
this.userSettingRepository
|
||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
userSettingRepository
|
||||
.setOrUpdate(userEntity.id, Setting.COIN_ANIMATION, coinanimation.toString())
|
||||
.catch((error) => {
|
||||
throw new Error(error)
|
||||
@ -331,3 +592,19 @@ export class UserResolver {
|
||||
return result.data.hasElopage
|
||||
}
|
||||
}
|
||||
|
||||
const rollbackAutoIncrement = async (
|
||||
queryRunner: QueryRunner,
|
||||
entity: typeof BaseEntity,
|
||||
entityName: string,
|
||||
) => {
|
||||
const count = await queryRunner.manager.count(entity)
|
||||
const queryString = 'ALTER TABLE `' + entityName + '` auto_increment = ' + count
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Database AlterTable Query: ', queryString)
|
||||
await queryRunner.query(queryString).catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('problems with reset auto increment: %o', error)
|
||||
throw new Error('Problems with reset auto increment: ' + error)
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,10 +5,9 @@ import jwt from 'jsonwebtoken'
|
||||
import CONFIG from '../config/'
|
||||
|
||||
// Generate an Access Token
|
||||
export default function encode(sessionId: number, pubKey: Buffer): string {
|
||||
const token = jwt.sign({ sessionId, pubKey }, CONFIG.JWT_SECRET, {
|
||||
export default function encode(pubKey: Buffer): string {
|
||||
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
|
||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||
subject: sessionId.toString(),
|
||||
})
|
||||
return token
|
||||
}
|
||||
|
||||
5
backend/src/typeorm/repository/LoginEmailOptIn.ts
Normal file
5
backend/src/typeorm/repository/LoginEmailOptIn.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from 'typeorm'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
|
||||
@EntityRepository(LoginEmailOptIn)
|
||||
export class LoginEmailOptInRepository extends Repository<LoginEmailOptIn> {}
|
||||
5
backend/src/typeorm/repository/LoginUserBackup.ts
Normal file
5
backend/src/typeorm/repository/LoginUserBackup.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from 'typeorm'
|
||||
import { LoginUserBackup } from '@entity/LoginUserBackup'
|
||||
|
||||
@EntityRepository(LoginUserBackup)
|
||||
export class LoginUserBackupRepository extends Repository<LoginUserBackup> {}
|
||||
@ -3918,18 +3918,6 @@ libphonenumber-js@^1.9.7:
|
||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz#944f59a3618a8f85d9b619767a0b6fb87523f285"
|
||||
integrity sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg==
|
||||
|
||||
libsodium-wrappers@^0.7.9:
|
||||
version "0.7.9"
|
||||
resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#4ffc2b69b8f7c7c7c5594a93a4803f80f6d0f346"
|
||||
integrity sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ==
|
||||
dependencies:
|
||||
libsodium "^0.7.0"
|
||||
|
||||
libsodium@^0.7.0:
|
||||
version "0.7.9"
|
||||
resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.9.tgz#4bb7bcbf662ddd920d8795c227ae25bbbfa3821b"
|
||||
integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A==
|
||||
|
||||
load-json-file@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
|
||||
@ -4223,6 +4211,11 @@ node-fetch@^2.6.1:
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-gyp-build@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
|
||||
integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
@ -4674,6 +4667,11 @@ queue-microtask@^1.2.2:
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
random-bigint@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/random-bigint/-/random-bigint-0.0.1.tgz#684de0a93784ab7448a441393916f0e632c95df9"
|
||||
integrity sha512-X+NTsf5Hzl/tRNLiNTD3N1LRU0eKdIE0+plNlV1CmXLTlnAxj6HipcTnOhWvFRoSytCz6l1f4KYFf/iH8NNSLw==
|
||||
|
||||
range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
@ -4966,6 +4964,13 @@ slice-ansi@^4.0.0:
|
||||
astral-regex "^2.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
|
||||
sodium-native@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/sodium-native/-/sodium-native-3.3.0.tgz#50ee52ac843315866cce3d0c08ab03eb78f22361"
|
||||
integrity sha512-rg6lCDM/qa3p07YGqaVD+ciAbUqm6SoO4xmlcfkbU5r1zIGrguXztLiEtaLYTV5U6k8KSIUFmnU3yQUSKmf6DA==
|
||||
dependencies:
|
||||
node-gyp-build "^4.3.0"
|
||||
|
||||
source-map-support@^0.5.6:
|
||||
version "0.5.20"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
|
||||
|
||||
26
database/entity/0003-login_server_tables/LoginEmailOptIn.ts
Normal file
26
database/entity/0003-login_server_tables/LoginEmailOptIn.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||
|
||||
// Moriz: I do not like the idea of having two user tables
|
||||
@Entity('login_email_opt_in')
|
||||
export class LoginEmailOptIn extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'user_id' })
|
||||
userId: number
|
||||
|
||||
@Column({ name: 'verification_code', type: 'bigint', unsigned: true, unique: true })
|
||||
verificationCode: BigInt
|
||||
|
||||
@Column({ name: 'email_opt_in_type_id' })
|
||||
emailOptInTypeId: number
|
||||
|
||||
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||
createdAt: Date
|
||||
|
||||
@Column({ name: 'resend_count', default: 0 })
|
||||
resendCount: number
|
||||
|
||||
@Column({ name: 'updated', default: () => 'CURRENT_TIMESTAMP' })
|
||||
updatedAt: Date
|
||||
}
|
||||
@ -22,7 +22,7 @@ export class LoginUser extends BaseEntity {
|
||||
description: string
|
||||
|
||||
@Column({ type: 'bigint', default: 0, unsigned: true })
|
||||
password: string
|
||||
password: BigInt
|
||||
|
||||
@Column({ name: 'pubkey', type: 'binary', length: 32, default: null, nullable: true })
|
||||
pubKey: Buffer
|
||||
|
||||
16
database/entity/0003-login_server_tables/LoginUserBackup.ts
Normal file
16
database/entity/0003-login_server_tables/LoginUserBackup.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||
|
||||
@Entity('login_user_backups')
|
||||
export class LoginUserBackup extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'user_id', nullable: false })
|
||||
userId: number
|
||||
|
||||
@Column({ type: 'text', name: 'passphrase', nullable: false })
|
||||
passphrase: string
|
||||
|
||||
@Column({ name: 'mnemonic_type', default: -1 })
|
||||
mnemonicType: number
|
||||
}
|
||||
1
database/entity/LoginEmailOptIn.ts
Normal file
1
database/entity/LoginEmailOptIn.ts
Normal file
@ -0,0 +1 @@
|
||||
export { LoginEmailOptIn } from './0003-login_server_tables/LoginEmailOptIn'
|
||||
1
database/entity/LoginUserBackup.ts
Normal file
1
database/entity/LoginUserBackup.ts
Normal file
@ -0,0 +1 @@
|
||||
export { LoginUserBackup } from './0003-login_server_tables/LoginUserBackup'
|
||||
@ -1,5 +1,7 @@
|
||||
import { Balance } from './Balance'
|
||||
import { LoginEmailOptIn } from './LoginEmailOptIn'
|
||||
import { LoginUser } from './LoginUser'
|
||||
import { LoginUserBackup } from './LoginUserBackup'
|
||||
import { Migration } from './Migration'
|
||||
import { Transaction } from './Transaction'
|
||||
import { TransactionCreation } from './TransactionCreation'
|
||||
@ -11,6 +13,8 @@ import { UserTransaction } from './UserTransaction'
|
||||
export const entities = [
|
||||
Balance,
|
||||
LoginUser,
|
||||
LoginUserBackup,
|
||||
LoginEmailOptIn,
|
||||
Migration,
|
||||
Transaction,
|
||||
TransactionCreation,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user