mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
commit
d894358426
5
backend/src/auth/CustomJwtPayload.ts
Normal file
5
backend/src/auth/CustomJwtPayload.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { JwtPayload } from 'jsonwebtoken'
|
||||
|
||||
export interface CustomJwtPayload extends JwtPayload {
|
||||
pubKey: Buffer
|
||||
}
|
||||
13
backend/src/auth/INALIENABLE_RIGHTS.ts
Normal file
13
backend/src/auth/INALIENABLE_RIGHTS.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const INALIENABLE_RIGHTS = [
|
||||
RIGHTS.LOGIN,
|
||||
RIGHTS.GET_COMMUNITY_INFO,
|
||||
RIGHTS.COMMUNITIES,
|
||||
RIGHTS.LOGIN_VIA_EMAIL_VERIFICATION_CODE,
|
||||
RIGHTS.CREATE_USER,
|
||||
RIGHTS.SEND_RESET_PASSWORD_EMAIL,
|
||||
RIGHTS.RESET_PASSWORD,
|
||||
RIGHTS.CHECK_USERNAME,
|
||||
RIGHTS.CHECK_EMAIL,
|
||||
]
|
||||
19
backend/src/auth/JWT.ts
Normal file
19
backend/src/auth/JWT.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from '../config/'
|
||||
import { CustomJwtPayload } from './CustomJwtPayload'
|
||||
|
||||
export const decode = (token: string): CustomJwtPayload | null => {
|
||||
if (!token) throw new Error('401 Unauthorized')
|
||||
try {
|
||||
return <CustomJwtPayload>jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const encode = (pubKey: Buffer): string => {
|
||||
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
|
||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||
})
|
||||
return token
|
||||
}
|
||||
26
backend/src/auth/RIGHTS.ts
Normal file
26
backend/src/auth/RIGHTS.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export enum RIGHTS {
|
||||
LOGIN = 'LOGIN',
|
||||
VERIFY_LOGIN = 'VERIFY_LOGIN',
|
||||
BALANCE = 'BALANCE',
|
||||
GET_COMMUNITY_INFO = 'GET_COMMUNITY_INFO',
|
||||
COMMUNITIES = 'COMMUNITIES',
|
||||
LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES',
|
||||
EXIST_PID = 'EXIST_PID',
|
||||
GET_KLICKTIPP_USER = 'GET_KLICKTIPP_USER',
|
||||
GET_KLICKTIPP_TAG_MAP = 'GET_KLICKTIPP_TAG_MAP',
|
||||
UNSUBSCRIBE_NEWSLETTER = 'UNSUBSCRIBE_NEWSLETTER',
|
||||
SUBSCRIBE_NEWSLETTER = 'SUBSCRIBE_NEWSLETTER',
|
||||
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
||||
SEND_COINS = 'SEND_COINS',
|
||||
LOGIN_VIA_EMAIL_VERIFICATION_CODE = 'LOGIN_VIA_EMAIL_VERIFICATION_CODE',
|
||||
LOGOUT = 'LOGOUT',
|
||||
CREATE_USER = 'CREATE_USER',
|
||||
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||
RESET_PASSWORD = 'RESET_PASSWORD',
|
||||
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||
CHECK_EMAIL = 'CHECK_EMAIL',
|
||||
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
||||
// Admin
|
||||
SEARCH_USERS = 'SEARCH_USERS',
|
||||
}
|
||||
25
backend/src/auth/ROLES.ts
Normal file
25
backend/src/auth/ROLES.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS'
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
import { Role } from './Role'
|
||||
|
||||
export const ROLE_UNAUTHORIZED = new Role('unauthorized', INALIENABLE_RIGHTS)
|
||||
export const ROLE_USER = new Role('user', [
|
||||
...INALIENABLE_RIGHTS,
|
||||
RIGHTS.VERIFY_LOGIN,
|
||||
RIGHTS.BALANCE,
|
||||
RIGHTS.LIST_GDT_ENTRIES,
|
||||
RIGHTS.EXIST_PID,
|
||||
RIGHTS.GET_KLICKTIPP_USER,
|
||||
RIGHTS.GET_KLICKTIPP_TAG_MAP,
|
||||
RIGHTS.UNSUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.SUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.TRANSACTION_LIST,
|
||||
RIGHTS.SEND_COINS,
|
||||
RIGHTS.LOGOUT,
|
||||
RIGHTS.UPDATE_USER_INFOS,
|
||||
RIGHTS.HAS_ELOPAGE,
|
||||
])
|
||||
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||
|
||||
// TODO from database
|
||||
export const ROLES = [ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN]
|
||||
15
backend/src/auth/Role.ts
Normal file
15
backend/src/auth/Role.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export class Role {
|
||||
id: string
|
||||
rights: RIGHTS[]
|
||||
|
||||
constructor(id: string, rights: RIGHTS[]) {
|
||||
this.id = id
|
||||
this.rights = rights
|
||||
}
|
||||
|
||||
hasRight = (right: RIGHTS): boolean => {
|
||||
return this.rights.includes(right)
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,9 @@ export default class CreateUserArgs {
|
||||
@Field(() => String)
|
||||
lastName: string
|
||||
|
||||
@Field(() => String)
|
||||
password: string
|
||||
|
||||
@Field(() => String)
|
||||
language?: string // Will default to DEFAULT_LANGUAGE
|
||||
|
||||
|
||||
@ -2,19 +2,44 @@
|
||||
|
||||
import { AuthChecker } from 'type-graphql'
|
||||
|
||||
import decode from '../../jwt/decode'
|
||||
import encode from '../../jwt/encode'
|
||||
import { decode, encode } from '../../auth/JWT'
|
||||
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '../../auth/ROLES'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
|
||||
import { getCustomRepository } from 'typeorm'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
|
||||
const isAuthorized: AuthChecker<any> = async (
|
||||
{ /* root, args, */ context /*, info */ } /*, roles */,
|
||||
) => {
|
||||
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||
|
||||
// Do we have a token?
|
||||
if (context.token) {
|
||||
const decoded = decode(context.token)
|
||||
if (!decoded) {
|
||||
// we always throw on an invalid token
|
||||
throw new Error('403.13 - Client certificate revoked')
|
||||
}
|
||||
// Set context pubKey
|
||||
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
|
||||
// set new header token
|
||||
// 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 = await getCustomRepository(UserRepository)
|
||||
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
const serverUserRepository = await getCustomRepository(ServerUserRepository)
|
||||
const countServerUsers = await serverUserRepository.count({ email: user.email })
|
||||
context.role = countServerUsers > 0 ? ROLE_ADMIN : ROLE_USER
|
||||
|
||||
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
||||
return true
|
||||
}
|
||||
throw new Error('401 Unauthorized')
|
||||
|
||||
// check for correct rights
|
||||
const missingRights = (<RIGHTS[]>rights).filter((right) => !context.role.hasRight(right))
|
||||
if (missingRights.length !== 0) {
|
||||
throw new Error('401 Unauthorized')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export default isAuthorized
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { Resolver, Query, Arg } from 'type-graphql'
|
||||
import { Resolver, Query, Arg, Authorized } from 'type-graphql'
|
||||
import { getCustomRepository } from 'typeorm'
|
||||
import { UserAdmin } from '../model/UserAdmin'
|
||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
|
||||
@Resolver()
|
||||
export class AdminResolver {
|
||||
@Authorized([RIGHTS.SEARCH_USERS])
|
||||
@Query(() => [UserAdmin])
|
||||
async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> {
|
||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||
|
||||
@ -8,10 +8,11 @@ import { BalanceRepository } from '../../typeorm/repository/Balance'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import { calculateDecay } from '../../util/decay'
|
||||
import { roundFloorFrom4 } from '../../util/round'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
|
||||
@Resolver()
|
||||
export class BalanceResolver {
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.BALANCE])
|
||||
@Query(() => Balance)
|
||||
async balance(@Ctx() context: any): Promise<Balance> {
|
||||
// load user and balance
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { Resolver, Query } from 'type-graphql'
|
||||
import { Resolver, Query, Authorized } from 'type-graphql'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
import CONFIG from '../../config'
|
||||
import { Community } from '../model/Community'
|
||||
|
||||
@Resolver()
|
||||
export class CommunityResolver {
|
||||
@Authorized([RIGHTS.GET_COMMUNITY_INFO])
|
||||
@Query(() => Community)
|
||||
async getCommunityInfo(): Promise<Community> {
|
||||
return new Community({
|
||||
@ -17,6 +19,7 @@ export class CommunityResolver {
|
||||
})
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.COMMUNITIES])
|
||||
@Query(() => [Community])
|
||||
async communities(): Promise<Community[]> {
|
||||
if (CONFIG.PRODUCTION)
|
||||
|
||||
@ -9,10 +9,11 @@ import Paginated from '../arg/Paginated'
|
||||
import { apiGet } from '../../apis/HttpRequest'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import { Order } from '../enum/Order'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
|
||||
@Resolver()
|
||||
export class GdtResolver {
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.LIST_GDT_ENTRIES])
|
||||
@Query(() => GdtEntryList)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async listGDTEntries(
|
||||
@ -33,7 +34,7 @@ export class GdtResolver {
|
||||
return new GdtEntryList(resultGDT.data)
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.EXIST_PID])
|
||||
@Query(() => Number)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async existPid(@Arg('pid') pid: number): Promise<number> {
|
||||
|
||||
@ -8,29 +8,30 @@ import {
|
||||
unsubscribe,
|
||||
signIn,
|
||||
} from '../../apis/KlicktippController'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
import SubscribeNewsletterArgs from '../arg/SubscribeNewsletterArgs'
|
||||
|
||||
@Resolver()
|
||||
export class KlicktippResolver {
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.GET_KLICKTIPP_USER])
|
||||
@Query(() => String)
|
||||
async getKlicktippUser(@Arg('email') email: string): Promise<string> {
|
||||
return await getKlickTippUser(email)
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.GET_KLICKTIPP_TAG_MAP])
|
||||
@Query(() => String)
|
||||
async getKlicktippTagMap(): Promise<string> {
|
||||
return await getKlicktippTagMap()
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.UNSUBSCRIBE_NEWSLETTER])
|
||||
@Mutation(() => Boolean)
|
||||
async unsubscribeNewsletter(@Arg('email') email: string): Promise<boolean> {
|
||||
return await unsubscribe(email)
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.SUBSCRIBE_NEWSLETTER])
|
||||
@Mutation(() => Boolean)
|
||||
async subscribeNewsletter(
|
||||
@Args() { email, language }: SubscribeNewsletterArgs,
|
||||
|
||||
@ -34,6 +34,7 @@ import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||
import { TransactionType } from '../enum/TransactionType'
|
||||
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
|
||||
/*
|
||||
# Test
|
||||
@ -465,7 +466,7 @@ async function getPublicKey(email: string): Promise<string | null> {
|
||||
|
||||
@Resolver()
|
||||
export class TransactionResolver {
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.TRANSACTION_LIST])
|
||||
@Query(() => TransactionList)
|
||||
async transactionList(
|
||||
@Args() { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
||||
@ -499,7 +500,7 @@ export class TransactionResolver {
|
||||
return transactions
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.SEND_COINS])
|
||||
@Mutation(() => String)
|
||||
async sendCoins(
|
||||
@Args() { email, amount, memo }: TransactionSendArgs,
|
||||
|
||||
@ -9,7 +9,7 @@ import { LoginViaVerificationCode } from '../model/LoginViaVerificationCode'
|
||||
import { SendPasswordResetEmailResponse } from '../model/SendPasswordResetEmailResponse'
|
||||
import { User } from '../model/User'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import encode from '../../jwt/encode'
|
||||
import { encode } from '../../auth/JWT'
|
||||
import ChangePasswordArgs from '../arg/ChangePasswordArgs'
|
||||
import CheckUsernameArgs from '../arg/CheckUsernameArgs'
|
||||
import CreateUserArgs from '../arg/CreateUserArgs'
|
||||
@ -30,6 +30,9 @@ import { LoginUserBackup } from '@entity/LoginUserBackup'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
import { sendEMail } from '../../util/sendEMail'
|
||||
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
|
||||
import { ROLE_ADMIN } from '../../auth/ROLES'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sodium = require('sodium-native')
|
||||
@ -194,37 +197,7 @@ const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: B
|
||||
|
||||
@Resolver()
|
||||
export class UserResolver {
|
||||
/*
|
||||
@Authorized()
|
||||
@Query(() => User)
|
||||
async verifyLogin(@Ctx() context: any): Promise<User> {
|
||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||
loginUser = loginUserRepository.findByPubkeyHex()
|
||||
const user = new User(result.data.user)
|
||||
|
||||
this.email = json.email
|
||||
this.firstName = json.first_name
|
||||
this.lastName = json.last_name
|
||||
this.username = json.username
|
||||
this.description = json.description
|
||||
this.pubkey = json.public_hex
|
||||
this.language = json.language
|
||||
this.publisherId = json.publisher_id
|
||||
this.isAdmin = json.isAdmin
|
||||
|
||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
const coinanimation = await userSettingRepository
|
||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||
.catch((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
user.isAdmin = true // TODO implement
|
||||
return user
|
||||
}
|
||||
*/
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.VERIFY_LOGIN])
|
||||
@Query(() => User)
|
||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||
async verifyLogin(@Ctx() context: any): Promise<User> {
|
||||
@ -253,10 +226,12 @@ export class UserResolver {
|
||||
throw new Error(error)
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
user.isAdmin = true // TODO implement
|
||||
|
||||
user.isAdmin = context.role === ROLE_ADMIN
|
||||
return user
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LOGIN])
|
||||
@Query(() => User)
|
||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||
async login(
|
||||
@ -329,7 +304,11 @@ export class UserResolver {
|
||||
throw new Error(error)
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
user.isAdmin = true // TODO implement
|
||||
|
||||
// context.role is not set to the actual role yet on login
|
||||
const serverUserRepository = await getCustomRepository(ServerUserRepository)
|
||||
const countServerUsers = await serverUserRepository.count({ email: user.email })
|
||||
user.isAdmin = countServerUsers > 0
|
||||
|
||||
context.setHeaders.push({
|
||||
key: 'token',
|
||||
@ -339,6 +318,7 @@ export class UserResolver {
|
||||
return user
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LOGIN_VIA_EMAIL_VERIFICATION_CODE])
|
||||
@Query(() => LoginViaVerificationCode)
|
||||
async loginViaEmailVerificationCode(
|
||||
@Arg('optin') optin: string,
|
||||
@ -354,7 +334,7 @@ export class UserResolver {
|
||||
return new LoginViaVerificationCode(result.data)
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.LOGOUT])
|
||||
@Query(() => String)
|
||||
async logout(): Promise<boolean> {
|
||||
// TODO: We dont need this anymore, but might need this in the future in oder to invalidate a valid JWT-Token.
|
||||
@ -365,9 +345,10 @@ export class UserResolver {
|
||||
return true
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.CREATE_USER])
|
||||
@Mutation(() => String)
|
||||
async createUser(
|
||||
@Args() { email, firstName, lastName, language, publisherId }: CreateUserArgs,
|
||||
@Args() { email, firstName, lastName, password, language, publisherId }: CreateUserArgs,
|
||||
): Promise<string> {
|
||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||
// default int publisher_id = 0;
|
||||
@ -377,13 +358,12 @@ export class UserResolver {
|
||||
language = DEFAULT_LANGUAGE
|
||||
}
|
||||
|
||||
// TODO: Register process
|
||||
// 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!',
|
||||
// )
|
||||
// }
|
||||
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
|
||||
@ -401,13 +381,11 @@ export class UserResolver {
|
||||
throw new Error(`User already exists.`)
|
||||
}
|
||||
|
||||
// TODO: Register process
|
||||
// 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 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()
|
||||
@ -416,15 +394,13 @@ export class UserResolver {
|
||||
loginUser.lastName = lastName
|
||||
loginUser.username = username
|
||||
loginUser.description = ''
|
||||
// TODO: Register process
|
||||
// loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||
loginUser.emailHash = emailHash
|
||||
loginUser.language = language
|
||||
loginUser.groupId = 1
|
||||
loginUser.publisherId = publisherId
|
||||
// TODO: Register process
|
||||
// loginUser.pubKey = keyPair[0]
|
||||
// loginUser.privKey = encryptedPrivkey
|
||||
loginUser.pubKey = keyPair[0]
|
||||
loginUser.privKey = encryptedPrivkey
|
||||
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
@ -436,24 +412,21 @@ export class UserResolver {
|
||||
throw new Error('insert user failed')
|
||||
})
|
||||
|
||||
// TODO: Register process
|
||||
// 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;
|
||||
const loginUserBackup = new LoginUserBackup()
|
||||
loginUserBackup.userId = loginUserId
|
||||
loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
|
||||
loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
|
||||
|
||||
// TODO: Register process
|
||||
// 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')
|
||||
// })
|
||||
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()
|
||||
// TODO: Register process
|
||||
// dbUser.pubkey = keyPair[0]
|
||||
dbUser.pubkey = keyPair[0]
|
||||
dbUser.email = email
|
||||
dbUser.firstName = firstName
|
||||
dbUser.lastName = lastName
|
||||
@ -513,6 +486,7 @@ export class UserResolver {
|
||||
return 'success'
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
||||
@Query(() => SendPasswordResetEmailResponse)
|
||||
async sendResetPasswordEmail(
|
||||
@Arg('email') email: string,
|
||||
@ -529,6 +503,7 @@ export class UserResolver {
|
||||
return new SendPasswordResetEmailResponse(response.data)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.RESET_PASSWORD])
|
||||
@Mutation(() => String)
|
||||
async resetPassword(
|
||||
@Args()
|
||||
@ -546,7 +521,7 @@ export class UserResolver {
|
||||
return 'success'
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.UPDATE_USER_INFOS])
|
||||
@Mutation(() => Boolean)
|
||||
async updateUserInfos(
|
||||
@Args()
|
||||
@ -655,6 +630,7 @@ export class UserResolver {
|
||||
return true
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.CHECK_USERNAME])
|
||||
@Query(() => Boolean)
|
||||
async checkUsername(@Args() { username }: CheckUsernameArgs): Promise<boolean> {
|
||||
// Username empty?
|
||||
@ -678,6 +654,7 @@ export class UserResolver {
|
||||
return true
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.CHECK_EMAIL])
|
||||
@Query(() => CheckEmailResponse)
|
||||
@UseMiddleware(klicktippRegistrationMiddleware)
|
||||
async checkEmail(@Arg('optin') optin: string): Promise<CheckEmailResponse> {
|
||||
@ -690,7 +667,7 @@ export class UserResolver {
|
||||
return new CheckEmailResponse(result.data)
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Authorized([RIGHTS.HAS_ELOPAGE])
|
||||
@Query(() => Boolean)
|
||||
async hasElopage(@Ctx() context: any): Promise<boolean> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
import jwt, { JwtPayload } from 'jsonwebtoken'
|
||||
import CONFIG from '../config/'
|
||||
|
||||
interface CustomJwtPayload extends JwtPayload {
|
||||
pubKey: Buffer
|
||||
}
|
||||
|
||||
type DecodedJwt = {
|
||||
token: string
|
||||
pubKey: Buffer
|
||||
}
|
||||
|
||||
export default (token: string): DecodedJwt => {
|
||||
if (!token) throw new Error('401 Unauthorized')
|
||||
let pubKey = null
|
||||
try {
|
||||
const decoded = <CustomJwtPayload>jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
pubKey = decoded.pubKey
|
||||
return {
|
||||
token,
|
||||
pubKey,
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error('403.13 - Client certificate revoked')
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from '../config/'
|
||||
|
||||
// Generate an Access Token
|
||||
export default function encode(pubKey: Buffer): string {
|
||||
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
|
||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||
})
|
||||
return token
|
||||
}
|
||||
5
backend/src/typeorm/repository/ServerUser.ts
Normal file
5
backend/src/typeorm/repository/ServerUser.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from 'typeorm'
|
||||
import { ServerUser } from '@entity/ServerUser'
|
||||
|
||||
@EntityRepository(ServerUser)
|
||||
export class ServerUserRepository extends Repository<ServerUser> {}
|
||||
@ -27,7 +27,6 @@
|
||||
|
||||
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
|
||||
import { LoginUser } from '@entity/LoginUser'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { UserResolver } from '../graphql/resolver/UserResolver'
|
||||
|
||||
export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
||||
@ -145,6 +144,7 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
||||
firstName,
|
||||
lastName,
|
||||
publisherId: loginElopgaeBuy.publisherId,
|
||||
password: '123', // TODO remove
|
||||
})
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
31
database/entity/0001-init_db/ServerUser.ts
Normal file
31
database/entity/0001-init_db/ServerUser.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||
|
||||
@Entity('server_users')
|
||||
export class ServerUser extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ length: 50 })
|
||||
username: string
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true })
|
||||
password: BigInt
|
||||
|
||||
@Column({ length: 50, unique: true })
|
||||
email: string
|
||||
|
||||
@Column({ length: 20, default: 'admin' })
|
||||
role: string
|
||||
|
||||
@Column({ default: 0 })
|
||||
activated: number
|
||||
|
||||
@Column({ name: 'last_login', default: null, nullable: true })
|
||||
lastLogin: Date
|
||||
|
||||
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||
created: Date
|
||||
|
||||
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||
modified: Date
|
||||
}
|
||||
@ -1,31 +1 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||
|
||||
@Entity('server_users')
|
||||
export class ServerUser extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ length: 50 })
|
||||
username: string
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true })
|
||||
password: BigInt
|
||||
|
||||
@Column({ length: 50, unique: true })
|
||||
email: string
|
||||
|
||||
@Column({ length: 20, default: 'admin' })
|
||||
role: string
|
||||
|
||||
@Column({ default: 0 })
|
||||
activated: number
|
||||
|
||||
@Column({ name: 'last_login', default: null, nullable: true })
|
||||
lastLogin: Date
|
||||
|
||||
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||
created: Date
|
||||
|
||||
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||
modified: Date
|
||||
}
|
||||
export { ServerUser } from './0001-init_db/ServerUser'
|
||||
|
||||
@ -4,6 +4,7 @@ import { LoginEmailOptIn } from './LoginEmailOptIn'
|
||||
import { LoginUser } from './LoginUser'
|
||||
import { LoginUserBackup } from './LoginUserBackup'
|
||||
import { Migration } from './Migration'
|
||||
import { ServerUser } from './ServerUser'
|
||||
import { Transaction } from './Transaction'
|
||||
import { TransactionCreation } from './TransactionCreation'
|
||||
import { TransactionSendCoin } from './TransactionSendCoin'
|
||||
@ -18,6 +19,7 @@ export const entities = [
|
||||
LoginUser,
|
||||
LoginUserBackup,
|
||||
Migration,
|
||||
ServerUser,
|
||||
Transaction,
|
||||
TransactionCreation,
|
||||
TransactionSendCoin,
|
||||
|
||||
@ -14,7 +14,6 @@ const addNavigationGuards = (router, store, apollo) => {
|
||||
// store token on authenticate
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (to.path === '/authenticate' && to.query.token) {
|
||||
// TODO verify user in order to get user data
|
||||
store.commit('token', to.query.token)
|
||||
const result = await apollo.query({
|
||||
query: verifyLogin,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user